hue control
This commit is contained in:
@@ -461,6 +461,22 @@ const AppLayout = () => {
|
|||||||
const stored = window.localStorage.getItem('papercrate_view_mode');
|
const stored = window.localStorage.getItem('papercrate_view_mode');
|
||||||
return stored === 'grid' ? 'grid' : 'list';
|
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 initialRowSelection = [];
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState(initialRowSelection);
|
const [selectedRowKeys, setSelectedRowKeys] = useState(initialRowSelection);
|
||||||
const [selectionOrder, setSelectionOrder] = useState(initialRowSelection);
|
const [selectionOrder, setSelectionOrder] = useState(initialRowSelection);
|
||||||
@@ -558,6 +574,27 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
}, [token, appStatus, refreshAccessToken]);
|
}, [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(() => {
|
const clearFilters = useCallback(() => {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
setActiveTagFilters([]);
|
setActiveTagFilters([]);
|
||||||
@@ -5035,6 +5072,8 @@ const AppLayout = () => {
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
onCreateFolder: handlePromptCreateFolder,
|
onCreateFolder: handlePromptCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
|
onNeutralHueChange: handleNeutralHueChange,
|
||||||
|
neutralHue,
|
||||||
tags,
|
tags,
|
||||||
activeTagIds: activeTagFilters,
|
activeTagIds: activeTagFilters,
|
||||||
onToggleTagFilter: toggleTagFilter,
|
onToggleTagFilter: toggleTagFilter,
|
||||||
@@ -5093,6 +5132,8 @@ const AppLayout = () => {
|
|||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
handlePromptCreateFolder,
|
handlePromptCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
|
handleNeutralHueChange,
|
||||||
|
neutralHue,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
FolderPlusIcon,
|
FolderPlusIcon,
|
||||||
|
RestoreIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
|
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
@@ -142,6 +143,8 @@ const Sidebar = ({
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
creatingFolder = false,
|
creatingFolder = false,
|
||||||
|
onNeutralHueChange,
|
||||||
|
neutralHue,
|
||||||
tags = [],
|
tags = [],
|
||||||
activeTagIds = [],
|
activeTagIds = [],
|
||||||
onToggleTagFilter,
|
onToggleTagFilter,
|
||||||
@@ -220,6 +223,10 @@ const Sidebar = ({
|
|||||||
onCreateFolder?.();
|
onCreateFolder?.();
|
||||||
}, [creatingFolder, onCreateFolder]);
|
}, [creatingFolder, onCreateFolder]);
|
||||||
|
|
||||||
|
const handleNeutralHueReset = useCallback(() => {
|
||||||
|
onNeutralHueChange?.('');
|
||||||
|
}, [onNeutralHueChange]);
|
||||||
|
|
||||||
const handleSearchInputChange = useCallback(
|
const handleSearchInputChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
onSearchChange?.(event.target.value);
|
onSearchChange?.(event.target.value);
|
||||||
@@ -587,6 +594,35 @@ const Sidebar = ({
|
|||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof neutralHue === 'number' || typeof neutralHue === 'string' ? (
|
||||||
|
<div className="sidebar-section">
|
||||||
|
<div className="sidebar-section__header">
|
||||||
|
<h3>Theme</h3>
|
||||||
|
<div className="sidebar-section__actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button"
|
||||||
|
onClick={handleNeutralHueReset}
|
||||||
|
aria-label="Reset neutral hue"
|
||||||
|
>
|
||||||
|
<RestoreIcon size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="sidebar-slider">
|
||||||
|
<span className="sidebar-slider__label">Neutral hue</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="360"
|
||||||
|
step="1"
|
||||||
|
value={neutralHue}
|
||||||
|
onChange={(event) => onNeutralHueChange?.(event.target.value)}
|
||||||
|
/>
|
||||||
|
<span className="sidebar-slider__value">{neutralHue}°</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1518,6 +1518,26 @@ button.danger:hover:not([disabled]) {
|
|||||||
color: var(--muted);
|
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 {
|
.sidebar-item {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 0.16rem 0.26rem;
|
padding: 0.16rem 0.26rem;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
IconTextScan2,
|
IconTextScan2,
|
||||||
IconFolderPlus,
|
IconFolderPlus,
|
||||||
IconRefresh,
|
IconRefresh,
|
||||||
|
IconRestore,
|
||||||
IconMinusVertical,
|
IconMinusVertical,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconChevronDown,
|
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 }) => (
|
||||||
|
<IconRestore
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
export const ArrowUpIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const ArrowUpIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconArrowUp
|
<IconArrowUp
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user