@edgeandnode/eds-react 1.0.1 → 1.1.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/README.md +50 -8
- package/dist/EDSContext.d.ts +4 -1
- package/dist/EDSContext.d.ts.map +1 -1
- package/dist/EDSContext.js +7 -0
- package/dist/EDSContext.js.map +1 -1
- package/dist/EDSProvider.d.ts +13 -6
- package/dist/EDSProvider.d.ts.map +1 -1
- package/dist/EDSProvider.js +60 -68
- package/dist/EDSProvider.js.map +1 -1
- package/dist/components/Address.d.ts +3 -3
- package/dist/components/Address.d.ts.map +1 -1
- package/dist/components/Address.js.map +1 -1
- package/dist/components/Avatar.d.ts +3 -3
- package/dist/components/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar.js +3 -2
- package/dist/components/Avatar.js.map +1 -1
- package/dist/components/Breadcrumbs.parts.d.ts +7 -6
- package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.parts.js +15 -14
- package/dist/components/Breadcrumbs.parts.js.map +1 -1
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +3 -2
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Card.d.ts +8 -4
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/Card.js +2 -1
- package/dist/components/Card.js.map +1 -1
- package/dist/components/Cluster.d.ts +3 -3
- package/dist/components/Cluster.d.ts.map +1 -1
- package/dist/components/Cluster.js.map +1 -1
- package/dist/components/CodeBlock.parts.d.ts.map +1 -1
- package/dist/components/CodeBlock.parts.js +16 -20
- package/dist/components/CodeBlock.parts.js.map +1 -1
- package/dist/components/CopyButton.d.ts.map +1 -1
- package/dist/components/CopyButton.js +3 -2
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/Icon.d.ts.map +1 -1
- package/dist/components/Icon.js +3 -3
- package/dist/components/Icon.js.map +1 -1
- package/dist/components/Keyboard.js +1 -1
- package/dist/components/LocalTheme.d.ts +29 -0
- package/dist/components/LocalTheme.d.ts.map +1 -0
- package/dist/components/LocalTheme.js +28 -0
- package/dist/components/LocalTheme.js.map +1 -0
- package/dist/components/Menu.parts.d.ts +43 -9
- package/dist/components/Menu.parts.d.ts.map +1 -1
- package/dist/components/Menu.parts.js +121 -115
- package/dist/components/Menu.parts.js.map +1 -1
- package/dist/components/Modal.parts.d.ts +1 -1
- package/dist/components/Modal.parts.d.ts.map +1 -1
- package/dist/components/Modal.parts.js +22 -11
- package/dist/components/Modal.parts.js.map +1 -1
- package/dist/components/Pane.parts.d.ts +23 -22
- package/dist/components/Pane.parts.d.ts.map +1 -1
- package/dist/components/Pane.parts.js +3 -3
- package/dist/components/Pane.parts.js.map +1 -1
- package/dist/components/Prose.d.ts +1 -1
- package/dist/components/Prose.d.ts.map +1 -1
- package/dist/components/Prose.js +3 -3
- package/dist/components/Prose.js.map +1 -1
- package/dist/components/Prose.meta.d.ts +5 -0
- package/dist/components/Prose.meta.d.ts.map +1 -1
- package/dist/components/Prose.meta.js +10 -0
- package/dist/components/Prose.meta.js.map +1 -1
- package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
- package/dist/components/SegmentedControl.parts.js +3 -2
- package/dist/components/SegmentedControl.parts.js.map +1 -1
- package/dist/components/Select.parts.d.ts +12 -7
- package/dist/components/Select.parts.d.ts.map +1 -1
- package/dist/components/Select.parts.js +28 -18
- package/dist/components/Select.parts.js.map +1 -1
- package/dist/components/TabSet.parts.d.ts.map +1 -1
- package/dist/components/TabSet.parts.js +3 -2
- package/dist/components/TabSet.parts.js.map +1 -1
- package/dist/components/Table.parts.d.ts.map +1 -1
- package/dist/components/Table.parts.js +3 -4
- package/dist/components/Table.parts.js.map +1 -1
- package/dist/components/ThemeSwitcher.d.ts +5 -5
- package/dist/components/ThemeSwitcher.d.ts.map +1 -1
- package/dist/components/ThemeSwitcher.js +6 -5
- package/dist/components/ThemeSwitcher.js.map +1 -1
- package/dist/components/Tooltip.parts.js +2 -2
- package/dist/components/Tooltip.parts.js.map +1 -1
- package/dist/components/base/Addon.d.ts.map +1 -1
- package/dist/components/base/Addon.js +2 -4
- package/dist/components/base/Addon.js.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.js +2 -1
- package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
- package/dist/components/base/MaybeButtonOrLink.d.ts +7 -5
- package/dist/components/base/MaybeButtonOrLink.d.ts.map +1 -1
- package/dist/components/base/MaybeButtonOrLink.js +7 -2
- package/dist/components/base/MaybeButtonOrLink.js.map +1 -1
- package/dist/components/base/Render.d.ts +25 -6
- package/dist/components/base/Render.d.ts.map +1 -1
- package/dist/components/base/Render.js +4 -3
- package/dist/components/base/Render.js.map +1 -1
- package/dist/components/base/index.d.ts +1 -1
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/base/index.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/useCSSProp.js +1 -1
- package/dist/hooks/useCSSProp.js.map +1 -1
- package/dist/hooks/useEDS.d.ts +24 -1
- package/dist/hooks/useEDS.d.ts.map +1 -1
- package/dist/hooks/useEDS.js +26 -2
- package/dist/hooks/useEDS.js.map +1 -1
- package/dist/hooks/useStyleObserver.d.ts.map +1 -1
- package/dist/hooks/useStyleObserver.js +2 -1
- package/dist/hooks/useStyleObserver.js.map +1 -1
- package/dist/icons/CalendarDynamicIcon.d.ts.map +1 -1
- package/dist/icons/CalendarDynamicIcon.js +3 -1
- package/dist/icons/CalendarDynamicIcon.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/theme-persistence.d.ts +18 -0
- package/dist/theme-persistence.d.ts.map +1 -0
- package/dist/theme-persistence.js +25 -0
- package/dist/theme-persistence.js.map +1 -0
- package/dist/utils/getReactNodeKey.d.ts.map +1 -1
- package/dist/utils/getReactNodeKey.js +31 -13
- package/dist/utils/getReactNodeKey.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/getTooltipProps.d.ts +9 -0
- package/dist/utils/internal/getTooltipProps.d.ts.map +1 -0
- package/dist/utils/internal/getTooltipProps.js +18 -0
- package/dist/utils/internal/getTooltipProps.js.map +1 -0
- package/dist/utils/internal/index.d.ts +4 -0
- package/dist/utils/internal/index.d.ts.map +1 -0
- package/dist/utils/internal/index.js +4 -0
- package/dist/utils/internal/index.js.map +1 -0
- package/dist/utils/internal/resolveLazy.d.ts +29 -0
- package/dist/utils/internal/resolveLazy.d.ts.map +1 -0
- package/dist/utils/internal/resolveLazy.js +40 -0
- package/dist/utils/internal/resolveLazy.js.map +1 -0
- package/dist/utils/internal/styleScheduler.d.ts.map +1 -0
- package/dist/utils/internal/styleScheduler.js.map +1 -0
- package/dist/utils/isComponentType.d.ts +9 -0
- package/dist/utils/isComponentType.d.ts.map +1 -0
- package/dist/utils/isComponentType.js +22 -0
- package/dist/utils/isComponentType.js.map +1 -0
- package/dist/utils/isReactNode.d.ts +7 -0
- package/dist/utils/isReactNode.d.ts.map +1 -1
- package/dist/utils/isReactNode.js +11 -0
- package/dist/utils/isReactNode.js.map +1 -1
- package/dist/utils/reactNodeToString.d.ts +3 -0
- package/dist/utils/reactNodeToString.d.ts.map +1 -1
- package/dist/utils/reactNodeToString.js +6 -0
- package/dist/utils/reactNodeToString.js.map +1 -1
- package/dist/utils/styleObserver.js +1 -1
- package/dist/utils/trimReactNode.d.ts +3 -0
- package/dist/utils/trimReactNode.d.ts.map +1 -1
- package/dist/utils/trimReactNode.js +6 -0
- package/dist/utils/trimReactNode.js.map +1 -1
- package/package.json +24 -23
- package/scripts/update-skill.cjs +18 -5
- package/skill/SKILL.md +95 -57
- package/skill/references/tokens.md +2 -8
- package/src/EDSContext.ts +10 -1
- package/src/EDSProvider.tsx +111 -80
- package/src/components/Address.tsx +4 -3
- package/src/components/Avatar.tsx +10 -5
- package/src/components/Breadcrumbs.parts.tsx +35 -18
- package/src/components/Button.tsx +7 -2
- package/src/components/Card.tsx +14 -8
- package/src/components/Cluster.tsx +5 -3
- package/src/components/CodeBlock.parts.tsx +20 -24
- package/src/components/CopyButton.tsx +3 -2
- package/src/components/Icon.tsx +3 -9
- package/src/components/Keyboard.tsx +1 -1
- package/src/components/LocalTheme.tsx +47 -0
- package/src/components/Menu.parts.tsx +368 -245
- package/src/components/Modal.parts.tsx +28 -11
- package/src/components/Pane.parts.tsx +31 -26
- package/src/components/Prose.meta.ts +10 -0
- package/src/components/Prose.tsx +4 -3
- package/src/components/SegmentedControl.parts.tsx +3 -8
- package/src/components/Select.parts.tsx +43 -34
- package/src/components/TabSet.parts.tsx +3 -5
- package/src/components/Table.parts.tsx +3 -3
- package/src/components/ThemeSwitcher.tsx +13 -15
- package/src/components/Tooltip.parts.tsx +2 -2
- package/src/components/base/Addon.tsx +3 -4
- package/src/components/base/ButtonOrLink.parts.tsx +2 -1
- package/src/components/base/MaybeButtonOrLink.tsx +11 -6
- package/src/components/base/Render.tsx +28 -4
- package/src/components/base/index.ts +2 -0
- package/src/components/index.ts +8 -0
- package/src/hooks/useCSSProp.ts +1 -1
- package/src/hooks/useEDS.ts +26 -2
- package/src/hooks/useStyleObserver.ts +2 -1
- package/src/icons/CalendarDynamicIcon.tsx +1 -0
- package/src/index.ts +2 -1
- package/src/theme-persistence.ts +31 -0
- package/src/utils/getReactNodeKey.ts +30 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/internal/getTooltipProps.ts +25 -0
- package/src/utils/internal/index.ts +3 -0
- package/src/utils/internal/resolveLazy.ts +44 -0
- package/src/utils/isComponentType.ts +27 -0
- package/src/utils/isReactNode.ts +11 -0
- package/src/utils/reactNodeToString.ts +7 -0
- package/src/utils/styleObserver.ts +1 -1
- package/src/utils/trimReactNode.tsx +7 -0
- package/dist/utils/styleScheduler.d.ts.map +0 -1
- package/dist/utils/styleScheduler.js.map +0 -1
- package/src/__snapshots__/compiled.css +0 -22499
- package/styles/tailwind.storybook.css +0 -19
- /package/dist/utils/{styleScheduler.d.ts → internal/styleScheduler.d.ts} +0 -0
- /package/dist/utils/{styleScheduler.js → internal/styleScheduler.js} +0 -0
- /package/src/utils/{styleScheduler.ts → internal/styleScheduler.ts} +0 -0
package/src/EDSProvider.tsx
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useSyncExternalStore,
|
|
10
|
+
type ComponentProps,
|
|
11
|
+
} from 'react'
|
|
4
12
|
import { mergeProps } from '@base-ui/react'
|
|
5
13
|
import { DirectionProvider } from '@base-ui/react/direction-provider'
|
|
6
14
|
import { Tooltip } from '@base-ui/react/tooltip'
|
|
7
15
|
|
|
8
16
|
import { ButtonOrLink, type ButtonOrLinkConfigProps } from './components/base/ButtonOrLink.tsx'
|
|
9
17
|
import { Pane } from './components/Pane.tsx'
|
|
10
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
EDSContext,
|
|
20
|
+
THEME_CLASSES,
|
|
21
|
+
type ResolvedTheme,
|
|
22
|
+
type Theme,
|
|
23
|
+
type ThemePersistence,
|
|
24
|
+
} from './EDSContext.ts'
|
|
11
25
|
import { useControlled } from './hooks/useControlled.ts'
|
|
12
26
|
import { useIsHydrated } from './hooks/useIsHydrated.ts'
|
|
27
|
+
import {
|
|
28
|
+
COOKIE_REGEX,
|
|
29
|
+
isValidTheme,
|
|
30
|
+
parseThemeCookie,
|
|
31
|
+
PERSISTENCE_KEY,
|
|
32
|
+
VALID_THEMES,
|
|
33
|
+
} from './theme-persistence.ts'
|
|
13
34
|
import { cn } from './utils/index.ts'
|
|
14
35
|
|
|
15
|
-
const PERSISTENCE_KEY = 'eds-active-theme'
|
|
16
|
-
const COOKIE_REGEX = new RegExp(`(?:^|; )${PERSISTENCE_KEY}=([^;]+)`)
|
|
17
|
-
|
|
18
|
-
const THEME_CLASSES: Record<Theme, string> = {
|
|
19
|
-
dark: 'eds-dark',
|
|
20
|
-
light: 'eds-light',
|
|
21
|
-
system: 'eds-system',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const VALID_THEMES: readonly Theme[] = ['dark', 'light', 'system']
|
|
25
|
-
|
|
26
|
-
function isValidTheme(value: unknown): value is Theme {
|
|
27
|
-
return typeof value === 'string' && (VALID_THEMES as readonly string[]).includes(value)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
36
|
interface PersistenceAdapter {
|
|
31
37
|
get: () => Theme | null
|
|
32
38
|
set: (value: Theme) => void
|
|
@@ -59,17 +65,14 @@ function resolvePersistence(persistTheme: ThemePersistence | undefined): Persist
|
|
|
59
65
|
return {
|
|
60
66
|
get: () => {
|
|
61
67
|
try {
|
|
62
|
-
|
|
63
|
-
if (match === null) return null
|
|
64
|
-
const value = decodeURIComponent(match[1] as string)
|
|
65
|
-
return isValidTheme(value) ? value : null
|
|
68
|
+
return parseThemeCookie(document.cookie) ?? null
|
|
66
69
|
} catch {
|
|
67
70
|
return null
|
|
68
71
|
}
|
|
69
72
|
},
|
|
70
73
|
set: (value) => {
|
|
71
74
|
try {
|
|
72
|
-
document.cookie = `${PERSISTENCE_KEY}=${
|
|
75
|
+
document.cookie = `${PERSISTENCE_KEY}=${value}; path=/; max-age=31536000; samesite=lax`
|
|
73
76
|
} catch {
|
|
74
77
|
// ignore restrictive environments
|
|
75
78
|
}
|
|
@@ -99,12 +102,13 @@ function resolvePersistence(persistTheme: ThemePersistence | undefined): Persist
|
|
|
99
102
|
const prefersDarkMediaQuery =
|
|
100
103
|
typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)') : null
|
|
101
104
|
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
function subscribeToSystemTheme(onChange: () => void) {
|
|
106
|
+
prefersDarkMediaQuery?.addEventListener('change', onChange)
|
|
107
|
+
return () => prefersDarkMediaQuery?.removeEventListener('change', onChange)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getSystemTheme(): ResolvedTheme {
|
|
111
|
+
return prefersDarkMediaQuery?.matches === false ? 'light' : 'dark'
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
export interface EDSProviderProps extends Omit<ComponentProps<'div'>, 'dir'> {
|
|
@@ -130,16 +134,22 @@ export interface EDSProviderProps extends Omit<ComponentProps<'div'>, 'dir'> {
|
|
|
130
134
|
* - `'localStorage'`: reads/writes `localStorage['eds-active-theme']`. Inline pre-hydration script
|
|
131
135
|
* reads the value before first paint — no SSR flash.
|
|
132
136
|
* - `'cookie'`: reads/writes a `eds-active-theme` cookie (`path=/; max-age=31536000;
|
|
133
|
-
* samesite=lax`). Same no-flash SSR;
|
|
137
|
+
* samesite=lax`). Same no-flash SSR; pass `getThemeFromCookies()` to `defaultTheme` on the
|
|
138
|
+
* server so it renders the saved theme too.
|
|
134
139
|
* - `{ get, set }`: custom adapter (different key, encryption, remote storage, etc.). Invalid
|
|
135
|
-
* values from `get()` fall back to `defaultTheme`.
|
|
136
|
-
* zero-flash SSR, inject your own inline script that adds the matching
|
|
137
|
-
* `<html>` before paint.
|
|
140
|
+
* values from `get()` fall back to `defaultTheme`. In uncontrolled mode, no pre-hydration
|
|
141
|
+
* script is emitted; for zero-flash SSR, inject your own inline script that adds the matching
|
|
142
|
+
* `eds-*` class to `<html>` before paint.
|
|
138
143
|
*
|
|
139
144
|
* The two string presets share the key `'eds-active-theme'` — use a custom adapter for per-app
|
|
140
145
|
* isolation on the same origin.
|
|
141
146
|
*/
|
|
142
147
|
persistTheme?: ThemePersistence | undefined
|
|
148
|
+
/**
|
|
149
|
+
* Nonce for the inline scripts and styles `EDSProvider` adds, if your Content Security Policy
|
|
150
|
+
* requires one.
|
|
151
|
+
*/
|
|
152
|
+
nonce?: string | undefined
|
|
143
153
|
/** @default 'ltr' */
|
|
144
154
|
direction?: ComponentProps<typeof DirectionProvider>['direction'] | undefined
|
|
145
155
|
/** @default 'en' // or for `direction="rtl"`, 'ar' */
|
|
@@ -153,7 +163,8 @@ export type { EDSContextValue, ResolvedTheme, Theme, ThemePersistence } from './
|
|
|
153
163
|
* Provides global configuration and enhancements for EDS components.
|
|
154
164
|
*
|
|
155
165
|
* Note: Only one EDSProvider should be used per app, typically at the root. If you need different
|
|
156
|
-
* themes for different parts of the app, use the `
|
|
166
|
+
* themes for different parts of the app, use the `Dark`, `Light`, and `LocalTheme` components
|
|
167
|
+
* instead.
|
|
157
168
|
*
|
|
158
169
|
* **Next.js App Router:** add `suppressHydrationWarning` to your `<html>` tag in `app/layout.tsx`.
|
|
159
170
|
* The pre-hydration script applies the theme class to `<html>` before React hydrates (preventing a
|
|
@@ -192,6 +203,7 @@ export function EDSProvider({
|
|
|
192
203
|
defaultTheme = 'dark',
|
|
193
204
|
onThemeChange,
|
|
194
205
|
persistTheme,
|
|
206
|
+
nonce,
|
|
195
207
|
direction = 'ltr',
|
|
196
208
|
language = direction === 'rtl' ? 'ar' : 'en',
|
|
197
209
|
buttonOrLinkConfig,
|
|
@@ -280,6 +292,7 @@ export function EDSProvider({
|
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
const style = document.createElement('style')
|
|
295
|
+
if (nonce !== undefined) style.nonce = nonce
|
|
283
296
|
style.textContent = '*:not(.eds-segmented-control *) { transition: none !important; }'
|
|
284
297
|
document.head.appendChild(style)
|
|
285
298
|
removeClasses()
|
|
@@ -293,30 +306,15 @@ export function EDSProvider({
|
|
|
293
306
|
style.remove()
|
|
294
307
|
removeClasses()
|
|
295
308
|
}
|
|
296
|
-
}, [theme])
|
|
309
|
+
}, [theme, nonce])
|
|
297
310
|
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const [activeTheme, setActiveTheme] = useState<ResolvedTheme>(() =>
|
|
304
|
-
theme === 'system' ? systemFallback : theme,
|
|
311
|
+
// The server can't know the OS preference, so it renders `'dark'`, `:root`'s default
|
|
312
|
+
const systemTheme = useSyncExternalStore<ResolvedTheme>(
|
|
313
|
+
subscribeToSystemTheme,
|
|
314
|
+
getSystemTheme,
|
|
315
|
+
() => 'dark',
|
|
305
316
|
)
|
|
306
317
|
|
|
307
|
-
useLayoutEffect(() => {
|
|
308
|
-
setActiveTheme(resolveTheme(theme, systemFallback))
|
|
309
|
-
|
|
310
|
-
if (theme !== 'system') return
|
|
311
|
-
|
|
312
|
-
const handleChange = (event: MediaQueryListEvent) => {
|
|
313
|
-
setActiveTheme(event.matches ? 'dark' : 'light')
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
prefersDarkMediaQuery?.addEventListener('change', handleChange)
|
|
317
|
-
return () => prefersDarkMediaQuery?.removeEventListener('change', handleChange)
|
|
318
|
-
}, [theme, systemFallback])
|
|
319
|
-
|
|
320
318
|
/**
|
|
321
319
|
* Add the `data-eds-active` attribute to the `html` element when the Space key is pressed to
|
|
322
320
|
* improve Tailwind's `active` state.
|
|
@@ -370,15 +368,34 @@ export function EDSProvider({
|
|
|
370
368
|
}
|
|
371
369
|
}, [])
|
|
372
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Warn if build tools transpiled `light-dark()` (e.g. Lightning CSS with older browser targets).
|
|
373
|
+
* Its fallback resolves tokens on `:root`, so components inside `Dark` and `Light` would get the
|
|
374
|
+
* page's colors.
|
|
375
|
+
*/
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
const lightDarkTest = getComputedStyle(document.documentElement).getPropertyValue(
|
|
378
|
+
'--eds-light-dark-test',
|
|
379
|
+
)
|
|
380
|
+
if (lightDarkTest !== '' && !lightDarkTest.includes('light-dark')) {
|
|
381
|
+
// oxlint-disable-next-line no-console
|
|
382
|
+
console.warn(
|
|
383
|
+
"[EDSProvider] `light-dark()` was transpiled by your build tools, so components inside `Dark`, `Light`, and `LocalTheme` will use the page's colors. Target Chrome 123, Safari 17.5, and Firefox 120 or newer (see the `@edgeandnode/eds-react` README).",
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
}, [])
|
|
387
|
+
|
|
388
|
+
const isHydrated = useIsHydrated()
|
|
389
|
+
|
|
373
390
|
const edsContextValue = useMemo(
|
|
374
391
|
() => ({
|
|
375
392
|
theme,
|
|
376
|
-
activeTheme,
|
|
377
393
|
setTheme,
|
|
394
|
+
systemTheme,
|
|
378
395
|
direction,
|
|
379
396
|
language,
|
|
380
397
|
}),
|
|
381
|
-
[theme,
|
|
398
|
+
[theme, setTheme, systemTheme, direction, language],
|
|
382
399
|
)
|
|
383
400
|
|
|
384
401
|
return (
|
|
@@ -387,10 +404,19 @@ export function EDSProvider({
|
|
|
387
404
|
<Pane.Provider>
|
|
388
405
|
<Tooltip.Provider timeout={300}>
|
|
389
406
|
<ButtonOrLink.Config {...buttonOrLinkConfig}>
|
|
390
|
-
{
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
407
|
+
{/**
|
|
408
|
+
* Apply the theme before first paint to prevent a flash, as the browser runs this script and
|
|
409
|
+
* applies this style while parsing the server-rendered HTML. The style applies a light or system
|
|
410
|
+
* theme until a theme class is set on `<html>`, in case there's no script (with a custom
|
|
411
|
+
* `persistTheme` adapter) or it can't run (e.g. blocked by a Content Security Policy). Both are
|
|
412
|
+
* removed after hydration.
|
|
413
|
+
*/}
|
|
414
|
+
{isHydrated ? null : (
|
|
415
|
+
<>
|
|
416
|
+
{renderThemeScript(controlledTheme, defaultTheme, persistTheme, nonce)}
|
|
417
|
+
{renderThemeStyle(controlledTheme ?? defaultTheme, nonce)}
|
|
418
|
+
</>
|
|
419
|
+
)}
|
|
394
420
|
<div
|
|
395
421
|
dir={direction}
|
|
396
422
|
lang={language}
|
|
@@ -406,38 +432,43 @@ export function EDSProvider({
|
|
|
406
432
|
)
|
|
407
433
|
}
|
|
408
434
|
|
|
409
|
-
function
|
|
435
|
+
function renderThemeScript(
|
|
410
436
|
controlledTheme: Theme | undefined,
|
|
411
437
|
defaultTheme: Theme,
|
|
412
438
|
persistTheme: ThemePersistence | undefined,
|
|
439
|
+
nonce: string | undefined,
|
|
413
440
|
) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return `document.documentElement.classList.add('eds-${controlledTheme}')`
|
|
418
|
-
}
|
|
419
|
-
if (persistTheme !== 'localStorage' && persistTheme !== 'cookie') {
|
|
420
|
-
return `document.documentElement.classList.add('eds-${defaultTheme}')`
|
|
441
|
+
if (controlledTheme !== undefined || persistTheme === undefined) {
|
|
442
|
+
const script = `document.documentElement.classList.add('eds-${controlledTheme ?? defaultTheme}')`
|
|
443
|
+
return <script nonce={nonce}>{script}</script>
|
|
421
444
|
}
|
|
445
|
+
// Custom adapters need the app's own script (see `persistTheme`), which this would conflict with
|
|
446
|
+
if (persistTheme !== 'localStorage' && persistTheme !== 'cookie') return null
|
|
422
447
|
|
|
423
|
-
const invalidCheck = VALID_THEMES.map((
|
|
448
|
+
const invalidCheck = VALID_THEMES.map((theme) => `t!=='${theme}'`).join('&&')
|
|
424
449
|
const readExpression =
|
|
425
450
|
persistTheme === 'localStorage'
|
|
426
451
|
? `localStorage.getItem('${PERSISTENCE_KEY}')`
|
|
427
|
-
: `
|
|
428
|
-
|
|
452
|
+
: `(document.cookie.match(${COOKIE_REGEX})||[])[1]`
|
|
453
|
+
const script = `(function(){try{var t=${readExpression};if(${invalidCheck})t='${defaultTheme}';document.documentElement.classList.add('eds-'+t)}catch(e){document.documentElement.classList.add('eds-${defaultTheme}')}})()`
|
|
454
|
+
return <script nonce={nonce}>{script}</script>
|
|
429
455
|
}
|
|
430
456
|
|
|
431
|
-
|
|
457
|
+
const ROOT_WITHOUT_THEME_CLASS = `:root:not(${Object.values(THEME_CLASSES)
|
|
458
|
+
.map((themeClass) => `.${themeClass}`)
|
|
459
|
+
.join(', ')})`
|
|
432
460
|
|
|
433
461
|
/**
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
* Outputs its tag during SSR and during the client's initial render (ensuring the markup matches),
|
|
437
|
-
* and removes itself after hydration to keep the DOM clean. The browser executes the inline script
|
|
438
|
-
* as it parses the HTML, before React hydrates and before any styled body content paints.
|
|
462
|
+
* `:root` is dark by default, so other themes need a style until the theme class is set on
|
|
463
|
+
* `<html>`.
|
|
439
464
|
*/
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
465
|
+
const THEME_STYLES: Record<Theme, string | null> = {
|
|
466
|
+
dark: null,
|
|
467
|
+
light: `${ROOT_WITHOUT_THEME_CLASS}{color-scheme:only light;--eds-color-scheme:light}`,
|
|
468
|
+
system: `${ROOT_WITHOUT_THEME_CLASS}{color-scheme:light dark}@media (prefers-color-scheme:light){${ROOT_WITHOUT_THEME_CLASS}{--eds-color-scheme:light}}`,
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function renderThemeStyle(theme: Theme, nonce: string | undefined) {
|
|
472
|
+
const style = THEME_STYLES[theme]
|
|
473
|
+
return style !== null ? <style nonce={nonce}>{style}</style> : null
|
|
443
474
|
}
|
|
@@ -26,14 +26,15 @@ export declare namespace AddressProps {
|
|
|
26
26
|
interface ButtonProps
|
|
27
27
|
extends
|
|
28
28
|
CommonProps,
|
|
29
|
-
MaybeButtonOrLinkProps.ButtonProps,
|
|
29
|
+
Omit<MaybeButtonOrLinkProps.ButtonProps, 'as'>,
|
|
30
30
|
MaybeButtonOrLinkProps.DisableableProps {}
|
|
31
31
|
interface LinkProps
|
|
32
32
|
extends
|
|
33
33
|
CommonProps,
|
|
34
|
-
MaybeButtonOrLinkProps.LinkProps,
|
|
34
|
+
Omit<MaybeButtonOrLinkProps.LinkProps, 'as'>,
|
|
35
35
|
MaybeButtonOrLinkProps.DisableableProps {}
|
|
36
|
-
interface OtherElementProps
|
|
36
|
+
interface OtherElementProps
|
|
37
|
+
extends CommonProps, Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as'> {}
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export type AddressProps =
|
|
@@ -9,7 +9,8 @@ import avatarPlaceholderRounded from '@edgeandnode/eds-react/assets/avatar-place
|
|
|
9
9
|
import { createIdenticon, PLACEHOLDER_ADDRESS } from '@edgeandnode/eds-utils'
|
|
10
10
|
|
|
11
11
|
import { useCSSPropsPolyfill, useEDS } from '../hooks/index.ts'
|
|
12
|
-
import { cn, getAssetUrl, getCSSPropsAttributes
|
|
12
|
+
import { cn, getAssetUrl, getCSSPropsAttributes } from '../utils/index.ts'
|
|
13
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
13
14
|
import { AvatarMeta } from './Avatar.meta.ts'
|
|
14
15
|
import { MaybeButtonOrLink, type MaybeButtonOrLinkProps } from './base/MaybeButtonOrLink.tsx'
|
|
15
16
|
import { Tooltip, type TooltipProps } from './Tooltip.tsx'
|
|
@@ -35,10 +36,14 @@ export declare namespace AvatarProps {
|
|
|
35
36
|
tooltip?: TooltipProps['content'] | Omit<TooltipProps, 'children'> | undefined
|
|
36
37
|
}
|
|
37
38
|
interface CommonProps extends OwnProps, EDSComponentProps<typeof AvatarMeta> {}
|
|
38
|
-
interface ButtonProps
|
|
39
|
-
|
|
39
|
+
interface ButtonProps
|
|
40
|
+
extends CommonProps, Omit<MaybeButtonOrLinkProps.ButtonProps, 'as' | 'children' | 'render'> {}
|
|
41
|
+
interface LinkProps
|
|
42
|
+
extends CommonProps, Omit<MaybeButtonOrLinkProps.LinkProps, 'as' | 'children' | 'render'> {}
|
|
40
43
|
interface OtherElementProps
|
|
41
|
-
extends
|
|
44
|
+
extends
|
|
45
|
+
CommonProps,
|
|
46
|
+
Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as' | 'children' | 'render'> {}
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
export type AvatarProps =
|
|
@@ -94,7 +99,7 @@ export function Avatar({
|
|
|
94
99
|
return (
|
|
95
100
|
<Tooltip
|
|
96
101
|
content={category !== 'other' ? alt : undefined}
|
|
97
|
-
{...(
|
|
102
|
+
{...getTooltipProps(tooltip)}
|
|
98
103
|
triggerProps={renderProps}
|
|
99
104
|
>
|
|
100
105
|
<Element {...elementProps}>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
createContext,
|
|
5
5
|
isValidElement,
|
|
6
6
|
useCallback,
|
|
7
|
+
useContext,
|
|
7
8
|
useMemo,
|
|
8
9
|
useRef,
|
|
9
10
|
useState,
|
|
10
11
|
type ComponentProps,
|
|
11
12
|
type CSSProperties,
|
|
12
|
-
type ReactElement,
|
|
13
13
|
} from 'react'
|
|
14
14
|
import { mergeProps } from '@base-ui/react'
|
|
15
15
|
import { useRenderCount, useThrottledEffect } from '@react-hookz/web'
|
|
@@ -21,12 +21,20 @@ import { ArrowLeftInteractiveIcon, DotsThreeIcon } from '@edgeandnode/eds-react/
|
|
|
21
21
|
|
|
22
22
|
import { useCSSPropsPolyfill, useCSSState, useEDS } from '../hooks/index.ts'
|
|
23
23
|
import { cn, getCSSPropsAttributes, splitProps } from '../utils/index.ts'
|
|
24
|
+
import { resolveLazy } from '../utils/internal/index.ts'
|
|
24
25
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
25
26
|
import { MaybeButtonOrLink, type MaybeButtonOrLinkProps } from './base/MaybeButtonOrLink.tsx'
|
|
26
27
|
import { BreadcrumbsItemMeta, BreadcrumbsMeta } from './Breadcrumbs.meta.ts'
|
|
27
28
|
import { Button } from './Button.tsx'
|
|
28
29
|
import { Menu } from './Menu.tsx'
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Whether a `Breadcrumbs.Item` is (in) the last item of `Breadcrumbs`, which makes it default to
|
|
33
|
+
* `current`. Provided through context rather than by cloning the item, so that it also works when
|
|
34
|
+
* items are wrapped in another component.
|
|
35
|
+
*/
|
|
36
|
+
const BreadcrumbsLastItemContext = createContext(false)
|
|
37
|
+
|
|
30
38
|
export interface BreadcrumbsProps
|
|
31
39
|
extends ComponentProps<'nav'>, EDSComponentProps<typeof BreadcrumbsMeta> {}
|
|
32
40
|
|
|
@@ -47,18 +55,23 @@ export function BreadcrumbsRoot({
|
|
|
47
55
|
|
|
48
56
|
const ariaLabel = passedAriaLabel !== undefined ? passedAriaLabel : 'Breadcrumbs'
|
|
49
57
|
|
|
50
|
-
// If the last `Breadcrumbs.Item` child doesn't explicitly set `current`, clone it to default
|
|
51
|
-
// `current` to `true`.
|
|
52
58
|
const processedChildren = useMemo(() => {
|
|
53
59
|
const flatChildren = flattenChildren(children)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const lastItemIndex = flatChildren.findLastIndex(
|
|
61
|
+
(child) => isValidElement(child) && resolveLazy(child.type) === BreadcrumbsItem,
|
|
62
|
+
)
|
|
63
|
+
// If items are wrapped in another component, the last element renders the last item
|
|
64
|
+
const lastIndex =
|
|
65
|
+
lastItemIndex !== -1 ? lastItemIndex : flatChildren.findLastIndex(isValidElement)
|
|
66
|
+
return flatChildren.map((child, index) =>
|
|
67
|
+
isValidElement(child) ? (
|
|
68
|
+
<BreadcrumbsLastItemContext.Provider key={child.key} value={index === lastIndex}>
|
|
69
|
+
{child}
|
|
70
|
+
</BreadcrumbsLastItemContext.Provider>
|
|
71
|
+
) : (
|
|
72
|
+
child
|
|
73
|
+
),
|
|
74
|
+
)
|
|
62
75
|
}, [children])
|
|
63
76
|
|
|
64
77
|
const hiddenContainerRef = useRef<HTMLDivElement>(null)
|
|
@@ -249,15 +262,17 @@ export declare namespace BreadcrumbsItemProps {
|
|
|
249
262
|
addonBefore?: AddonValue | undefined
|
|
250
263
|
/**
|
|
251
264
|
* Whether this breadcrumb item represents the current page. Defaults to `true` for the last
|
|
252
|
-
* `Breadcrumbs.Item` child of `Breadcrumbs
|
|
253
|
-
*
|
|
265
|
+
* `Breadcrumbs.Item` child of `Breadcrumbs` (or the item rendered by its last child, if items
|
|
266
|
+
* are wrapped in another component), and `false` for any other item. Set explicitly to override
|
|
267
|
+
* the default.
|
|
254
268
|
*/
|
|
255
269
|
current?: boolean | undefined
|
|
256
270
|
}
|
|
257
271
|
interface CommonProps extends OwnProps, EDSComponentProps<typeof BreadcrumbsItemMeta> {}
|
|
258
|
-
interface ButtonProps extends CommonProps, MaybeButtonOrLinkProps.ButtonProps {}
|
|
259
|
-
interface LinkProps extends CommonProps, MaybeButtonOrLinkProps.LinkProps {}
|
|
260
|
-
interface OtherElementProps
|
|
272
|
+
interface ButtonProps extends CommonProps, Omit<MaybeButtonOrLinkProps.ButtonProps, 'as'> {}
|
|
273
|
+
interface LinkProps extends CommonProps, Omit<MaybeButtonOrLinkProps.LinkProps, 'as'> {}
|
|
274
|
+
interface OtherElementProps
|
|
275
|
+
extends CommonProps, Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as'> {}
|
|
261
276
|
}
|
|
262
277
|
|
|
263
278
|
export type BreadcrumbsItemProps =
|
|
@@ -267,11 +282,13 @@ export type BreadcrumbsItemProps =
|
|
|
267
282
|
|
|
268
283
|
export function BreadcrumbsItem({
|
|
269
284
|
addonBefore,
|
|
270
|
-
current
|
|
285
|
+
current: passedCurrent,
|
|
271
286
|
hideLabel,
|
|
272
287
|
children,
|
|
273
288
|
...props
|
|
274
289
|
}: BreadcrumbsItemProps) {
|
|
290
|
+
const isLastItem = useContext(BreadcrumbsLastItemContext)
|
|
291
|
+
const current = passedCurrent ?? isLastItem
|
|
275
292
|
const { rootProps, nestedProps } = splitProps(props)
|
|
276
293
|
|
|
277
294
|
const [stateRef, state] = useCSSState({
|
|
@@ -7,7 +7,8 @@ import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
|
7
7
|
import { LoadingIcon } from '@edgeandnode/eds-react/icons'
|
|
8
8
|
|
|
9
9
|
import { useCSSPropsPolyfill, useCSSState, useEDS } from '../hooks/index.ts'
|
|
10
|
-
import { cn, getCSSPropsAttributes
|
|
10
|
+
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
11
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
11
12
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
12
13
|
import { ButtonOrLink, type ButtonOrLinkProps } from './base/ButtonOrLink.tsx'
|
|
13
14
|
import { ButtonMeta } from './Button.meta.ts'
|
|
@@ -132,7 +133,7 @@ export function Button({
|
|
|
132
133
|
render={(renderProps, { Element, elementProps }) => (
|
|
133
134
|
<Tooltip
|
|
134
135
|
closeOnClick={typeof props.checked === 'boolean' ? false : undefined} // If this is a toggle button, keep the tooltip open on click by default
|
|
135
|
-
{...(
|
|
136
|
+
{...getTooltipProps(tooltip)}
|
|
136
137
|
triggerProps={renderProps}
|
|
137
138
|
>
|
|
138
139
|
<Element {...elementProps}>
|
|
@@ -167,6 +168,10 @@ export function Button({
|
|
|
167
168
|
u:*:transition-opacity
|
|
168
169
|
u:*:@state-disabled/button:opacity-disabled
|
|
169
170
|
u:*:@state-disabled/button:grayscale
|
|
171
|
+
u:*:@prop-variant-inverse/button:scheme-only-dark
|
|
172
|
+
u:*:@prop-variant-inverse/button:dark:scheme-only-light
|
|
173
|
+
u:*:@prop-variant-primary/button:scheme-only-dark
|
|
174
|
+
u:*:@prop-variant-danger/button:scheme-only-dark
|
|
170
175
|
u:@prop-size-xsmall/button:addon-small
|
|
171
176
|
u:@prop-size-small/button:addon-small
|
|
172
177
|
u:@prop-size-medium/button:addon-medium
|
package/src/components/Card.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useId, type ReactNode } from 'react'
|
|
3
|
+
import { useId, type ElementType, type ReactNode, type Ref } from 'react'
|
|
4
4
|
import { mergeProps } from '@base-ui/react'
|
|
5
|
+
import type { DistributedOmit } from 'type-fest'
|
|
5
6
|
|
|
6
7
|
import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
7
8
|
import { ArrowUpRightInteractiveIcon } from '@edgeandnode/eds-react/icons'
|
|
@@ -32,20 +33,24 @@ export declare namespace CardProps {
|
|
|
32
33
|
*/
|
|
33
34
|
autoIcon?: boolean | undefined
|
|
34
35
|
}
|
|
36
|
+
interface CommonProps extends EDSComponentProps<typeof CardMeta> {
|
|
37
|
+
/** Attached to the card's outer element, even when the card is a button or link. */
|
|
38
|
+
ref?: Ref<HTMLElement> | undefined
|
|
39
|
+
}
|
|
35
40
|
interface ButtonProps
|
|
36
41
|
extends
|
|
37
|
-
|
|
42
|
+
CommonProps,
|
|
38
43
|
ClickableProps,
|
|
39
|
-
MaybeButtonOrLinkProps.ButtonProps,
|
|
44
|
+
Omit<MaybeButtonOrLinkProps.ButtonProps, 'ref' | 'render'>,
|
|
40
45
|
MaybeButtonOrLinkProps.DisableableProps {}
|
|
41
46
|
interface LinkProps
|
|
42
47
|
extends
|
|
43
|
-
|
|
48
|
+
CommonProps,
|
|
44
49
|
ClickableProps,
|
|
45
|
-
MaybeButtonOrLinkProps.LinkProps,
|
|
50
|
+
Omit<MaybeButtonOrLinkProps.LinkProps, 'ref' | 'render'>,
|
|
46
51
|
MaybeButtonOrLinkProps.DisableableProps {}
|
|
47
52
|
interface OtherElementProps
|
|
48
|
-
extends
|
|
53
|
+
extends CommonProps, Omit<MaybeButtonOrLinkProps.OtherElementProps, 'ref' | 'render'> {}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export type CardProps = CardProps.ButtonProps | CardProps.LinkProps | CardProps.OtherElementProps
|
|
@@ -57,7 +62,7 @@ export function Card({ ref: passedRef, as, variant, disabled, children, ...props
|
|
|
57
62
|
interactiveContent = false,
|
|
58
63
|
autoIcon = true,
|
|
59
64
|
...remainingProps
|
|
60
|
-
} = props as CardProps.ButtonProps | CardProps.LinkProps
|
|
65
|
+
} = props as DistributedOmit<CardProps.ButtonProps | CardProps.LinkProps, 'ref'>
|
|
61
66
|
const { rootProps, nestedProps } = splitProps(remainingProps)
|
|
62
67
|
|
|
63
68
|
const [statePassedRef, state] = useCSSState(
|
|
@@ -75,7 +80,8 @@ export function Card({ ref: passedRef, as, variant, disabled, children, ...props
|
|
|
75
80
|
{ ref: statePassedRef },
|
|
76
81
|
)
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
// The union of every HTML tag name is too complex a type for JSX
|
|
84
|
+
const Element = (as ?? 'div') as ElementType
|
|
79
85
|
const uniqueId = useId()
|
|
80
86
|
|
|
81
87
|
return (
|
|
@@ -11,11 +11,13 @@ import { ClusterMeta } from './Cluster.meta.ts'
|
|
|
11
11
|
|
|
12
12
|
export declare namespace ClusterProps {
|
|
13
13
|
interface ButtonProps
|
|
14
|
-
extends EDSComponentProps<typeof ClusterMeta>, MaybeButtonOrLinkProps.ButtonProps {}
|
|
14
|
+
extends EDSComponentProps<typeof ClusterMeta>, Omit<MaybeButtonOrLinkProps.ButtonProps, 'as'> {}
|
|
15
15
|
interface LinkProps
|
|
16
|
-
extends EDSComponentProps<typeof ClusterMeta>, MaybeButtonOrLinkProps.LinkProps {}
|
|
16
|
+
extends EDSComponentProps<typeof ClusterMeta>, Omit<MaybeButtonOrLinkProps.LinkProps, 'as'> {}
|
|
17
17
|
interface OtherElementProps
|
|
18
|
-
extends
|
|
18
|
+
extends
|
|
19
|
+
EDSComponentProps<typeof ClusterMeta>,
|
|
20
|
+
Omit<MaybeButtonOrLinkProps.OtherElementProps, 'as'> {}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export type ClusterProps =
|