@dryui/ui 1.5.0 → 1.6.0
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/app-frame/app-frame.svelte +10 -3
- package/dist/button-group/context.svelte.js +4 -7
- package/dist/calendar/calendar-root.svelte +15 -32
- package/dist/chip-group/context.svelte.d.ts +2 -4
- package/dist/chip-group/context.svelte.js +2 -9
- package/dist/context-menu/context-menu-content.svelte +24 -12
- package/dist/context-menu/context-menu-group.svelte +3 -2
- package/dist/context-menu/context-menu-item.svelte +8 -61
- package/dist/context-menu/context-menu-label.svelte +3 -11
- package/dist/context-menu/context-menu-root.svelte +10 -29
- package/dist/context-menu/context-menu-separator.svelte +2 -9
- package/dist/context-menu/context.svelte.d.ts +2 -12
- package/dist/date-picker/datepicker-content.svelte +11 -81
- package/dist/date-picker/datepicker-content.svelte.d.ts +1 -1
- package/dist/date-picker/datepicker-input-root.svelte +39 -47
- package/dist/date-range-picker/date-range-picker-content.svelte +11 -75
- package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +1 -1
- package/dist/date-range-picker/date-range-picker-root.svelte +44 -49
- package/dist/drag-and-drop/group-context.svelte.d.ts +1 -1
- package/dist/drag-and-drop/group-context.svelte.js +4 -4
- package/dist/dropdown-menu/context.svelte.d.ts +2 -8
- package/dist/dropdown-menu/dropdown-menu-content.svelte +15 -3
- package/dist/dropdown-menu/dropdown-menu-group.svelte +3 -2
- package/dist/dropdown-menu/dropdown-menu-item.svelte +8 -61
- package/dist/dropdown-menu/dropdown-menu-label.svelte +3 -11
- package/dist/dropdown-menu/dropdown-menu-root.svelte +10 -21
- package/dist/dropdown-menu/dropdown-menu-separator.svelte +2 -9
- package/dist/flip-card/context.svelte.d.ts +5 -0
- package/dist/flip-card/context.svelte.js +2 -0
- package/dist/flip-card/flip-card-back.svelte +2 -2
- package/dist/flip-card/flip-card-root.svelte +42 -15
- package/dist/heading/heading.svelte +10 -1
- package/dist/heading/heading.svelte.d.ts +1 -0
- package/dist/heading/index.d.ts +1 -0
- package/dist/hover-card/hover-card-content.svelte +9 -21
- package/dist/hover-card/hover-card-root.svelte +2 -2
- package/dist/hover-card/hover-card-root.svelte.d.ts +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal/anchored-overlay-content.svelte.d.ts +20 -0
- package/dist/internal/anchored-overlay-content.svelte.js +28 -0
- package/dist/internal/date-family-controller.svelte.d.ts +45 -0
- package/dist/internal/date-family-controller.svelte.js +99 -0
- package/dist/internal/menu-group.svelte +15 -0
- package/dist/internal/menu-group.svelte.d.ts +9 -0
- package/dist/internal/menu-item.svelte +82 -0
- package/dist/internal/menu-item.svelte.d.ts +11 -0
- package/dist/internal/menu-label.svelte +24 -0
- package/dist/internal/menu-label.svelte.d.ts +9 -0
- package/dist/internal/menu-root-state.svelte.d.ts +24 -0
- package/dist/internal/menu-root-state.svelte.js +42 -0
- package/dist/internal/menu-separator.svelte +19 -0
- package/dist/internal/menu-separator.svelte.d.ts +7 -0
- package/dist/internal/motion.js +12 -1
- package/dist/internal/picker-popover-content.svelte +112 -0
- package/dist/internal/picker-popover-content.svelte.d.ts +16 -0
- package/dist/link-preview/link-preview-content.svelte +7 -10
- package/dist/list/list-item-icon.svelte +3 -3
- package/dist/list/list-item-icon.svelte.d.ts +1 -1
- package/dist/list/list-item-text.svelte +3 -3
- package/dist/list/list-item-text.svelte.d.ts +1 -1
- package/dist/list/list-item.svelte +58 -35
- package/dist/list/list-item.svelte.d.ts +8 -2
- package/dist/popover/popover-content.svelte +9 -11
- package/dist/range-calendar/range-calendar-root.svelte +13 -19
- package/dist/text/index.d.ts +1 -0
- package/dist/text/text.svelte +3 -1
- package/dist/text/text.svelte.d.ts +1 -0
- package/dist/theme-toggle/index.d.ts +18 -0
- package/dist/theme-toggle/index.js +3 -0
- package/dist/theme-toggle/theme-controller.svelte.d.ts +54 -0
- package/dist/theme-toggle/theme-controller.svelte.js +121 -0
- package/dist/theme-toggle/theme-flash.d.ts +16 -0
- package/dist/theme-toggle/theme-flash.js +38 -0
- package/dist/theme-toggle/theme-toggle.svelte +189 -0
- package/dist/theme-toggle/theme-toggle.svelte.d.ts +40 -0
- package/dist/tooltip/tooltip-content.svelte +8 -10
- package/dist/typography/heading.svelte +13 -89
- package/dist/typography/heading.svelte.d.ts +3 -8
- package/dist/typography/index.d.ts +8 -7
- package/dist/typography/text.svelte +12 -84
- package/dist/typography/text.svelte.d.ts +3 -10
- package/package.json +7 -2
- package/skills/dryui/SKILL.md +18 -5
- package/skills/dryui/rules/composition.md +1 -1
- package/skills/dryui/rules/theming.md +1 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type ThemeMode = 'system' | 'light' | 'dark';
|
|
2
|
+
export interface ThemeControllerOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Storage key used to persist the explicit theme preference.
|
|
5
|
+
* When the user selects system mode the key is removed.
|
|
6
|
+
* Defaults to `'dryui-theme'`.
|
|
7
|
+
*/
|
|
8
|
+
storageKey?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ThemeController {
|
|
11
|
+
/** Current stored preference: `'system'`, `'light'`, or `'dark'`. */
|
|
12
|
+
readonly mode: ThemeMode;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the active rendered theme is dark. Tracks `prefers-color-scheme`
|
|
15
|
+
* when the mode is `'system'`.
|
|
16
|
+
*/
|
|
17
|
+
readonly isDark: boolean;
|
|
18
|
+
/** Whether the system color-scheme preference is currently dark. */
|
|
19
|
+
readonly systemPrefersDark: boolean;
|
|
20
|
+
/** Apply an explicit mode and persist it (or clear persistence for `'system'`). */
|
|
21
|
+
setMode(mode: ThemeMode): void;
|
|
22
|
+
/** Toggle between the two rendered themes, writing an explicit preference. */
|
|
23
|
+
cycle(): void;
|
|
24
|
+
/** Return to system mode and clear any persisted preference. */
|
|
25
|
+
reset(): void;
|
|
26
|
+
/** Stop watching `matchMedia` changes. Called automatically on HMR disposal. */
|
|
27
|
+
destroy(): void;
|
|
28
|
+
}
|
|
29
|
+
export declare const DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
|
|
30
|
+
export declare const DEFAULT_STORAGE_KEY = "dryui-theme";
|
|
31
|
+
/**
|
|
32
|
+
* Read the stored theme mode. Exported for testing; production callers
|
|
33
|
+
* should go through `createThemeController`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readStoredMode(storageKey: string): ThemeMode;
|
|
36
|
+
/**
|
|
37
|
+
* Persist the theme mode. When `mode === 'system'`, removes the key.
|
|
38
|
+
* Exported for testing.
|
|
39
|
+
*/
|
|
40
|
+
export declare function writeStoredMode(storageKey: string, mode: ThemeMode): void;
|
|
41
|
+
/**
|
|
42
|
+
* Apply the mode to `<html>` via `classList.theme-auto` and `dataset.theme`.
|
|
43
|
+
* Exported for testing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function applyModeToDom(mode: ThemeMode): void;
|
|
46
|
+
/**
|
|
47
|
+
* Create a theme controller that reads the current preference from storage,
|
|
48
|
+
* applies it to `<html>`, and watches the system color-scheme for changes.
|
|
49
|
+
*
|
|
50
|
+
* The returned object exposes reactive `mode`, `isDark`, and `systemPrefersDark`
|
|
51
|
+
* properties backed by Svelte 5 `$state`, plus imperative methods for changing
|
|
52
|
+
* or resetting the mode.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createThemeController(options?: ThemeControllerOptions): ThemeController;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';
|
|
2
|
+
export const DEFAULT_STORAGE_KEY = 'dryui-theme';
|
|
3
|
+
function isBrowser() {
|
|
4
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read the stored theme mode. Exported for testing; production callers
|
|
8
|
+
* should go through `createThemeController`.
|
|
9
|
+
*/
|
|
10
|
+
export function readStoredMode(storageKey) {
|
|
11
|
+
if (!isBrowser())
|
|
12
|
+
return 'system';
|
|
13
|
+
try {
|
|
14
|
+
const stored = window.localStorage.getItem(storageKey);
|
|
15
|
+
if (stored === 'light' || stored === 'dark') {
|
|
16
|
+
return stored;
|
|
17
|
+
}
|
|
18
|
+
if (stored !== null) {
|
|
19
|
+
window.localStorage.removeItem(storageKey);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Storage access can fail in private browsing contexts, so fall back to system mode.
|
|
24
|
+
}
|
|
25
|
+
return 'system';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Persist the theme mode. When `mode === 'system'`, removes the key.
|
|
29
|
+
* Exported for testing.
|
|
30
|
+
*/
|
|
31
|
+
export function writeStoredMode(storageKey, mode) {
|
|
32
|
+
if (!isBrowser())
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
if (mode === 'system') {
|
|
36
|
+
window.localStorage.removeItem(storageKey);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
window.localStorage.setItem(storageKey, mode);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Ignore storage failures; the in-memory state still reflects the choice.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Apply the mode to `<html>` via `classList.theme-auto` and `dataset.theme`.
|
|
47
|
+
* Exported for testing.
|
|
48
|
+
*/
|
|
49
|
+
export function applyModeToDom(mode) {
|
|
50
|
+
if (!isBrowser())
|
|
51
|
+
return;
|
|
52
|
+
const root = document.documentElement;
|
|
53
|
+
if (mode === 'system') {
|
|
54
|
+
root.classList.add('theme-auto');
|
|
55
|
+
delete root.dataset.theme;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
root.classList.remove('theme-auto');
|
|
59
|
+
root.dataset.theme = mode;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a theme controller that reads the current preference from storage,
|
|
63
|
+
* applies it to `<html>`, and watches the system color-scheme for changes.
|
|
64
|
+
*
|
|
65
|
+
* The returned object exposes reactive `mode`, `isDark`, and `systemPrefersDark`
|
|
66
|
+
* properties backed by Svelte 5 `$state`, plus imperative methods for changing
|
|
67
|
+
* or resetting the mode.
|
|
68
|
+
*/
|
|
69
|
+
export function createThemeController(options = {}) {
|
|
70
|
+
const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
71
|
+
const initialMode = readStoredMode(storageKey);
|
|
72
|
+
let mode = $state(initialMode);
|
|
73
|
+
let systemPrefersDark = $state(false);
|
|
74
|
+
let mediaQuery = null;
|
|
75
|
+
let stopWatching = null;
|
|
76
|
+
if (isBrowser()) {
|
|
77
|
+
mediaQuery = window.matchMedia(DARK_MEDIA_QUERY);
|
|
78
|
+
systemPrefersDark = mediaQuery.matches;
|
|
79
|
+
// Align the DOM with the stored preference in case the app-level flash
|
|
80
|
+
// script was skipped or the key changed between renders.
|
|
81
|
+
applyModeToDom(initialMode);
|
|
82
|
+
const handleChange = (event) => {
|
|
83
|
+
systemPrefersDark = event.matches;
|
|
84
|
+
};
|
|
85
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
86
|
+
stopWatching = () => {
|
|
87
|
+
mediaQuery?.removeEventListener('change', handleChange);
|
|
88
|
+
stopWatching = null;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const isDark = $derived(mode === 'dark' || (mode === 'system' && systemPrefersDark));
|
|
92
|
+
function setMode(next) {
|
|
93
|
+
mode = next;
|
|
94
|
+
applyModeToDom(next);
|
|
95
|
+
writeStoredMode(storageKey, next);
|
|
96
|
+
}
|
|
97
|
+
function cycle() {
|
|
98
|
+
setMode(isDark ? 'light' : 'dark');
|
|
99
|
+
}
|
|
100
|
+
function reset() {
|
|
101
|
+
setMode('system');
|
|
102
|
+
}
|
|
103
|
+
function destroy() {
|
|
104
|
+
stopWatching?.();
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
get mode() {
|
|
108
|
+
return mode;
|
|
109
|
+
},
|
|
110
|
+
get isDark() {
|
|
111
|
+
return isDark;
|
|
112
|
+
},
|
|
113
|
+
get systemPrefersDark() {
|
|
114
|
+
return systemPrefersDark;
|
|
115
|
+
},
|
|
116
|
+
setMode,
|
|
117
|
+
cycle,
|
|
118
|
+
reset,
|
|
119
|
+
destroy
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an IIFE string that synchronously applies the stored theme preference
|
|
3
|
+
* to `<html>` before the first paint. Embed the result inside a `<script>` tag
|
|
4
|
+
* in the document head, above any stylesheet imports, to prevent a flash of
|
|
5
|
+
* the wrong theme when a user has explicitly chosen light or dark.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```html
|
|
9
|
+
* <script>{@html themeFlashScript('dryui-theme')}</script>
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The script reads the given storage key, sets `html.classList.theme-auto`
|
|
13
|
+
* when no explicit preference is stored, and sets `html.dataset.theme` to
|
|
14
|
+
* `'light'` or `'dark'` otherwise. Invalid stored values are removed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function themeFlashScript(storageKey?: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an IIFE string that synchronously applies the stored theme preference
|
|
3
|
+
* to `<html>` before the first paint. Embed the result inside a `<script>` tag
|
|
4
|
+
* in the document head, above any stylesheet imports, to prevent a flash of
|
|
5
|
+
* the wrong theme when a user has explicitly chosen light or dark.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```html
|
|
9
|
+
* <script>{@html themeFlashScript('dryui-theme')}</script>
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The script reads the given storage key, sets `html.classList.theme-auto`
|
|
13
|
+
* when no explicit preference is stored, and sets `html.dataset.theme` to
|
|
14
|
+
* `'light'` or `'dark'` otherwise. Invalid stored values are removed.
|
|
15
|
+
*/
|
|
16
|
+
export function themeFlashScript(storageKey = 'dryui-theme') {
|
|
17
|
+
// Escape the storage key so it's safe to embed between JSON-style quotes.
|
|
18
|
+
const key = JSON.stringify(storageKey);
|
|
19
|
+
return `(() => {
|
|
20
|
+
const root = document.documentElement;
|
|
21
|
+
try {
|
|
22
|
+
const stored = window.localStorage.getItem(${key});
|
|
23
|
+
const explicit = stored === 'light' || stored === 'dark' ? stored : null;
|
|
24
|
+
if (!explicit && stored !== null) {
|
|
25
|
+
window.localStorage.removeItem(${key});
|
|
26
|
+
}
|
|
27
|
+
root.classList.toggle('theme-auto', explicit === null);
|
|
28
|
+
if (explicit) {
|
|
29
|
+
root.dataset.theme = explicit;
|
|
30
|
+
} else {
|
|
31
|
+
delete root.dataset.theme;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
delete root.dataset.theme;
|
|
35
|
+
root.classList.add('theme-auto');
|
|
36
|
+
}
|
|
37
|
+
})();`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy, type Snippet } from 'svelte';
|
|
3
|
+
import type { ClassValue, HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
import Toggle from '../toggle/toggle-button.svelte';
|
|
5
|
+
import {
|
|
6
|
+
createThemeController,
|
|
7
|
+
type ThemeController,
|
|
8
|
+
type ThemeMode
|
|
9
|
+
} from './theme-controller.svelte.js';
|
|
10
|
+
|
|
11
|
+
interface Props extends Omit<
|
|
12
|
+
HTMLButtonAttributes,
|
|
13
|
+
'onclick' | 'onkeydown' | 'disabled' | 'class'
|
|
14
|
+
> {
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
class?: ClassValue;
|
|
17
|
+
/**
|
|
18
|
+
* Storage key used to persist the explicit theme preference.
|
|
19
|
+
* Defaults to `'dryui-theme'`.
|
|
20
|
+
*/
|
|
21
|
+
storageKey?: string;
|
|
22
|
+
/** Toggle size forwarded to the underlying Toggle. */
|
|
23
|
+
size?: 'sm' | 'md' | 'lg';
|
|
24
|
+
/**
|
|
25
|
+
* Optional pre-built controller. When provided, the component uses the
|
|
26
|
+
* shared state instead of creating its own. Useful if multiple toggles
|
|
27
|
+
* exist in the same app or if external code wants to read the mode.
|
|
28
|
+
*/
|
|
29
|
+
controller?: ThemeController;
|
|
30
|
+
/** Accessible label. Defaults to `'Toggle theme'`. */
|
|
31
|
+
'aria-label'?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Custom sun icon snippet. When omitted, a built-in inline SVG is used.
|
|
34
|
+
* The icon is shown when the current theme is light.
|
|
35
|
+
*/
|
|
36
|
+
sunIcon?: Snippet;
|
|
37
|
+
/**
|
|
38
|
+
* Custom moon icon snippet. When omitted, a built-in inline SVG is used.
|
|
39
|
+
* The icon is shown when the current theme is dark.
|
|
40
|
+
*/
|
|
41
|
+
moonIcon?: Snippet;
|
|
42
|
+
/**
|
|
43
|
+
* Called with the new mode after it is applied. Invoked for explicit
|
|
44
|
+
* picks via click or reset via Alt-click / Escape.
|
|
45
|
+
*/
|
|
46
|
+
onModeChange?: (mode: ThemeMode) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
storageKey,
|
|
51
|
+
size = 'md',
|
|
52
|
+
controller: externalController,
|
|
53
|
+
'aria-label': ariaLabel = 'Toggle theme',
|
|
54
|
+
sunIcon,
|
|
55
|
+
moonIcon,
|
|
56
|
+
onModeChange,
|
|
57
|
+
title = 'Alt-click or press Escape to return to system theme',
|
|
58
|
+
disabled,
|
|
59
|
+
...rest
|
|
60
|
+
}: Props = $props();
|
|
61
|
+
|
|
62
|
+
// Read `externalController` and `storageKey` once at init time on purpose.
|
|
63
|
+
// Swapping the controller or key after mount is not supported; this is an
|
|
64
|
+
// init-time choice, so the linter warning about one-shot capture is expected.
|
|
65
|
+
// svelte-ignore state_referenced_locally
|
|
66
|
+
const ownsController = externalController === undefined;
|
|
67
|
+
// svelte-ignore state_referenced_locally
|
|
68
|
+
const controller = externalController ?? createThemeController({ storageKey });
|
|
69
|
+
|
|
70
|
+
onDestroy(() => {
|
|
71
|
+
if (ownsController) {
|
|
72
|
+
controller.destroy();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
type ToggleClickEvent = Parameters<NonNullable<HTMLButtonAttributes['onclick']>>[0];
|
|
77
|
+
type ToggleKeyEvent = Parameters<NonNullable<HTMLButtonAttributes['onkeydown']>>[0];
|
|
78
|
+
|
|
79
|
+
function handleClick(event: ToggleClickEvent) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
|
|
82
|
+
if (event.altKey) {
|
|
83
|
+
controller.reset();
|
|
84
|
+
onModeChange?.('system');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
controller.cycle();
|
|
89
|
+
onModeChange?.(controller.mode);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleKeydown(event: ToggleKeyEvent) {
|
|
93
|
+
if (event.key !== 'Escape') return;
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
controller.reset();
|
|
96
|
+
onModeChange?.('system');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const iconState = $derived(controller.isDark ? 'dark' : 'light');
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<Toggle
|
|
103
|
+
aria-label={ariaLabel}
|
|
104
|
+
aria-keyshortcuts="Escape"
|
|
105
|
+
pressed={controller.isDark}
|
|
106
|
+
data-theme-switch="true"
|
|
107
|
+
onclick={handleClick}
|
|
108
|
+
onkeydown={handleKeydown}
|
|
109
|
+
{disabled}
|
|
110
|
+
{size}
|
|
111
|
+
{title}
|
|
112
|
+
{...rest}
|
|
113
|
+
>
|
|
114
|
+
{#snippet icon()}
|
|
115
|
+
<span class="icons" data-mode={iconState}>
|
|
116
|
+
<span class="icon sun" aria-hidden="true">
|
|
117
|
+
{#if sunIcon}
|
|
118
|
+
{@render sunIcon()}
|
|
119
|
+
{:else}
|
|
120
|
+
<svg
|
|
121
|
+
viewBox="0 0 24 24"
|
|
122
|
+
fill="none"
|
|
123
|
+
stroke="currentColor"
|
|
124
|
+
stroke-width="2"
|
|
125
|
+
stroke-linecap="round"
|
|
126
|
+
stroke-linejoin="round"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
>
|
|
129
|
+
<circle cx="12" cy="12" r="4" />
|
|
130
|
+
<path d="M12 2v2" />
|
|
131
|
+
<path d="M12 20v2" />
|
|
132
|
+
<path d="m4.93 4.93 1.41 1.41" />
|
|
133
|
+
<path d="m17.66 17.66 1.41 1.41" />
|
|
134
|
+
<path d="M2 12h2" />
|
|
135
|
+
<path d="M20 12h2" />
|
|
136
|
+
<path d="m6.34 17.66-1.41 1.41" />
|
|
137
|
+
<path d="m19.07 4.93-1.41 1.41" />
|
|
138
|
+
</svg>
|
|
139
|
+
{/if}
|
|
140
|
+
</span>
|
|
141
|
+
<span class="icon moon" aria-hidden="true">
|
|
142
|
+
{#if moonIcon}
|
|
143
|
+
{@render moonIcon()}
|
|
144
|
+
{:else}
|
|
145
|
+
<svg
|
|
146
|
+
viewBox="0 0 24 24"
|
|
147
|
+
fill="none"
|
|
148
|
+
stroke="currentColor"
|
|
149
|
+
stroke-width="2"
|
|
150
|
+
stroke-linecap="round"
|
|
151
|
+
stroke-linejoin="round"
|
|
152
|
+
aria-hidden="true"
|
|
153
|
+
>
|
|
154
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
155
|
+
</svg>
|
|
156
|
+
{/if}
|
|
157
|
+
</span>
|
|
158
|
+
</span>
|
|
159
|
+
{/snippet}
|
|
160
|
+
</Toggle>
|
|
161
|
+
|
|
162
|
+
<style>
|
|
163
|
+
.icons {
|
|
164
|
+
display: grid;
|
|
165
|
+
place-items: center;
|
|
166
|
+
color: var(--dry-theme-toggle-icon, currentColor);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.icon {
|
|
170
|
+
grid-column: 1;
|
|
171
|
+
grid-row: 1;
|
|
172
|
+
display: grid;
|
|
173
|
+
place-items: center;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.icon svg {
|
|
177
|
+
display: block;
|
|
178
|
+
block-size: var(--dry-theme-toggle-icon-size, 0.75rem);
|
|
179
|
+
aspect-ratio: 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.icons[data-mode='dark'] .sun {
|
|
183
|
+
display: none;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.icons[data-mode='light'] .moon {
|
|
187
|
+
display: none;
|
|
188
|
+
}
|
|
189
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { ClassValue, HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { type ThemeController, type ThemeMode } from './theme-controller.svelte.js';
|
|
4
|
+
interface Props extends Omit<HTMLButtonAttributes, 'onclick' | 'onkeydown' | 'disabled' | 'class'> {
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
class?: ClassValue;
|
|
7
|
+
/**
|
|
8
|
+
* Storage key used to persist the explicit theme preference.
|
|
9
|
+
* Defaults to `'dryui-theme'`.
|
|
10
|
+
*/
|
|
11
|
+
storageKey?: string;
|
|
12
|
+
/** Toggle size forwarded to the underlying Toggle. */
|
|
13
|
+
size?: 'sm' | 'md' | 'lg';
|
|
14
|
+
/**
|
|
15
|
+
* Optional pre-built controller. When provided, the component uses the
|
|
16
|
+
* shared state instead of creating its own. Useful if multiple toggles
|
|
17
|
+
* exist in the same app or if external code wants to read the mode.
|
|
18
|
+
*/
|
|
19
|
+
controller?: ThemeController;
|
|
20
|
+
/** Accessible label. Defaults to `'Toggle theme'`. */
|
|
21
|
+
'aria-label'?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Custom sun icon snippet. When omitted, a built-in inline SVG is used.
|
|
24
|
+
* The icon is shown when the current theme is light.
|
|
25
|
+
*/
|
|
26
|
+
sunIcon?: Snippet;
|
|
27
|
+
/**
|
|
28
|
+
* Custom moon icon snippet. When omitted, a built-in inline SVG is used.
|
|
29
|
+
* The icon is shown when the current theme is dark.
|
|
30
|
+
*/
|
|
31
|
+
moonIcon?: Snippet;
|
|
32
|
+
/**
|
|
33
|
+
* Called with the new mode after it is applied. Invoked for explicit
|
|
34
|
+
* picks via click or reset via Alt-click / Escape.
|
|
35
|
+
*/
|
|
36
|
+
onModeChange?: (mode: ThemeMode) => void;
|
|
37
|
+
}
|
|
38
|
+
declare const ThemeToggle: import("svelte").Component<Props, {}, "">;
|
|
39
|
+
type ThemeToggle = ReturnType<typeof ThemeToggle>;
|
|
40
|
+
export default ThemeToggle;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
-
import {
|
|
4
|
+
import { type Placement } from '@dryui/primitives';
|
|
5
|
+
import { createAnchoredOverlayContent } from '../internal/anchored-overlay-content.svelte.js';
|
|
5
6
|
import { getTooltipCtx } from './context.svelte.js';
|
|
6
7
|
|
|
7
8
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
@@ -21,25 +22,22 @@
|
|
|
21
22
|
|
|
22
23
|
const ctx = getTooltipCtx();
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const popover = createAnchoredPopover({
|
|
27
|
-
triggerEl: () => ctx.triggerEl,
|
|
28
|
-
contentEl: () => contentEl ?? null,
|
|
29
|
-
open: () => ctx.open,
|
|
25
|
+
const overlay = createAnchoredOverlayContent({
|
|
26
|
+
ctx,
|
|
30
27
|
placement: () => placement,
|
|
31
|
-
offset: () => offset
|
|
28
|
+
offset: () => offset,
|
|
29
|
+
style: () => style
|
|
32
30
|
});
|
|
33
31
|
</script>
|
|
34
32
|
|
|
35
33
|
<div
|
|
36
|
-
|
|
34
|
+
{@attach overlay.bindContent}
|
|
35
|
+
{@attach overlay.position}
|
|
37
36
|
id={ctx.contentId}
|
|
38
37
|
role="tooltip"
|
|
39
38
|
popover="manual"
|
|
40
39
|
data-tooltip-content
|
|
41
40
|
data-state={ctx.open ? 'open' : 'closed'}
|
|
42
|
-
use:popover.applyPosition={style}
|
|
43
41
|
class={className}
|
|
44
42
|
{...rest}
|
|
45
43
|
>
|
|
@@ -1,92 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
children
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let { level = 2, variant = 'default', class: className, children, ...rest }: Props = $props();
|
|
2
|
+
import type { HeadingProps } from './index.js';
|
|
3
|
+
import Heading from '../heading/heading.svelte';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
level = 2,
|
|
7
|
+
variant = 'default',
|
|
8
|
+
class: className,
|
|
9
|
+
children,
|
|
10
|
+
...rest
|
|
11
|
+
}: HeadingProps = $props();
|
|
13
12
|
</script>
|
|
14
13
|
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
</h1>
|
|
19
|
-
{:else if level === 2}
|
|
20
|
-
<h2 class={className} {...variantAttrs({ level, variant })} {...rest}>
|
|
21
|
-
{@render children()}
|
|
22
|
-
</h2>
|
|
23
|
-
{:else if level === 3}
|
|
24
|
-
<h3 class={className} {...variantAttrs({ level, variant })} {...rest}>
|
|
25
|
-
{@render children()}
|
|
26
|
-
</h3>
|
|
27
|
-
{:else if level === 4}
|
|
28
|
-
<h4 class={className} {...variantAttrs({ level, variant })} {...rest}>
|
|
29
|
-
{@render children()}
|
|
30
|
-
</h4>
|
|
31
|
-
{:else if level === 5}
|
|
32
|
-
<h5 class={className} {...variantAttrs({ level, variant })} {...rest}>
|
|
33
|
-
{@render children()}
|
|
34
|
-
</h5>
|
|
35
|
-
{:else}
|
|
36
|
-
<h6 class={className} {...variantAttrs({ level, variant })} {...rest}>
|
|
37
|
-
{@render children()}
|
|
38
|
-
</h6>
|
|
39
|
-
{/if}
|
|
40
|
-
|
|
41
|
-
<style>
|
|
42
|
-
h1,
|
|
43
|
-
h2,
|
|
44
|
-
h3,
|
|
45
|
-
h4,
|
|
46
|
-
h5,
|
|
47
|
-
h6 {
|
|
48
|
-
margin: 0;
|
|
49
|
-
color: var(--dry-typography-heading-color, var(--dry-color-text-strong));
|
|
50
|
-
font-family: var(--dry-font-sans);
|
|
51
|
-
font-weight: 700;
|
|
52
|
-
line-height: var(--dry-type-heading-2-leading, 2.5rem);
|
|
53
|
-
letter-spacing: -0.03em;
|
|
54
|
-
text-wrap: balance;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
[data-level='1'] {
|
|
58
|
-
font-size: var(--dry-type-heading-1-size, var(--dry-text-4xl-size, 2.25rem));
|
|
59
|
-
line-height: var(--dry-type-heading-1-leading, 3rem);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
[data-level='2'] {
|
|
63
|
-
font-size: var(--dry-type-heading-2-size, var(--dry-text-3xl-size, 1.875rem));
|
|
64
|
-
line-height: var(--dry-type-heading-2-leading, 2.5rem);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
[data-level='3'] {
|
|
68
|
-
font-size: var(--dry-type-heading-3-size, var(--dry-text-2xl-size, 1.5rem));
|
|
69
|
-
line-height: var(--dry-type-heading-3-leading, 2rem);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
[data-level='4'] {
|
|
73
|
-
font-size: var(--dry-type-heading-4-size, var(--dry-text-xl-size, 1.25rem));
|
|
74
|
-
line-height: var(--dry-type-heading-4-leading, 1.75rem);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
[data-level='5'] {
|
|
78
|
-
font-size: var(--dry-type-small-size, var(--dry-text-lg-size, 1.125rem));
|
|
79
|
-
line-height: var(--dry-type-small-leading, 1.5rem);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
[data-level='6'] {
|
|
83
|
-
font-size: var(--dry-type-tiny-size, var(--dry-text-xs-size));
|
|
84
|
-
line-height: var(--dry-type-tiny-leading, 1.25rem);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
[data-variant='display'] {
|
|
88
|
-
font-size: var(--dry-type-display-size, var(--dry-text-4xl-size, 2.25rem));
|
|
89
|
-
line-height: var(--dry-type-display-leading, 4rem);
|
|
90
|
-
letter-spacing: -0.04em;
|
|
91
|
-
}
|
|
92
|
-
</style>
|
|
14
|
+
<Heading {level} {variant} {className} {...rest}>
|
|
15
|
+
{@render children()}
|
|
16
|
+
</Heading>
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
5
|
-
variant?: 'default' | 'display';
|
|
6
|
-
children: Snippet;
|
|
7
|
-
}
|
|
8
|
-
declare const Heading: import("svelte").Component<Props, {}, "">;
|
|
1
|
+
import type { HeadingProps } from './index.js';
|
|
2
|
+
import Heading from '../heading/heading.svelte';
|
|
3
|
+
declare const Heading: import("svelte").Component<HeadingProps, {}, "">;
|
|
9
4
|
type Heading = ReturnType<typeof Heading>;
|
|
10
5
|
export default Heading;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
variant?: 'default' | 'display';
|
|
4
|
-
}
|
|
1
|
+
import type { TextProps as SharedTextProps } from '../text/index.js';
|
|
2
|
+
export type { HeadingProps } from '../heading/index.js';
|
|
5
3
|
export type { CodeProps, BlockquoteProps } from '@dryui/primitives';
|
|
6
|
-
export interface TextProps extends
|
|
4
|
+
export interface TextProps extends Omit<SharedTextProps, 'color' | 'variant'> {
|
|
7
5
|
color?: 'default' | 'muted' | 'secondary';
|
|
8
|
-
variant?: 'default' | 'muted' | 'secondary';
|
|
9
|
-
size?: '
|
|
6
|
+
variant?: 'default' | 'muted' | 'secondary' | 'label';
|
|
7
|
+
size?: SharedTextProps['size'];
|
|
8
|
+
font?: SharedTextProps['font'];
|
|
9
|
+
weight?: SharedTextProps['weight'];
|
|
10
|
+
className?: SharedTextProps['className'];
|
|
10
11
|
}
|
|
11
12
|
import TypographyHeading from './heading.svelte';
|
|
12
13
|
import TypographyText from './text.svelte';
|