switch and reduce size

This commit is contained in:
EthanHealy01 2025-09-05 17:56:25 +01:00
parent 7251d6a7dc
commit 89726ecc57
5 changed files with 161 additions and 104 deletions

View File

@ -21,6 +21,7 @@ const BulkSelectionPanel = ({
onUpdatePagesFromCSV, onUpdatePagesFromCSV,
}: BulkSelectionPanelProps) => { }: BulkSelectionPanelProps) => {
const [syntaxError, setSyntaxError] = useState<string | null>(null); const [syntaxError, setSyntaxError] = useState<string | null>(null);
const [advancedOpened, setAdvancedOpened] = useState<boolean>(false);
const maxPages = displayDocument?.pages?.length ?? 0; const maxPages = displayDocument?.pages?.length ?? 0;
@ -51,6 +52,8 @@ const BulkSelectionPanel = ({
setCsvInput={setCsvInput} setCsvInput={setCsvInput}
onUpdatePagesFromCSV={onUpdatePagesFromCSV} onUpdatePagesFromCSV={onUpdatePagesFromCSV}
onClear={handleClear} onClear={handleClear}
advancedOpened={advancedOpened}
onToggleAdvanced={setAdvancedOpened}
/> />
<SelectedPagesDisplay <SelectedPagesDisplay
@ -64,6 +67,7 @@ const BulkSelectionPanel = ({
setCsvInput={setCsvInput} setCsvInput={setCsvInput}
onUpdatePagesFromCSV={onUpdatePagesFromCSV} onUpdatePagesFromCSV={onUpdatePagesFromCSV}
maxPages={maxPages} maxPages={maxPages}
advancedOpened={advancedOpened}
/> />
</div> </div>
); );

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Button, Text, NumberInput, Group } from '@mantine/core'; import { Button, Text, NumberInput, Group, Flex } from '@mantine/core';
import classes from './BulkSelectionPanel.module.css'; import classes from './BulkSelectionPanel.module.css';
import { import {
appendExpression, appendExpression,
@ -15,6 +15,7 @@ interface AdvancedSelectionPanelProps {
setCsvInput: (value: string) => void; setCsvInput: (value: string) => void;
onUpdatePagesFromCSV: (override?: string) => void; onUpdatePagesFromCSV: (override?: string) => void;
maxPages: number; maxPages: number;
advancedOpened?: boolean;
} }
const AdvancedSelectionPanel = ({ const AdvancedSelectionPanel = ({
@ -22,8 +23,9 @@ const AdvancedSelectionPanel = ({
setCsvInput, setCsvInput,
onUpdatePagesFromCSV, onUpdatePagesFromCSV,
maxPages, maxPages,
advancedOpened,
}: AdvancedSelectionPanelProps) => { }: AdvancedSelectionPanelProps) => {
const [advancedOpened, setAdvancedOpened] = useState<boolean>(false); // Visibility now controlled by parent
const [firstNValue, setFirstNValue] = useState<number | ''>(''); const [firstNValue, setFirstNValue] = useState<number | ''>('');
const [lastNValue, setLastNValue] = useState<number | ''>(''); const [lastNValue, setLastNValue] = useState<number | ''>('');
const [everyNthValue, setEveryNthValue] = useState<number | ''>(''); const [everyNthValue, setEveryNthValue] = useState<number | ''>('');
@ -46,40 +48,17 @@ const AdvancedSelectionPanel = ({
return ( return (
<> <>
{/* Advanced button */}
<div className={classes.dropdownContainer}>
<Button
variant="light"
size="xs"
onClick={() => setAdvancedOpened(!advancedOpened)}
>
Advanced
</Button>
</div>
{/* Advanced section */} {/* Advanced section */}
{advancedOpened && ( {advancedOpened && (
<div className={classes.advancedSection}> <div className={classes.advancedSection}>
<div className={classes.advancedHeader}>
<Text size="sm" fw={500}>Advanced Selection</Text>
<Button
size="xs"
variant="subtle"
color="gray"
onClick={() => setAdvancedOpened(false)}
className={classes.closeButton}
>
×
</Button>
</div>
<div className={classes.advancedContent}> <div className={classes.advancedContent}>
<div className={classes.leftCol}> {/* Cards row */}
<Flex direction="row" mb="xs" wrap="wrap">
{/* First N Pages - Card Style */} {/* First N Pages - Card Style */}
<div className={classes.advancedCard}> <div className={classes.advancedCard}>
<Text size="sm" fw={600} c="gray.7" mb="sm">First N Pages</Text> <Text size="sm" fw={600} c="var(--text-secondary)" mb="xs">First N Pages</Text>
{firstNError && (<Text size="xs" c="red" mb="xs">{firstNError}</Text>)} {firstNError && (<Text size="xs" c="var(--text-brand-accent)" mb="xs">{firstNError}</Text>)}
<div className={classes.inputGroup}> <div className={classes.inputGroup}>
<Text size="xs" c="gray.6" mb="xs">Number of pages:</Text>
<Group gap="sm" align="flex-end" wrap="nowrap"> <Group gap="sm" align="flex-end" wrap="nowrap">
<NumberInput <NumberInput
size="sm" size="sm"
@ -92,7 +71,7 @@ const AdvancedSelectionPanel = ({
else setFirstNError(null); else setFirstNError(null);
}} }}
min={1} min={1}
placeholder="10" placeholder="Number of pages"
className={classes.fullWidthInput} className={classes.fullWidthInput}
error={Boolean(firstNError)} error={Boolean(firstNError)}
/> />
@ -115,12 +94,11 @@ const AdvancedSelectionPanel = ({
{/* Range - Card Style */} {/* Range - Card Style */}
<div className={classes.advancedCard}> <div className={classes.advancedCard}>
<Text size="sm" fw={600} c="gray.7" mb="sm">Range</Text> <Text size="sm" fw={600} c="var(--text-secondary)" mb="xs">Range</Text>
{rangeError && (<Text size="xs" c="red" mb="xs">{rangeError}</Text>)} {rangeError && (<Text size="xs" c="var(--text-brand-accent)" mb="xs">{rangeError}</Text>)}
<div className={classes.inputGroup}> <div className={classes.inputGroup}>
<Group gap="sm" align="flex-end" wrap="nowrap" mb="sm"> <Group gap="sm" align="flex-end" wrap="nowrap" mb="xs">
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Text size="xs" c="gray.6" mb="xs">From:</Text>
<NumberInput <NumberInput
size="sm" size="sm"
value={rangeStart} value={rangeStart}
@ -134,12 +112,11 @@ const AdvancedSelectionPanel = ({
else setRangeError(null); else setRangeError(null);
}} }}
min={1} min={1}
placeholder="5" placeholder="From"
error={Boolean(rangeError)} error={Boolean(rangeError)}
/> />
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Text size="xs" c="gray.6" mb="xs">To:</Text>
<NumberInput <NumberInput
size="sm" size="sm"
value={rangeEnd} value={rangeEnd}
@ -153,7 +130,7 @@ const AdvancedSelectionPanel = ({
else setRangeError(null); else setRangeError(null);
}} }}
min={1} min={1}
placeholder="10" placeholder="To"
error={Boolean(rangeError)} error={Boolean(rangeError)}
/> />
</div> </div>
@ -180,10 +157,9 @@ const AdvancedSelectionPanel = ({
{/* Last N Pages - Card Style */} {/* Last N Pages - Card Style */}
<div className={classes.advancedCard}> <div className={classes.advancedCard}>
<Text size="sm" fw={600} c="gray.7" mb="sm">Last N Pages</Text> <Text size="sm" fw={600} c="var(--text-secondary)" mb="xs">Last N Pages</Text>
{lastNError && (<Text size="xs" c="red" mb="xs">{lastNError}</Text>)} {lastNError && (<Text size="xs" c="var(--text-brand-accent)" mb="xs">{lastNError}</Text>)}
<div className={classes.inputGroup}> <div className={classes.inputGroup}>
<Text size="xs" c="gray.6" mb="xs">Number of pages:</Text>
<Group gap="sm" align="flex-end" wrap="nowrap"> <Group gap="sm" align="flex-end" wrap="nowrap">
<NumberInput <NumberInput
size="sm" size="sm"
@ -196,7 +172,7 @@ const AdvancedSelectionPanel = ({
else setLastNError(null); else setLastNError(null);
}} }}
min={1} min={1}
placeholder="10" placeholder="Number of pages"
className={classes.fullWidthInput} className={classes.fullWidthInput}
error={Boolean(lastNError)} error={Boolean(lastNError)}
/> />
@ -219,16 +195,15 @@ const AdvancedSelectionPanel = ({
{/* Every Nth Page - Card Style */} {/* Every Nth Page - Card Style */}
<div className={classes.advancedCard}> <div className={classes.advancedCard}>
<Text size="sm" fw={600} c="gray.7" mb="sm">Every Nth Page</Text> <Text size="sm" fw={600} c="var(--text-secondary)" mb="xs">Every Nth Page</Text>
<div className={classes.inputGroup}> <div className={classes.inputGroup}>
<Text size="xs" c="gray.6" mb="xs">Step size:</Text>
<Group gap="sm" align="flex-end" wrap="nowrap"> <Group gap="sm" align="flex-end" wrap="nowrap">
<NumberInput <NumberInput
size="sm" size="sm"
value={everyNthValue} value={everyNthValue}
onChange={(val) => setEveryNthValue(typeof val === 'number' ? val : '')} onChange={(val) => setEveryNthValue(typeof val === 'number' ? val : '')}
min={1} min={1}
placeholder="5" placeholder="Step size"
className={classes.fullWidthInput} className={classes.fullWidthInput}
/> />
<Button <Button
@ -247,10 +222,11 @@ const AdvancedSelectionPanel = ({
</Group> </Group>
</div> </div>
</div> </div>
</div> </Flex>
<div className={classes.rightCol}> {/* Operators row at bottom */}
<Text size="xs" c="gray.6" fw={500} mb="sm">Add Operators:</Text> <div>
<div className={classes.operatorGroup}> <Text size="xs" c="var(--text-muted)" fw={500} mb="xs">Add Operators:</Text>
<Group gap="sm" wrap="nowrap">
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
@ -281,7 +257,7 @@ const AdvancedSelectionPanel = ({
> >
<Text size="xs" fw={500}>not</Text> <Text size="xs" fw={500}>not</Text>
</Button> </Button>
</div> </Group>
</div> </div>
</div> </div>
</div> </div>

View File

@ -31,7 +31,7 @@
.rightCol { .rightCol {
width: 8rem; width: 8rem;
border-left: 0.0625rem solid var(--mantine-color-gray-3); border-left: 0.0625rem solid var(--border-default);
padding-left: 0.75rem; padding-left: 0.75rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -46,22 +46,23 @@
.operatorChip { .operatorChip {
width: 100%; width: 100%;
border-radius: 1.25rem; border-radius: 1.25rem;
border: 0.0625rem solid var(--mantine-color-gray-4); border: 0.0625rem solid var(--bulk-card-border);
background-color: var(--mantine-color-white); background-color: var(--bulk-card-bg);
color: var(--text-primary);
transition: all 0.2s ease; transition: all 0.2s ease;
min-height: 2rem; min-height: 2rem;
} }
.operatorChip:hover:not(:disabled) { .operatorChip:hover:not(:disabled) {
border-color: var(--primary-color, #3b82f6); border-color: var(--bulk-card-hover-border);
background-color: var(--mantine-color-blue-0); background-color: var(--hover-bg);
transform: translateY(-0.0625rem); transform: translateY(-0.0625rem);
box-shadow: 0 0.125rem 0.25rem rgba(59, 130, 246, 0.1); box-shadow: var(--shadow-sm);
} }
.operatorChip:active:not(:disabled) { .operatorChip:active:not(:disabled) {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 0.0625rem 0.125rem rgba(59, 130, 246, 0.1); box-shadow: var(--shadow-xs);
} }
.operatorChip:disabled { .operatorChip:disabled {
@ -70,13 +71,15 @@
} }
:global([data-mantine-color-scheme='dark']) .operatorChip { :global([data-mantine-color-scheme='dark']) .operatorChip {
background-color: var(--mantine-color-dark-6); background-color: var(--bulk-card-bg);
border-color: var(--mantine-color-dark-4); border-color: var(--bulk-card-border);
color: var(--text-primary);
} }
:global([data-mantine-color-scheme='dark']) .operatorChip:hover:not(:disabled) { :global([data-mantine-color-scheme='dark']) .operatorChip:hover:not(:disabled) {
background-color: var(--mantine-color-dark-5); background-color: var(--hover-bg);
border-color: var(--primary-color, #3b82f6); border-color: var(--bulk-card-hover-border);
color: var(--text-primary);
} }
.dropdownHeader { .dropdownHeader {
@ -84,7 +87,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0.75rem; padding: 0.75rem;
border-bottom: 0.0625rem solid var(--mantine-color-gray-3); border-bottom: 0.0625rem solid var(--border-default);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@ -108,7 +111,7 @@
} }
.chevron { .chevron {
color: var(--mantine-color-dimmed); color: var(--text-muted);
} }
/* Icon-based chevrons */ /* Icon-based chevrons */
@ -135,22 +138,26 @@
} }
.menuItemCloseHover { .menuItemCloseHover {
background-color: var(--mantine-color-red-1); background-color: var(--text-brand-accent);
opacity: 0.1;
transition: background-color 150ms ease; transition: background-color 150ms ease;
} }
:global([data-mantine-color-scheme='dark']) .menuItemCloseHover { :global([data-mantine-color-scheme='dark']) .menuItemCloseHover {
background-color: var(--mantine-color-red-9); background-color: var(--text-brand-accent);
opacity: 0.2;
} }
.selectedList { .selectedList {
max-height: 8rem; max-height: 8rem;
overflow: auto; overflow: auto;
background-color: var(--mantine-color-gray-0); background-color: var(--bg-raised);
border-radius: 0.5rem; border: 0.0625rem solid var(--border-default);
border-radius: 0.75rem;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin-top: 0.5rem; margin-top: 0.5rem;
min-width: 24rem; min-width: 24rem;
color: var(--text-primary);
} }
.selectedText { .selectedText {
@ -168,14 +175,14 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0.75rem; padding: 0.75rem;
border-bottom: 0.0625rem solid var(--mantine-color-gray-3); border-bottom: 0.0625rem solid var(--border-default);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.advancedContent { .advancedContent {
display: flex; display: flex;
gap: 0.75rem; flex-direction: column;
padding: 0 0.75rem 0.75rem 0.75rem; gap: 0.5rem;
} }
.advancedItem { .advancedItem {
@ -186,37 +193,38 @@
} }
.advancedItem:hover { .advancedItem:hover {
background-color: var(--mantine-color-gray-1); background-color: var(--hover-bg);
} }
:global([data-mantine-color-scheme='dark']) .advancedItem:hover { :global([data-mantine-color-scheme='dark']) .advancedItem:hover {
background-color: var(--mantine-color-gray-8); background-color: var(--hover-bg);
} }
.advancedCard { .advancedCard {
background-color: var(--mantine-color-gray-0); background-color: var(--bulk-card-bg);
border: 0.0625rem solid var(--mantine-color-gray-2); border: 0.0625rem solid var(--bulk-card-border);
border-radius: 0.5rem; border-radius: 0.75rem;
padding: 1rem; padding: 0.75rem;
margin-bottom: 1rem; margin-bottom: 0.5rem;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
transition: all 0.2s ease; transition: all 0.2s ease;
color: var(--text-primary);
} }
.advancedCard:hover { .advancedCard:hover {
border-color: var(--mantine-color-gray-3); border-color: var(--bulk-card-hover-border);
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.05); box-shadow: var(--shadow-sm);
} }
:global([data-mantine-color-scheme='dark']) .advancedCard { :global([data-mantine-color-scheme='dark']) .advancedCard {
background-color: var(--mantine-color-dark-7); background-color: var(--bulk-card-bg);
border-color: var(--mantine-color-dark-5); border-color: var(--bulk-card-border);
} }
:global([data-mantine-color-scheme='dark']) .advancedCard:hover { :global([data-mantine-color-scheme='dark']) .advancedCard:hover {
border-color: var(--mantine-color-dark-4); border-color: var(--bulk-card-hover-border);
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.2); box-shadow: var(--shadow-sm);
} }
.inputGroup { .inputGroup {
@ -232,15 +240,33 @@
flex-shrink: 0; flex-shrink: 0;
} }
/* Style inputs and buttons within advanced cards to match bg-raised */
.advancedCard :global(.mantine-NumberInput-input) {
background-color: var(--bg-raised) !important;
border-color: var(--border-default) !important;
color: var(--text-primary) !important;
}
.advancedCard :global(.mantine-Button-root) {
background-color: var(--bg-raised) !important;
border-color: var(--border-default) !important;
color: var(--text-primary) !important;
}
.advancedCard :global(.mantine-Button-root:hover) {
background-color: var(--hover-bg) !important;
border-color: var(--border-strong) !important;
}
/* Error helper text above the input */ /* Error helper text above the input */
.errorText { .errorText {
margin-top: 0.25rem; margin-top: 0.25rem;
color: var(--mantine-color-red-6); color: var(--text-brand-accent);
} }
/* Dark-mode adjustments */ /* Dark-mode adjustments */
:global([data-mantine-color-scheme='dark']) .selectedList { :global([data-mantine-color-scheme='dark']) .selectedList {
background-color: var(--mantine-color-dark-6); background-color: var(--bg-raised);
} }
/* Small screens: allow the section to shrink instead of enforcing a large min width */ /* Small screens: allow the section to shrink instead of enforcing a large min width */
@ -257,5 +283,25 @@
.panelContainer { .panelContainer {
max-height: 95vh; max-height: 95vh;
overflow: auto; overflow: auto;
background-color: var(--bulk-panel-bg);
color: var(--text-primary);
border-radius: 0.5rem;
}
/* Override Mantine Popover dropdown background */
:global(.mantine-Popover-dropdown) {
background-color: var(--bulk-panel-bg) !important;
border-color: var(--bulk-card-border) !important;
color: var(--text-primary) !important;
}
/* Override Mantine Switch outline */
.advancedSwitch :global(.mantine-Switch-input) {
outline: none !important;
}
.advancedSwitch :global(.mantine-Switch-input:focus) {
outline: none !important;
box-shadow: none !important;
} }

View File

@ -1,4 +1,4 @@
import { Group, TextInput, Button, Text, Flex } from '@mantine/core'; import { Group, TextInput, Button, Text, Flex, Switch } from '@mantine/core';
import LocalIcon from '../../shared/LocalIcon'; import LocalIcon from '../../shared/LocalIcon';
import { Tooltip } from '../../shared/Tooltip'; import { Tooltip } from '../../shared/Tooltip';
import { usePageSelectionTips } from '../../tooltips/usePageSelectionTips'; import { usePageSelectionTips } from '../../tooltips/usePageSelectionTips';
@ -9,6 +9,8 @@ interface PageSelectionInputProps {
setCsvInput: (value: string) => void; setCsvInput: (value: string) => void;
onUpdatePagesFromCSV: (override?: string) => void; onUpdatePagesFromCSV: (override?: string) => void;
onClear: () => void; onClear: () => void;
advancedOpened?: boolean;
onToggleAdvanced?: (v: boolean) => void;
} }
const PageSelectionInput = ({ const PageSelectionInput = ({
@ -16,11 +18,44 @@ const PageSelectionInput = ({
setCsvInput, setCsvInput,
onUpdatePagesFromCSV, onUpdatePagesFromCSV,
onClear, onClear,
advancedOpened,
onToggleAdvanced,
}: PageSelectionInputProps) => { }: PageSelectionInputProps) => {
const pageSelectionTips = usePageSelectionTips(); const pageSelectionTips = usePageSelectionTips();
return ( return (
<Group className={classes.panelGroup}> <div className={classes.panelGroup}>
{/* Header row with tooltip/title and advanced toggle */}
<Flex justify="space-between" align="center" mb="sm">
<Tooltip
position="left"
offset={20}
header={pageSelectionTips.header}
portalTarget={document.body}
pinOnClick={true}
containerStyle={{ marginTop: "1rem"}}
tips={pageSelectionTips.tips}
>
<Flex onClick={(e) => e.stopPropagation()} align="center" gap="xs">
<LocalIcon icon="gpp-maybe-outline-rounded" width="1rem" height="1rem" style={{ color: 'var(--text-instruction)' }} />
<Text>Page Selection</Text>
</Flex>
</Tooltip>
{typeof advancedOpened === 'boolean' && (
<Flex align="center" gap="xs">
<Text size="sm" c="var(--text-secondary)">Advanced</Text>
<Switch
size="sm"
checked={!!advancedOpened}
onChange={(e) => onToggleAdvanced?.(e.currentTarget.checked)}
title="Advanced"
className={classes.advancedSwitch}
/>
</Flex>
)}
</Flex>
{/* Text input */}
<TextInput <TextInput
value={csvInput} value={csvInput}
onChange={(e) => { onChange={(e) => {
@ -36,7 +71,7 @@ const PageSelectionInput = ({
size="xs" size="xs"
onClick={onClear} onClick={onClear}
style={{ style={{
color: 'var(--mantine-color-gray-6)', color: 'var(--text-muted)',
minWidth: 'auto', minWidth: 'auto',
width: '24px', width: '24px',
height: '24px', height: '24px',
@ -47,26 +82,10 @@ const PageSelectionInput = ({
</Button> </Button>
) )
} }
label={
<Tooltip
position="left"
offset={20}
header={pageSelectionTips.header}
portalTarget={document.body}
pinOnClick={true}
containerStyle={{ marginTop: "1rem"}}
tips={pageSelectionTips.tips}
>
<Flex onClick={(e) => e.stopPropagation()} align="center" gap="xs" my="sm">
<LocalIcon icon="gpp-maybe-outline-rounded" width="1rem" height="1rem" style={{ color: 'var(--primary-color, #3b82f6)' }} />
<Text>Page Selection</Text>
</Flex>
</Tooltip>
}
onKeyDown={(e) => e.key === 'Enter' && onUpdatePagesFromCSV()} onKeyDown={(e) => e.key === 'Enter' && onUpdatePagesFromCSV()}
className={classes.textInput} className={classes.textInput}
/> />
</Group> </div>
); );
}; };

View File

@ -178,6 +178,12 @@
--checkbox-border: #2F83BF; --checkbox-border: #2F83BF;
--checkbox-checked-bg: #3FAFFF; --checkbox-checked-bg: #3FAFFF;
--checkbox-tick: #FFFFFF; --checkbox-tick: #FFFFFF;
/* Bulk selection panel specific colors (light mode) */
--bulk-panel-bg: #ffffff; /* white background for parent container */
--bulk-card-bg: #ffffff; /* white background for cards */
--bulk-card-border: #e5e7eb; /* light gray border for cards and buttons */
--bulk-card-hover-border: #d1d5db; /* slightly darker on hover */
} }
[data-mantine-color-scheme="dark"] { [data-mantine-color-scheme="dark"] {
@ -322,6 +328,12 @@
--tool-panel-search-bg: #1F2329; --tool-panel-search-bg: #1F2329;
--tool-panel-search-border-bottom: #4B525A; --tool-panel-search-border-bottom: #4B525A;
/* Bulk selection panel specific colors (dark mode) */
--bulk-panel-bg: var(--bg-raised); /* dark background for parent container */
--bulk-card-bg: var(--bg-raised); /* dark background for cards */
--bulk-card-border: var(--border-default); /* default border for cards and buttons */
--bulk-card-hover-border: var(--border-strong); /* stronger border on hover */
} }
/* Dropzone drop state styling */ /* Dropzone drop state styling */