@askrjs/themes 0.0.27 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THEMING.md +26 -0
- package/dist/components/theme/theme.js +5 -3
- package/dist/components/theme/theme.js.map +1 -1
- package/dist/themes/default/index.css +85 -30
- package/package.json +7 -6
- package/src/components/theme/theme.tsx +10 -2
- package/src/themes/default/index.css +1 -0
- package/src/themes/default/styles/actions/button.css +8 -1
- package/src/themes/default/styles/base/accessibility.css +88 -0
- package/src/themes/default/styles/forms/input.css +3 -2
- package/src/themes/default/styles/overlays/dialog.css +1 -1
- package/src/themes/default/styles/overlays/dropdown.css +4 -3
- package/src/themes/default/styles/overlays/menubar.css +8 -4
- package/src/themes/default/tokens.css +20 -20
- package/templates/theme/index.css +1 -0
- package/templates/theme/styles/actions/button.css +7 -0
- package/templates/theme/styles/base/accessibility.css +88 -0
- package/templates/theme/styles/forms/input.css +3 -2
- package/templates/theme/styles/overlays/dialog.css +1 -1
- package/templates/theme/styles/overlays/dropdown.css +4 -3
- package/templates/theme/styles/overlays/menubar.css +8 -4
- package/templates/theme/tokens.css +20 -20
package/THEMING.md
CHANGED
|
@@ -996,6 +996,32 @@ All official themes are tested for WCAG AA compliance:
|
|
|
996
996
|
|
|
997
997
|
The automated test suite (`tests/unit/contrast.test.ts`) validates these pairs across all themes in both light and dark modes.
|
|
998
998
|
|
|
999
|
+
The default light and dark modes additionally publish this canonical semantic
|
|
1000
|
+
pairing matrix. Ratios apply to browser-resolved colors after alpha and element
|
|
1001
|
+
opacity are composited:
|
|
1002
|
+
|
|
1003
|
+
| Semantic pairing | Minimum |
|
|
1004
|
+
| --------------------------------------------------------------------------------------- | ------: |
|
|
1005
|
+
| Ordinary or subtle text on its documented surface | 4.5:1 |
|
|
1006
|
+
| Strong boundaries, warning UI, and info UI on a surface | 3:1 |
|
|
1007
|
+
| The shared focus indicator on page, surface, muted, raised, overlay, and primary layers | 3:1 |
|
|
1008
|
+
| Disabled control fill against the page | 1.5:1 |
|
|
1009
|
+
| Disabled control boundary against the page when the fill is below 1.5:1 | 3:1 |
|
|
1010
|
+
|
|
1011
|
+
### Forced Colors
|
|
1012
|
+
|
|
1013
|
+
The default stylesheet includes a `forced-colors: active` layer. It uses
|
|
1014
|
+
system `Canvas`, `CanvasText`, `ButtonText`, `GrayText`, `Highlight`, and
|
|
1015
|
+
`HighlightText` colors for control boundaries, disabled states, overlay
|
|
1016
|
+
separation, and focus. Native system-color adjustment remains enabled; do not
|
|
1017
|
+
add `forced-color-adjust: none` unless a specific semantic image would
|
|
1018
|
+
otherwise lose meaning and the exception is documented and tested.
|
|
1019
|
+
|
|
1020
|
+
Normal browser CI resolves every canonical pairing in light and dark mode.
|
|
1021
|
+
Chromium CI also runs a real forced-colors context, while the stylesheet and
|
|
1022
|
+
template parity test protects the same contract for engines that cannot
|
|
1023
|
+
emulate it.
|
|
1024
|
+
|
|
999
1025
|
---
|
|
1000
1026
|
|
|
1001
1027
|
## Implementation Note
|
|
@@ -84,7 +84,7 @@ function ThemeScope(props) {
|
|
|
84
84
|
const ownedCoordinator = state(getDefaultThemeCoordinator())();
|
|
85
85
|
const coordinator = parentScope.coordinator ?? ownedCoordinator;
|
|
86
86
|
const scopeDepth = parentScope.depth + 1;
|
|
87
|
-
coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal, (nextTheme) => {
|
|
87
|
+
coordinator.register(scopeId, scopeDepth, storageKey, currentTheme, scopeSignal, (nextTheme) => {
|
|
88
88
|
if (themeState() !== nextTheme) themeState.set(nextTheme);
|
|
89
89
|
});
|
|
90
90
|
const setTheme = (nextTheme) => {
|
|
@@ -185,10 +185,11 @@ function createThemeCoordinator() {
|
|
|
185
185
|
}
|
|
186
186
|
schedule();
|
|
187
187
|
},
|
|
188
|
-
register(id, depth, themeName, signal, onThemeChange) {
|
|
188
|
+
register(id, depth, identity, themeName, signal, onThemeChange) {
|
|
189
189
|
const existing = scopes.get(id);
|
|
190
190
|
scopes.set(id, {
|
|
191
191
|
depth,
|
|
192
|
+
identity,
|
|
192
193
|
sequence: existing?.sequence ?? nextSequence++,
|
|
193
194
|
theme: themeName,
|
|
194
195
|
signal,
|
|
@@ -206,7 +207,8 @@ function createThemeCoordinator() {
|
|
|
206
207
|
if (scope) scope.theme = themeName;
|
|
207
208
|
explicitOwner = id;
|
|
208
209
|
const ownerDepth = scope?.depth;
|
|
209
|
-
|
|
210
|
+
const ownerIdentity = scope?.identity;
|
|
211
|
+
for (const [scopeId, registered] of scopes) if (scopeId !== id && registered.depth === ownerDepth && registered.identity === ownerIdentity) {
|
|
210
212
|
registered.theme = themeName;
|
|
211
213
|
registered.onThemeChange(themeName);
|
|
212
214
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[],"sources":["../../../src/components/theme/theme.tsx"],"sourcesContent":["import type { JSX } from \"@askrjs/askr/jsx-runtime\";\nimport { defineScope, getSignal, readScope, state } from \"@askrjs/askr\";\nimport { cloneElement, isElement, type JSXElement } from \"@askrjs/askr/foundations/structures\";\nimport { Button } from \"@askrjs/ui\";\nimport type { ButtonNativeProps, PressEvent } from \"@askrjs/ui\";\n\n/** Names of the built-in \"cat\" theme presets. */\nexport const CAT_THEME_NAMES = [\"tabby\", \"ginger\", \"tuxedo\", \"calico\", \"torty\"] as const;\n\n/** A built-in \"cat\" theme name, one of {@link CAT_THEME_NAMES}. */\nexport type CatThemeName = (typeof CAT_THEME_NAMES)[number];\n/** Any theme identifier accepted by {@link ThemeScope}: `\"light\"`, `\"dark\"`, `\"system\"`, a {@link CatThemeName}, or a custom string. */\nexport type ThemeName = \"light\" | \"dark\" | \"system\" | CatThemeName | (string & {});\n\n/** A theme choice offered by {@link ThemePicker}/{@link ThemeToggle}: a `value` paired with a display `label`. */\nexport type ThemeOption = {\n value: ThemeName;\n label: string;\n};\n\n/** Value read from {@link theme}: the active theme, its resolved system value, a setter, and the available theme options. */\nexport type ThemeScopeValue = {\n theme: () => ThemeName;\n resolvedSystemTheme: () => \"light\" | \"dark\";\n setTheme: (theme: ThemeName) => void;\n themes: readonly ThemeOption[];\n storageKey: string;\n};\n\n/** Props for the {@link ThemeScope} component. */\nexport type ThemeScopeProps = {\n children?: unknown;\n defaultTheme?: ThemeName;\n themes?: readonly ThemeOption[];\n storageKey?: string;\n};\n\n/** Props for the {@link ThemePicker} component. */\nexport type ThemePickerProps = Omit<\n JSX.IntrinsicElements[\"select\"],\n \"children\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n themes?: readonly ThemeOption[];\n label?: string;\n};\n\n/** Render context passed to a function-as-children {@link ThemeToggleProps.children}. */\nexport type ThemeToggleRenderContext = {\n theme: ThemeName;\n nextTheme: ThemeName;\n};\n\n/** Props for the {@link ThemeToggle} component. */\nexport type ThemeToggleProps = Omit<ButtonNativeProps, \"children\" | \"onPress\"> & {\n children?: unknown | ((context: ThemeToggleRenderContext) => unknown);\n lightIcon?: unknown;\n darkIcon?: unknown;\n systemIcon?: unknown;\n themes?: readonly ThemeName[];\n onPress?: (event: PressEvent) => void;\n};\n\n/** Default light/dark/system theme options used by {@link ThemeScope} and {@link ThemePicker}. */\nexport const DEFAULT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"system\", label: \"System\" },\n { value: \"light\", label: \"Light\" },\n { value: \"dark\", label: \"Dark\" },\n];\n\n/** Theme options for the built-in \"cat\" theme presets, keyed to {@link CAT_THEME_NAMES}. */\nexport const CAT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"tabby\", label: \"Tabby\" },\n { value: \"ginger\", label: \"Ginger\" },\n { value: \"tuxedo\", label: \"Tuxedo\" },\n { value: \"calico\", label: \"Calico\" },\n { value: \"torty\", label: \"Torty\" },\n];\n\nconst DEFAULT_STORAGE_KEY = \"askr-theme\";\nconst STATIC_CHILDREN = Symbol.for(\"askr.static-children\");\nconst documentThemeCoordinators = new WeakMap<Document, ThemeCoordinator>();\ntype ThemeCoordinator = ReturnType<typeof createThemeCoordinator>;\ntype InternalThemeScopeValue = ThemeScopeValue & {\n readonly coordinator: ThemeCoordinator | null;\n readonly depth: number;\n};\n\nconst ThemeScopeContext = defineScope<InternalThemeScopeValue>({\n theme: () => \"system\",\n resolvedSystemTheme: () => \"light\",\n setTheme: () => undefined,\n themes: DEFAULT_THEME_OPTIONS,\n storageKey: DEFAULT_STORAGE_KEY,\n coordinator: null,\n depth: -1,\n});\n\n/** Reads the current {@link ThemeScopeValue} from the nearest enclosing {@link ThemeScope}. */\nexport function theme(): ThemeScopeValue {\n return readScope(ThemeScopeContext);\n}\n\n/**\n * Establishes a theme boundary: tracks the active theme (persisted to\n * `localStorage` under `storageKey` and synced across tabs/scopes), resolves\n * the OS `\"system\"` preference, and reflects the choice onto the DOM via\n * `data-theme`/`data-theme-choice` attributes. Nested scopes cooperate\n * through a shared coordinator so the deepest explicitly-set scope wins.\n */\nexport function ThemeScope(props: ThemeScopeProps): JSX.Element {\n const {\n children,\n defaultTheme = \"system\",\n themes = DEFAULT_THEME_OPTIONS,\n storageKey = DEFAULT_STORAGE_KEY,\n } = props;\n\n const scopeId = state<symbol>(Symbol(\"ThemeScope\"))();\n const scopeSignal = getSignal();\n // The first render must be identical on the server and in the browser.\n // Browser persistence is adopted from the committed root ref, after Askr's\n // hydration verifier has accepted the server markup.\n const themeState = state<ThemeName>(defaultTheme);\n const localResolvedSystemTheme = state<\"light\" | \"dark\">(\"light\");\n const persistenceAdoption = state({ complete: false })();\n const currentTheme = themeState();\n const parentScope = readScope(ThemeScopeContext);\n const ownedCoordinator = state<ThemeCoordinator>(getDefaultThemeCoordinator())();\n const coordinator = parentScope.coordinator ?? ownedCoordinator;\n const scopeDepth = parentScope.depth + 1;\n coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal, (nextTheme) => {\n if (themeState() !== nextTheme) themeState.set(nextTheme);\n });\n\n const setTheme = (nextTheme: ThemeName) => {\n themeState.set(nextTheme);\n writeStoredTheme(storageKey, nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n\n const resolvedSystemTheme = parentScope.coordinator\n ? parentScope.resolvedSystemTheme\n : localResolvedSystemTheme;\n const value: InternalThemeScopeValue = {\n theme: themeState,\n resolvedSystemTheme,\n setTheme,\n themes,\n storageKey,\n coordinator,\n depth: scopeDepth,\n };\n\n return (\n <ThemeScopeContext value={value}>\n <div\n data-slot=\"theme-scope\"\n ref={\n parentScope.coordinator === null\n ? (element: HTMLElement | null) => {\n coordinator.attach(\n element,\n (nextTheme) => localResolvedSystemTheme.set(nextTheme),\n scopeSignal,\n );\n if (element && typeof window !== \"undefined\") {\n const onStorage = (event: StorageEvent) => {\n let storageMatches = event.storageArea == null;\n try {\n storageMatches ||= event.storageArea === window.localStorage;\n } catch {\n // Locked-down/private contexts may deny access to localStorage.\n }\n if (!storageMatches || event.key !== storageKey) {\n return;\n }\n const nextTheme = event.newValue as ThemeName | null;\n if (!nextTheme) return;\n themeState.set(nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n window.addEventListener(\"storage\", onStorage);\n scopeSignal.addEventListener(\n \"abort\",\n () => window.removeEventListener(\"storage\", onStorage),\n { once: true },\n );\n }\n if (!element || persistenceAdoption.complete) return;\n persistenceAdoption.complete = true;\n const storedTheme = readStoredTheme(storageKey);\n if (storedTheme && storedTheme !== themeState()) {\n themeState.set(storedTheme);\n coordinator.activate(scopeId, storedTheme);\n }\n }\n : undefined\n }\n >\n {children}\n </div>\n </ThemeScopeContext>\n );\n}\n\nfunction getDefaultThemeCoordinator(): ThemeCoordinator {\n if (typeof document === \"undefined\") {\n return createThemeCoordinator();\n }\n\n const existing = documentThemeCoordinators.get(document);\n if (existing) return existing;\n\n const coordinator = createThemeCoordinator();\n documentThemeCoordinators.set(document, coordinator);\n return coordinator;\n}\n\nfunction removeEmptyGeneratedStyleRegistries(root: Node | null): void {\n const ownerDocument =\n root?.nodeType === 9\n ? (root as Document)\n : (root?.ownerDocument ?? (typeof document === \"undefined\" ? null : document));\n if (!ownerDocument) return;\n\n for (const registry of ownerDocument.querySelectorAll<HTMLStyleElement>(\n \"style[data-askr-style-registry]\",\n )) {\n if (!registry.textContent?.trim()) registry.remove();\n }\n}\n\nfunction createThemeCoordinator() {\n const scopes = new Map<\n symbol,\n {\n depth: number;\n sequence: number;\n theme: ThemeName;\n signal: AbortSignal;\n onThemeChange: (themeName: ThemeName) => void;\n }\n >();\n let nextSequence = 0;\n let explicitOwner: symbol | undefined;\n let root: Node | null = null;\n let scheduled = false;\n\n const target = (): HTMLElement | null => {\n if (root?.nodeType === 9) return (root as Document).documentElement;\n if (root && \"host\" in root) return (root as ShadowRoot).host as HTMLElement;\n return typeof document === \"undefined\" ? null : document.documentElement;\n };\n const syncActive = (): void => {\n if (explicitOwner !== undefined) return;\n let candidate: { depth: number; sequence: number; theme: ThemeName } | undefined;\n for (const scope of scopes.values()) {\n if (\n !candidate ||\n scope.depth > candidate.depth ||\n (scope.depth === candidate.depth && scope.sequence > candidate.sequence)\n ) {\n candidate = scope;\n }\n }\n if (candidate) syncThemeTarget(target(), candidate.theme);\n };\n const schedule = (): void => {\n if (typeof document === \"undefined\" || scheduled) return;\n scheduled = true;\n setTimeout(() => {\n scheduled = false;\n syncActive();\n if (scopes.size === 0) removeEmptyGeneratedStyleRegistries(root);\n }, 0);\n };\n\n return Object.freeze({\n attach(\n element: HTMLElement | null,\n onResolvedSystemTheme: (themeName: \"light\" | \"dark\") => void,\n signal: AbortSignal,\n ) {\n if (element) root = element.getRootNode();\n if (element && typeof window !== \"undefined\" && typeof window.matchMedia === \"function\") {\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const update = () => onResolvedSystemTheme(media.matches ? \"dark\" : \"light\");\n update();\n media.addEventListener?.(\"change\", update);\n signal.addEventListener(\"abort\", () => media.removeEventListener?.(\"change\", update), {\n once: true,\n });\n }\n schedule();\n },\n register(\n id: symbol,\n depth: number,\n themeName: ThemeName,\n signal: AbortSignal,\n onThemeChange: (themeName: ThemeName) => void,\n ) {\n const existing = scopes.get(id);\n scopes.set(id, {\n depth,\n sequence: existing?.sequence ?? nextSequence++,\n theme: themeName,\n signal,\n onThemeChange,\n });\n if (!existing) {\n signal.addEventListener(\n \"abort\",\n () => {\n scopes.delete(id);\n if (explicitOwner === id) explicitOwner = undefined;\n schedule();\n },\n { once: true },\n );\n }\n schedule();\n },\n activate(id: symbol, themeName: ThemeName) {\n const scope = scopes.get(id);\n if (scope) scope.theme = themeName;\n explicitOwner = id;\n const ownerDepth = scope?.depth;\n for (const [scopeId, registered] of scopes) {\n if (scopeId !== id && registered.depth === ownerDepth) {\n registered.theme = themeName;\n registered.onThemeChange(themeName);\n }\n }\n syncThemeTarget(target(), themeName);\n },\n });\n}\n\n/** A `<select>` bound to the current {@link theme}, listing `themes` (defaults to the enclosing scope's options). */\nexport function ThemePicker(props: ThemePickerProps): JSX.Element {\n const activeTheme = theme();\n const { themes = activeTheme.themes, label = \"Theme\", ...rest } = props;\n const currentTheme = activeTheme.theme();\n\n return (\n <select\n {...rest}\n aria-label={rest[\"aria-label\"] ?? label}\n data-slot=\"theme-picker\"\n value={currentTheme}\n onChange={(event: Event) => {\n const target = getThemePickerTarget(event);\n if (target) {\n activeTheme.setTheme(target.value as ThemeName);\n }\n }}\n >\n {themes.map((option) => (\n <option key={option.value} value={option.value} selected={option.value === currentTheme}>\n {option.label}\n </option>\n ))}\n </select>\n );\n}\n\nfunction getThemePickerTarget(event: Event): HTMLSelectElement | null {\n if (typeof HTMLSelectElement === \"undefined\") {\n return null;\n }\n\n const path = typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const candidates = [event.target, event.currentTarget, ...path];\n\n for (const candidate of candidates) {\n if (candidate instanceof HTMLSelectElement) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * A button that cycles through `themes` (defaults to `[\"light\", \"dark\"]`) on\n * press. Supports icon props per theme, or a function-as-children render\n * prop receiving {@link ThemeToggleRenderContext}.\n */\nexport function ThemeToggle(props: ThemeToggleProps): JSX.Element {\n const activeTheme = theme();\n const {\n children,\n lightIcon,\n darkIcon,\n systemIcon,\n themes = [\"light\", \"dark\"],\n onPress,\n ...rest\n } = props;\n\n const currentTheme = activeTheme.theme();\n const nextTheme = getNextTheme(currentTheme, themes, activeTheme.resolvedSystemTheme());\n const renderContext = { theme: currentTheme, nextTheme };\n const ariaLabel = (rest as Record<string, unknown>)[\"aria-label\"];\n const themedIcon = resolveThemeToggleIcon(currentTheme, nextTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n });\n const renderedIcon = cloneThemeToggleIcon(themedIcon, currentTheme);\n const renderedIconSlots =\n renderThemeToggleIconSlots(currentTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n }) ?? renderedIcon;\n const content =\n typeof children === \"function\" ? children(renderContext) : (children ?? renderedIconSlots);\n\n return (\n <Button\n {...(rest as ButtonNativeProps)}\n aria-label={typeof ariaLabel === \"string\" ? ariaLabel : `Switch to ${nextTheme} theme`}\n data-theme-control=\"toggle\"\n data-theme-choice={currentTheme}\n data-next-theme={nextTheme}\n onPress={(event) => {\n onPress?.(event);\n if (!event.defaultPrevented && !Object.is(nextTheme, currentTheme)) {\n activeTheme.setTheme(nextTheme);\n }\n }}\n >\n <span data-slot=\"theme-toggle-content\">{content}</span>\n </Button>\n );\n}\n\nfunction getNextTheme(\n currentTheme: ThemeName,\n themes: readonly ThemeName[],\n resolvedSystemTheme: \"light\" | \"dark\" = \"light\",\n): ThemeName {\n if (themes.length === 0) return currentTheme;\n const index = themes.indexOf(currentTheme);\n if (index < 0 && currentTheme === \"system\") {\n if (themes.includes(\"light\") && themes.includes(\"dark\")) {\n return resolvedSystemTheme === \"dark\" ? \"light\" : \"dark\";\n }\n }\n return themes[index >= 0 && index < themes.length - 1 ? index + 1 : 0]!;\n}\n\nfunction getThemeIcon(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (theme === \"light\") return icons.lightIcon;\n if (theme === \"dark\") return icons.darkIcon;\n if (theme === \"system\") return icons.systemIcon;\n return undefined;\n}\n\nexport function resolveThemeToggleIcon(\n theme: ThemeName,\n nextTheme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n return getThemeIcon(theme, icons) ?? getThemeIcon(nextTheme, icons);\n}\n\nfunction renderThemeToggleIconSlots(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (getThemeIcon(theme, icons) === undefined) {\n return undefined;\n }\n\n const slots = [\n [\"light\", icons.lightIcon],\n [\"dark\", icons.darkIcon],\n [\"system\", icons.systemIcon],\n ] as const;\n const availableSlots = slots.filter(([, icon]) => icon !== undefined && icon !== null);\n\n if (availableSlots.length <= 1) {\n return undefined;\n }\n\n return availableSlots.map(([slotTheme, icon]) => (\n <span\n key={slotTheme}\n data-slot=\"theme-toggle-icon\"\n data-theme-toggle-icon={slotTheme}\n hidden={slotTheme === theme ? undefined : true}\n >\n {cloneThemeToggleIcon(icon, slotTheme)}\n </span>\n ));\n}\n\nfunction cloneThemeToggleIcon(icon: unknown, key?: string): unknown {\n if (Array.isArray(icon)) {\n const clonedChildren = icon.map((child) => cloneThemeToggleIcon(child));\n if ((icon as unknown as Record<symbol, unknown>)[STATIC_CHILDREN] === true) {\n Object.defineProperty(clonedChildren, STATIC_CHILDREN, {\n value: true,\n configurable: true,\n });\n }\n return clonedChildren;\n }\n\n if (!isElement(icon)) return icon;\n\n const props = icon.props as Record<string, unknown> | undefined;\n const clonedProps = props ? { ...props } : {};\n\n if (\"children\" in clonedProps) {\n clonedProps.children = cloneThemeToggleIcon(clonedProps.children);\n }\n\n const clonedIcon = cloneElement(icon, clonedProps);\n return {\n ...clonedIcon,\n key: icon.key ?? key ?? null,\n } satisfies JSXElement;\n}\n\nfunction syncThemeTarget(\n html: HTMLElement | null,\n themeChoice: ThemeName | null | undefined,\n): void {\n if (!html) return;\n\n if (themeChoice == null) {\n html.removeAttribute(\"data-theme\");\n html.removeAttribute(\"data-theme-choice\");\n return;\n }\n\n html.setAttribute(\"data-theme-choice\", themeChoice);\n\n if (themeChoice === \"system\") {\n html.removeAttribute(\"data-theme\");\n } else {\n html.setAttribute(\"data-theme\", themeChoice);\n }\n}\n\nfunction readStoredTheme(storageKey: string): ThemeName | undefined {\n if (typeof window === \"undefined\") return undefined;\n try {\n const storedTheme = window.localStorage.getItem(storageKey);\n return storedTheme ? (storedTheme as ThemeName) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeStoredTheme(storageKey: string, theme: ThemeName): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey, theme);\n } catch {\n // Storage can be unavailable in private or locked-down browser contexts.\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme.js","names":[],"sources":["../../../src/components/theme/theme.tsx"],"sourcesContent":["import type { JSX } from \"@askrjs/askr/jsx-runtime\";\nimport { defineScope, getSignal, readScope, state } from \"@askrjs/askr\";\nimport { cloneElement, isElement, type JSXElement } from \"@askrjs/askr/foundations/structures\";\nimport { Button } from \"@askrjs/ui\";\nimport type { ButtonNativeProps, PressEvent } from \"@askrjs/ui\";\n\n/** Names of the built-in \"cat\" theme presets. */\nexport const CAT_THEME_NAMES = [\"tabby\", \"ginger\", \"tuxedo\", \"calico\", \"torty\"] as const;\n\n/** A built-in \"cat\" theme name, one of {@link CAT_THEME_NAMES}. */\nexport type CatThemeName = (typeof CAT_THEME_NAMES)[number];\n/** Any theme identifier accepted by {@link ThemeScope}: `\"light\"`, `\"dark\"`, `\"system\"`, a {@link CatThemeName}, or a custom string. */\nexport type ThemeName = \"light\" | \"dark\" | \"system\" | CatThemeName | (string & {});\n\n/** A theme choice offered by {@link ThemePicker}/{@link ThemeToggle}: a `value` paired with a display `label`. */\nexport type ThemeOption = {\n value: ThemeName;\n label: string;\n};\n\n/** Value read from {@link theme}: the active theme, its resolved system value, a setter, and the available theme options. */\nexport type ThemeScopeValue = {\n theme: () => ThemeName;\n resolvedSystemTheme: () => \"light\" | \"dark\";\n setTheme: (theme: ThemeName) => void;\n themes: readonly ThemeOption[];\n storageKey: string;\n};\n\n/** Props for the {@link ThemeScope} component. */\nexport type ThemeScopeProps = {\n children?: unknown;\n defaultTheme?: ThemeName;\n themes?: readonly ThemeOption[];\n storageKey?: string;\n};\n\n/** Props for the {@link ThemePicker} component. */\nexport type ThemePickerProps = Omit<\n JSX.IntrinsicElements[\"select\"],\n \"children\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n themes?: readonly ThemeOption[];\n label?: string;\n};\n\n/** Render context passed to a function-as-children {@link ThemeToggleProps.children}. */\nexport type ThemeToggleRenderContext = {\n theme: ThemeName;\n nextTheme: ThemeName;\n};\n\n/** Props for the {@link ThemeToggle} component. */\nexport type ThemeToggleProps = Omit<ButtonNativeProps, \"children\" | \"onPress\"> & {\n children?: unknown | ((context: ThemeToggleRenderContext) => unknown);\n lightIcon?: unknown;\n darkIcon?: unknown;\n systemIcon?: unknown;\n themes?: readonly ThemeName[];\n onPress?: (event: PressEvent) => void;\n};\n\n/** Default light/dark/system theme options used by {@link ThemeScope} and {@link ThemePicker}. */\nexport const DEFAULT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"system\", label: \"System\" },\n { value: \"light\", label: \"Light\" },\n { value: \"dark\", label: \"Dark\" },\n];\n\n/** Theme options for the built-in \"cat\" theme presets, keyed to {@link CAT_THEME_NAMES}. */\nexport const CAT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"tabby\", label: \"Tabby\" },\n { value: \"ginger\", label: \"Ginger\" },\n { value: \"tuxedo\", label: \"Tuxedo\" },\n { value: \"calico\", label: \"Calico\" },\n { value: \"torty\", label: \"Torty\" },\n];\n\nconst DEFAULT_STORAGE_KEY = \"askr-theme\";\nconst STATIC_CHILDREN = Symbol.for(\"askr.static-children\");\nconst documentThemeCoordinators = new WeakMap<Document, ThemeCoordinator>();\ntype ThemeCoordinator = ReturnType<typeof createThemeCoordinator>;\ntype InternalThemeScopeValue = ThemeScopeValue & {\n readonly coordinator: ThemeCoordinator | null;\n readonly depth: number;\n};\n\nconst ThemeScopeContext = defineScope<InternalThemeScopeValue>({\n theme: () => \"system\",\n resolvedSystemTheme: () => \"light\",\n setTheme: () => undefined,\n themes: DEFAULT_THEME_OPTIONS,\n storageKey: DEFAULT_STORAGE_KEY,\n coordinator: null,\n depth: -1,\n});\n\n/** Reads the current {@link ThemeScopeValue} from the nearest enclosing {@link ThemeScope}. */\nexport function theme(): ThemeScopeValue {\n return readScope(ThemeScopeContext);\n}\n\n/**\n * Establishes a theme boundary: tracks the active theme (persisted to\n * `localStorage` under `storageKey` and synced across tabs/scopes), resolves\n * the OS `\"system\"` preference, and reflects the choice onto the DOM via\n * `data-theme`/`data-theme-choice` attributes. Nested scopes cooperate\n * through a shared coordinator so the deepest explicitly-set scope wins.\n */\nexport function ThemeScope(props: ThemeScopeProps): JSX.Element {\n const {\n children,\n defaultTheme = \"system\",\n themes = DEFAULT_THEME_OPTIONS,\n storageKey = DEFAULT_STORAGE_KEY,\n } = props;\n\n const scopeId = state<symbol>(Symbol(\"ThemeScope\"))();\n const scopeSignal = getSignal();\n // The first render must be identical on the server and in the browser.\n // Browser persistence is adopted from the committed root ref, after Askr's\n // hydration verifier has accepted the server markup.\n const themeState = state<ThemeName>(defaultTheme);\n const localResolvedSystemTheme = state<\"light\" | \"dark\">(\"light\");\n const persistenceAdoption = state({ complete: false })();\n const currentTheme = themeState();\n const parentScope = readScope(ThemeScopeContext);\n const ownedCoordinator = state<ThemeCoordinator>(getDefaultThemeCoordinator())();\n const coordinator = parentScope.coordinator ?? ownedCoordinator;\n const scopeDepth = parentScope.depth + 1;\n coordinator.register(scopeId, scopeDepth, storageKey, currentTheme, scopeSignal, (nextTheme) => {\n if (themeState() !== nextTheme) themeState.set(nextTheme);\n });\n\n const setTheme = (nextTheme: ThemeName) => {\n themeState.set(nextTheme);\n writeStoredTheme(storageKey, nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n\n const resolvedSystemTheme = parentScope.coordinator\n ? parentScope.resolvedSystemTheme\n : localResolvedSystemTheme;\n const value: InternalThemeScopeValue = {\n theme: themeState,\n resolvedSystemTheme,\n setTheme,\n themes,\n storageKey,\n coordinator,\n depth: scopeDepth,\n };\n\n return (\n <ThemeScopeContext value={value}>\n <div\n data-slot=\"theme-scope\"\n ref={\n parentScope.coordinator === null\n ? (element: HTMLElement | null) => {\n coordinator.attach(\n element,\n (nextTheme) => localResolvedSystemTheme.set(nextTheme),\n scopeSignal,\n );\n if (element && typeof window !== \"undefined\") {\n const onStorage = (event: StorageEvent) => {\n let storageMatches = event.storageArea == null;\n try {\n storageMatches ||= event.storageArea === window.localStorage;\n } catch {\n // Locked-down/private contexts may deny access to localStorage.\n }\n if (!storageMatches || event.key !== storageKey) {\n return;\n }\n const nextTheme = event.newValue as ThemeName | null;\n if (!nextTheme) return;\n themeState.set(nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n window.addEventListener(\"storage\", onStorage);\n scopeSignal.addEventListener(\n \"abort\",\n () => window.removeEventListener(\"storage\", onStorage),\n { once: true },\n );\n }\n if (!element || persistenceAdoption.complete) return;\n persistenceAdoption.complete = true;\n const storedTheme = readStoredTheme(storageKey);\n if (storedTheme && storedTheme !== themeState()) {\n themeState.set(storedTheme);\n coordinator.activate(scopeId, storedTheme);\n }\n }\n : undefined\n }\n >\n {children}\n </div>\n </ThemeScopeContext>\n );\n}\n\nfunction getDefaultThemeCoordinator(): ThemeCoordinator {\n if (typeof document === \"undefined\") {\n return createThemeCoordinator();\n }\n\n const existing = documentThemeCoordinators.get(document);\n if (existing) return existing;\n\n const coordinator = createThemeCoordinator();\n documentThemeCoordinators.set(document, coordinator);\n return coordinator;\n}\n\nfunction removeEmptyGeneratedStyleRegistries(root: Node | null): void {\n const ownerDocument =\n root?.nodeType === 9\n ? (root as Document)\n : (root?.ownerDocument ?? (typeof document === \"undefined\" ? null : document));\n if (!ownerDocument) return;\n\n for (const registry of ownerDocument.querySelectorAll<HTMLStyleElement>(\n \"style[data-askr-style-registry]\",\n )) {\n if (!registry.textContent?.trim()) registry.remove();\n }\n}\n\nfunction createThemeCoordinator() {\n const scopes = new Map<\n symbol,\n {\n depth: number;\n identity: string;\n sequence: number;\n theme: ThemeName;\n signal: AbortSignal;\n onThemeChange: (themeName: ThemeName) => void;\n }\n >();\n let nextSequence = 0;\n let explicitOwner: symbol | undefined;\n let root: Node | null = null;\n let scheduled = false;\n\n const target = (): HTMLElement | null => {\n if (root?.nodeType === 9) return (root as Document).documentElement;\n if (root && \"host\" in root) return (root as ShadowRoot).host as HTMLElement;\n return typeof document === \"undefined\" ? null : document.documentElement;\n };\n const syncActive = (): void => {\n if (explicitOwner !== undefined) return;\n let candidate: { depth: number; sequence: number; theme: ThemeName } | undefined;\n for (const scope of scopes.values()) {\n if (\n !candidate ||\n scope.depth > candidate.depth ||\n (scope.depth === candidate.depth && scope.sequence > candidate.sequence)\n ) {\n candidate = scope;\n }\n }\n if (candidate) syncThemeTarget(target(), candidate.theme);\n };\n const schedule = (): void => {\n if (typeof document === \"undefined\" || scheduled) return;\n scheduled = true;\n setTimeout(() => {\n scheduled = false;\n syncActive();\n if (scopes.size === 0) removeEmptyGeneratedStyleRegistries(root);\n }, 0);\n };\n\n return Object.freeze({\n attach(\n element: HTMLElement | null,\n onResolvedSystemTheme: (themeName: \"light\" | \"dark\") => void,\n signal: AbortSignal,\n ) {\n if (element) root = element.getRootNode();\n if (element && typeof window !== \"undefined\" && typeof window.matchMedia === \"function\") {\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const update = () => onResolvedSystemTheme(media.matches ? \"dark\" : \"light\");\n update();\n media.addEventListener?.(\"change\", update);\n signal.addEventListener(\"abort\", () => media.removeEventListener?.(\"change\", update), {\n once: true,\n });\n }\n schedule();\n },\n register(\n id: symbol,\n depth: number,\n identity: string,\n themeName: ThemeName,\n signal: AbortSignal,\n onThemeChange: (themeName: ThemeName) => void,\n ) {\n const existing = scopes.get(id);\n scopes.set(id, {\n depth,\n identity,\n sequence: existing?.sequence ?? nextSequence++,\n theme: themeName,\n signal,\n onThemeChange,\n });\n if (!existing) {\n signal.addEventListener(\n \"abort\",\n () => {\n scopes.delete(id);\n if (explicitOwner === id) explicitOwner = undefined;\n schedule();\n },\n { once: true },\n );\n }\n schedule();\n },\n activate(id: symbol, themeName: ThemeName) {\n const scope = scopes.get(id);\n if (scope) scope.theme = themeName;\n explicitOwner = id;\n const ownerDepth = scope?.depth;\n const ownerIdentity = scope?.identity;\n for (const [scopeId, registered] of scopes) {\n if (\n scopeId !== id &&\n registered.depth === ownerDepth &&\n registered.identity === ownerIdentity\n ) {\n registered.theme = themeName;\n registered.onThemeChange(themeName);\n }\n }\n syncThemeTarget(target(), themeName);\n },\n });\n}\n\n/** A `<select>` bound to the current {@link theme}, listing `themes` (defaults to the enclosing scope's options). */\nexport function ThemePicker(props: ThemePickerProps): JSX.Element {\n const activeTheme = theme();\n const { themes = activeTheme.themes, label = \"Theme\", ...rest } = props;\n const currentTheme = activeTheme.theme();\n\n return (\n <select\n {...rest}\n aria-label={rest[\"aria-label\"] ?? label}\n data-slot=\"theme-picker\"\n value={currentTheme}\n onChange={(event: Event) => {\n const target = getThemePickerTarget(event);\n if (target) {\n activeTheme.setTheme(target.value as ThemeName);\n }\n }}\n >\n {themes.map((option) => (\n <option key={option.value} value={option.value} selected={option.value === currentTheme}>\n {option.label}\n </option>\n ))}\n </select>\n );\n}\n\nfunction getThemePickerTarget(event: Event): HTMLSelectElement | null {\n if (typeof HTMLSelectElement === \"undefined\") {\n return null;\n }\n\n const path = typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const candidates = [event.target, event.currentTarget, ...path];\n\n for (const candidate of candidates) {\n if (candidate instanceof HTMLSelectElement) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * A button that cycles through `themes` (defaults to `[\"light\", \"dark\"]`) on\n * press. Supports icon props per theme, or a function-as-children render\n * prop receiving {@link ThemeToggleRenderContext}.\n */\nexport function ThemeToggle(props: ThemeToggleProps): JSX.Element {\n const activeTheme = theme();\n const {\n children,\n lightIcon,\n darkIcon,\n systemIcon,\n themes = [\"light\", \"dark\"],\n onPress,\n ...rest\n } = props;\n\n const currentTheme = activeTheme.theme();\n const nextTheme = getNextTheme(currentTheme, themes, activeTheme.resolvedSystemTheme());\n const renderContext = { theme: currentTheme, nextTheme };\n const ariaLabel = (rest as Record<string, unknown>)[\"aria-label\"];\n const themedIcon = resolveThemeToggleIcon(currentTheme, nextTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n });\n const renderedIcon = cloneThemeToggleIcon(themedIcon, currentTheme);\n const renderedIconSlots =\n renderThemeToggleIconSlots(currentTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n }) ?? renderedIcon;\n const content =\n typeof children === \"function\" ? children(renderContext) : (children ?? renderedIconSlots);\n\n return (\n <Button\n {...(rest as ButtonNativeProps)}\n aria-label={typeof ariaLabel === \"string\" ? ariaLabel : `Switch to ${nextTheme} theme`}\n data-theme-control=\"toggle\"\n data-theme-choice={currentTheme}\n data-next-theme={nextTheme}\n onPress={(event) => {\n onPress?.(event);\n if (!event.defaultPrevented && !Object.is(nextTheme, currentTheme)) {\n activeTheme.setTheme(nextTheme);\n }\n }}\n >\n <span data-slot=\"theme-toggle-content\">{content}</span>\n </Button>\n );\n}\n\nfunction getNextTheme(\n currentTheme: ThemeName,\n themes: readonly ThemeName[],\n resolvedSystemTheme: \"light\" | \"dark\" = \"light\",\n): ThemeName {\n if (themes.length === 0) return currentTheme;\n const index = themes.indexOf(currentTheme);\n if (index < 0 && currentTheme === \"system\") {\n if (themes.includes(\"light\") && themes.includes(\"dark\")) {\n return resolvedSystemTheme === \"dark\" ? \"light\" : \"dark\";\n }\n }\n return themes[index >= 0 && index < themes.length - 1 ? index + 1 : 0]!;\n}\n\nfunction getThemeIcon(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (theme === \"light\") return icons.lightIcon;\n if (theme === \"dark\") return icons.darkIcon;\n if (theme === \"system\") return icons.systemIcon;\n return undefined;\n}\n\nexport function resolveThemeToggleIcon(\n theme: ThemeName,\n nextTheme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n return getThemeIcon(theme, icons) ?? getThemeIcon(nextTheme, icons);\n}\n\nfunction renderThemeToggleIconSlots(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (getThemeIcon(theme, icons) === undefined) {\n return undefined;\n }\n\n const slots = [\n [\"light\", icons.lightIcon],\n [\"dark\", icons.darkIcon],\n [\"system\", icons.systemIcon],\n ] as const;\n const availableSlots = slots.filter(([, icon]) => icon !== undefined && icon !== null);\n\n if (availableSlots.length <= 1) {\n return undefined;\n }\n\n return availableSlots.map(([slotTheme, icon]) => (\n <span\n key={slotTheme}\n data-slot=\"theme-toggle-icon\"\n data-theme-toggle-icon={slotTheme}\n hidden={slotTheme === theme ? undefined : true}\n >\n {cloneThemeToggleIcon(icon, slotTheme)}\n </span>\n ));\n}\n\nfunction cloneThemeToggleIcon(icon: unknown, key?: string): unknown {\n if (Array.isArray(icon)) {\n const clonedChildren = icon.map((child) => cloneThemeToggleIcon(child));\n if ((icon as unknown as Record<symbol, unknown>)[STATIC_CHILDREN] === true) {\n Object.defineProperty(clonedChildren, STATIC_CHILDREN, {\n value: true,\n configurable: true,\n });\n }\n return clonedChildren;\n }\n\n if (!isElement(icon)) return icon;\n\n const props = icon.props as Record<string, unknown> | undefined;\n const clonedProps = props ? { ...props } : {};\n\n if (\"children\" in clonedProps) {\n clonedProps.children = cloneThemeToggleIcon(clonedProps.children);\n }\n\n const clonedIcon = cloneElement(icon, clonedProps);\n return {\n ...clonedIcon,\n key: icon.key ?? key ?? null,\n } satisfies JSXElement;\n}\n\nfunction syncThemeTarget(\n html: HTMLElement | null,\n themeChoice: ThemeName | null | undefined,\n): void {\n if (!html) return;\n\n if (themeChoice == null) {\n html.removeAttribute(\"data-theme\");\n html.removeAttribute(\"data-theme-choice\");\n return;\n }\n\n html.setAttribute(\"data-theme-choice\", themeChoice);\n\n if (themeChoice === \"system\") {\n html.removeAttribute(\"data-theme\");\n } else {\n html.setAttribute(\"data-theme\", themeChoice);\n }\n}\n\nfunction readStoredTheme(storageKey: string): ThemeName | undefined {\n if (typeof window === \"undefined\") return undefined;\n try {\n const storedTheme = window.localStorage.getItem(storageKey);\n return storedTheme ? (storedTheme as ThemeName) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeStoredTheme(storageKey: string, theme: ThemeName): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey, theme);\n } catch {\n // Storage can be unavailable in private or locked-down browser contexts.\n }\n}\n"],"mappings":"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"}
|
|
@@ -163,16 +163,16 @@
|
|
|
163
163
|
--ak-dark-color-primary-ink: oklch(98.5% 0 0);
|
|
164
164
|
--ak-dark-color-text: oklch(98.5% 0 0);
|
|
165
165
|
--ak-dark-color-text-muted: oklch(70.8% 0 0);
|
|
166
|
-
--ak-dark-color-text-subtle: oklch(
|
|
166
|
+
--ak-dark-color-text-subtle: oklch(74% 0 0);
|
|
167
167
|
--ak-dark-color-text-inverse: oklch(20.5% 0 0);
|
|
168
168
|
--ak-dark-color-bg: oklch(14.5% 0 0);
|
|
169
169
|
--ak-dark-color-surface: oklch(20.5% 0 0);
|
|
170
170
|
--ak-dark-color-surface-muted: oklch(26.9% 0 0);
|
|
171
|
-
--ak-dark-color-surface-raised: oklch(
|
|
172
|
-
--ak-dark-color-surface-overlay: oklch(
|
|
171
|
+
--ak-dark-color-surface-raised: oklch(23.5% 0 0);
|
|
172
|
+
--ak-dark-color-surface-overlay: oklch(26.9% 0 0);
|
|
173
173
|
--ak-dark-color-border-subtle: oklch(26.9% 0 0);
|
|
174
174
|
--ak-dark-color-border: oklch(100% 0 0 / .1);
|
|
175
|
-
--ak-dark-color-border-strong: oklch(
|
|
175
|
+
--ak-dark-color-border-strong: oklch(54% 0 0);
|
|
176
176
|
--ak-dark-color-success: #4ade80;
|
|
177
177
|
--ak-dark-color-success-soft: #052e16;
|
|
178
178
|
--ak-dark-color-success-ink: #bbf7d0;
|
|
@@ -187,11 +187,11 @@
|
|
|
187
187
|
--ak-dark-color-info-ink: #bae6fd;
|
|
188
188
|
--ak-dark-color-link: var(--ak-dark-color-text);
|
|
189
189
|
--ak-dark-color-link-hover: color-mix(in oklch, var(--ak-dark-color-link) 88%, transparent);
|
|
190
|
-
--ak-dark-color-focus-ring: oklch(
|
|
191
|
-
--ak-dark-color-disabled-bg: oklch(
|
|
190
|
+
--ak-dark-color-focus-ring: oklch(60% 0 0);
|
|
191
|
+
--ak-dark-color-disabled-bg: oklch(32% 0 0);
|
|
192
192
|
--ak-dark-color-disabled-surface: oklch(20.5% 0 0);
|
|
193
|
-
--ak-dark-color-disabled-border: oklch(
|
|
194
|
-
--ak-dark-color-disabled-text: oklch(
|
|
193
|
+
--ak-dark-color-disabled-border: oklch(54% 0 0);
|
|
194
|
+
--ak-dark-color-disabled-text: oklch(74% 0 0);
|
|
195
195
|
--ak-dark-color-selected: oklch(26.9% 0 0);
|
|
196
196
|
--ak-dark-color-selected-border: oklch(100% 0 0 / .1);
|
|
197
197
|
--ak-dark-color-hover: oklch(26.9% 0 0);
|
|
@@ -211,35 +211,35 @@
|
|
|
211
211
|
--ak-color-primary-ink: oklch(20.5% 0 0);
|
|
212
212
|
--ak-color-text: oklch(14.5% 0 0);
|
|
213
213
|
--ak-color-text-muted: oklch(55.6% 0 0);
|
|
214
|
-
--ak-color-text-subtle: oklch(
|
|
214
|
+
--ak-color-text-subtle: oklch(54% 0 0);
|
|
215
215
|
--ak-color-text-inverse: oklch(98.5% 0 0);
|
|
216
216
|
--ak-color-bg: oklch(100% 0 0);
|
|
217
|
-
--ak-color-surface: oklch(
|
|
218
|
-
--ak-color-surface-muted: oklch(
|
|
219
|
-
--ak-color-surface-raised: oklch(
|
|
220
|
-
--ak-color-surface-overlay: oklch(
|
|
217
|
+
--ak-color-surface: oklch(98.5% 0 0);
|
|
218
|
+
--ak-color-surface-muted: oklch(95% 0 0);
|
|
219
|
+
--ak-color-surface-raised: oklch(97% 0 0);
|
|
220
|
+
--ak-color-surface-overlay: oklch(94% 0 0);
|
|
221
221
|
--ak-color-border-subtle: oklch(92.2% 0 0);
|
|
222
222
|
--ak-color-border: oklch(92.2% 0 0);
|
|
223
|
-
--ak-color-border-strong: oklch(
|
|
223
|
+
--ak-color-border-strong: oklch(62% 0 0);
|
|
224
224
|
--ak-color-success: #16a34a;
|
|
225
225
|
--ak-color-success-soft: #16a34a24;
|
|
226
226
|
--ak-color-success-ink: #166534;
|
|
227
|
-
--ak-color-warning: #
|
|
227
|
+
--ak-color-warning: #b45309;
|
|
228
228
|
--ak-color-warning-soft: #f59e0b29;
|
|
229
229
|
--ak-color-warning-ink: #854d0e;
|
|
230
230
|
--ak-color-danger: oklch(57.7% .245 27.325);
|
|
231
231
|
--ak-color-danger-soft: oklch(57.7% .245 27.325 / .2);
|
|
232
232
|
--ak-color-danger-ink: #991b1b;
|
|
233
|
-
--ak-color-info: #
|
|
233
|
+
--ak-color-info: #0369a1;
|
|
234
234
|
--ak-color-info-soft: #0ea5e929;
|
|
235
235
|
--ak-color-info-ink: #0c4a6e;
|
|
236
236
|
--ak-color-link: var(--ak-color-primary);
|
|
237
237
|
--ak-color-link-hover: color-mix(in oklch, var(--ak-color-link) 88%, transparent);
|
|
238
|
-
--ak-color-focus-ring: oklch(
|
|
239
|
-
--ak-color-disabled-bg: oklch(
|
|
238
|
+
--ak-color-focus-ring: oklch(58% 0 0);
|
|
239
|
+
--ak-color-disabled-bg: oklch(90% 0 0);
|
|
240
240
|
--ak-color-disabled-surface: oklch(100% 0 0);
|
|
241
|
-
--ak-color-disabled-border: oklch(
|
|
242
|
-
--ak-color-disabled-text: oklch(
|
|
241
|
+
--ak-color-disabled-border: oklch(62% 0 0);
|
|
242
|
+
--ak-color-disabled-text: oklch(50% 0 0);
|
|
243
243
|
--ak-color-selected: oklch(97% 0 0);
|
|
244
244
|
--ak-color-selected-border: oklch(92.2% 0 0);
|
|
245
245
|
--ak-color-hover: oklch(97% 0 0);
|
|
@@ -1857,7 +1857,7 @@
|
|
|
1857
1857
|
}
|
|
1858
1858
|
|
|
1859
1859
|
:where([data-slot="theme-toggle-content"]) {
|
|
1860
|
-
--_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-
|
|
1860
|
+
--_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-font-size-sm));
|
|
1861
1861
|
flex: none;
|
|
1862
1862
|
justify-content: center;
|
|
1863
1863
|
align-items: center;
|
|
@@ -2105,6 +2105,12 @@
|
|
|
2105
2105
|
padding-inline: 0;
|
|
2106
2106
|
}
|
|
2107
2107
|
|
|
2108
|
+
:where(.btn, .btn-close, [data-slot="button"]):focus-visible {
|
|
2109
|
+
border-color: var(--ak-color-ring);
|
|
2110
|
+
box-shadow: 0 0 0 var(--ak-focus-ring-width) var(--ak-color-focus-ring);
|
|
2111
|
+
outline: none;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2108
2114
|
:where(.btn-group, [data-slot="button-group"]) {
|
|
2109
2115
|
align-items: stretch;
|
|
2110
2116
|
gap: 0;
|
|
@@ -2410,11 +2416,12 @@
|
|
|
2410
2416
|
|
|
2411
2417
|
:where(.input[data-disabled], .input:disabled, [data-slot="input"][data-disabled]) {
|
|
2412
2418
|
background: var(--ak-color-disabled-bg);
|
|
2413
|
-
border-color: var(--ak-color-
|
|
2419
|
+
border-color: var(--ak-color-disabled-border);
|
|
2420
|
+
color: var(--ak-color-disabled-text);
|
|
2414
2421
|
box-shadow: none;
|
|
2415
2422
|
pointer-events: none;
|
|
2416
2423
|
cursor: not-allowed;
|
|
2417
|
-
opacity:
|
|
2424
|
+
opacity: 1;
|
|
2418
2425
|
}
|
|
2419
2426
|
|
|
2420
2427
|
:where(.input-sm, [data-slot="input"][data-size="sm"]) {
|
|
@@ -4805,15 +4812,16 @@
|
|
|
4805
4812
|
color: var(--ak-color-text);
|
|
4806
4813
|
}
|
|
4807
4814
|
|
|
4808
|
-
:where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg), :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible > :where([data-slot="icon"], svg) {
|
|
4815
|
+
:where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg), :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) > :where([data-slot="icon"], svg) {
|
|
4809
4816
|
color: currentColor;
|
|
4810
4817
|
}
|
|
4811
4818
|
|
|
4812
|
-
:where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible {
|
|
4819
|
+
:where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) {
|
|
4820
|
+
outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
|
|
4821
|
+
outline-offset: calc(-1 * var(--ak-focus-ring-width));
|
|
4813
4822
|
background: var(--ak-color-hover);
|
|
4814
4823
|
color: var(--ak-color-text);
|
|
4815
4824
|
box-shadow: none;
|
|
4816
|
-
outline: none;
|
|
4817
4825
|
}
|
|
4818
4826
|
|
|
4819
4827
|
:where(.dropdown-item, [data-slot="dropdown-item"])[data-disabled] {
|
|
@@ -5036,15 +5044,16 @@
|
|
|
5036
5044
|
color: var(--ak-color-text);
|
|
5037
5045
|
}
|
|
5038
5046
|
|
|
5039
|
-
:where([data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-item"]:focus-visible > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:focus-visible > :where([data-slot="icon"], svg)) {
|
|
5047
|
+
:where([data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-item"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg)) {
|
|
5040
5048
|
color: currentColor;
|
|
5041
5049
|
}
|
|
5042
5050
|
|
|
5043
|
-
:where([data-slot="menubar-item"]:focus-visible, [data-slot="menubar-sub-trigger"]:focus-visible) {
|
|
5051
|
+
:where([data-slot="menubar-item"]:is(:focus, :focus-visible), [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)) {
|
|
5052
|
+
outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
|
|
5053
|
+
outline-offset: calc(-1 * var(--ak-focus-ring-width));
|
|
5044
5054
|
background: var(--ak-color-hover);
|
|
5045
5055
|
color: var(--ak-color-text);
|
|
5046
5056
|
box-shadow: none;
|
|
5047
|
-
outline: none;
|
|
5048
5057
|
}
|
|
5049
5058
|
|
|
5050
5059
|
:where([data-slot="menubar-item"][data-disabled], [data-slot="menubar-sub-trigger"][data-disabled]) {
|
|
@@ -5365,7 +5374,7 @@
|
|
|
5365
5374
|
}
|
|
5366
5375
|
|
|
5367
5376
|
:where([data-slot="dialog-content"], [data-slot="sheet-content"]) {
|
|
5368
|
-
--_dialog-surface: var(--ak-color-
|
|
5377
|
+
--_dialog-surface: var(--ak-color-surface-raised);
|
|
5369
5378
|
--_dialog-border: var(--ak-color-border);
|
|
5370
5379
|
gap: var(--ak-space-md);
|
|
5371
5380
|
box-sizing: border-box;
|
|
@@ -5935,3 +5944,49 @@
|
|
|
5935
5944
|
animation: none;
|
|
5936
5945
|
}
|
|
5937
5946
|
}
|
|
5947
|
+
|
|
5948
|
+
:where([data-slot="button"][data-disabled], [data-slot="input"][data-disabled], [data-slot="textarea"][data-disabled], [data-slot="select-trigger"][data-disabled], [data-slot="checkbox"][data-disabled], [data-slot="radio-group-item"][data-disabled], [data-slot="switch"][data-disabled]) {
|
|
5949
|
+
background: var(--ak-color-disabled-bg);
|
|
5950
|
+
border-color: var(--ak-color-disabled-border);
|
|
5951
|
+
color: var(--ak-color-disabled-text);
|
|
5952
|
+
box-shadow: none;
|
|
5953
|
+
opacity: 1;
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5956
|
+
@media (forced-colors: active) {
|
|
5957
|
+
:where([data-slot="button"], [data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"], [data-slot="checkbox"], [data-slot="radio-group-item"], [data-slot="switch"], [data-slot="dropdown-trigger"], [tabindex]:not([tabindex="-1"]), [role="menuitem"]):focus-visible {
|
|
5958
|
+
outline-offset: 2px;
|
|
5959
|
+
box-shadow: none;
|
|
5960
|
+
outline: 2px solid highlight;
|
|
5961
|
+
}
|
|
5962
|
+
|
|
5963
|
+
:where([data-slot="button"], [data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"], [data-slot="checkbox"], [data-slot="radio-group-item"], [data-slot="switch"], [data-slot="dropdown-content"], [data-slot="menubar-content"]) {
|
|
5964
|
+
border-color: buttontext;
|
|
5965
|
+
}
|
|
5966
|
+
|
|
5967
|
+
:where([data-disabled]) {
|
|
5968
|
+
color: graytext;
|
|
5969
|
+
opacity: 1;
|
|
5970
|
+
border-color: graytext;
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5973
|
+
:where([data-slot="dialog-content"], [data-slot="sheet-content"], [data-slot="popover-content"], [data-slot="dropdown-content"], [data-slot="menubar-content"]) {
|
|
5974
|
+
color: canvastext;
|
|
5975
|
+
box-shadow: none;
|
|
5976
|
+
background: canvas;
|
|
5977
|
+
border: 2px solid canvastext;
|
|
5978
|
+
}
|
|
5979
|
+
|
|
5980
|
+
:where([data-slot="dropdown-item"]:is(:focus, :focus-visible), [data-slot="menubar-item"]:is(:focus, :focus-visible), [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)) {
|
|
5981
|
+
outline-offset: -2px;
|
|
5982
|
+
color: highlighttext;
|
|
5983
|
+
background: highlight;
|
|
5984
|
+
outline: 2px solid highlight;
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5987
|
+
:where([data-slot="dialog-overlay"]) {
|
|
5988
|
+
opacity: .75;
|
|
5989
|
+
backdrop-filter: none;
|
|
5990
|
+
background: canvas;
|
|
5991
|
+
}
|
|
5992
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askrjs/themes",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"description": "Default theme tokens, styles, and component presets for Askr apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"askr",
|
|
@@ -101,10 +101,11 @@
|
|
|
101
101
|
"bench:tier2": "cross-env NODE_ENV=production vp test bench --run --reporter=default --config vitest.bench.tier2.config.ts",
|
|
102
102
|
"bench:tier3": "cross-env NODE_ENV=production vp test bench --run --reporter=default --config vitest.bench.tier3.config.ts",
|
|
103
103
|
"bench:tier4": "cross-env NODE_ENV=production vp test bench --run --reporter=default --config vitest.bench.tier4.config.ts --mode production",
|
|
104
|
-
"test": "npm run test:unit && npm run test:checks && npm run test:jsdom && npm run test:browser",
|
|
104
|
+
"test": "npm run test:unit && npm run test:checks && npm run test:jsdom && npm run test:browser && npm run test:forced-colors",
|
|
105
105
|
"test:unit": "vp test run -c vitest.test.unit.config.ts",
|
|
106
106
|
"test:jsdom": "cross-env NODE_ENV=production vp test run -c vitest.test.jsdom.config.ts",
|
|
107
107
|
"test:browser": "vp test run -c vitest.test.browser.config.ts --mode production",
|
|
108
|
+
"test:forced-colors": "vp test run -c vitest.test.forced-colors.config.ts --mode production",
|
|
108
109
|
"test:publint": "publint",
|
|
109
110
|
"test:installed": "node tests/installed-types.js",
|
|
110
111
|
"fmt": "vp fmt .",
|
|
@@ -119,8 +120,8 @@
|
|
|
119
120
|
"test:checks": "vp test run -c vitest.test.checks.config.ts"
|
|
120
121
|
},
|
|
121
122
|
"devDependencies": {
|
|
122
|
-
"@askrjs/askr": ">=0.0.
|
|
123
|
-
"@askrjs/ui": ">=0.0.
|
|
123
|
+
"@askrjs/askr": ">=0.0.92 <0.1.0",
|
|
124
|
+
"@askrjs/ui": ">=0.0.30 <0.1.0",
|
|
124
125
|
"@askrjs/vite": ">=0.0.12 <0.1.0",
|
|
125
126
|
"@tsdown/css": "0.22.14",
|
|
126
127
|
"@types/node": "^26.2.0",
|
|
@@ -135,8 +136,8 @@
|
|
|
135
136
|
"vite-plus": "^0.2.8"
|
|
136
137
|
},
|
|
137
138
|
"peerDependencies": {
|
|
138
|
-
"@askrjs/askr": ">=0.0.
|
|
139
|
-
"@askrjs/ui": ">=0.0.
|
|
139
|
+
"@askrjs/askr": ">=0.0.92 <0.1.0",
|
|
140
|
+
"@askrjs/ui": ">=0.0.30 <0.1.0"
|
|
140
141
|
},
|
|
141
142
|
"engines": {
|
|
142
143
|
"node": ">=24.0.0"
|
|
@@ -128,7 +128,7 @@ export function ThemeScope(props: ThemeScopeProps): JSX.Element {
|
|
|
128
128
|
const ownedCoordinator = state<ThemeCoordinator>(getDefaultThemeCoordinator())();
|
|
129
129
|
const coordinator = parentScope.coordinator ?? ownedCoordinator;
|
|
130
130
|
const scopeDepth = parentScope.depth + 1;
|
|
131
|
-
coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal, (nextTheme) => {
|
|
131
|
+
coordinator.register(scopeId, scopeDepth, storageKey, currentTheme, scopeSignal, (nextTheme) => {
|
|
132
132
|
if (themeState() !== nextTheme) themeState.set(nextTheme);
|
|
133
133
|
});
|
|
134
134
|
|
|
@@ -235,6 +235,7 @@ function createThemeCoordinator() {
|
|
|
235
235
|
symbol,
|
|
236
236
|
{
|
|
237
237
|
depth: number;
|
|
238
|
+
identity: string;
|
|
238
239
|
sequence: number;
|
|
239
240
|
theme: ThemeName;
|
|
240
241
|
signal: AbortSignal;
|
|
@@ -296,6 +297,7 @@ function createThemeCoordinator() {
|
|
|
296
297
|
register(
|
|
297
298
|
id: symbol,
|
|
298
299
|
depth: number,
|
|
300
|
+
identity: string,
|
|
299
301
|
themeName: ThemeName,
|
|
300
302
|
signal: AbortSignal,
|
|
301
303
|
onThemeChange: (themeName: ThemeName) => void,
|
|
@@ -303,6 +305,7 @@ function createThemeCoordinator() {
|
|
|
303
305
|
const existing = scopes.get(id);
|
|
304
306
|
scopes.set(id, {
|
|
305
307
|
depth,
|
|
308
|
+
identity,
|
|
306
309
|
sequence: existing?.sequence ?? nextSequence++,
|
|
307
310
|
theme: themeName,
|
|
308
311
|
signal,
|
|
@@ -326,8 +329,13 @@ function createThemeCoordinator() {
|
|
|
326
329
|
if (scope) scope.theme = themeName;
|
|
327
330
|
explicitOwner = id;
|
|
328
331
|
const ownerDepth = scope?.depth;
|
|
332
|
+
const ownerIdentity = scope?.identity;
|
|
329
333
|
for (const [scopeId, registered] of scopes) {
|
|
330
|
-
if (
|
|
334
|
+
if (
|
|
335
|
+
scopeId !== id &&
|
|
336
|
+
registered.depth === ownerDepth &&
|
|
337
|
+
registered.identity === ownerIdentity
|
|
338
|
+
) {
|
|
331
339
|
registered.theme = themeName;
|
|
332
340
|
registered.onThemeChange(themeName);
|
|
333
341
|
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
:where([data-slot="theme-toggle-content"]) {
|
|
40
|
-
--_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-
|
|
40
|
+
--_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-font-size-sm));
|
|
41
41
|
display: inline-flex;
|
|
42
42
|
align-items: center;
|
|
43
43
|
justify-content: center;
|
|
@@ -288,3 +288,10 @@
|
|
|
288
288
|
min-inline-size: var(--_button-height);
|
|
289
289
|
padding-inline: 0;
|
|
290
290
|
}
|
|
291
|
+
|
|
292
|
+
/* Keep the focus contract last so variant box-shadow resets cannot hide it. */
|
|
293
|
+
:where(.btn, .btn-close, [data-slot="button"]):focus-visible {
|
|
294
|
+
outline: none;
|
|
295
|
+
border-color: var(--ak-color-ring);
|
|
296
|
+
box-shadow: 0 0 0 var(--ak-focus-ring-width) var(--ak-color-focus-ring);
|
|
297
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cross-component accessibility states live at the end of the default-theme
|
|
3
|
+
* cascade so new controls inherit the same perceivability contract.
|
|
4
|
+
*/
|
|
5
|
+
:where(
|
|
6
|
+
[data-slot="button"][data-disabled],
|
|
7
|
+
[data-slot="input"][data-disabled],
|
|
8
|
+
[data-slot="textarea"][data-disabled],
|
|
9
|
+
[data-slot="select-trigger"][data-disabled],
|
|
10
|
+
[data-slot="checkbox"][data-disabled],
|
|
11
|
+
[data-slot="radio-group-item"][data-disabled],
|
|
12
|
+
[data-slot="switch"][data-disabled]
|
|
13
|
+
) {
|
|
14
|
+
background: var(--ak-color-disabled-bg);
|
|
15
|
+
border-color: var(--ak-color-disabled-border);
|
|
16
|
+
color: var(--ak-color-disabled-text);
|
|
17
|
+
box-shadow: none;
|
|
18
|
+
opacity: 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@media (forced-colors: active) {
|
|
22
|
+
:where(
|
|
23
|
+
[data-slot="button"],
|
|
24
|
+
[data-slot="input"],
|
|
25
|
+
[data-slot="textarea"],
|
|
26
|
+
[data-slot="select-trigger"],
|
|
27
|
+
[data-slot="checkbox"],
|
|
28
|
+
[data-slot="radio-group-item"],
|
|
29
|
+
[data-slot="switch"],
|
|
30
|
+
[data-slot="dropdown-trigger"],
|
|
31
|
+
[tabindex]:not([tabindex="-1"]),
|
|
32
|
+
[role="menuitem"]
|
|
33
|
+
):focus-visible {
|
|
34
|
+
outline: 2px solid Highlight;
|
|
35
|
+
outline-offset: 2px;
|
|
36
|
+
box-shadow: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:where(
|
|
40
|
+
[data-slot="button"],
|
|
41
|
+
[data-slot="input"],
|
|
42
|
+
[data-slot="textarea"],
|
|
43
|
+
[data-slot="select-trigger"],
|
|
44
|
+
[data-slot="checkbox"],
|
|
45
|
+
[data-slot="radio-group-item"],
|
|
46
|
+
[data-slot="switch"],
|
|
47
|
+
[data-slot="dropdown-content"],
|
|
48
|
+
[data-slot="menubar-content"]
|
|
49
|
+
) {
|
|
50
|
+
border-color: ButtonText;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:where([data-disabled]) {
|
|
54
|
+
border-color: GrayText;
|
|
55
|
+
color: GrayText;
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:where(
|
|
60
|
+
[data-slot="dialog-content"],
|
|
61
|
+
[data-slot="sheet-content"],
|
|
62
|
+
[data-slot="popover-content"],
|
|
63
|
+
[data-slot="dropdown-content"],
|
|
64
|
+
[data-slot="menubar-content"]
|
|
65
|
+
) {
|
|
66
|
+
border: 2px solid CanvasText;
|
|
67
|
+
background: Canvas;
|
|
68
|
+
color: CanvasText;
|
|
69
|
+
box-shadow: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:where(
|
|
73
|
+
[data-slot="dropdown-item"]:is(:focus, :focus-visible),
|
|
74
|
+
[data-slot="menubar-item"]:is(:focus, :focus-visible),
|
|
75
|
+
[data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
|
|
76
|
+
) {
|
|
77
|
+
outline: 2px solid Highlight;
|
|
78
|
+
outline-offset: -2px;
|
|
79
|
+
background: Highlight;
|
|
80
|
+
color: HighlightText;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:where([data-slot="dialog-overlay"]) {
|
|
84
|
+
background: Canvas;
|
|
85
|
+
opacity: 0.75;
|
|
86
|
+
backdrop-filter: none;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -44,11 +44,12 @@
|
|
|
44
44
|
|
|
45
45
|
:where(.input[data-disabled], .input:disabled, [data-slot="input"][data-disabled]) {
|
|
46
46
|
background: var(--ak-color-disabled-bg);
|
|
47
|
-
border-color: var(--ak-color-
|
|
47
|
+
border-color: var(--ak-color-disabled-border);
|
|
48
|
+
color: var(--ak-color-disabled-text);
|
|
48
49
|
box-shadow: none;
|
|
49
50
|
pointer-events: none;
|
|
50
51
|
cursor: not-allowed;
|
|
51
|
-
opacity:
|
|
52
|
+
opacity: 1;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
:where(.input-sm, [data-slot="input"][data-size="sm"]) {
|
|
@@ -158,13 +158,14 @@
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
:where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg),
|
|
161
|
-
:where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible
|
|
161
|
+
:where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible)
|
|
162
162
|
> :where([data-slot="icon"], svg) {
|
|
163
163
|
color: currentColor;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
:where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible {
|
|
167
|
-
outline:
|
|
166
|
+
:where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) {
|
|
167
|
+
outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
|
|
168
|
+
outline-offset: calc(-1 * var(--ak-focus-ring-width));
|
|
168
169
|
background: var(--ak-color-hover);
|
|
169
170
|
color: var(--ak-color-text);
|
|
170
171
|
box-shadow: none;
|
|
@@ -165,15 +165,19 @@
|
|
|
165
165
|
|
|
166
166
|
:where(
|
|
167
167
|
[data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg),
|
|
168
|
-
[data-slot="menubar-item"]:focus-visible > :where([data-slot="icon"], svg),
|
|
168
|
+
[data-slot="menubar-item"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg),
|
|
169
169
|
[data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg),
|
|
170
|
-
[data-slot="menubar-sub-trigger"]:focus-visible > :where([data-slot="icon"], svg)
|
|
170
|
+
[data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg)
|
|
171
171
|
) {
|
|
172
172
|
color: currentColor;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
:where(
|
|
176
|
-
|
|
175
|
+
:where(
|
|
176
|
+
[data-slot="menubar-item"]:is(:focus, :focus-visible),
|
|
177
|
+
[data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
|
|
178
|
+
) {
|
|
179
|
+
outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
|
|
180
|
+
outline-offset: calc(-1 * var(--ak-focus-ring-width));
|
|
177
181
|
background: var(--ak-color-hover);
|
|
178
182
|
color: var(--ak-color-text);
|
|
179
183
|
box-shadow: none;
|
|
@@ -178,18 +178,18 @@
|
|
|
178
178
|
|
|
179
179
|
--ak-dark-color-text: oklch(0.985 0 0);
|
|
180
180
|
--ak-dark-color-text-muted: oklch(0.708 0 0);
|
|
181
|
-
--ak-dark-color-text-subtle: oklch(0.
|
|
181
|
+
--ak-dark-color-text-subtle: oklch(0.74 0 0);
|
|
182
182
|
--ak-dark-color-text-inverse: oklch(0.205 0 0);
|
|
183
183
|
|
|
184
184
|
--ak-dark-color-bg: oklch(0.145 0 0);
|
|
185
185
|
--ak-dark-color-surface: oklch(0.205 0 0);
|
|
186
186
|
--ak-dark-color-surface-muted: oklch(0.269 0 0);
|
|
187
|
-
--ak-dark-color-surface-raised: oklch(0.
|
|
188
|
-
--ak-dark-color-surface-overlay: oklch(0.
|
|
187
|
+
--ak-dark-color-surface-raised: oklch(0.235 0 0);
|
|
188
|
+
--ak-dark-color-surface-overlay: oklch(0.269 0 0);
|
|
189
189
|
|
|
190
190
|
--ak-dark-color-border-subtle: oklch(0.269 0 0);
|
|
191
191
|
--ak-dark-color-border: oklch(1 0 0 / 10%);
|
|
192
|
-
--ak-dark-color-border-strong: oklch(0.
|
|
192
|
+
--ak-dark-color-border-strong: oklch(0.54 0 0);
|
|
193
193
|
|
|
194
194
|
--ak-dark-color-success: #4ade80;
|
|
195
195
|
--ak-dark-color-success-soft: #052e16;
|
|
@@ -206,11 +206,11 @@
|
|
|
206
206
|
|
|
207
207
|
--ak-dark-color-link: var(--ak-dark-color-text);
|
|
208
208
|
--ak-dark-color-link-hover: color-mix(in oklch, var(--ak-dark-color-link) 88%, transparent);
|
|
209
|
-
--ak-dark-color-focus-ring: oklch(0.
|
|
210
|
-
--ak-dark-color-disabled-bg: oklch(0.
|
|
209
|
+
--ak-dark-color-focus-ring: oklch(0.6 0 0);
|
|
210
|
+
--ak-dark-color-disabled-bg: oklch(0.32 0 0);
|
|
211
211
|
--ak-dark-color-disabled-surface: oklch(0.205 0 0);
|
|
212
|
-
--ak-dark-color-disabled-border: oklch(
|
|
213
|
-
--ak-dark-color-disabled-text: oklch(0.
|
|
212
|
+
--ak-dark-color-disabled-border: oklch(0.54 0 0);
|
|
213
|
+
--ak-dark-color-disabled-text: oklch(0.74 0 0);
|
|
214
214
|
--ak-dark-color-selected: oklch(0.269 0 0);
|
|
215
215
|
--ak-dark-color-selected-border: oklch(1 0 0 / 10%);
|
|
216
216
|
--ak-dark-color-hover: oklch(0.269 0 0);
|
|
@@ -233,39 +233,39 @@
|
|
|
233
233
|
|
|
234
234
|
--ak-color-text: oklch(0.145 0 0);
|
|
235
235
|
--ak-color-text-muted: oklch(0.556 0 0);
|
|
236
|
-
--ak-color-text-subtle: oklch(0.
|
|
236
|
+
--ak-color-text-subtle: oklch(0.54 0 0);
|
|
237
237
|
--ak-color-text-inverse: oklch(0.985 0 0);
|
|
238
238
|
|
|
239
239
|
--ak-color-bg: oklch(1 0 0);
|
|
240
|
-
--ak-color-surface: oklch(
|
|
241
|
-
--ak-color-surface-muted: oklch(0.
|
|
242
|
-
--ak-color-surface-raised: oklch(
|
|
243
|
-
--ak-color-surface-overlay: oklch(
|
|
240
|
+
--ak-color-surface: oklch(0.985 0 0);
|
|
241
|
+
--ak-color-surface-muted: oklch(0.95 0 0);
|
|
242
|
+
--ak-color-surface-raised: oklch(0.97 0 0);
|
|
243
|
+
--ak-color-surface-overlay: oklch(0.94 0 0);
|
|
244
244
|
|
|
245
245
|
--ak-color-border-subtle: oklch(0.922 0 0);
|
|
246
246
|
--ak-color-border: oklch(0.922 0 0);
|
|
247
|
-
--ak-color-border-strong: oklch(0.
|
|
247
|
+
--ak-color-border-strong: oklch(0.62 0 0);
|
|
248
248
|
|
|
249
249
|
--ak-color-success: #16a34a;
|
|
250
250
|
--ak-color-success-soft: rgba(22, 163, 74, 0.14);
|
|
251
251
|
--ak-color-success-ink: #166534;
|
|
252
|
-
--ak-color-warning: #
|
|
252
|
+
--ak-color-warning: #b45309;
|
|
253
253
|
--ak-color-warning-soft: rgba(245, 158, 11, 0.16);
|
|
254
254
|
--ak-color-warning-ink: #854d0e;
|
|
255
255
|
--ak-color-danger: oklch(0.577 0.245 27.325);
|
|
256
256
|
--ak-color-danger-soft: oklch(0.577 0.245 27.325 / 0.2);
|
|
257
257
|
--ak-color-danger-ink: #991b1b;
|
|
258
|
-
--ak-color-info: #
|
|
258
|
+
--ak-color-info: #0369a1;
|
|
259
259
|
--ak-color-info-soft: rgba(14, 165, 233, 0.16);
|
|
260
260
|
--ak-color-info-ink: #0c4a6e;
|
|
261
261
|
|
|
262
262
|
--ak-color-link: var(--ak-color-primary);
|
|
263
263
|
--ak-color-link-hover: color-mix(in oklch, var(--ak-color-link) 88%, transparent);
|
|
264
|
-
--ak-color-focus-ring: oklch(0.
|
|
265
|
-
--ak-color-disabled-bg: oklch(0.
|
|
264
|
+
--ak-color-focus-ring: oklch(0.58 0 0);
|
|
265
|
+
--ak-color-disabled-bg: oklch(0.9 0 0);
|
|
266
266
|
--ak-color-disabled-surface: oklch(1 0 0);
|
|
267
|
-
--ak-color-disabled-border: oklch(0.
|
|
268
|
-
--ak-color-disabled-text: oklch(0.
|
|
267
|
+
--ak-color-disabled-border: oklch(0.62 0 0);
|
|
268
|
+
--ak-color-disabled-text: oklch(0.5 0 0);
|
|
269
269
|
--ak-color-selected: oklch(0.97 0 0);
|
|
270
270
|
--ak-color-selected-border: oklch(0.922 0 0);
|
|
271
271
|
--ak-color-hover: oklch(0.97 0 0);
|
|
@@ -288,3 +288,10 @@
|
|
|
288
288
|
min-inline-size: var(--_button-height);
|
|
289
289
|
padding-inline: 0;
|
|
290
290
|
}
|
|
291
|
+
|
|
292
|
+
/* Keep the focus contract last so variant box-shadow resets cannot hide it. */
|
|
293
|
+
:where(.btn, .btn-close, [data-slot="button"]):focus-visible {
|
|
294
|
+
outline: none;
|
|
295
|
+
border-color: var(--ak-color-ring);
|
|
296
|
+
box-shadow: 0 0 0 var(--ak-focus-ring-width) var(--ak-color-focus-ring);
|
|
297
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cross-component accessibility states live at the end of the default-theme
|
|
3
|
+
* cascade so new controls inherit the same perceivability contract.
|
|
4
|
+
*/
|
|
5
|
+
:where(
|
|
6
|
+
[data-slot="button"][data-disabled],
|
|
7
|
+
[data-slot="input"][data-disabled],
|
|
8
|
+
[data-slot="textarea"][data-disabled],
|
|
9
|
+
[data-slot="select-trigger"][data-disabled],
|
|
10
|
+
[data-slot="checkbox"][data-disabled],
|
|
11
|
+
[data-slot="radio-group-item"][data-disabled],
|
|
12
|
+
[data-slot="switch"][data-disabled]
|
|
13
|
+
) {
|
|
14
|
+
background: var(--ak-color-disabled-bg);
|
|
15
|
+
border-color: var(--ak-color-disabled-border);
|
|
16
|
+
color: var(--ak-color-disabled-text);
|
|
17
|
+
box-shadow: none;
|
|
18
|
+
opacity: 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@media (forced-colors: active) {
|
|
22
|
+
:where(
|
|
23
|
+
[data-slot="button"],
|
|
24
|
+
[data-slot="input"],
|
|
25
|
+
[data-slot="textarea"],
|
|
26
|
+
[data-slot="select-trigger"],
|
|
27
|
+
[data-slot="checkbox"],
|
|
28
|
+
[data-slot="radio-group-item"],
|
|
29
|
+
[data-slot="switch"],
|
|
30
|
+
[data-slot="dropdown-trigger"],
|
|
31
|
+
[tabindex]:not([tabindex="-1"]),
|
|
32
|
+
[role="menuitem"]
|
|
33
|
+
):focus-visible {
|
|
34
|
+
outline: 2px solid Highlight;
|
|
35
|
+
outline-offset: 2px;
|
|
36
|
+
box-shadow: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:where(
|
|
40
|
+
[data-slot="button"],
|
|
41
|
+
[data-slot="input"],
|
|
42
|
+
[data-slot="textarea"],
|
|
43
|
+
[data-slot="select-trigger"],
|
|
44
|
+
[data-slot="checkbox"],
|
|
45
|
+
[data-slot="radio-group-item"],
|
|
46
|
+
[data-slot="switch"],
|
|
47
|
+
[data-slot="dropdown-content"],
|
|
48
|
+
[data-slot="menubar-content"]
|
|
49
|
+
) {
|
|
50
|
+
border-color: ButtonText;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:where([data-disabled]) {
|
|
54
|
+
border-color: GrayText;
|
|
55
|
+
color: GrayText;
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:where(
|
|
60
|
+
[data-slot="dialog-content"],
|
|
61
|
+
[data-slot="sheet-content"],
|
|
62
|
+
[data-slot="popover-content"],
|
|
63
|
+
[data-slot="dropdown-content"],
|
|
64
|
+
[data-slot="menubar-content"]
|
|
65
|
+
) {
|
|
66
|
+
border: 2px solid CanvasText;
|
|
67
|
+
background: Canvas;
|
|
68
|
+
color: CanvasText;
|
|
69
|
+
box-shadow: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:where(
|
|
73
|
+
[data-slot="dropdown-item"]:is(:focus, :focus-visible),
|
|
74
|
+
[data-slot="menubar-item"]:is(:focus, :focus-visible),
|
|
75
|
+
[data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
|
|
76
|
+
) {
|
|
77
|
+
outline: 2px solid Highlight;
|
|
78
|
+
outline-offset: -2px;
|
|
79
|
+
background: Highlight;
|
|
80
|
+
color: HighlightText;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:where([data-slot="dialog-overlay"]) {
|
|
84
|
+
background: Canvas;
|
|
85
|
+
opacity: 0.75;
|
|
86
|
+
backdrop-filter: none;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -44,11 +44,12 @@
|
|
|
44
44
|
|
|
45
45
|
:where(.input[data-disabled], .input:disabled, [data-slot="input"][data-disabled]) {
|
|
46
46
|
background: var(--ak-color-disabled-bg);
|
|
47
|
-
border-color: var(--ak-color-
|
|
47
|
+
border-color: var(--ak-color-disabled-border);
|
|
48
|
+
color: var(--ak-color-disabled-text);
|
|
48
49
|
box-shadow: none;
|
|
49
50
|
pointer-events: none;
|
|
50
51
|
cursor: not-allowed;
|
|
51
|
-
opacity:
|
|
52
|
+
opacity: 1;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
:where(.input-sm, [data-slot="input"][data-size="sm"]) {
|
|
@@ -158,13 +158,14 @@
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
:where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg),
|
|
161
|
-
:where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible
|
|
161
|
+
:where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible)
|
|
162
162
|
> :where([data-slot="icon"], svg) {
|
|
163
163
|
color: currentColor;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
:where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible {
|
|
167
|
-
outline:
|
|
166
|
+
:where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) {
|
|
167
|
+
outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
|
|
168
|
+
outline-offset: calc(-1 * var(--ak-focus-ring-width));
|
|
168
169
|
background: var(--ak-color-hover);
|
|
169
170
|
color: var(--ak-color-text);
|
|
170
171
|
box-shadow: none;
|
|
@@ -165,15 +165,19 @@
|
|
|
165
165
|
|
|
166
166
|
:where(
|
|
167
167
|
[data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg),
|
|
168
|
-
[data-slot="menubar-item"]:focus-visible > :where([data-slot="icon"], svg),
|
|
168
|
+
[data-slot="menubar-item"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg),
|
|
169
169
|
[data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg),
|
|
170
|
-
[data-slot="menubar-sub-trigger"]:focus-visible > :where([data-slot="icon"], svg)
|
|
170
|
+
[data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg)
|
|
171
171
|
) {
|
|
172
172
|
color: currentColor;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
:where(
|
|
176
|
-
|
|
175
|
+
:where(
|
|
176
|
+
[data-slot="menubar-item"]:is(:focus, :focus-visible),
|
|
177
|
+
[data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
|
|
178
|
+
) {
|
|
179
|
+
outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
|
|
180
|
+
outline-offset: calc(-1 * var(--ak-focus-ring-width));
|
|
177
181
|
background: var(--ak-color-hover);
|
|
178
182
|
color: var(--ak-color-text);
|
|
179
183
|
box-shadow: none;
|
|
@@ -178,18 +178,18 @@
|
|
|
178
178
|
|
|
179
179
|
--ak-dark-color-text: oklch(0.985 0 0);
|
|
180
180
|
--ak-dark-color-text-muted: oklch(0.708 0 0);
|
|
181
|
-
--ak-dark-color-text-subtle: oklch(0.
|
|
181
|
+
--ak-dark-color-text-subtle: oklch(0.74 0 0);
|
|
182
182
|
--ak-dark-color-text-inverse: oklch(0.205 0 0);
|
|
183
183
|
|
|
184
184
|
--ak-dark-color-bg: oklch(0.145 0 0);
|
|
185
185
|
--ak-dark-color-surface: oklch(0.205 0 0);
|
|
186
186
|
--ak-dark-color-surface-muted: oklch(0.269 0 0);
|
|
187
|
-
--ak-dark-color-surface-raised: oklch(0.
|
|
188
|
-
--ak-dark-color-surface-overlay: oklch(0.
|
|
187
|
+
--ak-dark-color-surface-raised: oklch(0.235 0 0);
|
|
188
|
+
--ak-dark-color-surface-overlay: oklch(0.269 0 0);
|
|
189
189
|
|
|
190
190
|
--ak-dark-color-border-subtle: oklch(0.269 0 0);
|
|
191
191
|
--ak-dark-color-border: oklch(1 0 0 / 10%);
|
|
192
|
-
--ak-dark-color-border-strong: oklch(0.
|
|
192
|
+
--ak-dark-color-border-strong: oklch(0.54 0 0);
|
|
193
193
|
|
|
194
194
|
--ak-dark-color-success: #4ade80;
|
|
195
195
|
--ak-dark-color-success-soft: #052e16;
|
|
@@ -206,11 +206,11 @@
|
|
|
206
206
|
|
|
207
207
|
--ak-dark-color-link: var(--ak-dark-color-text);
|
|
208
208
|
--ak-dark-color-link-hover: color-mix(in oklch, var(--ak-dark-color-link) 88%, transparent);
|
|
209
|
-
--ak-dark-color-focus-ring: oklch(0.
|
|
210
|
-
--ak-dark-color-disabled-bg: oklch(0.
|
|
209
|
+
--ak-dark-color-focus-ring: oklch(0.6 0 0);
|
|
210
|
+
--ak-dark-color-disabled-bg: oklch(0.32 0 0);
|
|
211
211
|
--ak-dark-color-disabled-surface: oklch(0.205 0 0);
|
|
212
|
-
--ak-dark-color-disabled-border: oklch(
|
|
213
|
-
--ak-dark-color-disabled-text: oklch(0.
|
|
212
|
+
--ak-dark-color-disabled-border: oklch(0.54 0 0);
|
|
213
|
+
--ak-dark-color-disabled-text: oklch(0.74 0 0);
|
|
214
214
|
--ak-dark-color-selected: oklch(0.269 0 0);
|
|
215
215
|
--ak-dark-color-selected-border: oklch(1 0 0 / 10%);
|
|
216
216
|
--ak-dark-color-hover: oklch(0.269 0 0);
|
|
@@ -233,39 +233,39 @@
|
|
|
233
233
|
|
|
234
234
|
--ak-color-text: oklch(0.145 0 0);
|
|
235
235
|
--ak-color-text-muted: oklch(0.556 0 0);
|
|
236
|
-
--ak-color-text-subtle: oklch(0.
|
|
236
|
+
--ak-color-text-subtle: oklch(0.54 0 0);
|
|
237
237
|
--ak-color-text-inverse: oklch(0.985 0 0);
|
|
238
238
|
|
|
239
239
|
--ak-color-bg: oklch(1 0 0);
|
|
240
|
-
--ak-color-surface: oklch(
|
|
241
|
-
--ak-color-surface-muted: oklch(0.
|
|
242
|
-
--ak-color-surface-raised: oklch(
|
|
243
|
-
--ak-color-surface-overlay: oklch(
|
|
240
|
+
--ak-color-surface: oklch(0.985 0 0);
|
|
241
|
+
--ak-color-surface-muted: oklch(0.95 0 0);
|
|
242
|
+
--ak-color-surface-raised: oklch(0.97 0 0);
|
|
243
|
+
--ak-color-surface-overlay: oklch(0.94 0 0);
|
|
244
244
|
|
|
245
245
|
--ak-color-border-subtle: oklch(0.922 0 0);
|
|
246
246
|
--ak-color-border: oklch(0.922 0 0);
|
|
247
|
-
--ak-color-border-strong: oklch(0.
|
|
247
|
+
--ak-color-border-strong: oklch(0.62 0 0);
|
|
248
248
|
|
|
249
249
|
--ak-color-success: #16a34a;
|
|
250
250
|
--ak-color-success-soft: rgba(22, 163, 74, 0.14);
|
|
251
251
|
--ak-color-success-ink: #166534;
|
|
252
|
-
--ak-color-warning: #
|
|
252
|
+
--ak-color-warning: #b45309;
|
|
253
253
|
--ak-color-warning-soft: rgba(245, 158, 11, 0.16);
|
|
254
254
|
--ak-color-warning-ink: #854d0e;
|
|
255
255
|
--ak-color-danger: oklch(0.577 0.245 27.325);
|
|
256
256
|
--ak-color-danger-soft: oklch(0.577 0.245 27.325 / 0.2);
|
|
257
257
|
--ak-color-danger-ink: #991b1b;
|
|
258
|
-
--ak-color-info: #
|
|
258
|
+
--ak-color-info: #0369a1;
|
|
259
259
|
--ak-color-info-soft: rgba(14, 165, 233, 0.16);
|
|
260
260
|
--ak-color-info-ink: #0c4a6e;
|
|
261
261
|
|
|
262
262
|
--ak-color-link: var(--ak-color-primary);
|
|
263
263
|
--ak-color-link-hover: color-mix(in oklch, var(--ak-color-link) 88%, transparent);
|
|
264
|
-
--ak-color-focus-ring: oklch(0.
|
|
265
|
-
--ak-color-disabled-bg: oklch(0.
|
|
264
|
+
--ak-color-focus-ring: oklch(0.58 0 0);
|
|
265
|
+
--ak-color-disabled-bg: oklch(0.9 0 0);
|
|
266
266
|
--ak-color-disabled-surface: oklch(1 0 0);
|
|
267
|
-
--ak-color-disabled-border: oklch(0.
|
|
268
|
-
--ak-color-disabled-text: oklch(0.
|
|
267
|
+
--ak-color-disabled-border: oklch(0.62 0 0);
|
|
268
|
+
--ak-color-disabled-text: oklch(0.5 0 0);
|
|
269
269
|
--ak-color-selected: oklch(0.97 0 0);
|
|
270
270
|
--ak-color-selected-border: oklch(0.922 0 0);
|
|
271
271
|
--ak-color-hover: oklch(0.97 0 0);
|