@duro-app/ui 0.35.4 → 0.36.1
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/ColorMode/ColorMode.d.ts +61 -0
- package/dist/components/ColorMode/ColorMode.d.ts.map +1 -0
- package/dist/components/ColorMode/ColorMode.meta.d.ts +3 -0
- package/dist/components/ColorMode/ColorMode.meta.d.ts.map +1 -0
- package/dist/components/ColorMode/ColorMode.stories.d.ts +12 -0
- package/dist/components/ColorMode/ColorMode.stories.d.ts.map +1 -0
- package/dist/components/ColorMode/index.d.ts +2 -0
- package/dist/components/ColorMode/index.d.ts.map +1 -0
- package/dist/components/ColorMode/styles.css.d.ts +24 -0
- package/dist/components/ColorMode/styles.css.d.ts.map +1 -0
- package/dist/components/Icon/Icon.d.ts +1 -1
- package/dist/components/Icon/Icon.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1882 -1682
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ColorMode/ColorMode.meta.ts +40 -0
- package/src/components/ColorMode/ColorMode.stories.tsx +87 -0
- package/src/components/ColorMode/ColorMode.tsx +203 -0
- package/src/components/ColorMode/index.ts +6 -0
- package/src/components/ColorMode/styles.css.ts +32 -0
- package/src/components/Icon/Icon.tsx +110 -0
- package/src/index.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@duro-app/tokens": "^0.
|
|
57
|
+
"@duro-app/tokens": "^0.36.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type {ComponentMeta} from '../component-meta'
|
|
2
|
+
|
|
3
|
+
export const meta: ComponentMeta = {
|
|
4
|
+
description:
|
|
5
|
+
'Color-mode controller + toggle. ColorModeProvider owns the preference (system/light/dark/high-contrast), resolves "system" against the OS scheme, persists it to localStorage, and applies the resolved theme via ThemeProvider. ColorModeToggle is an icon button that cycles the preference; useColorMode() reads it.',
|
|
6
|
+
whenToUse: [
|
|
7
|
+
'App-level light/dark (and high-contrast) switching that persists across reloads',
|
|
8
|
+
'Respecting the OS prefers-color-scheme with a user override',
|
|
9
|
+
'Any place that needs the resolved theme name (useColorMode().theme)',
|
|
10
|
+
],
|
|
11
|
+
whenNotToUse: [
|
|
12
|
+
'A single static theme — use ThemeProvider directly with a fixed theme prop',
|
|
13
|
+
'On/off setting unrelated to theming — use Switch',
|
|
14
|
+
],
|
|
15
|
+
anatomy: {
|
|
16
|
+
required: ['ColorModeProvider'],
|
|
17
|
+
optional: ['ColorModeToggle', 'useColorMode'],
|
|
18
|
+
},
|
|
19
|
+
relatedTo: [
|
|
20
|
+
{
|
|
21
|
+
component: 'ThemeProvider',
|
|
22
|
+
relationship:
|
|
23
|
+
'ColorModeProvider renders ThemeProvider with the resolved theme; use ThemeProvider alone for a fixed theme',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
component: 'Toggle',
|
|
27
|
+
relationship: 'Toggle is a generic pressed button; ColorModeToggle cycles theme modes',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
example: `// At the app root — replaces a bare <ThemeProvider>:
|
|
31
|
+
<ColorModeProvider defaultPreference="system" modes={['light', 'dark']}>
|
|
32
|
+
<App />
|
|
33
|
+
</ColorModeProvider>
|
|
34
|
+
|
|
35
|
+
// Anywhere inside (e.g. the top bar):
|
|
36
|
+
<ColorModeToggle />
|
|
37
|
+
|
|
38
|
+
// Or read the resolved theme:
|
|
39
|
+
const {theme, preference, setPreference} = useColorMode()`,
|
|
40
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react'
|
|
2
|
+
import {expect, fn} from 'storybook/test'
|
|
3
|
+
import {css, html} from 'react-strict-dom'
|
|
4
|
+
import {ColorModeProvider, ColorModeToggle, useColorMode} from './ColorMode'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof ColorModeToggle> = {
|
|
7
|
+
title: 'Components/ColorMode',
|
|
8
|
+
component: ColorModeToggle,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof ColorModeToggle>
|
|
13
|
+
|
|
14
|
+
/** Default cycle: light ⇄ dark. Starts on `system`. */
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: (args) => (
|
|
17
|
+
// storageKey null keeps stories isolated (no cross-test localStorage leak).
|
|
18
|
+
<ColorModeProvider storageKey={null} onPreferenceChange={fn()}>
|
|
19
|
+
<ColorModeToggle {...args} />
|
|
20
|
+
</ColorModeProvider>
|
|
21
|
+
),
|
|
22
|
+
play: async ({canvas, userEvent}) => {
|
|
23
|
+
const btn = canvas.getByRole('button')
|
|
24
|
+
await expect(btn.getAttribute('aria-label')).toContain('Color mode: system')
|
|
25
|
+
|
|
26
|
+
await userEvent.click(btn)
|
|
27
|
+
await expect(btn.getAttribute('aria-label')).toContain('Color mode: light')
|
|
28
|
+
|
|
29
|
+
await userEvent.click(btn)
|
|
30
|
+
await expect(btn.getAttribute('aria-label')).toContain('Color mode: dark')
|
|
31
|
+
|
|
32
|
+
// Wraps back to the first mode.
|
|
33
|
+
await userEvent.click(btn)
|
|
34
|
+
await expect(btn.getAttribute('aria-label')).toContain('Color mode: light')
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A four-way cycle including `system` and `high-contrast`. */
|
|
39
|
+
export const AllModes: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<ColorModeProvider
|
|
42
|
+
storageKey={null}
|
|
43
|
+
defaultPreference="system"
|
|
44
|
+
modes={['system', 'light', 'dark', 'high-contrast']}
|
|
45
|
+
>
|
|
46
|
+
<ColorModeToggle />
|
|
47
|
+
</ColorModeProvider>
|
|
48
|
+
),
|
|
49
|
+
play: async ({canvas, userEvent}) => {
|
|
50
|
+
const btn = canvas.getByRole('button')
|
|
51
|
+
await expect(btn.getAttribute('aria-label')).toContain('Switch to light')
|
|
52
|
+
await userEvent.click(btn)
|
|
53
|
+
await expect(btn.getAttribute('aria-label')).toContain('Color mode: light')
|
|
54
|
+
await userEvent.click(btn)
|
|
55
|
+
await userEvent.click(btn)
|
|
56
|
+
await expect(btn.getAttribute('aria-label')).toContain('high contrast')
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const readout = css.create({
|
|
61
|
+
row: {display: 'flex', alignItems: 'center', gap: 12},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function Readout() {
|
|
65
|
+
const {preference, theme} = useColorMode()
|
|
66
|
+
return (
|
|
67
|
+
<html.div style={readout.row}>
|
|
68
|
+
<ColorModeToggle />
|
|
69
|
+
<html.span>
|
|
70
|
+
preference: {preference} → theme: {theme}
|
|
71
|
+
</html.span>
|
|
72
|
+
</html.div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Reading the resolved theme via `useColorMode()`. */
|
|
77
|
+
export const WithReadout: Story = {
|
|
78
|
+
render: () => (
|
|
79
|
+
<ColorModeProvider storageKey={null} defaultPreference="light">
|
|
80
|
+
<Readout />
|
|
81
|
+
</ColorModeProvider>
|
|
82
|
+
),
|
|
83
|
+
play: async ({canvas}) => {
|
|
84
|
+
await expect(canvas.getByText(/preference: light/)).toBeInTheDocument()
|
|
85
|
+
await expect(canvas.getByText(/theme: light/)).toBeInTheDocument()
|
|
86
|
+
},
|
|
87
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import {html} from 'react-strict-dom'
|
|
11
|
+
import {isNative} from '../../platform'
|
|
12
|
+
import {Icon, type IconName} from '../Icon/Icon'
|
|
13
|
+
import {ThemeProvider, type ThemeName} from '../ThemeProvider/ThemeProvider'
|
|
14
|
+
import {styles} from './styles.css'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The user's *preference*. `'system'` follows the OS `prefers-color-scheme`;
|
|
18
|
+
* the others pin a concrete {@link ThemeName}. Resolves to a ThemeName via
|
|
19
|
+
* {@link useColorMode}().theme.
|
|
20
|
+
*/
|
|
21
|
+
export type ColorModePreference = 'system' | ThemeName
|
|
22
|
+
|
|
23
|
+
const PREFERENCES: readonly ColorModePreference[] = ['system', 'light', 'dark', 'high-contrast']
|
|
24
|
+
|
|
25
|
+
const ICON: Record<ColorModePreference, IconName> = {
|
|
26
|
+
system: 'monitor',
|
|
27
|
+
light: 'sun',
|
|
28
|
+
dark: 'moon',
|
|
29
|
+
'high-contrast': 'contrast',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const LABEL: Record<ColorModePreference, string> = {
|
|
33
|
+
system: 'system',
|
|
34
|
+
light: 'light',
|
|
35
|
+
dark: 'dark',
|
|
36
|
+
'high-contrast': 'high contrast',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ColorModeContextValue {
|
|
40
|
+
/** The user's chosen preference. */
|
|
41
|
+
preference: ColorModePreference
|
|
42
|
+
/** The concrete theme the preference resolves to (system → OS scheme). */
|
|
43
|
+
theme: ThemeName
|
|
44
|
+
/** Set the preference explicitly. */
|
|
45
|
+
setPreference: (preference: ColorModePreference) => void
|
|
46
|
+
/** Advance to the next preference in {@link modes} (wraps). */
|
|
47
|
+
cycle: () => void
|
|
48
|
+
/** The cycle order used by `cycle()` and the toggle. */
|
|
49
|
+
modes: readonly ColorModePreference[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ColorModeContext = createContext<ColorModeContextValue | null>(null)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read the active color mode. Throws if used outside a {@link ColorModeProvider}.
|
|
56
|
+
*/
|
|
57
|
+
export function useColorMode(): ColorModeContextValue {
|
|
58
|
+
const ctx = useContext(ColorModeContext)
|
|
59
|
+
if (!ctx) {
|
|
60
|
+
throw new Error('useColorMode must be used within a <ColorModeProvider>')
|
|
61
|
+
}
|
|
62
|
+
return ctx
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const canUseDom =
|
|
66
|
+
!isNative && typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
|
67
|
+
|
|
68
|
+
function isPreference(value: unknown): value is ColorModePreference {
|
|
69
|
+
return typeof value === 'string' && (PREFERENCES as readonly string[]).includes(value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readStored(key: string | null): ColorModePreference | null {
|
|
73
|
+
if (!key || isNative || typeof window === 'undefined') return null
|
|
74
|
+
try {
|
|
75
|
+
const raw = window.localStorage.getItem(key)
|
|
76
|
+
return isPreference(raw) ? raw : null
|
|
77
|
+
} catch {
|
|
78
|
+
// Private mode / blocked storage — degrade to no persistence.
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function writeStored(key: string | null, value: ColorModePreference): void {
|
|
84
|
+
if (!key || isNative || typeof window === 'undefined') return
|
|
85
|
+
try {
|
|
86
|
+
window.localStorage.setItem(key, value)
|
|
87
|
+
} catch {
|
|
88
|
+
/* ignore */
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface ColorModeProviderProps {
|
|
93
|
+
children: ReactNode
|
|
94
|
+
/** Initial preference (uncontrolled). Default `'system'`. For SSR with no
|
|
95
|
+
* hydration flash, pass the value you persisted server-side (e.g. a cookie). */
|
|
96
|
+
defaultPreference?: ColorModePreference
|
|
97
|
+
/** Controlled preference — when set, the provider does not own the state. */
|
|
98
|
+
preference?: ColorModePreference
|
|
99
|
+
/** Fired whenever the preference changes (controlled or not). */
|
|
100
|
+
onPreferenceChange?: (preference: ColorModePreference) => void
|
|
101
|
+
/** Cycle order for `cycle()` / `<ColorModeToggle>`. Default `['light', 'dark']`. */
|
|
102
|
+
modes?: readonly ColorModePreference[]
|
|
103
|
+
/** localStorage key for persistence; `null` disables it. Default `'duro-color-mode'`. */
|
|
104
|
+
storageKey?: string | null
|
|
105
|
+
/** Wrap children in the DS `ThemeProvider` with the resolved theme. Default `true`.
|
|
106
|
+
* Set `false` to drive your own ThemeProvider from `useColorMode().theme`. */
|
|
107
|
+
applyTheme?: boolean
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Owns color-mode state, resolves `'system'` against the OS scheme, persists the
|
|
112
|
+
* choice, and (by default) applies the resolved theme via `ThemeProvider`.
|
|
113
|
+
* Pair with {@link ColorModeToggle} or read state via {@link useColorMode}.
|
|
114
|
+
*/
|
|
115
|
+
export function ColorModeProvider({
|
|
116
|
+
children,
|
|
117
|
+
defaultPreference = 'system',
|
|
118
|
+
preference: controlled,
|
|
119
|
+
onPreferenceChange,
|
|
120
|
+
modes = ['light', 'dark'],
|
|
121
|
+
storageKey = 'duro-color-mode',
|
|
122
|
+
applyTheme = true,
|
|
123
|
+
}: ColorModeProviderProps) {
|
|
124
|
+
const [internal, setInternal] = useState<ColorModePreference>(controlled ?? defaultPreference)
|
|
125
|
+
const preference = controlled ?? internal
|
|
126
|
+
|
|
127
|
+
// Hydrate from storage AFTER mount (not in the initializer) so the first
|
|
128
|
+
// client render matches the server and React doesn't warn about a mismatch.
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (controlled !== undefined) return
|
|
131
|
+
const stored = readStored(storageKey)
|
|
132
|
+
if (stored) setInternal(stored)
|
|
133
|
+
// Only on mount / when the key changes.
|
|
134
|
+
}, [controlled, storageKey])
|
|
135
|
+
|
|
136
|
+
const setPreference = useCallback(
|
|
137
|
+
(next: ColorModePreference) => {
|
|
138
|
+
if (controlled === undefined) setInternal(next)
|
|
139
|
+
writeStored(storageKey, next)
|
|
140
|
+
onPreferenceChange?.(next)
|
|
141
|
+
},
|
|
142
|
+
[controlled, storageKey, onPreferenceChange],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// Track the OS scheme so `'system'` stays live. The initial value MUST be the
|
|
146
|
+
// SSR-stable default ('dark', what the server renders without a DOM) so the
|
|
147
|
+
// first client render matches the server — reading matchMedia in the
|
|
148
|
+
// initializer would diverge (server 'dark' vs client's real scheme) and trip
|
|
149
|
+
// a hydration mismatch. The effect reconciles to the real scheme after mount.
|
|
150
|
+
const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>('dark')
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (!canUseDom) return
|
|
153
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
154
|
+
const onChange = () => setSystemTheme(mq.matches ? 'dark' : 'light')
|
|
155
|
+
onChange()
|
|
156
|
+
mq.addEventListener('change', onChange)
|
|
157
|
+
return () => mq.removeEventListener('change', onChange)
|
|
158
|
+
}, [])
|
|
159
|
+
|
|
160
|
+
const theme: ThemeName = preference === 'system' ? systemTheme : preference
|
|
161
|
+
|
|
162
|
+
const cycle = useCallback(() => {
|
|
163
|
+
const i = modes.indexOf(preference)
|
|
164
|
+
const next = modes[(i + 1) % modes.length] ?? modes[0]
|
|
165
|
+
if (next) setPreference(next)
|
|
166
|
+
}, [modes, preference, setPreference])
|
|
167
|
+
|
|
168
|
+
const value = useMemo<ColorModeContextValue>(
|
|
169
|
+
() => ({preference, theme, setPreference, cycle, modes}),
|
|
170
|
+
[preference, theme, setPreference, cycle, modes],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<ColorModeContext.Provider value={value}>
|
|
175
|
+
{applyTheme ? <ThemeProvider theme={theme}>{children}</ThemeProvider> : children}
|
|
176
|
+
</ColorModeContext.Provider>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
interface ColorModeToggleProps {
|
|
181
|
+
/** Icon size in px. Default `20`. */
|
|
182
|
+
size?: number
|
|
183
|
+
/** Override the announced label (otherwise auto-generated from current/next). */
|
|
184
|
+
'aria-label'?: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* A compact icon button that cycles the color-mode preference through the
|
|
189
|
+
* provider's `modes`. The glyph reflects the current preference
|
|
190
|
+
* (sun/moon/monitor/contrast). Must be inside a {@link ColorModeProvider}.
|
|
191
|
+
*/
|
|
192
|
+
export function ColorModeToggle({size = 20, 'aria-label': ariaLabel}: ColorModeToggleProps) {
|
|
193
|
+
const {preference, cycle, modes} = useColorMode()
|
|
194
|
+
const i = modes.indexOf(preference)
|
|
195
|
+
const next = modes[(i + 1) % modes.length] ?? modes[0] ?? preference
|
|
196
|
+
const label = ariaLabel ?? `Color mode: ${LABEL[preference]}. Switch to ${LABEL[next]}.`
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<html.button type="button" aria-label={label} onClick={cycle} style={styles.button}>
|
|
200
|
+
<Icon name={ICON[preference]} size={size} />
|
|
201
|
+
</html.button>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {css} from 'react-strict-dom'
|
|
2
|
+
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
3
|
+
import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
|
|
4
|
+
|
|
5
|
+
export const styles = css.create({
|
|
6
|
+
button: {
|
|
7
|
+
display: 'inline-flex',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
justifyContent: 'center',
|
|
10
|
+
padding: spacing.sm,
|
|
11
|
+
borderWidth: 1,
|
|
12
|
+
borderStyle: 'solid',
|
|
13
|
+
borderColor: {
|
|
14
|
+
default: colors.border,
|
|
15
|
+
':hover': colors.textMuted,
|
|
16
|
+
},
|
|
17
|
+
borderRadius: radii.sm,
|
|
18
|
+
backgroundColor: {
|
|
19
|
+
default: 'transparent',
|
|
20
|
+
':hover': colors.bgCardHover,
|
|
21
|
+
},
|
|
22
|
+
color: colors.text,
|
|
23
|
+
cursor: 'pointer',
|
|
24
|
+
transitionProperty: 'background-color, border-color, color',
|
|
25
|
+
transitionDuration: '150ms',
|
|
26
|
+
transitionTimingFunction: 'ease',
|
|
27
|
+
outlineWidth: {default: 0, ':focus-visible': 2},
|
|
28
|
+
outlineStyle: 'solid',
|
|
29
|
+
outlineColor: colors.accent,
|
|
30
|
+
outlineOffset: 2,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -11,6 +11,21 @@ export type IconName =
|
|
|
11
11
|
| 'shield'
|
|
12
12
|
| 'lock'
|
|
13
13
|
| 'key'
|
|
14
|
+
// Navigation / wayfinding glyphs
|
|
15
|
+
| 'map'
|
|
16
|
+
| 'layers'
|
|
17
|
+
| 'repeat'
|
|
18
|
+
| 'database'
|
|
19
|
+
| 'shield-check'
|
|
20
|
+
| 'route'
|
|
21
|
+
| 'git-branch'
|
|
22
|
+
| 'menu'
|
|
23
|
+
| 'pin'
|
|
24
|
+
// Color-mode glyphs
|
|
25
|
+
| 'sun'
|
|
26
|
+
| 'moon'
|
|
27
|
+
| 'monitor'
|
|
28
|
+
| 'contrast'
|
|
14
29
|
// Filled variants (solid shape with cutout symbol)
|
|
15
30
|
| 'info-circle-filled'
|
|
16
31
|
| 'alert-triangle-filled'
|
|
@@ -78,6 +93,101 @@ const strokeIcons: Partial<Record<IconName, ReactNode>> = {
|
|
|
78
93
|
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.78 7.78 5.5 5.5 0 0 1 7.78-7.78zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
|
|
79
94
|
</>
|
|
80
95
|
),
|
|
96
|
+
// ---- navigation / wayfinding ----
|
|
97
|
+
map: (
|
|
98
|
+
<>
|
|
99
|
+
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6" />
|
|
100
|
+
<line x1="8" y1="2" x2="8" y2="18" />
|
|
101
|
+
<line x1="16" y1="6" x2="16" y2="22" />
|
|
102
|
+
</>
|
|
103
|
+
),
|
|
104
|
+
layers: (
|
|
105
|
+
<>
|
|
106
|
+
<polygon points="12 2 2 7 12 12 22 7 12 2" />
|
|
107
|
+
<polyline points="2 17 12 22 22 17" />
|
|
108
|
+
<polyline points="2 12 12 17 22 12" />
|
|
109
|
+
</>
|
|
110
|
+
),
|
|
111
|
+
// Two crossing arrows — exchange / interface flows.
|
|
112
|
+
repeat: (
|
|
113
|
+
<>
|
|
114
|
+
<polyline points="17 1 21 5 17 9" />
|
|
115
|
+
<path d="M3 11V9a4 4 0 0 1 4-4h14" />
|
|
116
|
+
<polyline points="7 23 3 19 7 15" />
|
|
117
|
+
<path d="M21 13v2a4 4 0 0 1-4 4H3" />
|
|
118
|
+
</>
|
|
119
|
+
),
|
|
120
|
+
database: (
|
|
121
|
+
<>
|
|
122
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
123
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
|
124
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
125
|
+
</>
|
|
126
|
+
),
|
|
127
|
+
'shield-check': (
|
|
128
|
+
<>
|
|
129
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
130
|
+
<polyline points="9 12 11 14 15 10" />
|
|
131
|
+
</>
|
|
132
|
+
),
|
|
133
|
+
// Milestone path — roadmap / itinerary.
|
|
134
|
+
route: (
|
|
135
|
+
<>
|
|
136
|
+
<circle cx="6" cy="19" r="3" />
|
|
137
|
+
<path d="M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15" />
|
|
138
|
+
<circle cx="18" cy="5" r="3" />
|
|
139
|
+
</>
|
|
140
|
+
),
|
|
141
|
+
'git-branch': (
|
|
142
|
+
<>
|
|
143
|
+
<line x1="6" y1="3" x2="6" y2="15" />
|
|
144
|
+
<circle cx="18" cy="6" r="3" />
|
|
145
|
+
<circle cx="6" cy="18" r="3" />
|
|
146
|
+
<path d="M18 9a9 9 0 0 1-9 9" />
|
|
147
|
+
</>
|
|
148
|
+
),
|
|
149
|
+
menu: (
|
|
150
|
+
<>
|
|
151
|
+
<line x1="3" y1="6" x2="21" y2="6" />
|
|
152
|
+
<line x1="3" y1="12" x2="21" y2="12" />
|
|
153
|
+
<line x1="3" y1="18" x2="21" y2="18" />
|
|
154
|
+
</>
|
|
155
|
+
),
|
|
156
|
+
pin: (
|
|
157
|
+
<>
|
|
158
|
+
<line x1="12" y1="17" x2="12" y2="22" />
|
|
159
|
+
<path d="M9 10.76V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v6.76a2 2 0 0 0 .55 1.38l1.68 1.78A1 1 0 0 1 16.5 16h-9a1 1 0 0 1-.73-1.68l1.68-1.78A2 2 0 0 0 9 10.76z" />
|
|
160
|
+
</>
|
|
161
|
+
),
|
|
162
|
+
// ---- color mode ----
|
|
163
|
+
sun: (
|
|
164
|
+
<>
|
|
165
|
+
<circle cx="12" cy="12" r="5" />
|
|
166
|
+
<line x1="12" y1="1" x2="12" y2="3" />
|
|
167
|
+
<line x1="12" y1="21" x2="12" y2="23" />
|
|
168
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
169
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
170
|
+
<line x1="1" y1="12" x2="3" y2="12" />
|
|
171
|
+
<line x1="21" y1="12" x2="23" y2="12" />
|
|
172
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
173
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
174
|
+
</>
|
|
175
|
+
),
|
|
176
|
+
moon: <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />,
|
|
177
|
+
monitor: (
|
|
178
|
+
<>
|
|
179
|
+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
|
|
180
|
+
<line x1="8" y1="21" x2="16" y2="21" />
|
|
181
|
+
<line x1="12" y1="17" x2="12" y2="21" />
|
|
182
|
+
</>
|
|
183
|
+
),
|
|
184
|
+
// Circle, half solid — high-contrast / mode indicator.
|
|
185
|
+
contrast: (
|
|
186
|
+
<>
|
|
187
|
+
<circle cx="12" cy="12" r="10" />
|
|
188
|
+
<path d="M12 2a10 10 0 0 1 0 20z" fill="currentColor" stroke="none" />
|
|
189
|
+
</>
|
|
190
|
+
),
|
|
81
191
|
}
|
|
82
192
|
|
|
83
193
|
/**
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,12 @@ export {
|
|
|
62
62
|
usePortalMount,
|
|
63
63
|
type ThemeName,
|
|
64
64
|
} from './components/ThemeProvider/ThemeProvider'
|
|
65
|
+
export {
|
|
66
|
+
ColorModeProvider,
|
|
67
|
+
ColorModeToggle,
|
|
68
|
+
useColorMode,
|
|
69
|
+
type ColorModePreference,
|
|
70
|
+
} from './components/ColorMode'
|
|
65
71
|
export {Text, type TextVariant, type TextColor} from './components/Text/Text'
|
|
66
72
|
export {Toggle, type ToggleSize} from './components/Toggle/Toggle'
|
|
67
73
|
export {ToggleGroup} from './components/ToggleGroup/ToggleGroup'
|