This commit is contained in:
2025-11-10 12:50:28 +01:00
parent 9757fd0c10
commit bf1e5cd107
2 changed files with 128 additions and 73 deletions
+49 -49
View File
@@ -189,7 +189,6 @@ const Sidebar = ({
onSelectTenant, onSelectTenant,
onOpenSettings, onOpenSettings,
onUploadFiles, onUploadFiles,
loading = false,
}) => { }) => {
const { const {
setCollapsed, setCollapsed,
@@ -318,6 +317,50 @@ const Sidebar = ({
cycleThemeMode(); cycleThemeMode();
}, [cycleThemeMode]); }, [cycleThemeMode]);
const themeMenuSection =
typeof neutralHue === 'number' || typeof neutralHue === 'string'
? (
<div className="menu__section">
<div className="menu__heading menu__heading--with-actions">
<span>Theme</span>
<div className="menu__heading-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="menu__slider" htmlFor={neutralHueInputId}>
<span className="menu__slider-label">Hue</span>
<input
id={neutralHueInputId}
type="range"
min="0"
max="360"
step="1"
value={neutralHue}
onChange={(event) => handleNeutralHueChange(event.target.value)}
/>
<span className="menu__slider-value">{neutralHue}°</span>
</label>
</div>
)
: null;
const handleSearchInputChange = useCallback( const handleSearchInputChange = useCallback(
(event) => { (event) => {
onSearchChange?.(event.target.value); onSearchChange?.(event.target.value);
@@ -348,6 +391,7 @@ const Sidebar = ({
offset: 6, offset: 6,
}); });
const showTenantList = tenants.length > 1; const showTenantList = tenants.length > 1;
const menuClassName = `menu${!showTenantList && !themeMenuSection ? ' menu--simple' : ''}`;
const toggleTenantMenu = useCallback(() => { const toggleTenantMenu = useCallback(() => {
if (!tenantMenuOpen && tenants.length === 0 && onSelectTenant) { if (!tenantMenuOpen && tenants.length === 0 && onSelectTenant) {
@@ -426,50 +470,6 @@ const Sidebar = ({
); );
const rootNode = folderNodes.get('root'); 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) => handleNeutralHueChange(event.target.value)}
/>
<span className="sidebar-slider__value">{neutralHue}°</span>
</label>
</div>
)
: null;
return ( return (
<aside className="sidebar"> <aside className="sidebar">
<input <input
@@ -505,7 +505,7 @@ const Sidebar = ({
&& typeof document !== 'undefined' && typeof document !== 'undefined'
? createPortal( ? createPortal(
<div <div
className={`menu${showTenantList ? '' : ' menu--simple'}`} className={menuClassName}
ref={tenantMenuRef} ref={tenantMenuRef}
role="menu" role="menu"
aria-label="Account menu" aria-label="Account menu"
@@ -540,7 +540,7 @@ const Sidebar = ({
<div className="menu__footer"> <div className="menu__footer">
<button <button
type="button" type="button"
className="menu__settings" className="menu__button"
onClick={handleSettingsFromMenu} onClick={handleSettingsFromMenu}
> >
<SettingsIcon size={16} /> <SettingsIcon size={16} />
@@ -548,13 +548,14 @@ const Sidebar = ({
</button> </button>
<button <button
type="button" type="button"
className="menu__logout" className="menu__button menu__button--danger"
onClick={handleLogoutFromMenu} onClick={handleLogoutFromMenu}
> >
<LogoutIcon size={16} /> <LogoutIcon size={16} />
Log out Log out
</button> </button>
</div> </div>
{themeMenuSection}
</div>, </div>,
document.body, document.body,
) )
@@ -760,7 +761,6 @@ const Sidebar = ({
</ul> </ul>
</div> </div>
</div> </div>
{themeControls ? <div className="sidebar__footer">{themeControls}</div> : null}
</aside> </aside>
); );
}; };
+79 -24
View File
@@ -200,12 +200,23 @@
color: var(--muted); color: var(--muted);
} }
.sidebar__title-chevron {
transition: transform 0.2s ease; .sidebar__title-button .sidebar__title-chevron {
opacity: 0;
transform: translateY(-1px);
transition:
opacity 0.12s ease,
transform 0.2s ease;
}
.sidebar__title-button:hover .sidebar__title-chevron,
.sidebar__title-button:focus-visible .sidebar__title-chevron,
.sidebar__title-chevron.is-open {
opacity: 1;
} }
.sidebar__title-chevron.is-open { .sidebar__title-chevron.is-open {
transform: rotate(180deg); transform: rotate(180deg) translateY(-1px);
} }
.menu { .menu {
@@ -225,10 +236,10 @@
.menu__list { .menu__list {
max-height: 260px; max-height: 260px;
overflow-y: auto; overflow-y: auto;
padding: 0.35rem 0; padding: 0.35rem 0.5rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.15rem; gap: 0.25rem;
} }
.menu__heading { .menu__heading {
@@ -238,17 +249,25 @@
color: var(--muted); color: var(--muted);
} }
.menu__heading--with-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.menu button.menu__item, .menu button.menu__item,
.menu .menu__item { .menu .menu__item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
gap: 0.6rem; gap: 0.5rem;
width: 100%; width: 100%;
padding: 0.55rem 1rem; padding: 0.45rem 0.75rem;
border: none; border: none;
border-radius: 0.4rem;
background: none; background: none;
color: inherit; color: var(--sidebar-fg);
font: inherit; font: inherit;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
@@ -267,6 +286,7 @@
.menu .menu__item.active { .menu .menu__item.active {
font-weight: 600; font-weight: 600;
color: var(--accent); color: var(--accent);
background: var(--accent-soft);
} }
.menu button.menu__item:focus-visible, .menu button.menu__item:focus-visible,
@@ -313,7 +333,50 @@
border-top: none; border-top: none;
} }
.menu__settings {
.menu__section {
border-top: 1px solid var(--border);
padding: 0.35rem 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.menu__heading-actions {
display: inline-flex;
gap: 0.35rem;
}
.menu__slider {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8rem;
color: var(--muted);
padding: 0 1rem 0.25rem;
}
.menu__section > .menu__slider {
margin-top: -0.25rem;
}
.menu__slider input[type='range'] {
flex: 1;
accent-color: var(--accent);
}
.menu__slider-label {
min-width: 2.5rem;
}
.menu__slider-value {
font-variant-numeric: tabular-nums;
color: var(--fg);
min-width: 3rem;
text-align: right;
}
.menu__button {
width: 100%; width: 100%;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -325,30 +388,22 @@
cursor: pointer; cursor: pointer;
padding: 0.4rem 0.5rem; padding: 0.4rem 0.5rem;
border-radius: 0.35rem; border-radius: 0.35rem;
transition: background 0.15s ease, color 0.15s ease;
} }
.menu__settings:hover, .menu__button:hover,
.menu__settings:focus-visible { .menu__button:focus-visible {
background: var(--sidebar-hover-bg); background: var(--sidebar-hover-bg);
} }
.menu__logout { .menu__button--danger {
width: 100%;
display: inline-flex;
align-items: center;
gap: 0.4rem;
border: none;
background: none;
color: var(--danger); color: var(--danger);
font: inherit;
cursor: pointer;
padding: 0.4rem 0.5rem;
border-radius: 0.35rem;
} }
.menu__logout:hover, .menu__button--danger:hover,
.menu__logout:focus-visible { .menu__button--danger:focus-visible {
background: var(--surface-danger-subtle); background: var(--surface-danger-subtle);
color: var(--danger);
} }
.sidebar__search { .sidebar__search {