diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index b2f5ed8..35f2880 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -461,6 +461,22 @@ const AppLayout = () => { const stored = window.localStorage.getItem('papercrate_view_mode'); return stored === 'grid' ? 'grid' : 'list'; }); + const [neutralHue, setNeutralHue] = useState(() => { + if (typeof window === 'undefined') { + return 260; + } + const stored = window.localStorage.getItem('papercrate_neutral_hue'); + if (stored) { + const parsedStored = Number.parseInt(stored, 10); + if (!Number.isNaN(parsedStored)) { + return parsedStored; + } + } + const root = document.documentElement; + const current = root.style.getPropertyValue('--neutral-hue'); + const parsed = Number.parseInt(current, 10); + return Number.isNaN(parsed) ? 260 : parsed; + }); const initialRowSelection = []; const [selectedRowKeys, setSelectedRowKeys] = useState(initialRowSelection); const [selectionOrder, setSelectionOrder] = useState(initialRowSelection); @@ -558,6 +574,27 @@ const AppLayout = () => { } }, [token, appStatus, refreshAccessToken]); + useEffect(() => { + if (typeof document !== 'undefined') { + document.documentElement.style.setProperty('--neutral-hue', `${neutralHue}deg`); + } + if (typeof window !== 'undefined') { + window.localStorage.setItem('papercrate_neutral_hue', String(neutralHue)); + } + }, [neutralHue]); + + const handleNeutralHueChange = useCallback((value) => { + if (value === '') { + setNeutralHue(260); + return; + } + const parsed = Number(value); + if (Number.isNaN(parsed)) { + return; + } + setNeutralHue(parsed); + }, []); + const clearFilters = useCallback(() => { setSearchQuery(''); setActiveTagFilters([]); @@ -5035,6 +5072,8 @@ const AppLayout = () => { draggedFolderId, onCreateFolder: handlePromptCreateFolder, creatingFolder, + onNeutralHueChange: handleNeutralHueChange, + neutralHue, tags, activeTagIds: activeTagFilters, onToggleTagFilter: toggleTagFilter, @@ -5093,6 +5132,8 @@ const AppLayout = () => { handleCorrespondentCreate, handlePromptCreateFolder, creatingFolder, + handleNeutralHueChange, + neutralHue, ], ); diff --git a/frontend/src/sidebar/Sidebar.jsx b/frontend/src/sidebar/Sidebar.jsx index be2e00d..92c01ff 100644 --- a/frontend/src/sidebar/Sidebar.jsx +++ b/frontend/src/sidebar/Sidebar.jsx @@ -11,6 +11,7 @@ import { CheckIcon, PlusIcon, FolderPlusIcon, + RestoreIcon, } from '../ui/icons'; import { getTagColorStyle } from '../utils/colors'; @@ -142,6 +143,8 @@ const Sidebar = ({ draggedFolderId, onCreateFolder, creatingFolder = false, + onNeutralHueChange, + neutralHue, tags = [], activeTagIds = [], onToggleTagFilter, @@ -220,6 +223,10 @@ const Sidebar = ({ onCreateFolder?.(); }, [creatingFolder, onCreateFolder]); + const handleNeutralHueReset = useCallback(() => { + onNeutralHueChange?.(''); + }, [onNeutralHueChange]); + const handleSearchInputChange = useCallback( (event) => { onSearchChange?.(event.target.value); @@ -587,6 +594,35 @@ const Sidebar = ({ })} + {typeof neutralHue === 'number' || typeof neutralHue === 'string' ? ( +
+
+

Theme

+
+ +
+
+ +
+ ) : null} ); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 3d6f8ff..941d0d1 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1518,6 +1518,26 @@ button.danger:hover:not([disabled]) { color: var(--muted); } +.sidebar-slider { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8rem; + color: var(--muted); +} + +.sidebar-slider input[type='range'] { + flex: 1; + accent-color: var(--accent); +} + +.sidebar-slider__value { + min-width: 3rem; + text-align: right; + color: var(--fg); + font-variant-numeric: tabular-nums; +} + .sidebar-item { background: transparent; padding: 0.16rem 0.26rem; diff --git a/frontend/src/ui/icons.js b/frontend/src/ui/icons.js index f62f958..91af1e2 100644 --- a/frontend/src/ui/icons.js +++ b/frontend/src/ui/icons.js @@ -17,6 +17,7 @@ import { IconTextScan2, IconFolderPlus, IconRefresh, + IconRestore, IconMinusVertical, IconLogout, IconChevronDown, @@ -171,6 +172,15 @@ export const RefreshIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) /> ); +export const RestoreIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => ( + +); + export const ArrowUpIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (