@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
|
@@ -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'
|
|
@@ -113,10 +114,13 @@ export const ModalRoot = <
|
|
|
113
114
|
defaultOpen,
|
|
114
115
|
onOpenChange,
|
|
115
116
|
dismissible: passedDismissible,
|
|
116
|
-
role,
|
|
117
117
|
steps: passedSteps = 1 as Steps,
|
|
118
118
|
currentStep: controlledCurrentStep,
|
|
119
119
|
onCurrentStepChange,
|
|
120
|
+
role,
|
|
121
|
+
'aria-label': ariaLabel,
|
|
122
|
+
'aria-labelledby': ariaLabelledBy,
|
|
123
|
+
'aria-describedby': ariaDescribedBy,
|
|
120
124
|
children,
|
|
121
125
|
...props
|
|
122
126
|
}: ModalProps<Steps, Step>) => {
|
|
@@ -275,7 +279,13 @@ export const ModalRoot = <
|
|
|
275
279
|
hidden={undefined}
|
|
276
280
|
/>
|
|
277
281
|
</Portal>
|
|
278
|
-
<Dialog.Portal
|
|
282
|
+
<Dialog.Portal
|
|
283
|
+
/**
|
|
284
|
+
* Modals cover the whole page, so they're rendered in `<body>`, even when opened from
|
|
285
|
+
* inside another portal (e.g. a `Menu`), where Base UI would put them otherwise.
|
|
286
|
+
*/
|
|
287
|
+
container={typeof document !== 'undefined' ? document.body : undefined}
|
|
288
|
+
>
|
|
279
289
|
<div
|
|
280
290
|
ref={passedRef}
|
|
281
291
|
/**
|
|
@@ -318,6 +328,10 @@ export const ModalRoot = <
|
|
|
318
328
|
? 'dialog'
|
|
319
329
|
: 'alertdialog')
|
|
320
330
|
}
|
|
331
|
+
aria-label={ariaLabel}
|
|
332
|
+
// Only when passed, so Base UI's title and description wiring still applies
|
|
333
|
+
{...(ariaLabelledBy !== undefined && { 'aria-labelledby': ariaLabelledBy })}
|
|
334
|
+
{...(ariaDescribedBy !== undefined && { 'aria-describedby': ariaDescribedBy })}
|
|
321
335
|
className="
|
|
322
336
|
fixed inset-0 m-auto h-max
|
|
323
337
|
max-h-[min(max(100cqb,100vh-100cqb*9999),var(--strict-max-height))]
|
|
@@ -355,15 +369,18 @@ export const ModalRoot = <
|
|
|
355
369
|
has-nested/modal-slot:p-0
|
|
356
370
|
"
|
|
357
371
|
>
|
|
358
|
-
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
{/* Modals cover the whole page, so they use its theme rather than the local one */}
|
|
373
|
+
<LocalThemeContext.Provider value={undefined}>
|
|
374
|
+
{typeof children === 'function'
|
|
375
|
+
? children({
|
|
376
|
+
open,
|
|
377
|
+
setOpen,
|
|
378
|
+
closeModal: () => setOpen(false),
|
|
379
|
+
currentStep,
|
|
380
|
+
setCurrentStep,
|
|
381
|
+
})
|
|
382
|
+
: children}
|
|
383
|
+
</LocalThemeContext.Provider>
|
|
367
384
|
</div>
|
|
368
385
|
</Dialog.Popup>
|
|
369
386
|
</div>
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
useState,
|
|
12
12
|
useSyncExternalStore,
|
|
13
13
|
type ComponentProps,
|
|
14
|
-
type ElementType,
|
|
15
14
|
type MouseEvent,
|
|
16
15
|
type ReactNode,
|
|
17
16
|
type RefObject,
|
|
@@ -25,7 +24,12 @@ import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
|
25
24
|
import { useCSSPropsPolyfill, useFirstRender, usePrevious, useStyle } from '../hooks/index.ts'
|
|
26
25
|
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
27
26
|
import { Presence } from './base/Presence.tsx'
|
|
28
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
Render,
|
|
29
|
+
type AsOrRenderProps,
|
|
30
|
+
type HTMLElementProps,
|
|
31
|
+
type RenderProp,
|
|
32
|
+
} from './base/Render.tsx'
|
|
29
33
|
import { PaneContainerMeta, PaneMeta } from './Pane.meta.ts'
|
|
30
34
|
import { ToggleButton, type ToggleButtonProps } from './ToggleButton.tsx'
|
|
31
35
|
|
|
@@ -282,30 +286,31 @@ export function PaneContainer({
|
|
|
282
286
|
}
|
|
283
287
|
PaneContainer.displayName = 'Pane.Container'
|
|
284
288
|
|
|
285
|
-
export
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
289
|
+
export declare namespace PaneProps {
|
|
290
|
+
interface CommonProps extends HTMLElementProps, EDSComponentProps<typeof PaneMeta> {
|
|
291
|
+
/** Unique name for this pane. */
|
|
292
|
+
name: string
|
|
293
|
+
/**
|
|
294
|
+
* Whether the pane is pseudo-modal in overlay mode. When `true`, the overlay shows a backdrop,
|
|
295
|
+
* receives focus when opened, and can be closed by pressing Escape or interacting with elements
|
|
296
|
+
* covered by the backdrop.
|
|
297
|
+
*
|
|
298
|
+
* @default true
|
|
299
|
+
*/
|
|
300
|
+
overlayModal?: boolean | undefined
|
|
301
|
+
/**
|
|
302
|
+
* Whether the content should stay mounted when the pane is closed.
|
|
303
|
+
*
|
|
304
|
+
* @default true
|
|
305
|
+
*/
|
|
306
|
+
keepMounted?: boolean | undefined
|
|
307
|
+
}
|
|
304
308
|
}
|
|
305
309
|
|
|
310
|
+
export type PaneProps = PaneProps.CommonProps & AsOrRenderProps
|
|
311
|
+
|
|
306
312
|
export function PaneRoot({
|
|
307
313
|
ref: passedRef,
|
|
308
|
-
as: Element = 'div',
|
|
309
314
|
name,
|
|
310
315
|
layout,
|
|
311
316
|
side,
|
|
@@ -316,7 +321,7 @@ export function PaneRoot({
|
|
|
316
321
|
}: PaneProps) {
|
|
317
322
|
const { store, pane } = usePaneInternal(name)
|
|
318
323
|
|
|
319
|
-
const rootRef = useRef<
|
|
324
|
+
const rootRef = useRef<HTMLElement>(null)
|
|
320
325
|
const rootPassedRef = useMergedRefs(rootRef, passedRef)
|
|
321
326
|
|
|
322
327
|
// Observe `--tw-duration` to allow customizing the transition duration via CSS
|
|
@@ -379,7 +384,7 @@ export function PaneRoot({
|
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
return (
|
|
382
|
-
<
|
|
387
|
+
<Render
|
|
383
388
|
ref={rootPassedRef}
|
|
384
389
|
id={pane.id}
|
|
385
390
|
data-eds-exposed-open={open}
|
|
@@ -587,7 +592,7 @@ export function PaneRoot({
|
|
|
587
592
|
return <Fragment key={key} />
|
|
588
593
|
})}
|
|
589
594
|
</Presence>
|
|
590
|
-
</
|
|
595
|
+
</Render>
|
|
591
596
|
)
|
|
592
597
|
}
|
|
593
598
|
PaneRoot.displayName = 'Pane'
|
|
@@ -604,7 +609,7 @@ function useOverlayModalFocus({
|
|
|
604
609
|
layout,
|
|
605
610
|
overlayOpen,
|
|
606
611
|
}: {
|
|
607
|
-
rootRef: RefObject<
|
|
612
|
+
rootRef: RefObject<HTMLElement | null>
|
|
608
613
|
store: PaneStore
|
|
609
614
|
name: string
|
|
610
615
|
enabled: boolean
|
|
@@ -8,5 +8,15 @@ export const ProseMeta = createComponent('Prose', {
|
|
|
8
8
|
values: ['xsmall', 'small', 'medium'],
|
|
9
9
|
defaultValue: 'medium',
|
|
10
10
|
},
|
|
11
|
+
/**
|
|
12
|
+
* Use the serif font for headings, e.g. for editorial or marketing content.
|
|
13
|
+
*
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
serif: {
|
|
17
|
+
type: 'values',
|
|
18
|
+
values: [false, true],
|
|
19
|
+
defaultValue: false,
|
|
20
|
+
},
|
|
11
21
|
},
|
|
12
22
|
})
|
package/src/components/Prose.tsx
CHANGED
|
@@ -20,12 +20,12 @@ export interface ProseProps
|
|
|
20
20
|
children?: string | undefined
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function Prose({ ref: passedRef, size, children, ...props }: ProseProps) {
|
|
23
|
+
export function Prose({ ref: passedRef, size, serif, children, ...props }: ProseProps) {
|
|
24
24
|
useEDS()
|
|
25
25
|
|
|
26
26
|
const [cssPropsPolyfillPassedRef, cssPropsPolyfillAttributes] = useCSSPropsPolyfill(
|
|
27
27
|
ProseMeta,
|
|
28
|
-
{ size },
|
|
28
|
+
{ size, serif },
|
|
29
29
|
{ ref: passedRef },
|
|
30
30
|
)
|
|
31
31
|
|
|
@@ -34,7 +34,7 @@ export function Prose({ ref: passedRef, size, children, ...props }: ProseProps)
|
|
|
34
34
|
ref={cssPropsPolyfillPassedRef}
|
|
35
35
|
{...mergeProps(
|
|
36
36
|
{ className: cn('eds-prose u:text-muted u:text-wrap-style-pretty') },
|
|
37
|
-
getCSSPropsAttributes(ProseMeta, { size }),
|
|
37
|
+
getCSSPropsAttributes(ProseMeta, { size, serif }),
|
|
38
38
|
cssPropsPolyfillAttributes,
|
|
39
39
|
props,
|
|
40
40
|
)}
|
|
@@ -45,6 +45,7 @@ export function Prose({ ref: passedRef, size, children, ...props }: ProseProps)
|
|
|
45
45
|
@prop-size-xsmall/prose:prose-xsmall
|
|
46
46
|
@prop-size-small/prose:prose-small
|
|
47
47
|
@prop-size-medium/prose:prose-medium
|
|
48
|
+
@prop-serif-true/prose:prose-serif
|
|
48
49
|
"
|
|
49
50
|
>
|
|
50
51
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
|
|
@@ -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
|
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
createContext,
|
|
6
6
|
isValidElement,
|
|
7
7
|
useContext,
|
|
8
|
+
useState,
|
|
8
9
|
type ComponentProps,
|
|
9
10
|
type ElementType,
|
|
10
11
|
type ReactElement,
|
|
11
12
|
type ReactNode,
|
|
12
13
|
} from 'react'
|
|
13
14
|
import { mergeProps } from '@base-ui/react'
|
|
15
|
+
import { Menu as BaseUIMenu } from '@base-ui/react/menu'
|
|
14
16
|
import flattenChildren from 'react-keyed-flatten-children'
|
|
15
17
|
|
|
16
18
|
import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
@@ -18,11 +20,12 @@ import { CaretDownInteractiveIcon } from '@edgeandnode/eds-react/icons'
|
|
|
18
20
|
|
|
19
21
|
import { useControlled } from '../hooks/index.ts'
|
|
20
22
|
import { cn, reactNodeToString, splitProps, type OptionValue } from '../utils/index.ts'
|
|
23
|
+
import { resolveLazy } from '../utils/internal/index.ts'
|
|
21
24
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
22
25
|
import { Field, type FieldProps } from './base/Field.tsx'
|
|
23
26
|
import { Render, type RenderFnHandlers, type RenderFnProps } from './base/Render.tsx'
|
|
24
27
|
import type { MenuMeta } from './Menu.meta.ts'
|
|
25
|
-
import { Menu } from './Menu.tsx'
|
|
28
|
+
import { Menu, type MenuProps } from './Menu.tsx'
|
|
26
29
|
import type { SelectMeta, SelectOptionMeta } from './Select.meta.ts'
|
|
27
30
|
|
|
28
31
|
type MenuCSSProps = EDSComponentProps<typeof MenuMeta>
|
|
@@ -281,21 +284,25 @@ export declare namespace SelectProps {
|
|
|
281
284
|
/**
|
|
282
285
|
* `true` for the default filter; a function for custom filtering. Auto-enables when
|
|
283
286
|
* `searchValue` or `onSearchValueChange` is set. When truthy and `header` is unset,
|
|
284
|
-
* `Menu.Search` becomes the default header.
|
|
287
|
+
* `Menu.Search` becomes the default header. A custom `header` must include `Menu.Search`.
|
|
285
288
|
*/
|
|
286
289
|
searchable?: boolean | SelectSearchableFn<T>
|
|
287
290
|
searchValue?: string
|
|
288
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Called when the search query changes, including when it's reset after the menu closes (then
|
|
293
|
+
* `eventDetails.reason` is `'popup-close'`).
|
|
294
|
+
*/
|
|
295
|
+
onSearchValueChange?: MenuProps['onSearchValueChange']
|
|
289
296
|
}
|
|
290
297
|
|
|
291
298
|
interface DeclarativeProps {
|
|
292
299
|
options?: never
|
|
293
300
|
/**
|
|
294
301
|
* `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.
|
|
302
|
+
* Wrapping options here (e.g., `<Tooltip><Select.Option/></Tooltip>`) is unsupported: options
|
|
303
|
+
* are identified by their type, which only matches direct children, so a wrapped option
|
|
304
|
+
* wouldn't appear in `optionsList`. Use the data API for that — its `children` render fn is
|
|
305
|
+
* opaque to `Select`, so wrapping is free.
|
|
299
306
|
*/
|
|
300
307
|
children?: ReactNode
|
|
301
308
|
}
|
|
@@ -349,7 +356,7 @@ export function SelectRoot<T extends OptionValue = number>({
|
|
|
349
356
|
alignOffset,
|
|
350
357
|
matchTriggerWidth = 'at-least',
|
|
351
358
|
matchTriggerHeight,
|
|
352
|
-
header
|
|
359
|
+
header,
|
|
353
360
|
footer,
|
|
354
361
|
empty,
|
|
355
362
|
trigger: passedTrigger,
|
|
@@ -373,11 +380,12 @@ export function SelectRoot<T extends OptionValue = number>({
|
|
|
373
380
|
onValueChange as ((value: T | T[] | undefined) => void) | undefined,
|
|
374
381
|
)
|
|
375
382
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
)
|
|
383
|
+
/**
|
|
384
|
+
* Not `useControlled`, which skips a change equal to the previous one even if the parent ignored
|
|
385
|
+
* it (e.g. the reset after the menu closes), so clearing the search would then do nothing.
|
|
386
|
+
*/
|
|
387
|
+
const [uncontrolledSearchValue, setUncontrolledSearchValue] = useState('')
|
|
388
|
+
const searchValue = controlledSearchValue ?? uncontrolledSearchValue
|
|
381
389
|
|
|
382
390
|
/**
|
|
383
391
|
* `searchable` auto-enables when `searchValue` / `onSearchValueChange` is wired (matches how
|
|
@@ -423,16 +431,9 @@ export function SelectRoot<T extends OptionValue = number>({
|
|
|
423
431
|
}
|
|
424
432
|
: null
|
|
425
433
|
|
|
426
|
-
/** Default `header` to a wired `<Menu.Search>` when search is enabled and none was provided. */
|
|
427
|
-
const header =
|
|
428
|
-
passedHeader !== undefined ? (
|
|
429
|
-
passedHeader
|
|
430
|
-
) : searchEnabled ? (
|
|
431
|
-
<Menu.Search value={searchValue} onValueChange={setSearchValue} />
|
|
432
|
-
) : undefined
|
|
433
|
-
|
|
434
434
|
const renderMenuContent = () => {
|
|
435
|
-
|
|
435
|
+
// A searchable menu shows `empty` itself when no option matches
|
|
436
|
+
if (augmentedChildren.length === 0 && !searchEnabled) {
|
|
436
437
|
if (empty === undefined) return <Menu.Empty />
|
|
437
438
|
return typeof empty === 'string' ? <Menu.Empty>{empty}</Menu.Empty> : empty
|
|
438
439
|
}
|
|
@@ -582,14 +583,7 @@ export function SelectRoot<T extends OptionValue = number>({
|
|
|
582
583
|
{...radioGroupProps}
|
|
583
584
|
open={open}
|
|
584
585
|
defaultOpen={defaultOpen}
|
|
585
|
-
onOpenChange={
|
|
586
|
-
// Reset uncontrolled search on open (not close) — resetting on close would flash
|
|
587
|
-
// the unfiltered list mid close-animation. Controlled consumers own their lifecycle.
|
|
588
|
-
if (newOpen && searchEnabled && controlledSearchValue === undefined) {
|
|
589
|
-
setSearchValue('')
|
|
590
|
-
}
|
|
591
|
-
onOpenChange?.(newOpen)
|
|
592
|
-
}}
|
|
586
|
+
onOpenChange={onOpenChange}
|
|
593
587
|
side={side}
|
|
594
588
|
gap={gap}
|
|
595
589
|
align={align}
|
|
@@ -598,6 +592,13 @@ export function SelectRoot<T extends OptionValue = number>({
|
|
|
598
592
|
matchTriggerHeight={matchTriggerHeight}
|
|
599
593
|
header={header}
|
|
600
594
|
footer={footer}
|
|
595
|
+
searchable={searchEnabled && 'manual'}
|
|
596
|
+
searchValue={searchValue}
|
|
597
|
+
onSearchValueChange={(newSearchValue, eventDetails) => {
|
|
598
|
+
setUncontrolledSearchValue(newSearchValue)
|
|
599
|
+
onSearchValueChange?.(newSearchValue, eventDetails)
|
|
600
|
+
}}
|
|
601
|
+
empty={empty}
|
|
601
602
|
highlightCheckedOnOpen
|
|
602
603
|
trigger={menuTrigger}
|
|
603
604
|
>
|
|
@@ -653,6 +654,8 @@ function useSelectOptions<T extends OptionValue>({
|
|
|
653
654
|
addonBefore: ReactNode
|
|
654
655
|
addonAfter: ReactNode
|
|
655
656
|
}> = []
|
|
657
|
+
const { contains } = BaseUIMenu.useFilter()
|
|
658
|
+
const trimmedSearchValue = searchValue.trim()
|
|
656
659
|
|
|
657
660
|
/**
|
|
658
661
|
* Per-iteration helper: records the option in `optionsList` (always the full set) and returns the
|
|
@@ -667,12 +670,18 @@ function useSelectOptions<T extends OptionValue>({
|
|
|
667
670
|
renderNode: () => ReactNode,
|
|
668
671
|
): ReactNode[] => {
|
|
669
672
|
optionsList.push({ value, label, addonBefore, addonAfter })
|
|
670
|
-
if (searchEnabled) {
|
|
673
|
+
if (searchEnabled && trimmedSearchValue !== '') {
|
|
671
674
|
const stringLabel = reactNodeToString(label)
|
|
672
675
|
const passes =
|
|
673
676
|
typeof searchable === 'function'
|
|
674
|
-
? searchable({
|
|
675
|
-
|
|
677
|
+
? searchable({
|
|
678
|
+
value,
|
|
679
|
+
label: stringLabel,
|
|
680
|
+
addonBefore,
|
|
681
|
+
addonAfter,
|
|
682
|
+
searchValue: trimmedSearchValue,
|
|
683
|
+
})
|
|
684
|
+
: contains(stringLabel, trimmedSearchValue)
|
|
676
685
|
if (!passes) return []
|
|
677
686
|
}
|
|
678
687
|
return [renderNode()]
|
|
@@ -727,7 +736,7 @@ function useSelectOptions<T extends OptionValue>({
|
|
|
727
736
|
const flatChildren = flattenChildren(children as ReactNode)
|
|
728
737
|
let autoValueCounter = 0
|
|
729
738
|
augmentedChildren = flatChildren.flatMap((child) => {
|
|
730
|
-
if (isValidElement(child) && child.type === SelectOption) {
|
|
739
|
+
if (isValidElement(child) && resolveLazy(child.type) === SelectOption) {
|
|
731
740
|
const childProps = child.props as SelectOptionProps<T>
|
|
732
741
|
const hasExplicitValue = childProps.value !== undefined
|
|
733
742
|
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'
|
|
@@ -18,19 +18,16 @@ export interface ThemeSwitcherProps
|
|
|
18
18
|
Omit<SegmentedControlProps<'dark' | 'light'>, 'value' | 'defaultValue' | 'onValueChange'>,
|
|
19
19
|
EDSComponentProps<typeof ThemeSwitcherMeta> {
|
|
20
20
|
/**
|
|
21
|
-
* Defaults to a label that flips with
|
|
22
|
-
*
|
|
23
|
-
* disable.
|
|
21
|
+
* Defaults to a label that flips with the page's theme (e.g. `Switch to light theme` when it's
|
|
22
|
+
* dark). Accepts a function that receives the page's theme, resolved to `'dark'` or `'light'`,
|
|
23
|
+
* for dynamic content. Pass `null` to disable.
|
|
24
24
|
*/
|
|
25
|
-
tooltip?:
|
|
26
|
-
| ThemeSwitcherTooltip
|
|
27
|
-
| ((activeTheme: ResolvedTheme) => ThemeSwitcherTooltip)
|
|
28
|
-
| undefined
|
|
25
|
+
tooltip?: ThemeSwitcherTooltip | ((theme: ResolvedTheme) => ThemeSwitcherTooltip) | undefined
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
/**
|
|
32
29
|
* Toggles the EDS theme between light and dark. Right-click for a context menu with a "Use system
|
|
33
|
-
* appearance" toggle. Always uncontrolled — reads
|
|
30
|
+
* appearance" toggle. Always uncontrolled — reads the theme from `useEDS()` and drives
|
|
34
31
|
* `setTheme()`. `theme="system"` (or `defaultTheme="system"`) on `EDSProvider` also opts into the
|
|
35
32
|
* user's OS preference.
|
|
36
33
|
*/
|
|
@@ -39,23 +36,24 @@ export function ThemeSwitcher({
|
|
|
39
36
|
tooltip: passedTooltip,
|
|
40
37
|
...props
|
|
41
38
|
}: ThemeSwitcherProps) {
|
|
42
|
-
const { theme,
|
|
39
|
+
const { theme, setTheme, systemTheme } = useEDS()
|
|
40
|
+
const pageTheme = theme === 'system' ? systemTheme : theme
|
|
43
41
|
|
|
44
|
-
const tooltip = typeof passedTooltip === 'function' ? passedTooltip(
|
|
42
|
+
const tooltip = typeof passedTooltip === 'function' ? passedTooltip(pageTheme) : passedTooltip
|
|
45
43
|
|
|
46
44
|
return (
|
|
47
45
|
<Menu
|
|
48
46
|
triggerMode="right-click"
|
|
49
47
|
trigger={
|
|
50
48
|
<Tooltip
|
|
51
|
-
content={`Switch to ${
|
|
49
|
+
content={`Switch to ${pageTheme === 'dark' ? 'light' : 'dark'} theme`}
|
|
52
50
|
closeOnClick={false}
|
|
53
|
-
{...(
|
|
51
|
+
{...getTooltipProps(tooltip)}
|
|
54
52
|
triggerProps="forward"
|
|
55
53
|
>
|
|
56
54
|
<SegmentedControl<'dark' | 'light'>
|
|
57
55
|
aria-label={ariaLabel}
|
|
58
|
-
value={
|
|
56
|
+
value={pageTheme}
|
|
59
57
|
onValueChange={setTheme}
|
|
60
58
|
{...props}
|
|
61
59
|
>
|
|
@@ -72,7 +70,7 @@ export function ThemeSwitcher({
|
|
|
72
70
|
<Menu.Item
|
|
73
71
|
type="checkbox"
|
|
74
72
|
checked={theme === 'system'}
|
|
75
|
-
onCheckedChange={(checked) => setTheme(checked ? 'system' :
|
|
73
|
+
onCheckedChange={(checked) => setTheme(checked ? 'system' : pageTheme)}
|
|
76
74
|
>
|
|
77
75
|
Use system appearance
|
|
78
76
|
</Menu.Item>
|
|
@@ -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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { ElementType } from 'react'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ButtonOrLink,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type InternalButtonOrLinkProps,
|
|
9
9
|
type OmitInternalButtonOrLinkProps,
|
|
10
10
|
} from './ButtonOrLink.tsx'
|
|
11
|
-
import type { RenderFn } from './Render.tsx'
|
|
11
|
+
import type { HTMLElementProps, RenderFn } from './Render.tsx'
|
|
12
12
|
|
|
13
13
|
type OtherElementRenderState = {
|
|
14
14
|
category: 'other'
|
|
@@ -43,9 +43,9 @@ export declare namespace MaybeButtonOrLinkProps {
|
|
|
43
43
|
}
|
|
44
44
|
export type OtherElementProps =
|
|
45
45
|
OmitInternalButtonOrLinkProps<InternalButtonOrLinkProps.CommonProps> &
|
|
46
|
-
|
|
46
|
+
HTMLElementProps & {
|
|
47
47
|
onClick?: InternalButtonOrLinkProps.ButtonProps['onClick'] // Not technically correct, but prevents the event from being `any` when `as` is not specified
|
|
48
|
-
as?:
|
|
48
|
+
as?: keyof HTMLElementTagNameMap | undefined
|
|
49
49
|
render?: RenderFn<MaybeButtonOrLinkRenderState> | undefined
|
|
50
50
|
disabled?: undefined
|
|
51
51
|
type?: undefined
|
|
@@ -67,14 +67,17 @@ type InternalMaybeButtonOrLinkProps = MaybeButtonOrLinkProps &
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Renders a `ButtonOrLink` if one of `href` or `onClick` is passed along with no `as`. Otherwise,
|
|
70
|
-
* renders the
|
|
70
|
+
* renders the tag name passed in `as` (defaulting to `span`), so an element that isn't a button can
|
|
71
|
+
* still have an `onClick` (e.g. a `label`). Like `ButtonOrLink`'s (and unlike `Render`'s), the
|
|
72
|
+
* `render` function wraps that element instead of replacing it: it receives it as `Element`, along
|
|
73
|
+
* with the `elementProps` to spread on it.
|
|
71
74
|
*/
|
|
72
75
|
export function MaybeButtonOrLink(props: InternalMaybeButtonOrLinkProps) {
|
|
73
76
|
if ((props.href !== undefined || props.onClick !== undefined) && props.as === undefined) {
|
|
74
77
|
return <ButtonOrLink {...(props as InternalButtonOrLinkProps)} />
|
|
75
78
|
} else {
|
|
76
79
|
const {
|
|
77
|
-
as:
|
|
80
|
+
as: tagName = 'span',
|
|
78
81
|
render,
|
|
79
82
|
disabled: _disabled,
|
|
80
83
|
inline: _inline,
|
|
@@ -86,6 +89,8 @@ export function MaybeButtonOrLink(props: InternalMaybeButtonOrLinkProps) {
|
|
|
86
89
|
...otherElementProps
|
|
87
90
|
} = props
|
|
88
91
|
|
|
92
|
+
// The union of every HTML tag name is too complex a type for JSX
|
|
93
|
+
const Element = tagName as ElementType
|
|
89
94
|
const renderState: OtherElementRenderState = {
|
|
90
95
|
category: 'other',
|
|
91
96
|
role: props.role ?? 'none',
|