fixes
This commit is contained in:
@@ -4,11 +4,20 @@ set -euo pipefail
|
|||||||
API_PROXY_PASS_TRIMMED="${API_PROXY_PASS:-}"
|
API_PROXY_PASS_TRIMMED="${API_PROXY_PASS:-}"
|
||||||
API_PROXY_PASS_TRIMMED="${API_PROXY_PASS_TRIMMED%%/}"
|
API_PROXY_PASS_TRIMMED="${API_PROXY_PASS_TRIMMED%%/}"
|
||||||
|
|
||||||
cat <<'BASE' > /etc/nginx/conf.d/default.conf
|
MAX_BODY_SIZE_RAW="${UPLOAD_BODY_LIMIT_BYTES:-}"
|
||||||
|
if [ -n "$MAX_BODY_SIZE_RAW" ]; then
|
||||||
|
MAX_BODY_SIZE=$(printf '%sm' "$((MAX_BODY_SIZE_RAW / (1024 * 1024)))")
|
||||||
|
else
|
||||||
|
MAX_BODY_SIZE="128m"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<BASE > /etc/nginx/conf.d/default.conf
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
|
client_max_body_size ${MAX_BODY_SIZE};
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
@@ -21,6 +30,7 @@ if [ -n "$API_PROXY_PASS_TRIMMED" ]; then
|
|||||||
cat <<PROXY >> /etc/nginx/conf.d/default.conf
|
cat <<PROXY >> /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
client_max_body_size ${MAX_BODY_SIZE};
|
||||||
proxy_pass ${API_PROXY_PASS_TRIMMED};
|
proxy_pass ${API_PROXY_PASS_TRIMMED};
|
||||||
proxy_set_header Host \$host;
|
proxy_set_header Host \$host;
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
@@ -29,6 +39,7 @@ cat <<PROXY >> /etc/nginx/conf.d/default.conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /download/ {
|
location /download/ {
|
||||||
|
client_max_body_size ${MAX_BODY_SIZE};
|
||||||
proxy_pass ${API_PROXY_PASS_TRIMMED};
|
proxy_pass ${API_PROXY_PASS_TRIMMED};
|
||||||
proxy_set_header Host \$host;
|
proxy_set_header Host \$host;
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const DEFAULT_FOLDER_NAME = 'All Documents';
|
|||||||
const ROW_KEY_SEPARATOR = ':';
|
const ROW_KEY_SEPARATOR = ':';
|
||||||
const DOCUMENT_ROW_PREFIX = 'document';
|
const DOCUMENT_ROW_PREFIX = 'document';
|
||||||
const FOLDER_ROW_PREFIX = 'folder';
|
const FOLDER_ROW_PREFIX = 'folder';
|
||||||
|
const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
||||||
|
|
||||||
const resolveApiPath = (path = '') => path;
|
const resolveApiPath = (path = '') => path;
|
||||||
|
|
||||||
@@ -197,22 +198,66 @@ 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(() => {
|
|
||||||
|
const loadThemeSettings = () => {
|
||||||
|
const defaults = { neutralHue: 260, mode: 'system' };
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return 260;
|
if (typeof document !== 'undefined') {
|
||||||
}
|
const current = document.documentElement.style.getPropertyValue('--neutral-hue');
|
||||||
const stored = window.localStorage.getItem('papercrate_neutral_hue');
|
const parsed = Number.parseInt(current, 10);
|
||||||
if (stored) {
|
return {
|
||||||
const parsedStored = Number.parseInt(stored, 10);
|
neutralHue: Number.isNaN(parsed) ? defaults.neutralHue : parsed,
|
||||||
if (!Number.isNaN(parsedStored)) {
|
mode: defaults.mode,
|
||||||
return parsedStored;
|
};
|
||||||
}
|
}
|
||||||
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
|
const loadFromRoot = () => {
|
||||||
const current = root.style.getPropertyValue('--neutral-hue');
|
const current = root.style.getPropertyValue('--neutral-hue');
|
||||||
const parsed = Number.parseInt(current, 10);
|
const parsed = Number.parseInt(current, 10);
|
||||||
return Number.isNaN(parsed) ? 260 : parsed;
|
return Number.isNaN(parsed) ? defaults.neutralHue : parsed;
|
||||||
});
|
};
|
||||||
|
|
||||||
|
let neutralHueValue = loadFromRoot();
|
||||||
|
let modeValue = defaults.mode;
|
||||||
|
|
||||||
|
const composite = window.localStorage.getItem(THEME_STORAGE_KEY);
|
||||||
|
if (composite) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(composite);
|
||||||
|
const storedHue = Number.parseInt(parsed?.neutralHue, 10);
|
||||||
|
if (!Number.isNaN(storedHue)) {
|
||||||
|
neutralHueValue = storedHue;
|
||||||
|
}
|
||||||
|
const storedMode = parsed?.mode;
|
||||||
|
if (storedMode === 'light' || storedMode === 'dark' || storedMode === 'system') {
|
||||||
|
modeValue = storedMode;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[theme] failed to parse stored theme settings', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const legacyHue = window.localStorage.getItem('papercrate_neutral_hue');
|
||||||
|
if (legacyHue) {
|
||||||
|
const parsedLegacyHue = Number.parseInt(legacyHue, 10);
|
||||||
|
if (!Number.isNaN(parsedLegacyHue)) {
|
||||||
|
neutralHueValue = parsedLegacyHue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const legacyMode = window.localStorage.getItem('papercrate_theme_mode');
|
||||||
|
if (legacyMode === 'light' || legacyMode === 'dark' || legacyMode === 'system') {
|
||||||
|
modeValue = legacyMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { neutralHue: neutralHueValue, mode: modeValue };
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialThemeSettings = loadThemeSettings();
|
||||||
|
const [neutralHue, setNeutralHue] = useState(initialThemeSettings.neutralHue);
|
||||||
|
const [themeMode, setThemeMode] = useState(initialThemeSettings.mode);
|
||||||
const initialRowSelection = [];
|
const initialRowSelection = [];
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState(initialRowSelection);
|
const [selectedRowKeys, setSelectedRowKeys] = useState(initialRowSelection);
|
||||||
const [selectionOrder, setSelectionOrder] = useState(initialRowSelection);
|
const [selectionOrder, setSelectionOrder] = useState(initialRowSelection);
|
||||||
@@ -314,11 +359,33 @@ const AppLayout = () => {
|
|||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
document.documentElement.style.setProperty('--neutral-hue', `${neutralHue}deg`);
|
document.documentElement.style.setProperty('--neutral-hue', `${neutralHue}deg`);
|
||||||
}
|
}
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.localStorage.setItem('papercrate_neutral_hue', String(neutralHue));
|
|
||||||
}
|
|
||||||
}, [neutralHue]);
|
}, [neutralHue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
const root = document.documentElement;
|
||||||
|
if (themeMode === 'system') {
|
||||||
|
root.removeAttribute('data-theme');
|
||||||
|
} else {
|
||||||
|
root.setAttribute('data-theme', themeMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [themeMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const payload = JSON.stringify({ neutralHue, mode: themeMode });
|
||||||
|
window.localStorage.setItem(THEME_STORAGE_KEY, payload);
|
||||||
|
window.localStorage.removeItem('papercrate_neutral_hue');
|
||||||
|
window.localStorage.removeItem('papercrate_theme_mode');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[theme] failed to persist theme settings', error);
|
||||||
|
}
|
||||||
|
}, [neutralHue, themeMode]);
|
||||||
|
|
||||||
const handleNeutralHueChange = useCallback((value) => {
|
const handleNeutralHueChange = useCallback((value) => {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
setNeutralHue(260);
|
setNeutralHue(260);
|
||||||
@@ -331,6 +398,13 @@ const AppLayout = () => {
|
|||||||
setNeutralHue(parsed);
|
setNeutralHue(parsed);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleThemeModeChange = useCallback((mode) => {
|
||||||
|
if (mode !== 'light' && mode !== 'dark' && mode !== 'system') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setThemeMode(mode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const clearFilters = useCallback(() => {
|
const clearFilters = useCallback(() => {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
setActiveTagFilters([]);
|
setActiveTagFilters([]);
|
||||||
@@ -4764,6 +4838,8 @@ const AppLayout = () => {
|
|||||||
creatingFolder,
|
creatingFolder,
|
||||||
onNeutralHueChange: handleNeutralHueChange,
|
onNeutralHueChange: handleNeutralHueChange,
|
||||||
neutralHue,
|
neutralHue,
|
||||||
|
themeMode,
|
||||||
|
onThemeModeChange: handleThemeModeChange,
|
||||||
tags,
|
tags,
|
||||||
activeTagIds: activeTagFilters,
|
activeTagIds: activeTagFilters,
|
||||||
onToggleTagFilter: toggleTagFilter,
|
onToggleTagFilter: toggleTagFilter,
|
||||||
@@ -4824,6 +4900,8 @@ const AppLayout = () => {
|
|||||||
creatingFolder,
|
creatingFolder,
|
||||||
handleNeutralHueChange,
|
handleNeutralHueChange,
|
||||||
neutralHue,
|
neutralHue,
|
||||||
|
themeMode,
|
||||||
|
handleThemeModeChange,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState, useId } from 'react';
|
||||||
import {
|
import {
|
||||||
ChevronIcon,
|
ChevronIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
PlusIcon,
|
PlusIcon,
|
||||||
FolderPlusIcon,
|
FolderPlusIcon,
|
||||||
RestoreIcon,
|
RestoreIcon,
|
||||||
|
SunIcon,
|
||||||
|
MoonIcon,
|
||||||
|
DesktopIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
import PanelHeader from '../ui/PanelHeader';
|
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 = ({
|
const Sidebar = ({
|
||||||
folderNodes,
|
folderNodes,
|
||||||
onToggle,
|
onToggle,
|
||||||
@@ -146,6 +156,8 @@ const Sidebar = ({
|
|||||||
creatingFolder = false,
|
creatingFolder = false,
|
||||||
onNeutralHueChange,
|
onNeutralHueChange,
|
||||||
neutralHue,
|
neutralHue,
|
||||||
|
themeMode = 'system',
|
||||||
|
onThemeModeChange,
|
||||||
tags = [],
|
tags = [],
|
||||||
activeTagIds = [],
|
activeTagIds = [],
|
||||||
onToggleTagFilter,
|
onToggleTagFilter,
|
||||||
@@ -170,6 +182,7 @@ const Sidebar = ({
|
|||||||
onSelectTenant,
|
onSelectTenant,
|
||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
}) => {
|
}) => {
|
||||||
|
const neutralHueInputId = useId();
|
||||||
const sortedCorrespondents = useMemo(() => {
|
const sortedCorrespondents = useMemo(() => {
|
||||||
if (!Array.isArray(correspondents)) {
|
if (!Array.isArray(correspondents)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -230,6 +243,28 @@ const Sidebar = ({
|
|||||||
onNeutralHueChange?.('');
|
onNeutralHueChange?.('');
|
||||||
}, [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(
|
const handleSearchInputChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
onSearchChange?.(event.target.value);
|
onSearchChange?.(event.target.value);
|
||||||
@@ -350,6 +385,50 @@ 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) => onNeutralHueChange?.(event.target.value)}
|
||||||
|
/>
|
||||||
|
<span className="sidebar-slider__value">{neutralHue}°</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<PanelHeader
|
<PanelHeader
|
||||||
@@ -606,36 +685,8 @@ 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>
|
||||||
|
{themeControls ? <div className="sidebar__footer">{themeControls}</div> : null}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+98
-7
@@ -96,8 +96,88 @@
|
|||||||
--documents-grid-title-size: 0.8rem;
|
--documents-grid-title-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[data-theme='light'] {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] {
|
||||||
|
color-scheme: dark;
|
||||||
|
|
||||||
|
--dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
|
||||||
|
--dark-foreground-hue: calc(var(--neutral-hue) + var(--dark-foreground-hue-offset));
|
||||||
|
|
||||||
|
--bg: oklch(0.15 0.01 var(--dark-neutral-hue));
|
||||||
|
--surface: oklch(0.19 0.012 var(--dark-neutral-hue));
|
||||||
|
--surface-subtle: oklch(0.23 0.012 var(--dark-neutral-hue));
|
||||||
|
--fg: oklch(0.89 0.015 var(--dark-foreground-hue));
|
||||||
|
--muted: oklch(0.72 0.02 var(--dark-foreground-hue));
|
||||||
|
--sidebar-fg: oklch(0.78 0.02 var(--dark-foreground-hue));
|
||||||
|
--border: oklch(0.33 0.01 var(--dark-foreground-hue));
|
||||||
|
|
||||||
|
--accent: oklch(0.75 0.2 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
|
||||||
|
--accent-hover: oklch(0.82 0.16 calc(var(--dark-foreground-hue) + var(--accent-hue-offset)));
|
||||||
|
--on-accent: oklch(0.15 0.015 var(--dark-foreground-hue));
|
||||||
|
|
||||||
|
--folder-icon-back: color-mix(in oklch, var(--accent) 72%, black 12%);
|
||||||
|
--folder-icon-mid: color-mix(in oklch, var(--accent) 50%, white 24%);
|
||||||
|
--folder-icon-front: color-mix(in oklch, var(--accent) 30%, white 50%);
|
||||||
|
|
||||||
|
--accent-soft: color-mix(in oklch, var(--accent) 22%, transparent);
|
||||||
|
--accent-elevated: color-mix(in oklch, var(--accent) 28%, transparent);
|
||||||
|
--accent-elevated-strong: color-mix(in oklch, var(--accent) 38%, transparent);
|
||||||
|
--accent-outline: color-mix(in oklch, var(--accent) 55%, transparent);
|
||||||
|
--accent-outline-strong: color-mix(in oklch, var(--accent) 90%, transparent);
|
||||||
|
--accent-focus: color-mix(in oklch, var(--accent) 80%, transparent);
|
||||||
|
--surface-overlay: color-mix(in oklch, black 60%, transparent);
|
||||||
|
|
||||||
|
--selection: oklch(0.44 0.04 220deg);
|
||||||
|
--selection-soft: color-mix(in oklch, var(--selection) 18%, transparent);
|
||||||
|
|
||||||
|
--shadow-faint: color-mix(in oklch, black 35%, transparent);
|
||||||
|
--shadow-soft: color-mix(in oklch, black 50%, transparent);
|
||||||
|
--shadow-medium: color-mix(in oklch, black 65%, transparent);
|
||||||
|
--shadow-strong: color-mix(in oklch, black 80%, transparent);
|
||||||
|
--shadow-deep: color-mix(in oklch, black 90%, transparent);
|
||||||
|
--shadow-pop: color-mix(in oklch, black 70%, transparent);
|
||||||
|
--outline-subtle: color-mix(in oklch, white 10%, transparent);
|
||||||
|
--overlay-dark: color-mix(in oklch, black 70%, transparent);
|
||||||
|
--overlay-dim: color-mix(in oklch, oklch(0.42 0.04 var(--dark-neutral-hue)) 35%, transparent);
|
||||||
|
--overlay-darker: color-mix(in oklch, black 85%, transparent);
|
||||||
|
--surface-danger-subtle: color-mix(in oklch, var(--danger) 26%, transparent);
|
||||||
|
--overlay-accent-subtle: color-mix(in oklch, var(--accent) 32%, transparent);
|
||||||
|
--border-strong: color-mix(in oklch, var(--fg) 30%, transparent);
|
||||||
|
--surface-hover: color-mix(in oklch, white 6%, transparent);
|
||||||
|
--overlay-accent-strong: color-mix(in oklch, var(--accent) 55%, transparent);
|
||||||
|
--overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
|
||||||
|
--overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
|
||||||
|
|
||||||
|
--success: oklch(0.62 0.16 var(--success-hue));
|
||||||
|
--warning: oklch(0.68 0.17 var(--warning-hue));
|
||||||
|
--danger: oklch(0.60 0.2 var(--danger-hue));
|
||||||
|
|
||||||
|
--success-subtle: color-mix(in oklch, var(--success) 20%, transparent);
|
||||||
|
--warning-subtle: color-mix(in oklch, var(--warning) 20%, transparent);
|
||||||
|
--danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
|
||||||
|
--danger-soft: color-mix(in oklch, var(--danger) 24%, transparent);
|
||||||
|
--danger-subtle: color-mix(in oklch, var(--danger) 18%, transparent);
|
||||||
|
|
||||||
|
--row-hover-bg: color-mix(in oklch, white 4%, transparent);
|
||||||
|
--row-active-bg: color-mix(in oklch, var(--selection) 20%, transparent);
|
||||||
|
--sidebar-hover-bg: color-mix(in oklch, white 6%, transparent);
|
||||||
|
--sidebar-active-bg: color-mix(in oklch, var(--selection) 26%, transparent);
|
||||||
|
--selection-ring: oklch(0.68 0.16 var(--dark-foreground-hue));
|
||||||
|
--sidebar-active-pill-border: color-mix(in oklch, var(--accent) 72%, transparent);
|
||||||
|
--link: color-mix(in oklch, var(--accent) 92%, transparent);
|
||||||
|
--link-visited: color-mix(in oklch, var(--accent) 70%, white 20%);
|
||||||
|
--preview-nav-bg: oklch(0.3 0.04 var(--dark-neutral-hue));
|
||||||
|
--preview-nav-bg-hover: oklch(0.35 0.04 var(--dark-neutral-hue));
|
||||||
|
--preview-nav-fg: var(--fg);
|
||||||
|
--text-on-dark: color-mix(in oklch, white 92%, transparent);
|
||||||
|
--surface-ink-soft: color-mix(in oklch, white 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root:not([data-theme='light']) {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
--dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
|
--dark-neutral-hue: calc(var(--neutral-hue) + var(--dark-hue-offset));
|
||||||
@@ -1201,7 +1281,7 @@ button.danger:hover:not([disabled]) {
|
|||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
overflow-y: auto;
|
overflow: hidden;
|
||||||
color: var(--sidebar-fg);
|
color: var(--sidebar-fg);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1216,6 +1296,18 @@ button.danger:hover:not([disabled]) {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 0.75rem 1rem 1rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__title {
|
.sidebar__title {
|
||||||
@@ -1433,13 +1525,12 @@ button.danger:hover:not([disabled]) {
|
|||||||
background: var(--sidebar-hover-bg);
|
background: var(--sidebar-hover-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__footer {
|
.sidebar__footer .sidebar-section {
|
||||||
margin-top: auto;
|
margin-top: 0;
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__footer button {
|
.sidebar__footer .sidebar-section__actions {
|
||||||
width: 100%;
|
gap: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section:first-of-type,
|
.sidebar-section:first-of-type,
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import {
|
|||||||
IconSettings,
|
IconSettings,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
|
IconSun,
|
||||||
|
IconMoon,
|
||||||
|
IconDeviceLaptop,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import FolderSvg from '../assets/folder.svg';
|
import FolderSvg from '../assets/folder.svg';
|
||||||
|
|
||||||
@@ -226,6 +229,33 @@ export const PlusIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) =>
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SunIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconSun
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MoonIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconMoon
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DesktopIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconDeviceLaptop
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
export const CheckIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const CheckIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconCheck
|
<IconCheck
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user