This commit is contained in:
2025-10-31 13:39:06 +01:00
parent 3af656b09f
commit b254c651a7
5 changed files with 314 additions and 53 deletions
+81 -30
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';
import {
ChevronIcon,
TrashIcon,
@@ -12,6 +12,9 @@ import {
PlusIcon,
FolderPlusIcon,
RestoreIcon,
SunIcon,
MoonIcon,
DesktopIcon,
} from '../ui/icons';
import PanelHeader from '../ui/PanelHeader';
@@ -129,6 +132,13 @@ const FolderNode = ({
);
};
const THEME_MODES = ['system', 'light', 'dark'];
const THEME_MODE_LABELS = {
system: 'System',
light: 'Light',
dark: 'Dark',
};
const Sidebar = ({
folderNodes,
onToggle,
@@ -146,6 +156,8 @@ const Sidebar = ({
creatingFolder = false,
onNeutralHueChange,
neutralHue,
themeMode = 'system',
onThemeModeChange,
tags = [],
activeTagIds = [],
onToggleTagFilter,
@@ -170,6 +182,7 @@ const Sidebar = ({
onSelectTenant,
onOpenSettings,
}) => {
const neutralHueInputId = useId();
const sortedCorrespondents = useMemo(() => {
if (!Array.isArray(correspondents)) {
return [];
@@ -230,6 +243,28 @@ const Sidebar = ({
onNeutralHueChange?.('');
}, [onNeutralHueChange]);
const themeModeIndex = THEME_MODES.indexOf(themeMode);
const safeThemeModeIndex = themeModeIndex === -1 ? 0 : themeModeIndex;
const resolvedThemeMode = THEME_MODES[safeThemeModeIndex];
const nextThemeMode = THEME_MODES[(safeThemeModeIndex + 1) % THEME_MODES.length];
const themeModeLabel = THEME_MODE_LABELS[resolvedThemeMode];
const nextThemeLabel = THEME_MODE_LABELS[nextThemeMode];
const themeModeIcon = resolvedThemeMode === 'dark'
? <MoonIcon size={16} />
: resolvedThemeMode === 'light'
? <SunIcon size={16} />
: <DesktopIcon size={16} />;
const handleThemeModeToggle = useCallback(() => {
if (!onThemeModeChange) {
return;
}
const currentIndex = THEME_MODES.indexOf(themeMode);
const safeIndex = currentIndex === -1 ? 0 : currentIndex;
const nextMode = THEME_MODES[(safeIndex + 1) % THEME_MODES.length];
onThemeModeChange(nextMode);
}, [onThemeModeChange, themeMode]);
const handleSearchInputChange = useCallback(
(event) => {
onSearchChange?.(event.target.value);
@@ -350,6 +385,50 @@ const Sidebar = ({
);
const rootNode = folderNodes.get('root');
const themeControls =
typeof neutralHue === 'number' || typeof neutralHue === 'string'
? (
<div className="sidebar-section sidebar-theme">
<div className="sidebar-section__header">
<h3>Theme</h3>
<div className="sidebar-section__actions">
<button
type="button"
className="icon-button"
onClick={handleThemeModeToggle}
aria-label={`Switch theme (next: ${nextThemeLabel})`}
title={`Theme: ${themeModeLabel} (next: ${nextThemeLabel})`}
>
{themeModeIcon}
</button>
<button
type="button"
className="icon-button"
onClick={handleNeutralHueReset}
aria-label="Reset neutral hue"
title="Reset neutral hue"
>
<RestoreIcon size={16} />
</button>
</div>
</div>
<label className="sidebar-slider" htmlFor={neutralHueInputId}>
<span className="sidebar-slider__label">Hue</span>
<input
id={neutralHueInputId}
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;
return (
<aside className="sidebar">
<PanelHeader
@@ -606,36 +685,8 @@ const Sidebar = ({
})}
</ul>
</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>
{themeControls ? <div className="sidebar__footer">{themeControls}</div> : null}
</aside>
);
};