mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Merge desktop zoom settings polish into main
Bring the verified settings zoom interaction cleanup from the desktop worktree onto local main while preserving the existing main history. Constraint: Local main already contains unrelated desktop commits after the worktree base. Confidence: high Scope-risk: narrow Tested: bunx vitest run src/__tests__/generalSettings.test.tsx src/theme/globals.test.ts Tested: browser smoke verified dark-theme order and slider visibility at localhost:5174 Not-tested: Full bun run verify after merge
This commit is contained in:
commit
57e66f0019
@ -255,18 +255,48 @@ describe('Settings > General tab', () => {
|
||||
expect(screen.getByRole('button', { name: 'Warm Classic' })).toHaveAttribute('aria-pressed', 'false')
|
||||
})
|
||||
|
||||
it('updates the shared UI zoom setting from the General display control', async () => {
|
||||
it('keeps UI zoom below system notifications because it is a secondary setting', () => {
|
||||
render(<Settings />)
|
||||
|
||||
fireEvent.click(screen.getByText('General'))
|
||||
|
||||
const notificationsHeading = screen.getByRole('heading', { name: 'System Notifications' })
|
||||
const uiZoomHeading = screen.getByRole('heading', { name: 'UI Zoom' })
|
||||
const webFetchHeading = screen.getByRole('heading', { name: 'WebFetch Preflight' })
|
||||
|
||||
expect((notificationsHeading.compareDocumentPosition(uiZoomHeading) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
|
||||
expect((uiZoomHeading.compareDocumentPosition(webFetchHeading) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
|
||||
})
|
||||
|
||||
it('previews UI zoom while dragging and applies it only when the user releases the slider', async () => {
|
||||
render(<Settings />)
|
||||
|
||||
fireEvent.click(screen.getByText('General'))
|
||||
expect(screen.getByText('Shortcuts are faster:')).toBeInTheDocument()
|
||||
expect(screen.getByText('macOS')).toBeInTheDocument()
|
||||
expect(screen.getByText('Windows / Linux')).toBeInTheDocument()
|
||||
expect(screen.getByText('0 resets zoom to 100%.')).toBeInTheDocument()
|
||||
|
||||
const slider = screen.getByLabelText('UI Zoom')
|
||||
|
||||
fireEvent.pointerDown(slider, { pointerId: 1 })
|
||||
await act(async () => {
|
||||
fireEvent.change(screen.getByLabelText('UI Zoom'), {
|
||||
fireEvent.change(slider, {
|
||||
target: { value: '1.25', valueAsNumber: 1.25 },
|
||||
})
|
||||
})
|
||||
|
||||
expect(screen.getAllByText('125%')).toHaveLength(2)
|
||||
expect(useSettingsStore.getState().setUiZoom).not.toHaveBeenCalledWith(1.25)
|
||||
expect(slider).toHaveClass('settings-zoom-range')
|
||||
expect(slider.closest('.settings-zoom-control')).toHaveClass('is-dragging')
|
||||
expect(slider.closest('.settings-zoom-control')).toHaveStyle({ '--settings-zoom-range-progress': '50%' })
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.pointerUp(slider, { pointerId: 1 })
|
||||
})
|
||||
|
||||
expect(useSettingsStore.getState().setUiZoom).toHaveBeenCalledWith(1.25)
|
||||
expect(screen.getByText('125%')).toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Reset UI zoom to 100%' }))
|
||||
|
||||
@ -807,6 +807,10 @@ export const en = {
|
||||
'settings.general.webSearchSave': 'Save',
|
||||
'settings.general.uiZoom': 'UI Zoom',
|
||||
'settings.general.uiZoomDescription': 'Adjust the size of the entire interface.',
|
||||
'settings.general.uiZoomShortcutHint': 'Shortcuts are faster:',
|
||||
'settings.general.uiZoomShortcutMac': 'macOS',
|
||||
'settings.general.uiZoomShortcutWindows': 'Windows / Linux',
|
||||
'settings.general.uiZoomShortcutResetHint': '0 resets zoom to 100%.',
|
||||
'settings.general.uiZoomReset': 'Reset UI zoom to 100%',
|
||||
|
||||
// ─── Empty Session ──────────────────────────────────────
|
||||
|
||||
@ -809,6 +809,10 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'settings.general.webSearchSave': '保存',
|
||||
'settings.general.uiZoom': '界面缩放',
|
||||
'settings.general.uiZoomDescription': '调整整个界面的显示大小。',
|
||||
'settings.general.uiZoomShortcutHint': '快捷键更直接:',
|
||||
'settings.general.uiZoomShortcutMac': 'macOS',
|
||||
'settings.general.uiZoomShortcutWindows': 'Windows / Linux',
|
||||
'settings.general.uiZoomShortcutResetHint': '0 表示恢复到 100%。',
|
||||
'settings.general.uiZoomReset': '重置界面缩放到 100%',
|
||||
|
||||
// ─── Empty Session ──────────────────────────────────────
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo, useRef, type ReactNode } from 'react'
|
||||
import { useState, useEffect, useMemo, useRef, type CSSProperties, type ReactNode } from 'react'
|
||||
import QRCode from 'qrcode'
|
||||
import { Copy, Eye, EyeOff, PowerOff, QrCode, RotateCw } from 'lucide-react'
|
||||
import { useSettingsStore, UI_ZOOM_DEFAULT, UI_ZOOM_MIN, UI_ZOOM_MAX, UI_ZOOM_STEP } from '../stores/settingsStore'
|
||||
@ -1392,12 +1392,22 @@ function GeneralSettings() {
|
||||
const [webSearchDraft, setWebSearchDraft] = useState(webSearch)
|
||||
const [notificationPermission, setNotificationPermission] = useState<DesktopNotificationPermission>('default')
|
||||
const [notificationActionRunning, setNotificationActionRunning] = useState(false)
|
||||
const [uiZoomDraft, setUiZoomDraft] = useState(uiZoom)
|
||||
const [isUiZoomDragging, setIsUiZoomDragging] = useState(false)
|
||||
const webSearchDirty = JSON.stringify(webSearchDraft) !== JSON.stringify(webSearch)
|
||||
const uiZoomPercent = Math.round(uiZoomDraft * 100)
|
||||
const uiZoomRangeProgress = `${((uiZoomDraft - UI_ZOOM_MIN) / (UI_ZOOM_MAX - UI_ZOOM_MIN)) * 100}%`
|
||||
|
||||
useEffect(() => {
|
||||
setWebSearchDraft(webSearch)
|
||||
}, [webSearch])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUiZoomDragging) {
|
||||
setUiZoomDraft(uiZoom)
|
||||
}
|
||||
}, [isUiZoomDragging, uiZoom])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
getDesktopNotificationPermission().then((permission) => {
|
||||
@ -1513,6 +1523,107 @@ function GeneralSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
const commitUiZoom = (value: number) => {
|
||||
const nextZoom = Number.isFinite(value) ? value : UI_ZOOM_DEFAULT
|
||||
setIsUiZoomDragging(false)
|
||||
setUiZoomDraft(nextZoom)
|
||||
setUiZoom(nextZoom)
|
||||
}
|
||||
|
||||
const uiZoomSection = (
|
||||
<div className="mt-8">
|
||||
<div className="mb-3 flex items-end justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-base font-semibold text-[var(--color-text-primary)] mb-1">{t('settings.general.uiZoom')}</h2>
|
||||
<p className="text-sm text-[var(--color-text-tertiary)]">{t('settings.general.uiZoomDescription')}</p>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-[var(--color-text-tertiary)]">
|
||||
<span>{t('settings.general.uiZoomShortcutHint')}</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span className="font-medium text-[var(--color-text-secondary)]">{t('settings.general.uiZoomShortcutMac')}</span>
|
||||
<kbd className="settings-zoom-kbd">⌘</kbd>
|
||||
<kbd className="settings-zoom-kbd">+</kbd>
|
||||
<span>/</span>
|
||||
<kbd className="settings-zoom-kbd">⌘</kbd>
|
||||
<kbd className="settings-zoom-kbd">-</kbd>
|
||||
<span>/</span>
|
||||
<kbd className="settings-zoom-kbd">⌘</kbd>
|
||||
<kbd className="settings-zoom-kbd">0</kbd>
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span className="font-medium text-[var(--color-text-secondary)]">{t('settings.general.uiZoomShortcutWindows')}</span>
|
||||
<kbd className="settings-zoom-kbd">Ctrl</kbd>
|
||||
<kbd className="settings-zoom-kbd">+</kbd>
|
||||
<span>/</span>
|
||||
<kbd className="settings-zoom-kbd">Ctrl</kbd>
|
||||
<kbd className="settings-zoom-kbd">-</kbd>
|
||||
<span>/</span>
|
||||
<kbd className="settings-zoom-kbd">Ctrl</kbd>
|
||||
<kbd className="settings-zoom-kbd">0</kbd>
|
||||
</span>
|
||||
<span>{t('settings.general.uiZoomShortcutResetHint')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<span className="min-w-[48px] rounded-md bg-[var(--color-surface-container-low)] px-2 py-1 text-center text-sm font-medium text-[var(--color-text-secondary)]">
|
||||
{uiZoomPercent}%
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('settings.general.uiZoomReset')}
|
||||
title={t('settings.general.uiZoomReset')}
|
||||
onClick={() => {
|
||||
setIsUiZoomDragging(false)
|
||||
setUiZoomDraft(UI_ZOOM_DEFAULT)
|
||||
setUiZoom(UI_ZOOM_DEFAULT)
|
||||
}}
|
||||
className="inline-flex h-8 items-center gap-1.5 rounded-md border border-[var(--color-border)] px-2 text-xs font-medium text-[var(--color-text-secondary)] transition-colors hover:border-[var(--color-border-focus)] hover:bg-[var(--color-surface-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)]"
|
||||
>
|
||||
<RotateCw className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
100%
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`settings-zoom-control flex items-center gap-3 ${isUiZoomDragging ? 'is-dragging' : ''}`}
|
||||
style={{ '--settings-zoom-range-progress': uiZoomRangeProgress } as CSSProperties}
|
||||
>
|
||||
<span className="w-9 text-right text-xs text-[var(--color-text-tertiary)]">{Math.round(UI_ZOOM_MIN * 100)}%</span>
|
||||
<div className="settings-zoom-range-wrap flex-1">
|
||||
<div className="settings-zoom-preview" aria-hidden="true">
|
||||
{uiZoomPercent}%
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
aria-label={t('settings.general.uiZoom')}
|
||||
min={UI_ZOOM_MIN}
|
||||
max={UI_ZOOM_MAX}
|
||||
step={UI_ZOOM_STEP}
|
||||
value={uiZoomDraft}
|
||||
onPointerDown={(e) => {
|
||||
setIsUiZoomDragging(true)
|
||||
e.currentTarget.setPointerCapture?.(e.pointerId)
|
||||
}}
|
||||
onPointerUp={(e) => commitUiZoom(e.currentTarget.valueAsNumber)}
|
||||
onPointerCancel={(e) => commitUiZoom(e.currentTarget.valueAsNumber)}
|
||||
onChange={(e) => setUiZoomDraft(e.currentTarget.valueAsNumber)}
|
||||
onKeyUp={(e) => {
|
||||
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(e.key)) {
|
||||
commitUiZoom(e.currentTarget.valueAsNumber)
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (uiZoomDraft !== uiZoom) {
|
||||
commitUiZoom(e.currentTarget.valueAsNumber)
|
||||
}
|
||||
}}
|
||||
className="settings-zoom-range w-full"
|
||||
/>
|
||||
</div>
|
||||
<span className="w-9 text-xs text-[var(--color-text-tertiary)]">{Math.round(UI_ZOOM_MAX * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="max-w-xl">
|
||||
{/* Appearance selector */}
|
||||
@ -1535,45 +1646,6 @@ function GeneralSettings() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* UI Zoom */}
|
||||
<div className="mb-8">
|
||||
<div className="mb-3 flex items-end justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-base font-semibold text-[var(--color-text-primary)] mb-1">{t('settings.general.uiZoom')}</h2>
|
||||
<p className="text-sm text-[var(--color-text-tertiary)]">{t('settings.general.uiZoomDescription')}</p>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<span className="min-w-[48px] rounded-md bg-[var(--color-surface-container-low)] px-2 py-1 text-center text-sm font-medium text-[var(--color-text-secondary)]">
|
||||
{Math.round(uiZoom * 100)}%
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('settings.general.uiZoomReset')}
|
||||
title={t('settings.general.uiZoomReset')}
|
||||
onClick={() => setUiZoom(UI_ZOOM_DEFAULT)}
|
||||
className="inline-flex h-8 items-center gap-1.5 rounded-md border border-[var(--color-border)] px-2 text-xs font-medium text-[var(--color-text-secondary)] transition-colors hover:border-[var(--color-border-focus)] hover:bg-[var(--color-surface-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)]"
|
||||
>
|
||||
<RotateCw className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
100%
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-9 text-right text-xs text-[var(--color-text-tertiary)]">{Math.round(UI_ZOOM_MIN * 100)}%</span>
|
||||
<input
|
||||
type="range"
|
||||
aria-label={t('settings.general.uiZoom')}
|
||||
min={UI_ZOOM_MIN}
|
||||
max={UI_ZOOM_MAX}
|
||||
step={UI_ZOOM_STEP}
|
||||
value={uiZoom}
|
||||
onChange={(e) => setUiZoom(e.currentTarget.valueAsNumber)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="w-9 text-xs text-[var(--color-text-tertiary)]">{Math.round(UI_ZOOM_MAX * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Language selector */}
|
||||
<h2 className="text-base font-semibold text-[var(--color-text-primary)] mb-1">{t('settings.general.languageTitle')}</h2>
|
||||
<p className="text-sm text-[var(--color-text-tertiary)] mb-3">{t('settings.general.languageDescription')}</p>
|
||||
@ -1704,6 +1776,8 @@ function GeneralSettings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{uiZoomSection}
|
||||
|
||||
<div className="mt-8">
|
||||
<h2 className="text-base font-semibold text-[var(--color-text-primary)] mb-1">{t('settings.general.webFetchPreflightTitle')}</h2>
|
||||
<p className="text-sm text-[var(--color-text-tertiary)] mb-3">{t('settings.general.webFetchPreflightDescription')}</p>
|
||||
|
||||
@ -108,6 +108,194 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settings-zoom-kbd {
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 5px;
|
||||
background: var(--color-surface-container-lowest);
|
||||
color: var(--color-text-secondary);
|
||||
font-family: var(--font-label);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
box-shadow: inset 0 -1px 0 rgba(27, 28, 26, 0.08);
|
||||
}
|
||||
|
||||
.settings-zoom-control {
|
||||
--settings-zoom-range-progress: 33.333%;
|
||||
--settings-zoom-preview-scale: 0.88;
|
||||
--settings-zoom-preview-opacity: 0;
|
||||
--settings-zoom-track-empty: var(--color-surface-container-high);
|
||||
--settings-zoom-thumb-bg: var(--color-surface-container-lowest);
|
||||
--settings-zoom-thumb-border: var(--color-border);
|
||||
--settings-zoom-thumb-shadow: 0 2px 8px rgba(27, 28, 26, 0.16);
|
||||
padding-top: 22px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .settings-zoom-control {
|
||||
--settings-zoom-track-empty: var(--color-surface-container-highest);
|
||||
--settings-zoom-thumb-bg: var(--color-surface-bright);
|
||||
--settings-zoom-thumb-border: rgba(255, 181, 159, 0.78);
|
||||
--settings-zoom-thumb-shadow: 0 0 0 3px rgba(255, 181, 159, 0.16), 0 10px 24px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.settings-zoom-control.is-dragging {
|
||||
--settings-zoom-preview-scale: 1;
|
||||
--settings-zoom-preview-opacity: 1;
|
||||
}
|
||||
|
||||
.settings-zoom-range-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-zoom-preview {
|
||||
position: absolute;
|
||||
left: clamp(24px, var(--settings-zoom-range-progress), calc(100% - 24px));
|
||||
top: -30px;
|
||||
z-index: 1;
|
||||
min-width: 52px;
|
||||
transform: translateX(-50%) scale(var(--settings-zoom-preview-scale));
|
||||
transform-origin: 50% 100%;
|
||||
border: 1px solid var(--color-surface-glass-border);
|
||||
border-radius: 999px;
|
||||
background: var(--color-surface-glass);
|
||||
padding: 4px 9px;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
opacity: var(--settings-zoom-preview-opacity);
|
||||
box-shadow: var(--shadow-dropdown);
|
||||
backdrop-filter: blur(14px) saturate(1.15);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.15);
|
||||
pointer-events: none;
|
||||
transition:
|
||||
left 260ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
opacity 160ms ease,
|
||||
transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.settings-zoom-preview::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -4px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
border-right: 1px solid var(--color-surface-glass-border);
|
||||
border-bottom: 1px solid var(--color-surface-glass-border);
|
||||
background: var(--color-surface-glass);
|
||||
}
|
||||
|
||||
.settings-zoom-range {
|
||||
appearance: none;
|
||||
display: block;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
accent-color: var(--color-brand);
|
||||
}
|
||||
|
||||
.settings-zoom-range:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settings-zoom-range:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settings-zoom-range::-webkit-slider-runnable-track {
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--color-border);
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
var(--color-brand) 0%,
|
||||
var(--color-brand) var(--settings-zoom-range-progress),
|
||||
var(--settings-zoom-track-empty) var(--settings-zoom-range-progress),
|
||||
var(--settings-zoom-track-empty) 100%
|
||||
);
|
||||
transition: background 220ms ease, border-color 180ms ease;
|
||||
}
|
||||
|
||||
.settings-zoom-range::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-top: -7px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--settings-zoom-thumb-border);
|
||||
background: var(--settings-zoom-thumb-bg);
|
||||
box-shadow: var(--settings-zoom-thumb-shadow);
|
||||
transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.settings-zoom-range:hover::-webkit-slider-thumb {
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
.settings-zoom-control.is-dragging .settings-zoom-range::-webkit-slider-thumb {
|
||||
transform: scale(1.18);
|
||||
border-color: var(--color-brand);
|
||||
box-shadow: var(--shadow-dropdown);
|
||||
}
|
||||
|
||||
.settings-zoom-range:focus-visible::-webkit-slider-thumb {
|
||||
border-color: var(--color-brand);
|
||||
box-shadow: var(--shadow-focus-ring), 0 2px 8px rgba(27, 28, 26, 0.16);
|
||||
}
|
||||
|
||||
.settings-zoom-range::-moz-range-track {
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--settings-zoom-track-empty);
|
||||
transition: background 220ms ease, border-color 180ms ease;
|
||||
}
|
||||
|
||||
.settings-zoom-range::-moz-range-progress {
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: var(--color-brand);
|
||||
}
|
||||
|
||||
.settings-zoom-range::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--settings-zoom-thumb-border);
|
||||
background: var(--settings-zoom-thumb-bg);
|
||||
box-shadow: var(--settings-zoom-thumb-shadow);
|
||||
transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.settings-zoom-control.is-dragging .settings-zoom-range::-moz-range-thumb {
|
||||
transform: scale(1.18);
|
||||
border-color: var(--color-brand);
|
||||
box-shadow: var(--shadow-dropdown);
|
||||
}
|
||||
|
||||
.settings-zoom-range:focus-visible::-moz-range-thumb {
|
||||
border-color: var(--color-brand);
|
||||
box-shadow: var(--shadow-focus-ring), 0 2px 8px rgba(27, 28, 26, 0.16);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.settings-zoom-preview,
|
||||
.settings-zoom-range::-webkit-slider-runnable-track,
|
||||
.settings-zoom-range::-webkit-slider-thumb,
|
||||
.settings-zoom-range::-moz-range-track,
|
||||
.settings-zoom-range::-moz-range-thumb {
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Tailwind Theme Override (matches prototype design system) ─── */
|
||||
@theme {
|
||||
--color-primary: #8F482F;
|
||||
|
||||
@ -66,4 +66,11 @@ describe('desktop theme tokens', () => {
|
||||
it('avoids color-mix in startup-critical shell chrome for Safari 15 WebView support', () => {
|
||||
expect(css).not.toContain('color-mix(')
|
||||
})
|
||||
|
||||
it('keeps the UI zoom slider thumb visible in dark mode', () => {
|
||||
expect(css).toContain('[data-theme="dark"] .settings-zoom-control')
|
||||
expect(css).toContain('--settings-zoom-thumb-bg: var(--color-surface-bright);')
|
||||
expect(css).toContain('--settings-zoom-thumb-border: rgba(255, 181, 159, 0.78);')
|
||||
expect(css).toContain('box-shadow: var(--settings-zoom-thumb-shadow);')
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user