@askrjs/themes 0.0.25 → 0.0.27
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/dist/components/_internal/style.js +2 -1
- package/dist/components/_internal/style.js.map +1 -1
- package/dist/components/alert/alert.d.ts +5 -0
- package/dist/components/alert/alert.js +5 -0
- package/dist/components/alert/alert.js.map +1 -1
- package/dist/components/alert/alert.types.d.ts +3 -0
- package/dist/components/aside/aside.d.ts +1 -0
- package/dist/components/aside/aside.js +1 -0
- package/dist/components/aside/aside.js.map +1 -1
- package/dist/components/aside/aside.types.d.ts +1 -0
- package/dist/components/aspect-ratio/aspect-ratio.d.ts +5 -0
- package/dist/components/aspect-ratio/aspect-ratio.js.map +1 -1
- package/dist/components/aspect-ratio/aspect-ratio.types.d.ts +3 -0
- package/dist/components/badge/badge.d.ts +1 -0
- package/dist/components/badge/badge.js.map +1 -1
- package/dist/components/badge/badge.types.d.ts +4 -0
- package/dist/components/block/block.d.ts +6 -0
- package/dist/components/block/block.js.map +1 -1
- package/dist/components/block/block.types.d.ts +8 -0
- package/dist/components/brand/brand.d.ts +3 -0
- package/dist/components/brand/brand.js +3 -0
- package/dist/components/brand/brand.js.map +1 -1
- package/dist/components/brand/brand.types.d.ts +6 -0
- package/dist/components/button-group/button-group.d.ts +1 -0
- package/dist/components/button-group/button-group.js +1 -0
- package/dist/components/button-group/button-group.js.map +1 -1
- package/dist/components/button-group/button-group.types.d.ts +2 -0
- package/dist/components/card/card.d.ts +7 -0
- package/dist/components/card/card.js +7 -0
- package/dist/components/card/card.js.map +1 -1
- package/dist/components/card/card.types.d.ts +9 -0
- package/dist/components/catalog.d.ts +122 -0
- package/dist/components/catalog.js +118 -0
- package/dist/components/catalog.js.map +1 -1
- package/dist/components/close/close.d.ts +1 -0
- package/dist/components/close/close.js.map +1 -1
- package/dist/components/close/close.types.d.ts +2 -0
- package/dist/components/command-palette/command-palette.d.ts +14 -0
- package/dist/components/command-palette/command-palette.js +13 -0
- package/dist/components/command-palette/command-palette.js.map +1 -1
- package/dist/components/command-palette/command-palette.types.d.ts +6 -0
- package/dist/components/container/container.d.ts +1 -0
- package/dist/components/container/container.js +1 -0
- package/dist/components/container/container.js.map +1 -1
- package/dist/components/container/container.types.d.ts +1 -0
- package/dist/components/empty-state/empty-state.d.ts +1 -0
- package/dist/components/empty-state/empty-state.js +1 -0
- package/dist/components/empty-state/empty-state.js.map +1 -1
- package/dist/components/empty-state/empty-state.types.d.ts +2 -0
- package/dist/components/field/field.d.ts +3 -0
- package/dist/components/field/field.js +3 -0
- package/dist/components/field/field.js.map +1 -1
- package/dist/components/field/field.types.d.ts +3 -0
- package/dist/components/footer/footer.d.ts +7 -0
- package/dist/components/footer/footer.js +7 -0
- package/dist/components/footer/footer.js.map +1 -1
- package/dist/components/footer/footer.types.d.ts +7 -0
- package/dist/components/grid/grid.d.ts +1 -0
- package/dist/components/grid/grid.js +1 -0
- package/dist/components/grid/grid.js.map +1 -1
- package/dist/components/grid/grid.types.d.ts +4 -0
- package/dist/components/header/header.d.ts +1 -0
- package/dist/components/header/header.js +1 -0
- package/dist/components/header/header.js.map +1 -1
- package/dist/components/header/header.types.d.ts +1 -0
- package/dist/components/input-group/input-group.d.ts +2 -0
- package/dist/components/input-group/input-group.js +1 -0
- package/dist/components/input-group/input-group.js.map +1 -1
- package/dist/components/input-group/input-group.types.d.ts +4 -0
- package/dist/components/main/main.d.ts +1 -0
- package/dist/components/main/main.js +1 -0
- package/dist/components/main/main.js.map +1 -1
- package/dist/components/main/main.types.d.ts +1 -0
- package/dist/components/nav/nav.d.ts +6 -0
- package/dist/components/nav/nav.js +3 -0
- package/dist/components/nav/nav.js.map +1 -1
- package/dist/components/nav/nav.types.d.ts +14 -0
- package/dist/components/navbar/navbar.d.ts +8 -0
- package/dist/components/navbar/navbar.js +8 -0
- package/dist/components/navbar/navbar.js.map +1 -1
- package/dist/components/navbar/navbar.types.d.ts +5 -0
- package/dist/components/overlays/dropdown-content.d.ts +5 -0
- package/dist/components/overlays/dropdown-content.js.map +1 -1
- package/dist/components/page/page.d.ts +1 -0
- package/dist/components/page/page.js +1 -0
- package/dist/components/page/page.js.map +1 -1
- package/dist/components/page/page.types.d.ts +1 -0
- package/dist/components/page-header/page-header.d.ts +1 -0
- package/dist/components/page-header/page-header.js +1 -0
- package/dist/components/page-header/page-header.js.map +1 -1
- package/dist/components/page-header/page-header.types.d.ts +1 -0
- package/dist/components/section/section.d.ts +1 -0
- package/dist/components/section/section.js +1 -0
- package/dist/components/section/section.js.map +1 -1
- package/dist/components/section/section.types.d.ts +1 -0
- package/dist/components/separator/separator.d.ts +5 -0
- package/dist/components/separator/separator.js.map +1 -1
- package/dist/components/separator/separator.types.d.ts +4 -0
- package/dist/components/sidebar/sidebar.d.ts +16 -0
- package/dist/components/sidebar/sidebar.js +16 -0
- package/dist/components/sidebar/sidebar.js.map +1 -1
- package/dist/components/sidebar/sidebar.types.d.ts +8 -0
- package/dist/components/skeleton/skeleton.d.ts +1 -0
- package/dist/components/skeleton/skeleton.js.map +1 -1
- package/dist/components/skeleton/skeleton.types.d.ts +3 -0
- package/dist/components/spinner/spinner.d.ts +1 -0
- package/dist/components/spinner/spinner.js +1 -0
- package/dist/components/spinner/spinner.js.map +1 -1
- package/dist/components/spinner/spinner.types.d.ts +3 -0
- package/dist/components/stat/stat.d.ts +4 -0
- package/dist/components/stat/stat.js +4 -0
- package/dist/components/stat/stat.js.map +1 -1
- package/dist/components/stat/stat.types.d.ts +4 -0
- package/dist/components/text/text.d.ts +1 -0
- package/dist/components/text/text.js +1 -0
- package/dist/components/text/text.js.map +1 -1
- package/dist/components/text/text.types.d.ts +8 -0
- package/dist/components/theme/theme.d.ts +25 -0
- package/dist/components/theme/theme.js +17 -0
- package/dist/components/theme/theme.js.map +1 -1
- package/dist/components/toolbar/toolbar.d.ts +1 -0
- package/dist/components/toolbar/toolbar.js +1 -0
- package/dist/components/toolbar/toolbar.js.map +1 -1
- package/dist/components/toolbar/toolbar.types.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/_internal/style.ts +8 -1
- package/src/components/alert/alert.tsx +5 -0
- package/src/components/alert/alert.types.ts +3 -0
- package/src/components/aside/aside.tsx +1 -0
- package/src/components/aside/aside.types.ts +1 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +5 -0
- package/src/components/aspect-ratio/aspect-ratio.types.ts +3 -0
- package/src/components/badge/badge.tsx +1 -0
- package/src/components/badge/badge.types.ts +4 -0
- package/src/components/block/block.tsx +6 -0
- package/src/components/block/block.types.ts +8 -0
- package/src/components/brand/brand.tsx +3 -0
- package/src/components/brand/brand.types.ts +6 -0
- package/src/components/button-group/button-group.tsx +1 -0
- package/src/components/button-group/button-group.types.ts +2 -0
- package/src/components/card/card.tsx +7 -0
- package/src/components/card/card.types.ts +9 -0
- package/src/components/catalog.tsx +122 -0
- package/src/components/close/close.tsx +1 -0
- package/src/components/close/close.types.ts +2 -0
- package/src/components/command-palette/command-palette.tsx +14 -0
- package/src/components/command-palette/command-palette.types.ts +6 -0
- package/src/components/container/container.tsx +1 -0
- package/src/components/container/container.types.ts +1 -0
- package/src/components/empty-state/empty-state.tsx +1 -0
- package/src/components/empty-state/empty-state.types.ts +2 -0
- package/src/components/field/field.tsx +3 -0
- package/src/components/field/field.types.ts +3 -0
- package/src/components/footer/footer.tsx +7 -0
- package/src/components/footer/footer.types.ts +7 -0
- package/src/components/grid/grid.tsx +1 -0
- package/src/components/grid/grid.types.ts +4 -0
- package/src/components/header/header.tsx +1 -0
- package/src/components/header/header.types.ts +1 -0
- package/src/components/input-group/input-group.tsx +2 -0
- package/src/components/input-group/input-group.types.ts +4 -0
- package/src/components/main/main.tsx +1 -0
- package/src/components/main/main.types.ts +1 -0
- package/src/components/nav/nav.tsx +6 -0
- package/src/components/nav/nav.types.ts +14 -0
- package/src/components/navbar/navbar.tsx +8 -0
- package/src/components/navbar/navbar.types.ts +5 -0
- package/src/components/overlays/dropdown-content.tsx +5 -0
- package/src/components/page/page.tsx +1 -0
- package/src/components/page/page.types.ts +1 -0
- package/src/components/page-header/page-header.tsx +1 -0
- package/src/components/page-header/page-header.types.ts +1 -0
- package/src/components/section/section.tsx +1 -0
- package/src/components/section/section.types.ts +1 -0
- package/src/components/separator/separator.tsx +5 -0
- package/src/components/separator/separator.types.ts +4 -0
- package/src/components/sidebar/sidebar.tsx +16 -0
- package/src/components/sidebar/sidebar.types.ts +8 -0
- package/src/components/skeleton/skeleton.tsx +1 -0
- package/src/components/skeleton/skeleton.types.ts +3 -0
- package/src/components/spinner/spinner.tsx +1 -0
- package/src/components/spinner/spinner.types.ts +3 -0
- package/src/components/stat/stat.tsx +4 -0
- package/src/components/stat/stat.types.ts +4 -0
- package/src/components/text/text.tsx +1 -0
- package/src/components/text/text.types.ts +8 -0
- package/src/components/theme/theme.tsx +25 -0
- package/src/components/toolbar/toolbar.tsx +1 -0
- package/src/components/toolbar/toolbar.types.ts +1 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { ButtonNativeProps, PressEvent } from "@askrjs/ui";
|
|
2
2
|
import { JSX } from "@askrjs/askr/jsx-runtime";
|
|
3
3
|
//#region src/components/theme/theme.d.ts
|
|
4
|
+
/** Names of the built-in "cat" theme presets. */
|
|
4
5
|
declare const CAT_THEME_NAMES: readonly ["tabby", "ginger", "tuxedo", "calico", "torty"];
|
|
6
|
+
/** A built-in "cat" theme name, one of {@link CAT_THEME_NAMES}. */
|
|
5
7
|
type CatThemeName = (typeof CAT_THEME_NAMES)[number];
|
|
8
|
+
/** Any theme identifier accepted by {@link ThemeScope}: `"light"`, `"dark"`, `"system"`, a {@link CatThemeName}, or a custom string. */
|
|
6
9
|
type ThemeName = "light" | "dark" | "system" | CatThemeName | (string & {});
|
|
10
|
+
/** A theme choice offered by {@link ThemePicker}/{@link ThemeToggle}: a `value` paired with a display `label`. */
|
|
7
11
|
type ThemeOption = {
|
|
8
12
|
value: ThemeName;
|
|
9
13
|
label: string;
|
|
10
14
|
};
|
|
15
|
+
/** Value read from {@link theme}: the active theme, its resolved system value, a setter, and the available theme options. */
|
|
11
16
|
type ThemeScopeValue = {
|
|
12
17
|
theme: () => ThemeName;
|
|
13
18
|
resolvedSystemTheme: () => "light" | "dark";
|
|
@@ -15,20 +20,24 @@ type ThemeScopeValue = {
|
|
|
15
20
|
themes: readonly ThemeOption[];
|
|
16
21
|
storageKey: string;
|
|
17
22
|
};
|
|
23
|
+
/** Props for the {@link ThemeScope} component. */
|
|
18
24
|
type ThemeScopeProps = {
|
|
19
25
|
children?: unknown;
|
|
20
26
|
defaultTheme?: ThemeName;
|
|
21
27
|
themes?: readonly ThemeOption[];
|
|
22
28
|
storageKey?: string;
|
|
23
29
|
};
|
|
30
|
+
/** Props for the {@link ThemePicker} component. */
|
|
24
31
|
type ThemePickerProps = Omit<JSX.IntrinsicElements["select"], "children" | "value" | "defaultValue" | "onChange"> & {
|
|
25
32
|
themes?: readonly ThemeOption[];
|
|
26
33
|
label?: string;
|
|
27
34
|
};
|
|
35
|
+
/** Render context passed to a function-as-children {@link ThemeToggleProps.children}. */
|
|
28
36
|
type ThemeToggleRenderContext = {
|
|
29
37
|
theme: ThemeName;
|
|
30
38
|
nextTheme: ThemeName;
|
|
31
39
|
};
|
|
40
|
+
/** Props for the {@link ThemeToggle} component. */
|
|
32
41
|
type ThemeToggleProps = Omit<ButtonNativeProps, "children" | "onPress"> & {
|
|
33
42
|
children?: unknown | ((context: ThemeToggleRenderContext) => unknown);
|
|
34
43
|
lightIcon?: unknown;
|
|
@@ -37,11 +46,27 @@ type ThemeToggleProps = Omit<ButtonNativeProps, "children" | "onPress"> & {
|
|
|
37
46
|
themes?: readonly ThemeName[];
|
|
38
47
|
onPress?: (event: PressEvent) => void;
|
|
39
48
|
};
|
|
49
|
+
/** Default light/dark/system theme options used by {@link ThemeScope} and {@link ThemePicker}. */
|
|
40
50
|
declare const DEFAULT_THEME_OPTIONS: readonly ThemeOption[];
|
|
51
|
+
/** Theme options for the built-in "cat" theme presets, keyed to {@link CAT_THEME_NAMES}. */
|
|
41
52
|
declare const CAT_THEME_OPTIONS: readonly ThemeOption[];
|
|
53
|
+
/** Reads the current {@link ThemeScopeValue} from the nearest enclosing {@link ThemeScope}. */
|
|
42
54
|
declare function theme(): ThemeScopeValue;
|
|
55
|
+
/**
|
|
56
|
+
* Establishes a theme boundary: tracks the active theme (persisted to
|
|
57
|
+
* `localStorage` under `storageKey` and synced across tabs/scopes), resolves
|
|
58
|
+
* the OS `"system"` preference, and reflects the choice onto the DOM via
|
|
59
|
+
* `data-theme`/`data-theme-choice` attributes. Nested scopes cooperate
|
|
60
|
+
* through a shared coordinator so the deepest explicitly-set scope wins.
|
|
61
|
+
*/
|
|
43
62
|
declare function ThemeScope(props: ThemeScopeProps): JSX.Element;
|
|
63
|
+
/** A `<select>` bound to the current {@link theme}, listing `themes` (defaults to the enclosing scope's options). */
|
|
44
64
|
declare function ThemePicker(props: ThemePickerProps): JSX.Element;
|
|
65
|
+
/**
|
|
66
|
+
* A button that cycles through `themes` (defaults to `["light", "dark"]`) on
|
|
67
|
+
* press. Supports icon props per theme, or a function-as-children render
|
|
68
|
+
* prop receiving {@link ThemeToggleRenderContext}.
|
|
69
|
+
*/
|
|
45
70
|
declare function ThemeToggle(props: ThemeToggleProps): JSX.Element;
|
|
46
71
|
//#endregion
|
|
47
72
|
export { CAT_THEME_NAMES, CAT_THEME_OPTIONS, CatThemeName, DEFAULT_THEME_OPTIONS, ThemeName, ThemeOption, ThemePicker, ThemePickerProps, ThemeScope, ThemeScopeProps, ThemeScopeValue, ThemeToggle, ThemeToggleProps, ThemeToggleRenderContext, theme };
|
|
@@ -3,6 +3,7 @@ import { jsx } from "@askrjs/askr/jsx-runtime";
|
|
|
3
3
|
import { defineScope, getSignal, readScope, state } from "@askrjs/askr";
|
|
4
4
|
import { cloneElement, isElement } from "@askrjs/askr/foundations/structures";
|
|
5
5
|
//#region src/components/theme/theme.tsx
|
|
6
|
+
/** Names of the built-in "cat" theme presets. */
|
|
6
7
|
const CAT_THEME_NAMES = [
|
|
7
8
|
"tabby",
|
|
8
9
|
"ginger",
|
|
@@ -10,6 +11,7 @@ const CAT_THEME_NAMES = [
|
|
|
10
11
|
"calico",
|
|
11
12
|
"torty"
|
|
12
13
|
];
|
|
14
|
+
/** Default light/dark/system theme options used by {@link ThemeScope} and {@link ThemePicker}. */
|
|
13
15
|
const DEFAULT_THEME_OPTIONS = [
|
|
14
16
|
{
|
|
15
17
|
value: "system",
|
|
@@ -24,6 +26,7 @@ const DEFAULT_THEME_OPTIONS = [
|
|
|
24
26
|
label: "Dark"
|
|
25
27
|
}
|
|
26
28
|
];
|
|
29
|
+
/** Theme options for the built-in "cat" theme presets, keyed to {@link CAT_THEME_NAMES}. */
|
|
27
30
|
const CAT_THEME_OPTIONS = [
|
|
28
31
|
{
|
|
29
32
|
value: "tabby",
|
|
@@ -58,9 +61,17 @@ const ThemeScopeContext = defineScope({
|
|
|
58
61
|
coordinator: null,
|
|
59
62
|
depth: -1
|
|
60
63
|
});
|
|
64
|
+
/** Reads the current {@link ThemeScopeValue} from the nearest enclosing {@link ThemeScope}. */
|
|
61
65
|
function theme() {
|
|
62
66
|
return readScope(ThemeScopeContext);
|
|
63
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Establishes a theme boundary: tracks the active theme (persisted to
|
|
70
|
+
* `localStorage` under `storageKey` and synced across tabs/scopes), resolves
|
|
71
|
+
* the OS `"system"` preference, and reflects the choice onto the DOM via
|
|
72
|
+
* `data-theme`/`data-theme-choice` attributes. Nested scopes cooperate
|
|
73
|
+
* through a shared coordinator so the deepest explicitly-set scope wins.
|
|
74
|
+
*/
|
|
64
75
|
function ThemeScope(props) {
|
|
65
76
|
const { children, defaultTheme = "system", themes = DEFAULT_THEME_OPTIONS, storageKey = DEFAULT_STORAGE_KEY } = props;
|
|
66
77
|
const scopeId = state(Symbol("ThemeScope"))();
|
|
@@ -203,6 +214,7 @@ function createThemeCoordinator() {
|
|
|
203
214
|
}
|
|
204
215
|
});
|
|
205
216
|
}
|
|
217
|
+
/** A `<select>` bound to the current {@link theme}, listing `themes` (defaults to the enclosing scope's options). */
|
|
206
218
|
function ThemePicker(props) {
|
|
207
219
|
const activeTheme = theme();
|
|
208
220
|
const { themes = activeTheme.themes, label = "Theme", ...rest } = props;
|
|
@@ -234,6 +246,11 @@ function getThemePickerTarget(event) {
|
|
|
234
246
|
for (const candidate of candidates) if (candidate instanceof HTMLSelectElement) return candidate;
|
|
235
247
|
return null;
|
|
236
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* A button that cycles through `themes` (defaults to `["light", "dark"]`) on
|
|
251
|
+
* press. Supports icon props per theme, or a function-as-children render
|
|
252
|
+
* prop receiving {@link ThemeToggleRenderContext}.
|
|
253
|
+
*/
|
|
237
254
|
function ThemeToggle(props) {
|
|
238
255
|
const activeTheme = theme();
|
|
239
256
|
const { children, lightIcon, darkIcon, systemIcon, themes = ["light", "dark"], onPress, ...rest } = props;
|
|
@@ -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\nexport const CAT_THEME_NAMES = [\"tabby\", \"ginger\", \"tuxedo\", \"calico\", \"torty\"] as const;\n\nexport type CatThemeName = (typeof CAT_THEME_NAMES)[number];\nexport type ThemeName = \"light\" | \"dark\" | \"system\" | CatThemeName | (string & {});\n\nexport type ThemeOption = {\n value: ThemeName;\n label: string;\n};\n\nexport type ThemeScopeValue = {\n theme: () => ThemeName;\n resolvedSystemTheme: () => \"light\" | \"dark\";\n setTheme: (theme: ThemeName) => void;\n themes: readonly ThemeOption[];\n storageKey: string;\n};\n\nexport type ThemeScopeProps = {\n children?: unknown;\n defaultTheme?: ThemeName;\n themes?: readonly ThemeOption[];\n storageKey?: string;\n};\n\nexport type ThemePickerProps = Omit<\n JSX.IntrinsicElements[\"select\"],\n \"children\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n themes?: readonly ThemeOption[];\n label?: string;\n};\n\nexport type ThemeToggleRenderContext = {\n theme: ThemeName;\n nextTheme: ThemeName;\n};\n\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\nexport const DEFAULT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"system\", label: \"System\" },\n { value: \"light\", label: \"Light\" },\n { value: \"dark\", label: \"Dark\" },\n];\n\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\nexport function theme(): ThemeScopeValue {\n return readScope(ThemeScopeContext);\n}\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\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\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, 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,6 +1,7 @@
|
|
|
1
1
|
import { ToolbarProps } from "./toolbar.types.js";
|
|
2
2
|
import { JSX } from "@askrjs/askr/jsx-runtime";
|
|
3
3
|
//#region src/components/toolbar/toolbar.d.ts
|
|
4
|
+
/** Titled toolbar row with an actions area, wrapping to a new line on small screens. */
|
|
4
5
|
declare function Toolbar(props: ToolbarProps): JSX.Element;
|
|
5
6
|
//#endregion
|
|
6
7
|
export { Toolbar };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Block } from "../block/block.js";
|
|
2
2
|
import { jsx, jsxs } from "@askrjs/askr/jsx-runtime";
|
|
3
3
|
//#region src/components/toolbar/toolbar.tsx
|
|
4
|
+
/** Titled toolbar row with an actions area, wrapping to a new line on small screens. */
|
|
4
5
|
function Toolbar(props) {
|
|
5
6
|
const { title, actions, ...rest } = props;
|
|
6
7
|
return /* @__PURE__ */ jsxs(Block, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar.js","names":[],"sources":["../../../src/components/toolbar/toolbar.tsx"],"sourcesContent":["import type { JSX } from \"@askrjs/askr/jsx-runtime\";\nimport { Block } from \"../block\";\nimport type { ToolbarProps } from \"./toolbar.types\";\n\nexport function Toolbar(props: ToolbarProps): JSX.Element {\n const { title, actions, ...rest } = props;\n\n return (\n <Block\n rowFrom=\"md\"\n align={{ base: \"start\", md: \"center\" }}\n justify=\"between\"\n gap=\"md\"\n {...rest}\n data-slot=\"toolbar\"\n >\n <h2 data-slot=\"toolbar-title\">{title}</h2>\n {actions !== undefined ? (\n <Block direction=\"row\" gap=\"sm\" data-slot=\"toolbar-actions\">\n {actions}\n </Block>\n ) : null}\n </Block>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"toolbar.js","names":[],"sources":["../../../src/components/toolbar/toolbar.tsx"],"sourcesContent":["import type { JSX } from \"@askrjs/askr/jsx-runtime\";\nimport { Block } from \"../block\";\nimport type { ToolbarProps } from \"./toolbar.types\";\n\n/** Titled toolbar row with an actions area, wrapping to a new line on small screens. */\nexport function Toolbar(props: ToolbarProps): JSX.Element {\n const { title, actions, ...rest } = props;\n\n return (\n <Block\n rowFrom=\"md\"\n align={{ base: \"start\", md: \"center\" }}\n justify=\"between\"\n gap=\"md\"\n {...rest}\n data-slot=\"toolbar\"\n >\n <h2 data-slot=\"toolbar-title\">{title}</h2>\n {actions !== undefined ? (\n <Block direction=\"row\" gap=\"sm\" data-slot=\"toolbar-actions\">\n {actions}\n </Block>\n ) : null}\n </Block>\n );\n}\n"],"mappings":";;;;AAKA,SAAgB,QAAQ,OAAkC;CACxD,MAAM,EAAE,OAAO,SAAS,GAAG,SAAS;CAEpC,OACE,qBAAC,OAAD;EACE,SAAQ;EACR,OAAO;GAAE,MAAM;GAAS,IAAI;EAAS;EACrC,SAAQ;EACR,KAAI;EACJ,GAAI;EACJ,aAAU;EANZ,UAAA,CAQE,oBAAC,MAAD;GAAI,aAAU;GAAiB,UAAA;EAAU,CAAA,GACxC,YAAY,KAAA,IACX,oBAAC,OAAD;GAAO,WAAU;GAAM,KAAI;GAAK,aAAU;GACvC,UAAA;EACI,CAAA,IACL,IACC;;AAEX"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BlockDivProps } from "../block/block.types.js";
|
|
2
2
|
import "../block/index.js";
|
|
3
3
|
//#region src/components/toolbar/toolbar.types.d.ts
|
|
4
|
+
/** Props for the {@link Toolbar} component. */
|
|
4
5
|
type ToolbarProps = BlockDivProps & {
|
|
5
6
|
title: unknown;
|
|
6
7
|
actions?: unknown;
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ const cssPropertyNameCache = new Map<string, string>();
|
|
|
4
4
|
const MAX_PROPERTY_CACHE = 256;
|
|
5
5
|
|
|
6
6
|
const CSS_UNSAFE_RE = /[{}<>\\]/;
|
|
7
|
+
const CSS_COMMENT_DELIMITER_RE = /\/\*|\*\//;
|
|
7
8
|
const CSS_URI_SCHEME_RE = /(?:^|[\s(,])([a-z][a-z0-9+.-]*):/i;
|
|
8
9
|
const CSS_FUNCTION_NAME_RE = /([a-z-][a-z0-9-]*)\s*\(/gi;
|
|
9
10
|
const CSS_ALLOWED_FUNCTIONS = new Set([
|
|
@@ -57,7 +58,13 @@ function isSafeCssPropertyName(name: string): boolean {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
function isSafeCssValue(value: string): boolean {
|
|
60
|
-
if (
|
|
61
|
+
if (
|
|
62
|
+
CSS_UNSAFE_RE.test(value) ||
|
|
63
|
+
CSS_COMMENT_DELIMITER_RE.test(value) ||
|
|
64
|
+
CSS_URI_SCHEME_RE.test(value)
|
|
65
|
+
) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
61
68
|
|
|
62
69
|
for (const match of value.matchAll(CSS_FUNCTION_NAME_RE)) {
|
|
63
70
|
if (!CSS_ALLOWED_FUNCTIONS.has(match[1]!.toLowerCase())) return false;
|
|
@@ -11,6 +11,11 @@ function resolveAlertRole(variant: AlertProps["variant"] | undefined): "alert" |
|
|
|
11
11
|
return "status";
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Renders an alert/status banner with an optional icon, title, description,
|
|
16
|
+
* actions, and a dismiss button. Automatically picks an "alert" or "status"
|
|
17
|
+
* ARIA role from `variant` unless `role` is set explicitly.
|
|
18
|
+
*/
|
|
14
19
|
export function Alert(props: AlertProps): JSX.Element {
|
|
15
20
|
const {
|
|
16
21
|
actions,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
2
2
|
import type { Ref } from "@askrjs/askr/foundations/utilities";
|
|
3
3
|
|
|
4
|
+
/** Visual tone of an {@link AlertProps} alert; `"danger"`/`"warning"` render with `role="alert"`. */
|
|
4
5
|
export type AlertVariant = "default" | "info" | "success" | "warning" | "danger";
|
|
6
|
+
/** Heading tag allowed for an alert's title via `titleAs`. */
|
|
5
7
|
export type AlertHeadingTag = "h2" | "h3" | "h4" | "h5" | "h6";
|
|
6
8
|
|
|
7
9
|
type DivProps = Omit<JSX.IntrinsicElements["div"], "children" | "ref">;
|
|
8
10
|
|
|
11
|
+
/** Props for the {@link Alert} component. */
|
|
9
12
|
export type AlertProps = DivProps & {
|
|
10
13
|
actions?: unknown;
|
|
11
14
|
children?: unknown;
|
|
@@ -2,6 +2,7 @@ import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
|
2
2
|
import { Block } from "../block";
|
|
3
3
|
import type { AsideProps } from "./aside.types";
|
|
4
4
|
|
|
5
|
+
/** Renders a `<aside>`-element {@link Block} tagged with `data-slot="aside"`. */
|
|
5
6
|
export function Aside(props: AsideProps): JSX.Element {
|
|
6
7
|
const { children, ...rest } = props;
|
|
7
8
|
|
|
@@ -6,6 +6,11 @@ import { styleDeclarationsToClass } from "../_internal/style";
|
|
|
6
6
|
import { intrinsicElement } from "../_internal/jsx";
|
|
7
7
|
import type { AspectRatioAsChildProps, AspectRatioProps } from "./aspect-ratio.types";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Constrains its content to a fixed width/height ratio (defaults to `1`)
|
|
11
|
+
* using a computed `aspect-ratio` style, rendering as a `<div>` or, with
|
|
12
|
+
* `asChild`, merging its layout into the single child element.
|
|
13
|
+
*/
|
|
9
14
|
export function AspectRatio(props: AspectRatioProps): JSX.Element;
|
|
10
15
|
export function AspectRatio(props: AspectRatioAsChildProps): JSX.Element;
|
|
11
16
|
export function AspectRatio(props: AspectRatioProps | AspectRatioAsChildProps) {
|
|
@@ -2,18 +2,21 @@ import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
|
2
2
|
import type { JSXElement } from "@askrjs/askr/foundations";
|
|
3
3
|
import type { Ref } from "@askrjs/askr/foundations/utilities";
|
|
4
4
|
|
|
5
|
+
/** Props shared by both {@link AspectRatio} call signatures. */
|
|
5
6
|
export type AspectRatioOwnProps = {
|
|
6
7
|
children?: unknown;
|
|
7
8
|
ratio?: number | string;
|
|
8
9
|
style?: unknown;
|
|
9
10
|
};
|
|
10
11
|
|
|
12
|
+
/** Props for {@link AspectRatio} rendered as its default `<div>` element. */
|
|
11
13
|
export type AspectRatioProps = Omit<JSX.IntrinsicElements["div"], "children" | "ref"> &
|
|
12
14
|
AspectRatioOwnProps & {
|
|
13
15
|
asChild?: false;
|
|
14
16
|
ref?: Ref<HTMLDivElement>;
|
|
15
17
|
};
|
|
16
18
|
|
|
19
|
+
/** Props for {@link AspectRatio} rendered with `asChild`, merging layout onto a single child element. */
|
|
17
20
|
export type AspectRatioAsChildProps = AspectRatioOwnProps & {
|
|
18
21
|
asChild: true;
|
|
19
22
|
children: JSXElement;
|
|
@@ -9,6 +9,7 @@ function normalizeBadgeVariant(variant: BadgeProps["variant"] | undefined) {
|
|
|
9
9
|
return variant && variant !== "default" ? `badge-${variant}` : undefined;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/** Renders a small status/label badge, defaulting to a `<span>` (or merging onto a child via `asChild`), with a `variant` color modifier. */
|
|
12
13
|
export function Badge(props: BadgeProps): JSX.Element;
|
|
13
14
|
export function Badge(props: BadgeAsChildProps): JSX.Element;
|
|
14
15
|
export function Badge(props: BadgeProps | BadgeAsChildProps) {
|
|
@@ -2,6 +2,7 @@ import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
|
2
2
|
import type { JSXElement } from "@askrjs/askr/foundations";
|
|
3
3
|
import type { Ref } from "@askrjs/askr/foundations/utilities";
|
|
4
4
|
|
|
5
|
+
/** Visual tone of a {@link BadgeProps} badge. */
|
|
5
6
|
export type BadgeVariant =
|
|
6
7
|
| "default"
|
|
7
8
|
| "secondary"
|
|
@@ -11,17 +12,20 @@ export type BadgeVariant =
|
|
|
11
12
|
| "danger"
|
|
12
13
|
| "info";
|
|
13
14
|
|
|
15
|
+
/** Props shared by both {@link Badge} call signatures. */
|
|
14
16
|
export type BadgeOwnProps = {
|
|
15
17
|
children?: unknown;
|
|
16
18
|
variant?: BadgeVariant;
|
|
17
19
|
};
|
|
18
20
|
|
|
21
|
+
/** Props for {@link Badge} rendered as its default `<span>` element. */
|
|
19
22
|
export type BadgeProps = Omit<JSX.IntrinsicElements["span"], "children" | "ref"> &
|
|
20
23
|
BadgeOwnProps & {
|
|
21
24
|
asChild?: false;
|
|
22
25
|
ref?: Ref<HTMLSpanElement>;
|
|
23
26
|
};
|
|
24
27
|
|
|
28
|
+
/** Props for {@link Badge} rendered with `asChild`, merging its attributes onto a single child element. */
|
|
25
29
|
export type BadgeAsChildProps = BadgeOwnProps & {
|
|
26
30
|
asChild: true;
|
|
27
31
|
children: JSXElement;
|
|
@@ -13,6 +13,12 @@ import type { BlockAsChildProps, BlockNativeProps } from "./block.types";
|
|
|
13
13
|
|
|
14
14
|
const DEFAULT_ELEMENT = "div";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Low-level layout primitive: renders any element in {@link BlockElement}
|
|
18
|
+
* (default `<div>`) and turns layout-only props (spacing, alignment,
|
|
19
|
+
* direction, sizing, etc.) into a generated utility class. With `asChild`,
|
|
20
|
+
* the layout is merged onto a single child element instead of a wrapper.
|
|
21
|
+
*/
|
|
16
22
|
export function Block(props: BlockNativeProps): JSX.Element;
|
|
17
23
|
export function Block(props: BlockAsChildProps): JSX.Element;
|
|
18
24
|
export function Block(props: BlockNativeProps | BlockAsChildProps) {
|
|
@@ -32,6 +32,7 @@ export type {
|
|
|
32
32
|
ResponsiveValue as BlockResponsiveValue,
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/** Element tags {@link Block} can render as via its `as` prop. */
|
|
35
36
|
export type BlockElement =
|
|
36
37
|
| "div"
|
|
37
38
|
| "span"
|
|
@@ -48,12 +49,14 @@ export type BlockElement =
|
|
|
48
49
|
| "form"
|
|
49
50
|
| "article";
|
|
50
51
|
|
|
52
|
+
/** Layout and structural props accepted by {@link Block}, independent of the rendered element. */
|
|
51
53
|
export type BlockOwnProps = BlockLayoutProps & {
|
|
52
54
|
as?: BlockElement;
|
|
53
55
|
children?: unknown;
|
|
54
56
|
className?: string;
|
|
55
57
|
};
|
|
56
58
|
|
|
59
|
+
/** {@link Block} props specialized for a given {@link BlockElement} tag `TElement`. */
|
|
57
60
|
export type BlockElementProps<TElement extends BlockElement> = Omit<
|
|
58
61
|
JSX.IntrinsicElements[TElement],
|
|
59
62
|
"children" | "ref"
|
|
@@ -64,16 +67,21 @@ export type BlockElementProps<TElement extends BlockElement> = Omit<
|
|
|
64
67
|
ref?: Ref<unknown>;
|
|
65
68
|
};
|
|
66
69
|
|
|
70
|
+
/** Union of {@link BlockElementProps} across every {@link BlockElement}, used for {@link Block}'s native (non-`asChild`) overload. */
|
|
67
71
|
export type BlockNativeProps = {
|
|
68
72
|
[TElement in BlockElement]: BlockElementProps<TElement>;
|
|
69
73
|
}[BlockElement];
|
|
70
74
|
|
|
75
|
+
/** Props for {@link Block} rendered with `asChild`, merging layout onto a single child element. */
|
|
71
76
|
export type BlockAsChildProps = Omit<BlockOwnProps, "as"> & {
|
|
72
77
|
asChild: true;
|
|
73
78
|
children: JSXElement;
|
|
74
79
|
ref?: Ref<unknown>;
|
|
75
80
|
};
|
|
76
81
|
|
|
82
|
+
/** {@link Block} props specialized for a `<div>` element. */
|
|
77
83
|
export type BlockDivProps = BlockElementProps<"div">;
|
|
84
|
+
/** {@link Block} props specialized for a `<span>` element. */
|
|
78
85
|
export type BlockSpanProps = BlockElementProps<"span">;
|
|
86
|
+
/** Union of all prop shapes accepted by {@link Block}. */
|
|
79
87
|
export type BlockProps = BlockNativeProps | BlockAsChildProps;
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
const DEFAULT_ELEMENT = "div";
|
|
15
15
|
|
|
16
|
+
/** Renders a brand/logo wrapper, defaulting to a `<div>` (or `<a>`/`<span>` via `as`), or merges onto a single child via `asChild`. */
|
|
16
17
|
export function Brand<TElement extends "div" | "a" | "span" = "div">(
|
|
17
18
|
props: BrandProps<TElement>,
|
|
18
19
|
): JSX.Element {
|
|
@@ -42,6 +43,7 @@ export function Brand<TElement extends "div" | "a" | "span" = "div">(
|
|
|
42
43
|
return intrinsicElement(as ?? DEFAULT_ELEMENT, finalProps, children);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/** Renders the icon/mark portion of a {@link Brand}. */
|
|
45
47
|
export function BrandMark(props: BrandMarkProps): JSX.Element {
|
|
46
48
|
const { children, class: className, ref, ...rest } = props;
|
|
47
49
|
const finalProps = mergeProps(rest, {
|
|
@@ -53,6 +55,7 @@ export function BrandMark(props: BrandMarkProps): JSX.Element {
|
|
|
53
55
|
return <span {...finalProps}>{children}</span>;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
/** Renders the text label portion of a {@link Brand}. */
|
|
56
59
|
export function BrandLabel(props: BrandLabelProps): JSX.Element {
|
|
57
60
|
const { children, class: className, ref, ...rest } = props;
|
|
58
61
|
const finalProps = mergeProps(rest, {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
2
2
|
import type { Ref } from "@askrjs/askr/foundations/utilities";
|
|
3
3
|
|
|
4
|
+
/** Element tags {@link Brand} can render as via its `as` prop. */
|
|
4
5
|
export type BrandElement = "div" | "a" | "span";
|
|
5
6
|
|
|
6
7
|
type BrandIntrinsicProps<TElement extends BrandElement> = Omit<
|
|
@@ -9,6 +10,7 @@ type BrandIntrinsicProps<TElement extends BrandElement> = Omit<
|
|
|
9
10
|
>;
|
|
10
11
|
type SpanProps = Omit<JSX.IntrinsicElements["span"], "children" | "ref">;
|
|
11
12
|
|
|
13
|
+
/** {@link Brand} props for its native (non-`asChild`) rendering, specialized for a given {@link BrandElement} `TElement`. */
|
|
12
14
|
export type BrandNativeProps<TElement extends BrandElement = "div"> =
|
|
13
15
|
BrandIntrinsicProps<TElement> & {
|
|
14
16
|
as?: TElement;
|
|
@@ -17,6 +19,7 @@ export type BrandNativeProps<TElement extends BrandElement = "div"> =
|
|
|
17
19
|
ref?: Ref<HTMLElement>;
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
/** Props for {@link Brand} rendered with `asChild`, merging its attributes onto a single child element. */
|
|
20
23
|
export type BrandAsChildProps = {
|
|
21
24
|
asChild: true;
|
|
22
25
|
children: JSX.Element;
|
|
@@ -25,15 +28,18 @@ export type BrandAsChildProps = {
|
|
|
25
28
|
ref?: Ref<HTMLElement>;
|
|
26
29
|
};
|
|
27
30
|
|
|
31
|
+
/** Union of all prop shapes accepted by {@link Brand}. */
|
|
28
32
|
export type BrandProps<TElement extends BrandElement = "div"> =
|
|
29
33
|
| BrandNativeProps<TElement>
|
|
30
34
|
| BrandAsChildProps;
|
|
31
35
|
|
|
36
|
+
/** Props for the {@link BrandMark} component. */
|
|
32
37
|
export type BrandMarkProps = SpanProps & {
|
|
33
38
|
children?: unknown;
|
|
34
39
|
ref?: Ref<HTMLSpanElement>;
|
|
35
40
|
};
|
|
36
41
|
|
|
42
|
+
/** Props for the {@link BrandLabel} component. */
|
|
37
43
|
export type BrandLabelProps = SpanProps & {
|
|
38
44
|
children?: unknown;
|
|
39
45
|
ref?: Ref<HTMLSpanElement>;
|
|
@@ -2,6 +2,7 @@ import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
|
2
2
|
import { classes } from "../_internal/classes";
|
|
3
3
|
import { ButtonGroupProps } from "./button-group.types";
|
|
4
4
|
|
|
5
|
+
/** Groups related buttons together, optionally visually attached, with a horizontal or vertical orientation and `role="group"` by default. */
|
|
5
6
|
export function ButtonGroup(props: ButtonGroupProps): JSX.Element {
|
|
6
7
|
const {
|
|
7
8
|
attached = true,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { JSX } from "@askrjs/askr/jsx-runtime";
|
|
2
2
|
import type { Ref } from "@askrjs/askr/foundations/utilities";
|
|
3
3
|
|
|
4
|
+
/** Layout direction of a {@link ButtonGroup}. */
|
|
4
5
|
export type ButtonGroupOrientation = "horizontal" | "vertical";
|
|
5
6
|
|
|
6
7
|
type DivProps = Omit<JSX.IntrinsicElements["div"], "children" | "ref">;
|
|
7
8
|
|
|
9
|
+
/** Props for the {@link ButtonGroup} component. */
|
|
8
10
|
export type ButtonGroupProps = DivProps & {
|
|
9
11
|
children?: unknown;
|
|
10
12
|
attached?: boolean;
|