@edgeandnode/eds-react 1.0.0 → 1.0.2
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 +21 -4
- package/dist/EDSContext.d.ts +3 -0
- 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 +1 -1
- package/dist/EDSProvider.d.ts.map +1 -1
- package/dist/EDSProvider.js +15 -8
- package/dist/EDSProvider.js.map +1 -1
- 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 +4 -3
- 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/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/LocalTheme.d.ts +16 -0
- package/dist/components/LocalTheme.d.ts.map +1 -0
- package/dist/components/LocalTheme.js +22 -0
- package/dist/components/LocalTheme.js.map +1 -0
- package/dist/components/Menu.parts.d.ts.map +1 -1
- package/dist/components/Menu.parts.js +6 -4
- package/dist/components/Menu.parts.js.map +1 -1
- package/dist/components/Modal.parts.d.ts.map +1 -1
- package/dist/components/Modal.parts.js +21 -10
- package/dist/components/Modal.parts.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 +4 -4
- package/dist/components/Select.parts.d.ts.map +1 -1
- package/dist/components/Select.parts.js +2 -1
- 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.js +2 -2
- 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/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 +16 -1
- package/dist/hooks/useEDS.d.ts.map +1 -1
- package/dist/hooks/useEDS.js +18 -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/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 +9 -8
- package/scripts/update-skill.cjs +18 -5
- package/skill/SKILL.md +17 -10
- package/skill/references/tokens.md +26 -20
- package/src/EDSContext.ts +9 -0
- package/src/EDSProvider.tsx +34 -9
- package/src/components/Avatar.tsx +3 -2
- package/src/components/Breadcrumbs.parts.tsx +31 -15
- package/src/components/Button.tsx +3 -2
- 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/LocalTheme.tsx +39 -0
- package/src/components/Menu.parts.tsx +5 -3
- package/src/components/Modal.parts.tsx +20 -10
- package/src/components/SegmentedControl.parts.tsx +3 -8
- package/src/components/Select.parts.tsx +6 -5
- package/src/components/TabSet.parts.tsx +3 -5
- package/src/components/Table.parts.tsx +3 -3
- package/src/components/ThemeSwitcher.tsx +2 -2
- 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/index.ts +1 -0
- package/src/hooks/useCSSProp.ts +1 -1
- package/src/hooks/useEDS.ts +18 -2
- package/src/hooks/useStyleObserver.ts +2 -1
- package/src/icons/CalendarDynamicIcon.tsx +1 -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 -22525
- 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
|
@@ -50,15 +50,6 @@ import { TabSetContext } from './TabSet.context.ts'
|
|
|
50
50
|
import { TabSet, type TabSetTabProps } from './TabSet.tsx'
|
|
51
51
|
import { ToggleButton } from './ToggleButton.tsx'
|
|
52
52
|
|
|
53
|
-
// Can't import `@shikijs/vscode-textmate`'s `FontStyle` enum due to `verbatimModuleSyntax`
|
|
54
|
-
const FontStyle = {
|
|
55
|
-
NotSet: -1,
|
|
56
|
-
None: 0,
|
|
57
|
-
Italic: 1,
|
|
58
|
-
Bold: 2,
|
|
59
|
-
Underline: 4,
|
|
60
|
-
} as const
|
|
61
|
-
|
|
62
53
|
const THEMES = {
|
|
63
54
|
dark: 'night-owl',
|
|
64
55
|
light: 'min-light',
|
|
@@ -166,7 +157,7 @@ export function CodeBlockRoot({
|
|
|
166
157
|
children,
|
|
167
158
|
...props
|
|
168
159
|
}: CodeBlockProps) {
|
|
169
|
-
|
|
160
|
+
useEDS()
|
|
170
161
|
const tabsContext = useContext(CodeBlockTabsContext)
|
|
171
162
|
const hasAncestorTabs = tabsContext !== null
|
|
172
163
|
const hasAncestorHeader = useContext(CodeBlockHeaderContext) !== false
|
|
@@ -209,7 +200,6 @@ export function CodeBlockRoot({
|
|
|
209
200
|
highlights,
|
|
210
201
|
secrets,
|
|
211
202
|
language,
|
|
212
|
-
activeTheme,
|
|
213
203
|
highlighter,
|
|
214
204
|
hasAncestorHeader,
|
|
215
205
|
})
|
|
@@ -363,7 +353,18 @@ export function CodeBlockRoot({
|
|
|
363
353
|
<span className="select-none"> </span>
|
|
364
354
|
) : (
|
|
365
355
|
<span
|
|
356
|
+
/**
|
|
357
|
+
* `light-dark()` only works for colors, so the font styles use the `dark:`
|
|
358
|
+
* variant (and highlighted tokens need `i:font-bold` to override the weight).
|
|
359
|
+
*/
|
|
366
360
|
className="
|
|
361
|
+
*:font-(--shiki-light-font-weight)
|
|
362
|
+
*:text-[light-dark(var(--shiki-light),var(--shiki-dark))]
|
|
363
|
+
*:[font-style:var(--shiki-light-font-style)]
|
|
364
|
+
*:[text-decoration:var(--shiki-light-text-decoration)]
|
|
365
|
+
dark:*:font-(--shiki-dark-font-weight)
|
|
366
|
+
dark:*:[font-style:var(--shiki-dark-font-style)]
|
|
367
|
+
dark:*:[text-decoration:var(--shiki-dark-text-decoration)]
|
|
367
368
|
@prop-wrap-true/code-block:wrap-anywhere
|
|
368
369
|
@prop-wrap-true/code-block:whitespace-pre-wrap
|
|
369
370
|
"
|
|
@@ -379,23 +380,18 @@ export function CodeBlockRoot({
|
|
|
379
380
|
data-whitespace={chunk.content.trim() === '' || undefined}
|
|
380
381
|
data-highlighted={chunk.highlighted || undefined}
|
|
381
382
|
className={cn(
|
|
382
|
-
chunk.
|
|
383
|
-
chunk.fontStyle === FontStyle.Italic ? 'italic' : undefined,
|
|
384
|
-
chunk.fontStyle === FontStyle.Underline ? 'underline' : undefined,
|
|
385
|
-
chunk.highlighted ? `z-10 bg-code-highlighted py-1 font-bold
|
|
383
|
+
chunk.highlighted ? `z-10 bg-code-highlighted py-1
|
|
386
384
|
not-has-[+[data-highlighted]]:-me-1
|
|
387
385
|
not-has-[+[data-highlighted]]:rounded-e-4
|
|
388
386
|
not-has-[+[data-highlighted]]:pe-1
|
|
389
387
|
not-[[data-highlighted]+*]:-ms-1
|
|
390
388
|
not-[[data-highlighted]+*]:rounded-s-4
|
|
391
|
-
not-[[data-highlighted]+*]:ps-1
|
|
389
|
+
not-[[data-highlighted]+*]:ps-1
|
|
390
|
+
i:font-bold` : 'z-20',
|
|
392
391
|
// Prevent wrapping between the indentation and the code
|
|
393
392
|
chunkIndex === 1 ? '[[data-whitespace]+*]:break-all' : undefined,
|
|
394
393
|
)}
|
|
395
|
-
style={
|
|
396
|
-
color: chunk.color,
|
|
397
|
-
backgroundColor: chunk.bgColor,
|
|
398
|
-
}}
|
|
394
|
+
style={chunk.htmlStyle}
|
|
399
395
|
>
|
|
400
396
|
{chunkContent}
|
|
401
397
|
</span>
|
|
@@ -423,7 +419,6 @@ function useCodeLines({
|
|
|
423
419
|
highlights,
|
|
424
420
|
secrets,
|
|
425
421
|
language,
|
|
426
|
-
activeTheme,
|
|
427
422
|
highlighter,
|
|
428
423
|
hasAncestorHeader,
|
|
429
424
|
}: {
|
|
@@ -432,7 +427,6 @@ function useCodeLines({
|
|
|
432
427
|
highlights: string[]
|
|
433
428
|
secrets: string[]
|
|
434
429
|
language: Language
|
|
435
|
-
activeTheme: 'dark' | 'light'
|
|
436
430
|
highlighter: Highlighter
|
|
437
431
|
hasAncestorHeader: boolean
|
|
438
432
|
}): { lines: CodeLine[]; codeToCopy: string } {
|
|
@@ -461,7 +455,9 @@ function useCodeLines({
|
|
|
461
455
|
if (hasAncestorHeader) return []
|
|
462
456
|
|
|
463
457
|
const { tokens } = highlighter.codeToTokens(code, {
|
|
464
|
-
|
|
458
|
+
// Both themes' styles become CSS variables, so tokens follow the local color scheme
|
|
459
|
+
themes: THEMES,
|
|
460
|
+
defaultColor: false,
|
|
465
461
|
lang: LANGUAGES.includes(language) ? language : 'text',
|
|
466
462
|
})
|
|
467
463
|
|
|
@@ -526,7 +522,7 @@ function useCodeLines({
|
|
|
526
522
|
|
|
527
523
|
return { chunks, style: lineStyle }
|
|
528
524
|
})
|
|
529
|
-
}, [highlighter, passedCode,
|
|
525
|
+
}, [highlighter, passedCode, language, highlights, secrets, diff])
|
|
530
526
|
|
|
531
527
|
return { lines, codeToCopy }
|
|
532
528
|
}
|
|
@@ -8,7 +8,8 @@ import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
|
8
8
|
import { CopyInteractiveIcon } from '@edgeandnode/eds-react/icons'
|
|
9
9
|
|
|
10
10
|
import { useEDS } from '../hooks/index.ts'
|
|
11
|
-
import { cn
|
|
11
|
+
import { cn } from '../utils/index.ts'
|
|
12
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
12
13
|
import { Button, type ButtonProps } from './Button.tsx'
|
|
13
14
|
import type { CopyButtonMeta } from './CopyButton.meta.ts'
|
|
14
15
|
import { Tooltip } from './Tooltip.tsx'
|
|
@@ -49,7 +50,7 @@ export function CopyButton({
|
|
|
49
50
|
|
|
50
51
|
const hasChildren = children != null
|
|
51
52
|
const copyIcon = <CopyInteractiveIcon copied={copied} />
|
|
52
|
-
const tooltipProps =
|
|
53
|
+
const tooltipProps = getTooltipProps(tooltip)
|
|
53
54
|
const [tooltipOpen, setTooltipOpen] = useState(false)
|
|
54
55
|
|
|
55
56
|
return (
|
package/src/components/Icon.tsx
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
createElement,
|
|
5
|
-
isValidElement,
|
|
6
|
-
type ComponentProps,
|
|
7
|
-
type ComponentType,
|
|
8
|
-
type ReactElement,
|
|
9
|
-
} from 'react'
|
|
3
|
+
import { createElement, type ComponentProps, type ComponentType, type ReactElement } from 'react'
|
|
10
4
|
import { mergeProps } from '@base-ui/react'
|
|
11
5
|
|
|
12
6
|
import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
13
7
|
|
|
14
8
|
import { useEDS } from '../hooks/index.ts'
|
|
15
|
-
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
9
|
+
import { cn, getCSSPropsAttributes, isComponentType } from '../utils/index.ts'
|
|
16
10
|
import { IconMeta } from './Icon.meta.ts'
|
|
17
11
|
import { useTooltipCollector } from './Tooltip.tsx'
|
|
18
12
|
|
|
@@ -37,7 +31,7 @@ export interface IconPropsWithSrc extends IconProps {
|
|
|
37
31
|
export function Icon({ src, alt, size, color, ...props }: IconPropsWithSrc) {
|
|
38
32
|
useEDS()
|
|
39
33
|
|
|
40
|
-
const iconContent =
|
|
34
|
+
const iconContent = isComponentType(src) ? createElement(src) : src
|
|
41
35
|
const collectTooltipContent = useTooltipCollector()
|
|
42
36
|
|
|
43
37
|
if (collectTooltipContent && alt) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps, ElementType } from 'react'
|
|
4
|
+
import { mergeProps } from '@base-ui/react'
|
|
5
|
+
|
|
6
|
+
import { LocalThemeContext, THEME_CLASSES, type ResolvedTheme } from '../EDSContext.ts'
|
|
7
|
+
|
|
8
|
+
export interface LocalThemeProps extends ComponentProps<'div'> {
|
|
9
|
+
/** @default 'div' */
|
|
10
|
+
as?: ElementType | undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function LocalTheme({
|
|
14
|
+
theme,
|
|
15
|
+
as: Element = 'div',
|
|
16
|
+
...props
|
|
17
|
+
}: LocalThemeProps & { theme: ResolvedTheme }) {
|
|
18
|
+
return (
|
|
19
|
+
<LocalThemeContext.Provider value={theme}>
|
|
20
|
+
<Element {...mergeProps({ className: THEME_CLASSES[theme] }, props)} />
|
|
21
|
+
</LocalThemeContext.Provider>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Renders its content in the dark theme, whatever the page's theme is. Components that need to know
|
|
27
|
+
* the theme in JavaScript can read it with `useEDS().localTheme`.
|
|
28
|
+
*/
|
|
29
|
+
export function Dark(props: LocalThemeProps) {
|
|
30
|
+
return <LocalTheme theme="dark" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders its content in the light theme, whatever the page's theme is. Components that need to
|
|
35
|
+
* know the theme in JavaScript can read it with `useEDS().localTheme`.
|
|
36
|
+
*/
|
|
37
|
+
export function Light(props: LocalThemeProps) {
|
|
38
|
+
return <LocalTheme theme="light" {...props} />
|
|
39
|
+
}
|
|
@@ -166,7 +166,7 @@ export function MenuRoot<T extends OptionValue>({
|
|
|
166
166
|
children,
|
|
167
167
|
...props
|
|
168
168
|
}: MenuProps<T>) {
|
|
169
|
-
const { dirProps } = useEDS()
|
|
169
|
+
const { dirProps, localThemeClass } = useEDS()
|
|
170
170
|
|
|
171
171
|
const isSubmenu = useContext(MenuGroupContext) !== null
|
|
172
172
|
const isContextMenu = triggerMode === 'right-click' && !isSubmenu
|
|
@@ -307,6 +307,8 @@ export function MenuRoot<T extends OptionValue>({
|
|
|
307
307
|
? document.body
|
|
308
308
|
: undefined
|
|
309
309
|
}
|
|
310
|
+
// Inline menus are portalled in place, so they already inherit the local theme
|
|
311
|
+
className={hasTrigger ? localThemeClass : undefined}
|
|
310
312
|
>
|
|
311
313
|
<Menu.Positioner
|
|
312
314
|
ref={passedRef}
|
|
@@ -535,8 +537,8 @@ function MenuInlineFallback({
|
|
|
535
537
|
{...mergeProps(
|
|
536
538
|
{
|
|
537
539
|
className: cn(`
|
|
538
|
-
pointer-events-none absolute inset-[anchor(inside)] size-auto! [position-anchor:--eds-menu]
|
|
539
|
-
not-open:hidden
|
|
540
|
+
pointer-events-none! absolute! inset-[anchor(inside)]! z-auto! m-0! size-auto! [position-anchor:--eds-menu]!
|
|
541
|
+
not-open:hidden!
|
|
540
542
|
u:rounded-12
|
|
541
543
|
`),
|
|
542
544
|
},
|
|
@@ -17,6 +17,7 @@ import { Dialog } from '@base-ui/react/dialog'
|
|
|
17
17
|
import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
18
18
|
import { ArrowLeftIcon, XIcon } from '@edgeandnode/eds-react/icons'
|
|
19
19
|
|
|
20
|
+
import { LocalThemeContext } from '../EDSContext.ts'
|
|
20
21
|
import { useControlled, useCSSPropsPolyfill, useEDS } from '../hooks/index.ts'
|
|
21
22
|
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
22
23
|
import { Portal } from './base/Portal.tsx'
|
|
@@ -275,7 +276,13 @@ export const ModalRoot = <
|
|
|
275
276
|
hidden={undefined}
|
|
276
277
|
/>
|
|
277
278
|
</Portal>
|
|
278
|
-
<Dialog.Portal
|
|
279
|
+
<Dialog.Portal
|
|
280
|
+
/**
|
|
281
|
+
* Modals cover the whole page, so they're rendered in `<body>`, even when opened from
|
|
282
|
+
* inside another portal (e.g. a `Menu`), where Base UI would put them otherwise.
|
|
283
|
+
*/
|
|
284
|
+
container={typeof document !== 'undefined' ? document.body : undefined}
|
|
285
|
+
>
|
|
279
286
|
<div
|
|
280
287
|
ref={passedRef}
|
|
281
288
|
/**
|
|
@@ -355,15 +362,18 @@ export const ModalRoot = <
|
|
|
355
362
|
has-nested/modal-slot:p-0
|
|
356
363
|
"
|
|
357
364
|
>
|
|
358
|
-
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
{/* Modals cover the whole page, so they use its theme rather than the local one */}
|
|
366
|
+
<LocalThemeContext.Provider value={undefined}>
|
|
367
|
+
{typeof children === 'function'
|
|
368
|
+
? children({
|
|
369
|
+
open,
|
|
370
|
+
setOpen,
|
|
371
|
+
closeModal: () => setOpen(false),
|
|
372
|
+
currentStep,
|
|
373
|
+
setCurrentStep,
|
|
374
|
+
})
|
|
375
|
+
: children}
|
|
376
|
+
</LocalThemeContext.Provider>
|
|
367
377
|
</div>
|
|
368
378
|
</Dialog.Popup>
|
|
369
379
|
</div>
|
|
@@ -22,13 +22,8 @@ import {
|
|
|
22
22
|
useCSSState,
|
|
23
23
|
useEDS,
|
|
24
24
|
} from '../hooks/index.ts'
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
getCSSPropsAttributes,
|
|
28
|
-
isReactNode,
|
|
29
|
-
splitProps,
|
|
30
|
-
type OptionValue,
|
|
31
|
-
} from '../utils/index.ts'
|
|
25
|
+
import { cn, getCSSPropsAttributes, splitProps, type OptionValue } from '../utils/index.ts'
|
|
26
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
32
27
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
33
28
|
import { SegmentedControlMeta, SegmentedControlOptionMeta } from './SegmentedControl.meta.ts'
|
|
34
29
|
import { Tooltip, type TooltipProps } from './Tooltip.tsx'
|
|
@@ -193,7 +188,7 @@ export function SegmentedControlOption<T extends OptionValue>({
|
|
|
193
188
|
}
|
|
194
189
|
|
|
195
190
|
return (
|
|
196
|
-
<Tooltip {...(
|
|
191
|
+
<Tooltip {...getTooltipProps(tooltip)}>
|
|
197
192
|
{/**
|
|
198
193
|
* This wrapper is necessary to ensure this component returns a single element (which some Tailwind
|
|
199
194
|
* classes might assume) because `Radio.Root` renders a `<input type="radio">` as a sibling of the
|
|
@@ -18,6 +18,7 @@ import { CaretDownInteractiveIcon } from '@edgeandnode/eds-react/icons'
|
|
|
18
18
|
|
|
19
19
|
import { useControlled } from '../hooks/index.ts'
|
|
20
20
|
import { cn, reactNodeToString, splitProps, type OptionValue } from '../utils/index.ts'
|
|
21
|
+
import { resolveLazy } from '../utils/internal/index.ts'
|
|
21
22
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
22
23
|
import { Field, type FieldProps } from './base/Field.tsx'
|
|
23
24
|
import { Render, type RenderFnHandlers, type RenderFnProps } from './base/Render.tsx'
|
|
@@ -292,10 +293,10 @@ export declare namespace SelectProps {
|
|
|
292
293
|
options?: never
|
|
293
294
|
/**
|
|
294
295
|
* `Select.Option` elements (fragments / arrays are fine — `flattenChildren` flattens them).
|
|
295
|
-
* Wrapping options here (e.g., `<Tooltip><Select.Option/></Tooltip>`) is unsupported:
|
|
296
|
-
*
|
|
297
|
-
* appear in `optionsList`. Use the data API for that — its `children` render fn is
|
|
298
|
-
* `Select`, so wrapping is free.
|
|
296
|
+
* Wrapping options here (e.g., `<Tooltip><Select.Option/></Tooltip>`) is unsupported: options
|
|
297
|
+
* are identified by their type, which only matches direct children, so a wrapped option
|
|
298
|
+
* wouldn't appear in `optionsList`. Use the data API for that — its `children` render fn is
|
|
299
|
+
* opaque to `Select`, so wrapping is free.
|
|
299
300
|
*/
|
|
300
301
|
children?: ReactNode
|
|
301
302
|
}
|
|
@@ -727,7 +728,7 @@ function useSelectOptions<T extends OptionValue>({
|
|
|
727
728
|
const flatChildren = flattenChildren(children as ReactNode)
|
|
728
729
|
let autoValueCounter = 0
|
|
729
730
|
augmentedChildren = flatChildren.flatMap((child) => {
|
|
730
|
-
if (isValidElement(child) && child.type === SelectOption) {
|
|
731
|
+
if (isValidElement(child) && resolveLazy(child.type) === SelectOption) {
|
|
731
732
|
const childProps = child.props as SelectOptionProps<T>
|
|
732
733
|
const hasExplicitValue = childProps.value !== undefined
|
|
733
734
|
const effectiveValue = (hasExplicitValue ? childProps.value : autoValueCounter) as T
|
|
@@ -15,7 +15,8 @@ import flattenChildren from 'react-keyed-flatten-children'
|
|
|
15
15
|
import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
16
16
|
|
|
17
17
|
import { useControlled, useCSSPropsPolyfill, useCSSState, useEDS } from '../hooks/index.ts'
|
|
18
|
-
import { cn, getCSSPropsAttributes
|
|
18
|
+
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
19
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
19
20
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
20
21
|
import { ButtonOrLink, type ButtonOrLinkProps } from './base/ButtonOrLink.tsx'
|
|
21
22
|
import {
|
|
@@ -270,10 +271,7 @@ export function TabSetTab({
|
|
|
270
271
|
tabRenderProps,
|
|
271
272
|
)}
|
|
272
273
|
render={(buttonOrLinkRenderProps, { Element, elementProps }) => (
|
|
273
|
-
<Tooltip
|
|
274
|
-
{...(isReactNode(tooltip) && tooltip !== undefined ? { content: tooltip } : tooltip)}
|
|
275
|
-
triggerProps={buttonOrLinkRenderProps}
|
|
276
|
-
>
|
|
274
|
+
<Tooltip {...getTooltipProps(tooltip)} triggerProps={buttonOrLinkRenderProps}>
|
|
277
275
|
<Element {...elementProps}>
|
|
278
276
|
{addonBefore ? <TabAddon>{renderAddon(addonBefore)}</TabAddon> : null}
|
|
279
277
|
{children ? (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
Children,
|
|
4
5
|
createContext,
|
|
5
6
|
Fragment,
|
|
6
7
|
isValidElement,
|
|
@@ -753,9 +754,8 @@ export function TableCell({
|
|
|
753
754
|
) : null}
|
|
754
755
|
</span>
|
|
755
756
|
)
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
: isValidElement(children)
|
|
757
|
+
// `Children.toArray` also resolves lazy nodes (elements passed from server components)
|
|
758
|
+
const cellContentMayContainInteractiveElements = Children.toArray(children).some(isValidElement)
|
|
759
759
|
|
|
760
760
|
return (
|
|
761
761
|
<td
|
|
@@ -5,7 +5,7 @@ import { MoonIcon, SunIcon } from '@edgeandnode/eds-react/icons'
|
|
|
5
5
|
|
|
6
6
|
import type { ResolvedTheme } from '../EDSContext.ts'
|
|
7
7
|
import { useEDS } from '../hooks/index.ts'
|
|
8
|
-
import {
|
|
8
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
9
9
|
import { Menu } from './Menu.tsx'
|
|
10
10
|
import { SegmentedControl, type SegmentedControlProps } from './SegmentedControl.tsx'
|
|
11
11
|
import type { ThemeSwitcherMeta } from './ThemeSwitcher.meta.ts'
|
|
@@ -50,7 +50,7 @@ export function ThemeSwitcher({
|
|
|
50
50
|
<Tooltip
|
|
51
51
|
content={`Switch to ${activeTheme === 'dark' ? 'light' : 'dark'} theme`}
|
|
52
52
|
closeOnClick={false}
|
|
53
|
-
{...(
|
|
53
|
+
{...getTooltipProps(tooltip)}
|
|
54
54
|
triggerProps="forward"
|
|
55
55
|
>
|
|
56
56
|
<SegmentedControl<'dark' | 'light'>
|
|
@@ -75,7 +75,7 @@ export function TooltipRoot({
|
|
|
75
75
|
triggerProps: passedTriggerProps,
|
|
76
76
|
...passedProps
|
|
77
77
|
}: TooltipProps) {
|
|
78
|
-
const { dirProps } = useEDS()
|
|
78
|
+
const { dirProps, localThemeClass } = useEDS()
|
|
79
79
|
|
|
80
80
|
const ancestorTooltip = useContext(TooltipContext)
|
|
81
81
|
|
|
@@ -190,7 +190,7 @@ export function TooltipRoot({
|
|
|
190
190
|
hidden={undefined}
|
|
191
191
|
/>
|
|
192
192
|
</Portal>
|
|
193
|
-
<Tooltip.Portal>
|
|
193
|
+
<Tooltip.Portal className={localThemeClass}>
|
|
194
194
|
<Tooltip.Positioner
|
|
195
195
|
key={getReactNodeKey(content)} // Refresh the tooltip's position when the content changes (see `WithDynamicContent` story)
|
|
196
196
|
side={
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createElement, type ComponentType, type ReactNode } from 'react'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { isComponentType } from '../../utils/index.ts'
|
|
3
4
|
|
|
4
5
|
type AddonComponent = ComponentType<{
|
|
5
6
|
__addonCompatible?: undefined
|
|
@@ -10,9 +11,7 @@ type AddonComponent = ComponentType<{
|
|
|
10
11
|
export type AddonValue = ReactNode | AddonComponent
|
|
11
12
|
|
|
12
13
|
export function renderAddon(value: AddonValue): ReactNode {
|
|
13
|
-
return
|
|
14
|
-
? createElement(value)
|
|
15
|
-
: (value as ReactNode)
|
|
14
|
+
return isComponentType(value) ? createElement(value) : value
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -21,6 +21,7 @@ import { useButton } from 'react-aria'
|
|
|
21
21
|
import type { Merge } from 'type-fest'
|
|
22
22
|
|
|
23
23
|
import { cn } from '../../utils/index.ts'
|
|
24
|
+
import { resolveLazy } from '../../utils/internal/index.ts'
|
|
24
25
|
import { Render, type RenderFn, type RenderFnProps } from './Render.tsx'
|
|
25
26
|
import { ButtonOrLinkContext, useAncestorButtonOrLink } from './useAncestorButtonOrLink.ts'
|
|
26
27
|
|
|
@@ -345,7 +346,7 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps): ReactE
|
|
|
345
346
|
}
|
|
346
347
|
}
|
|
347
348
|
} else if (!disabled) {
|
|
348
|
-
linkComponent = passedLinkComponent
|
|
349
|
+
linkComponent = resolveLazy(passedLinkComponent)
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
|
package/src/components/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export { Label, type LabelProps } from './Label.tsx'
|
|
|
44
44
|
export { LabelMeta } from './Label.meta.ts'
|
|
45
45
|
export { Link, type LinkProps } from './Link.tsx'
|
|
46
46
|
export { LinkMeta } from './Link.meta.ts'
|
|
47
|
+
export { Dark, Light, type LocalThemeProps } from './LocalTheme.tsx'
|
|
47
48
|
export {
|
|
48
49
|
Menu,
|
|
49
50
|
type MenuEmptyProps,
|
package/src/hooks/useCSSProp.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type EDSComponent,
|
|
9
9
|
} from '@edgeandnode/eds-css'
|
|
10
10
|
|
|
11
|
-
import { styleScheduler } from '../utils/index.ts'
|
|
11
|
+
import { styleScheduler } from '../utils/internal/index.ts'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Returns a `ref` to set on an element, and an async function to get the current value of a CSS
|
package/src/hooks/useEDS.ts
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
2
|
|
|
3
|
-
import { EDSContext } from '../EDSContext.ts'
|
|
3
|
+
import { EDSContext, LocalThemeContext, THEME_CLASSES } from '../EDSContext.ts'
|
|
4
4
|
|
|
5
5
|
/** Throws an error if used outside of a `EDSProvider`. */
|
|
6
6
|
export function useEDS() {
|
|
7
7
|
const context = useContext(EDSContext)
|
|
8
|
+
const localTheme = useContext(LocalThemeContext)
|
|
8
9
|
|
|
9
10
|
if (context === null) {
|
|
10
11
|
throw new Error('EDS components must be wrapped in a `EDSProvider`')
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
return {
|
|
15
|
+
/** The page's theme: `'dark'`, `'light'`, or `'system'` (follows the OS preference) */
|
|
14
16
|
theme: context.theme,
|
|
15
|
-
|
|
17
|
+
/** Changes the page's theme (use `ThemeSwitcher` for the UI) */
|
|
16
18
|
setTheme: context.setTheme,
|
|
19
|
+
/** The page's theme, with `'system'` resolved to `'dark'` or `'light'` */
|
|
20
|
+
activeTheme: context.activeTheme,
|
|
21
|
+
/**
|
|
22
|
+
* The theme of the nearest `Dark` or `Light` ancestor, or `undefined` if there's none. Use
|
|
23
|
+
* `localTheme ?? activeTheme` to get the theme content is rendered in.
|
|
24
|
+
*/
|
|
25
|
+
localTheme,
|
|
26
|
+
/**
|
|
27
|
+
* The class of the nearest `Dark` or `Light` ancestor, or `undefined` if there's none. Add it
|
|
28
|
+
* to content rendered outside of that ancestor in the DOM (e.g. with `createPortal`) so it
|
|
29
|
+
* keeps its theme.
|
|
30
|
+
*/
|
|
31
|
+
localThemeClass: localTheme !== undefined ? THEME_CLASSES[localTheme] : undefined,
|
|
32
|
+
/** The `dir` and `lang` of `EDSProvider`, to spread on content rendered outside of it. */
|
|
17
33
|
dirProps: {
|
|
18
34
|
dir: context.direction,
|
|
19
35
|
lang: context.language,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useCallback, useLayoutEffect, useRef, type Ref } from 'react'
|
|
2
2
|
import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
|
|
3
3
|
|
|
4
|
-
import { styleObserver,
|
|
4
|
+
import { styleObserver, type StyleObserverRecord } from '../utils/index.ts'
|
|
5
|
+
import { styleScheduler } from '../utils/internal/index.ts'
|
|
5
6
|
import { useEffectWithRefDeps } from './useEffectWithRefDeps.ts'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -16,6 +16,7 @@ export interface CalendarDynamicIconProps extends Omit<IconProps, 'alt'> {
|
|
|
16
16
|
|
|
17
17
|
export function CalendarDynamicIcon({
|
|
18
18
|
alt,
|
|
19
|
+
// oxlint-disable-next-line react/purity -- `useState(() => new Date())` would need `'use client'`, which would cause hydration mismatches (time zone, locale) where this is rendered by a server component
|
|
19
20
|
date = new Date(),
|
|
20
21
|
...props
|
|
21
22
|
}: CalendarDynamicIconProps) {
|
|
@@ -2,6 +2,7 @@ import { Children, isValidElement, type ReactNode } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { getKey } from '@edgeandnode/eds-utils'
|
|
4
4
|
|
|
5
|
+
import { resolveLazy } from './internal/index.ts'
|
|
5
6
|
import { reactNodeToString } from './reactNodeToString.ts'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -12,18 +13,33 @@ import { reactNodeToString } from './reactNodeToString.ts'
|
|
|
12
13
|
* @returns A string key that uniquely identifies the node structure and content.
|
|
13
14
|
*/
|
|
14
15
|
export function getReactNodeKey(reactNode: ReactNode): string {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
try {
|
|
17
|
+
const keys =
|
|
18
|
+
Children.map(reactNode, (child) => {
|
|
19
|
+
if (!child || typeof child === 'boolean') {
|
|
20
|
+
return 'empty'
|
|
21
|
+
}
|
|
22
|
+
if (!isValidElement(child)) {
|
|
23
|
+
return getKey(child)
|
|
24
|
+
}
|
|
25
|
+
if (child.key !== null) {
|
|
26
|
+
return `key:${child.key}`
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Key the type by the component it resolves to, so that identical content created by a
|
|
30
|
+
* server component gets the same key in every server response (each of which has new lazy
|
|
31
|
+
* types)
|
|
32
|
+
*/
|
|
33
|
+
return `${getKey(resolveLazy(child.type))}:${reactNodeToString(child)}`
|
|
34
|
+
}) ?? []
|
|
35
|
+
return keys.length > 0 ? keys.join('|') : 'empty'
|
|
36
|
+
} catch (error) {
|
|
37
|
+
/**
|
|
38
|
+
* Resolving a lazy value (e.g. from a server component) suspends (throws a thenable) while it's
|
|
39
|
+
* still loading, which isn't possible in effects, where this is also called. Fall back to the
|
|
40
|
+
* node's identity then, but let any other error through.
|
|
41
|
+
*/
|
|
42
|
+
if (typeof error !== 'object' || error === null || !('then' in error)) throw error
|
|
43
|
+
return getKey(reactNode)
|
|
44
|
+
}
|
|
29
45
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -3,12 +3,12 @@ export { getAssetUrl } from './getAssetUrl.ts'
|
|
|
3
3
|
export { getCSSPropsAttributes } from './getCSSPropsAttributes.ts'
|
|
4
4
|
export { getReactNodeKey } from './getReactNodeKey.ts'
|
|
5
5
|
export { InlineCounter } from './InlineCounter.tsx'
|
|
6
|
+
export { isComponentType } from './isComponentType.ts'
|
|
6
7
|
export { isReactNode } from './isReactNode.ts'
|
|
7
8
|
export { reactNodeToString } from './reactNodeToString.ts'
|
|
8
9
|
export { RenderCount } from './RenderCount.tsx'
|
|
9
10
|
export { setInputValue } from './setInputValue.ts'
|
|
10
11
|
export { splitProps } from './splitProps.ts'
|
|
11
12
|
export { styleObserver, type StyleObserverRecord } from './styleObserver.ts'
|
|
12
|
-
export { styleScheduler } from './styleScheduler.ts'
|
|
13
13
|
export { trimReactNode } from './trimReactNode.tsx'
|
|
14
14
|
export type { OptionValue, Stringifiable } from './types.ts'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TooltipProps } from '../../components/Tooltip.tsx'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a component's `tooltip` prop, which accepts either the tooltip's content or its props.
|
|
5
|
+
* Checks for props (a plain object that isn't a React node) rather than for content, so that any
|
|
6
|
+
* kind of node is treated as content, including ones that `isReactNode` doesn't recognize (e.g.
|
|
7
|
+
* portals or promises).
|
|
8
|
+
*/
|
|
9
|
+
export function getTooltipProps(
|
|
10
|
+
tooltip: TooltipProps['content'] | Omit<TooltipProps, 'children'>,
|
|
11
|
+
): Omit<TooltipProps, 'children'> {
|
|
12
|
+
if (tooltip === undefined) return {}
|
|
13
|
+
return isTooltipProps(tooltip) ? tooltip : { content: tooltip }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isTooltipProps(
|
|
17
|
+
tooltip: Exclude<TooltipProps['content'], undefined> | Omit<TooltipProps, 'children'>,
|
|
18
|
+
): tooltip is Omit<TooltipProps, 'children'> {
|
|
19
|
+
return (
|
|
20
|
+
typeof tooltip === 'object' &&
|
|
21
|
+
tooltip !== null &&
|
|
22
|
+
!('$$typeof' in tooltip) &&
|
|
23
|
+
[Object.prototype, null].includes(Object.getPrototypeOf(tooltip))
|
|
24
|
+
)
|
|
25
|
+
}
|