@sanity/ui 5.0.0-alpha.1 → 5.0.0-alpha.10

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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/install.js +2 -1
  3. package/dist/index.d.ts +362 -76
  4. package/dist/index.js +420 -158
  5. package/dist/index.js.map +1 -1
  6. package/dist/polyfills.d.ts +1 -0
  7. package/dist/polyfills.js +5 -0
  8. package/dist/polyfills.js.map +1 -0
  9. package/dist/styles.css +1 -1
  10. package/package.json +9 -3
  11. package/src/cli/install.ts +2 -1
  12. package/src/components/badge/Badge.tsx +53 -0
  13. package/src/components/badge/badge.css +6 -0
  14. package/src/components/badge/badge.props.ts +31 -0
  15. package/src/components/box/Box.tsx +6 -1
  16. package/src/components/box/box.props.ts +7 -2
  17. package/src/components/button/button.props.ts +27 -9
  18. package/src/components/card/card.props.ts +6 -2
  19. package/src/components/checkbox/Checkbox.tsx +3 -2
  20. package/src/components/checkbox/checkbox.props.ts +11 -3
  21. package/src/components/code/code.props.ts +10 -3
  22. package/src/components/container/container.props.ts +6 -2
  23. package/src/components/dialog/Dialog.tsx +139 -0
  24. package/src/components/dialog/dialog.css +49 -0
  25. package/src/components/dialog/dialog.props.ts +45 -0
  26. package/src/components/eyebrow/Eyebrow.tsx +1 -1
  27. package/src/components/eyebrow/eyebrow.props.ts +6 -2
  28. package/src/components/flex/Flex.tsx +6 -1
  29. package/src/components/flex/flex.props.ts +6 -2
  30. package/src/components/grid/Grid.tsx +6 -1
  31. package/src/components/grid/grid.props.ts +6 -2
  32. package/src/components/h-stack/hStack.props.ts +3 -1
  33. package/src/components/heading/Heading.tsx +1 -1
  34. package/src/components/heading/heading.props.ts +8 -2
  35. package/src/components/icon/icon.props.ts +6 -2
  36. package/src/components/icon-button/iconButton.props.ts +7 -2
  37. package/src/components/index.css +3 -0
  38. package/src/components/indicator/indicator.props.ts +6 -2
  39. package/src/components/indicator-stack/indicatorStack.props.ts +3 -1
  40. package/src/components/inline/inline.props.ts +6 -2
  41. package/src/components/label/label.props.ts +7 -1
  42. package/src/components/link/link.props.ts +4 -4
  43. package/src/components/list/list.props.ts +27 -9
  44. package/src/components/popover/Popover.tsx +85 -0
  45. package/src/components/popover/popover.css +21 -0
  46. package/src/components/popover/popover.props.ts +38 -0
  47. package/src/components/press-area/pressArea.props.ts +3 -1
  48. package/src/components/radio/Radio.tsx +1 -1
  49. package/src/components/radio/radio.props.ts +7 -2
  50. package/src/components/skip-to-content/skipToContent.props.ts +9 -3
  51. package/src/components/spinner/Spinner.tsx +1 -1
  52. package/src/components/spinner/spinner.props.ts +3 -0
  53. package/src/components/switch/Switch.tsx +1 -1
  54. package/src/components/switch/switch.props.ts +7 -2
  55. package/src/components/text/Text.tsx +1 -1
  56. package/src/components/text/text.props.ts +6 -2
  57. package/src/components/tooltip/Tooltip.tsx +59 -55
  58. package/src/components/tooltip/tooltip.css +19 -40
  59. package/src/components/tooltip/tooltip.props.ts +24 -8
  60. package/src/components/tooltip-group/TooltipGroup.tsx +2 -5
  61. package/src/components/tooltip-group/tooltipGroup.props.ts +3 -1
  62. package/src/components/v-stack/vStack.props.ts +3 -1
  63. package/src/components/visually-hidden/visuallyHidden.props.ts +6 -2
  64. package/src/css/classes/generic/display.css +8 -8
  65. package/src/css/classes/generic/placement.css +31 -0
  66. package/src/css/classes/local/animation.css +4 -0
  67. package/src/css/classes/local/index.css +2 -0
  68. package/src/css/classes/local/top-anchor.css +4 -0
  69. package/src/css/global/reset.css +20 -18
  70. package/src/css/tokens/semantic.css +4 -0
  71. package/src/hooks/useIsClient.ts +10 -0
  72. package/src/index.ts +5 -0
  73. package/src/polyfills.ts +15 -0
  74. package/src/utils/mergeTriggerProps.ts +66 -0
  75. package/src/utils/renderPortal.tsx +14 -0
  76. package/src/version.ts +2 -2
@@ -41,7 +41,7 @@ export function Heading<T extends ElementType = 'h2'>({
41
41
 
42
42
  return (
43
43
  <Component
44
- className={clsx(headingClassName, 'sui-overflow-hidden', className)}
44
+ className={clsx(headingClassName, props.truncate && 'sui-overflow-hidden', className)}
45
45
  style={style}
46
46
  data-ui="Heading"
47
47
  {...rest}
@@ -5,9 +5,15 @@ import type {Responsive} from '../../types/Responsive'
5
5
 
6
6
  /** @public */
7
7
  export interface HeadingProps extends TypographyProps {
8
- /** Element to render */
8
+ /**
9
+ * Semantic heading element to render.
10
+ * @remarks Always set this explicitly.
11
+ */
9
12
  as?: HeadingTag
10
- /** CSS **font-size** property */
13
+ /**
14
+ * Sets font size.
15
+ * @remarks Independent of `as`.
16
+ */
11
17
  size?: Responsive<HeadingSize>
12
18
  }
13
19
 
@@ -10,9 +10,13 @@ import type {Responsive} from '../../types/Responsive'
10
10
  /** @public */
11
11
  export interface IconProps
12
12
  extends ComponentProps<'svg'>, Pick<TypographyProps, 'muted'>, MarginProps, ToneProps {
13
- /** Icon to render */
13
+ /**
14
+ * The icon component to render.
15
+ */
14
16
  icon: React.ComponentType<SVGProps<SVGSVGElement>>
15
- /** CSS **font-size** property */
17
+ /**
18
+ * Sets icon size.
19
+ */
16
20
  size?: Responsive<IconSize>
17
21
  }
18
22
 
@@ -9,9 +9,14 @@ export interface IconButtonProps<T extends React.ElementType = 'button'> extends
9
9
  ButtonProps<T>,
10
10
  'as' | 'density' | 'level' | 'loading' | 'tone'
11
11
  > {
12
- /** Button label */
12
+ /**
13
+ * Accessible name for the button.
14
+ * @remarks Because IconButtons have no visible text, this is required.
15
+ */
13
16
  'aria-label': string
14
- /** Icon */
17
+ /**
18
+ * Icon to render.
19
+ */
15
20
  'icon': React.ComponentType<SVGProps<SVGSVGElement>>
16
21
  }
17
22
 
@@ -1,9 +1,11 @@
1
+ @import './badge/badge.css';
1
2
  @import './box/box.css';
2
3
  @import './button/button.css';
3
4
  @import './card/card.css';
4
5
  @import './checkbox/checkbox.css';
5
6
  @import './code/code.css';
6
7
  @import './container/container.css';
8
+ @import './dialog/dialog.css';
7
9
  @import './eyebrow/eyebrow.css';
8
10
  @import './flex/flex.css';
9
11
  @import './grid/grid.css';
@@ -15,6 +17,7 @@
15
17
  @import './label/label.css';
16
18
  @import './link/link.css';
17
19
  @import './list/list.css';
20
+ @import './popover/popover.css';
18
21
  @import './press-area/press-area.css';
19
22
  @import './radio/radio.css';
20
23
  @import './spinner/spinner.css';
@@ -3,9 +3,13 @@ import {type PropDef} from '../../types/PropDef'
3
3
 
4
4
  /** @beta */
5
5
  export interface IndicatorProps<T extends React.ElementType = 'span'> extends ToneProps {
6
- /** Element to render */
6
+ /**
7
+ * HTML element to render.
8
+ */
7
9
  as?: T
8
- /** Label for aria-label attribute */
10
+ /**
11
+ * Accessible label.
12
+ */
9
13
  label?: string
10
14
  }
11
15
 
@@ -2,7 +2,9 @@ import {type PropDef} from '../../types/PropDef'
2
2
 
3
3
  /** @public */
4
4
  export interface IndicatorStackProps<T extends React.ElementType = 'div'> {
5
- /** Element to render */
5
+ /**
6
+ * HTML element to render.
7
+ */
6
8
  as?: T
7
9
  }
8
10
 
@@ -7,9 +7,13 @@ import {SPACE, type SpaceInherit} from '../../types/Space'
7
7
  /** @deprecated Use HStack component instead */
8
8
  /** @public */
9
9
  export interface InlineProps<T extends React.ElementType = 'div'> extends PaddingProps {
10
- /** Element to render */
10
+ /**
11
+ * HTML element or component to render.
12
+ */
11
13
  as?: T
12
- /** CSS **gap** property */
14
+ /**
15
+ * Space between children.
16
+ */
13
17
  gap?: Responsive<SpaceInherit>
14
18
  }
15
19
 
@@ -3,8 +3,14 @@ import {type PropDef} from '../../types/PropDef'
3
3
 
4
4
  /** @beta */
5
5
  export interface LabelProps extends React.ComponentProps<'label'>, MarginProps {
6
- /** Element to render */
6
+ /**
7
+ * Renders the label in a muted color.
8
+ */
7
9
  disabled?: boolean
10
+ /**
11
+ * Renders the label in the error color.
12
+ * @remarks Use when the linked input has a validation error.
13
+ */
8
14
  error?: boolean
9
15
  }
10
16
 
@@ -2,12 +2,12 @@ import {type PropDef} from '../../types/PropDef'
2
2
 
3
3
  /** @beta */
4
4
  export interface LinkProps extends React.ComponentProps<'a'> {
5
- /** If true, sets `target="_blank"` and `rel="noopener noreferrer"` */
5
+ /**
6
+ * Sets the link to `target="_blank"` and `rel="noopener noreferrer"`.
7
+ */
6
8
  openInNewTab?: boolean
7
9
  /**
8
- * If true, sets `text-decoration: underline`
9
- *
10
- * @defaultValue true
10
+ * Underlines the link.
11
11
  */
12
12
  underlined?: boolean
13
13
  }
@@ -11,7 +11,9 @@ import type {Responsive} from '../../types/Responsive'
11
11
 
12
12
  /** @beta */
13
13
  export interface ListProps<T extends ListTag = 'ul'> extends Pick<GapProps, 'gap'> {
14
- /** Element to render */
14
+ /**
15
+ * HTML element to render.
16
+ */
15
17
  as?: T
16
18
  }
17
19
 
@@ -24,11 +26,17 @@ export const listProps: Record<string, PropDef> = {
24
26
 
25
27
  /** @beta */
26
28
  export interface ListItemProps extends React.ComponentProps<'li'> {
27
- /** Composite prop for setting padding and gap */
29
+ /**
30
+ * Sets horizontal padding, gap, and minimum row height together.
31
+ */
28
32
  density?: Responsive<Density>
29
- /** Starting slot */
33
+ /**
34
+ * Slot at the left edge of the row.
35
+ */
30
36
  start?: React.ReactNode
31
- /** Ending slot */
37
+ /**
38
+ * Slot at the right edge of the row.
39
+ */
32
40
  end?: React.ReactNode
33
41
  }
34
42
 
@@ -73,9 +81,13 @@ export const listItemProps: Record<string, PropDef> = {
73
81
 
74
82
  /** @beta */
75
83
  export interface ListButtonItemProps<T extends React.ElementType = 'button'> extends ListItemProps {
76
- /** Element to render */
84
+ /**
85
+ * Element or component to render for the press target.
86
+ */
77
87
  as?: InteractiveAs<T>
78
- /** Selected state */
88
+ /**
89
+ * Marks the row as the current selection.
90
+ */
79
91
  selected?: boolean
80
92
  }
81
93
 
@@ -91,11 +103,17 @@ export const listButtonItemProps: Record<string, PropDef> = {
91
103
 
92
104
  /** @beta */
93
105
  export interface ListItemTextProps<T extends React.ElementType = 'div'> {
94
- /** Element to render */
106
+ /**
107
+ * HTML element to render.
108
+ */
95
109
  as?: T
96
- /** Title text */
110
+ /**
111
+ * Main label.
112
+ */
97
113
  title?: React.ReactNode
98
- /** Subtitle text */
114
+ /**
115
+ * Text below the title.
116
+ */
99
117
  subtitle?: React.ReactNode
100
118
  }
101
119
 
@@ -0,0 +1,85 @@
1
+ import clsx from 'clsx'
2
+ import {Activity, cloneElement, useId, useState, type ToggleEvent} from 'react'
3
+
4
+ import {useIsClient} from '../../hooks/useIsClient'
5
+ import {getProps} from '../../utils/getProps'
6
+ import {mergeTriggerProps} from '../../utils/mergeTriggerProps'
7
+ import {renderPortal} from '../../utils/renderPortal'
8
+ import {suffixClassName} from '../../utils/suffixClassName'
9
+ import {type PopoverProps, popoverProps} from './popover.props'
10
+
11
+ const popoverClassName = suffixClassName('sui-PopoverContent')
12
+
13
+ function PopoverRoot({
14
+ placement = 'bottom',
15
+ ...props
16
+ }: PopoverProps & {
17
+ triggerProps?: Record<string, unknown>
18
+ }) {
19
+ const {
20
+ children,
21
+ className,
22
+ style,
23
+ id: idProp,
24
+ anchorName,
25
+ content,
26
+ portal,
27
+ triggerProps: forwardedTriggerProps,
28
+ ...rest
29
+ } = getProps({placement, ...props}, popoverProps)
30
+ const reactId = useId()
31
+ const id = idProp || reactId
32
+ const [open, setOpen] = useState(false)
33
+ const isClient = useIsClient()
34
+
35
+ const handleToggle = (e: ToggleEvent) => {
36
+ setOpen(e.newState === 'open')
37
+ }
38
+
39
+ const triggerProps = {
40
+ popoverTarget: id,
41
+ style: {anchorName: `--anchor-${anchorName || id}`},
42
+ }
43
+
44
+ const trigger = children.type.forwardsTriggerProps
45
+ ? cloneElement(children, {triggerProps})
46
+ : cloneElement(children, mergeTriggerProps(children.props, forwardedTriggerProps, triggerProps))
47
+
48
+ return (
49
+ <>
50
+ {trigger}
51
+
52
+ {renderPortal(
53
+ <Activity mode={open ? 'visible' : 'hidden'}>
54
+ <div
55
+ className={clsx(
56
+ popoverClassName,
57
+ 'sui-px2 sui-py1 sui-radius2 sui-position-fixed sui-shadow2',
58
+ className,
59
+ )}
60
+ style={{
61
+ ...style,
62
+ positionAnchor: `--anchor-${anchorName || id}`,
63
+ }}
64
+ data-ui="Popover"
65
+ popover="auto"
66
+ id={id}
67
+ onToggle={handleToggle}
68
+ {...rest}
69
+ >
70
+ {content}
71
+ </div>
72
+ </Activity>,
73
+ isClient,
74
+ portal,
75
+ )}
76
+ </>
77
+ )
78
+ }
79
+
80
+ /** @beta */
81
+ export const Popover = Object.assign(PopoverRoot, {
82
+ forwardsTriggerProps: true,
83
+ })
84
+
85
+ export type {PopoverProps}
@@ -0,0 +1,21 @@
1
+ .sui-PopoverContent {
2
+ visibility: hidden;
3
+ opacity: 0;
4
+ scale: 0.9;
5
+ transition: none;
6
+ }
7
+
8
+ .sui-PopoverContent:popover-open {
9
+ visibility: visible;
10
+ opacity: 1;
11
+ scale: 1;
12
+ transition: var(--fade-in-transition);
13
+ }
14
+
15
+ @starting-style {
16
+ .sui-PopoverContent:popover-open {
17
+ visibility: hidden;
18
+ opacity: 0;
19
+ scale: 0.9;
20
+ }
21
+ }
@@ -0,0 +1,38 @@
1
+ import {type PlacementProps, placementProps} from '../../props/placement'
2
+ import {type PropDef} from '../../types/PropDef'
3
+
4
+ /** @beta */
5
+ export interface PopoverProps
6
+ extends Omit<React.ComponentProps<'div'>, 'children' | 'content'>, PlacementProps {
7
+ /**
8
+ * Shared anchor identifier.
9
+ * @remarks Set the same value on a Tooltip and a Popover to point both at one trigger.
10
+ */
11
+ anchorName?: React.ReactNode
12
+ /**
13
+ * The trigger element.
14
+ * @remarks Popover clones it and attaches the `popovertarget` attribute that opens the floating layer.
15
+ */
16
+ children: React.ReactElement<Record<string, unknown>>
17
+ /**
18
+ * The floating content.
19
+ */
20
+ content?: React.ReactNode
21
+ /**
22
+ * Renders the content into `document.body` through a React portal instead of inline.
23
+ */
24
+ portal?: boolean
25
+ }
26
+
27
+ export const popoverProps: Record<string, PropDef> = {
28
+ anchorName: {
29
+ type: 'string',
30
+ },
31
+ content: {
32
+ type: 'string',
33
+ },
34
+ portal: {
35
+ type: 'boolean',
36
+ },
37
+ ...placementProps,
38
+ }
@@ -3,7 +3,9 @@ import {type PropDef} from '../../types/PropDef'
3
3
 
4
4
  /** @public */
5
5
  export interface PressAreaProps<T extends React.ElementType = 'button'> {
6
- /** Element to render */
6
+ /**
7
+ * Element or component to render.
8
+ */
7
9
  as?: InteractiveAs<T>
8
10
  }
9
11
 
@@ -17,7 +17,7 @@ export function Radio(props: RadioProps) {
17
17
 
18
18
  return (
19
19
  <Label
20
- className={clsx(radioClassName, className)}
20
+ className={clsx(radioClassName, 'sui-position-relative', className)}
21
21
  style={style}
22
22
  data-ui="Radio"
23
23
  disabled={disabled}
@@ -3,9 +3,14 @@ import {type PropDef} from '../../types/PropDef'
3
3
 
4
4
  /** @beta */
5
5
  export interface RadioProps extends React.ComponentProps<'input'>, MarginProps {
6
- /** Error state */
6
+ /**
7
+ * Applies error styling to the radio mark.
8
+ * @remarks Use when form validation fails. Pair with a visible error message. The prop is visual only.
9
+ */
7
10
  error?: boolean
8
- /** Input label */
11
+ /**
12
+ * Visible label next to the radio.
13
+ */
9
14
  label: React.ReactNode
10
15
  }
11
16
 
@@ -3,12 +3,18 @@ import {type PropDef} from '../../types/PropDef'
3
3
  /** @beta */
4
4
  export interface SkipToContentProps extends React.ComponentProps<'a'> {
5
5
  /**
6
- * Anchor href. The target element should be focusable or have `tabindex="-1"`.
6
+ * Same-page fragment to jump to.
7
+ * @remarks Note: The hash character # is required for proper linking.
7
8
  */
8
9
  href: string
9
- /** Visible/announced label */
10
+ /**
11
+ * Visible and announced text.
12
+ */
10
13
  label: string
11
- /** Use label instead */
14
+ /**
15
+ * Not accepted.
16
+ * @remarks Pass text via `label`.
17
+ */
12
18
  children?: never
13
19
  }
14
20
 
@@ -1,4 +1,4 @@
1
- import {SpinnerIcon} from '@sanity/icons'
1
+ import {SpinnerIcon} from '@sanity/icons/Spinner'
2
2
  import clsx from 'clsx'
3
3
 
4
4
  import {getProps} from '../../utils/getProps'
@@ -4,6 +4,9 @@ import type {Responsive} from '../../types/Responsive'
4
4
 
5
5
  /** @public */
6
6
  export interface SpinnerProps extends React.ComponentProps<'svg'> {
7
+ /**
8
+ * Size of the spinner icon.
9
+ */
7
10
  size?: Responsive<IconSize>
8
11
  }
9
12
 
@@ -17,7 +17,7 @@ export function Switch(props: SwitchProps) {
17
17
 
18
18
  return (
19
19
  <Label
20
- className={clsx(switchClassName, className)}
20
+ className={clsx(switchClassName, 'sui-position-relative', className)}
21
21
  style={style}
22
22
  data-ui="Switch"
23
23
  disabled={props.disabled}
@@ -3,9 +3,14 @@ import {type PropDef} from '../../types/PropDef'
3
3
 
4
4
  /** @beta */
5
5
  export interface SwitchProps extends React.ComponentProps<'input'>, MarginProps {
6
- /** Error state */
6
+ /**
7
+ * Applies error styling to the switch track.
8
+ * @remarks Use when form validation fails. Pair with a visible error message. The prop is visual only.
9
+ */
7
10
  error?: boolean
8
- /** Input label */
11
+ /**
12
+ * Visible label next to the switch.
13
+ */
9
14
  label: React.ReactNode
10
15
  }
11
16
 
@@ -40,7 +40,7 @@ export function Text<T extends ElementType = 'span'>({
40
40
 
41
41
  return (
42
42
  <Component
43
- className={clsx(textClassName, 'sui-overflow-hidden', className)}
43
+ className={clsx(textClassName, props.truncate && 'sui-overflow-hidden', className)}
44
44
  style={style}
45
45
  data-ui="Text"
46
46
  {...rest}
@@ -5,9 +5,13 @@ import {TEXT_SIZE, type TextSize} from '../../types/Text'
5
5
 
6
6
  /** @public */
7
7
  export interface TextProps<T extends React.ElementType = 'span'> extends TypographyProps {
8
- /** Element to render */
8
+ /**
9
+ * HTML element to render.
10
+ */
9
11
  as?: T
10
- /** CSS **font-size** property */
12
+ /**
13
+ * Sets font size.
14
+ */
11
15
  size?: Responsive<TextSize>
12
16
  }
13
17
 
@@ -1,93 +1,97 @@
1
1
  import clsx from 'clsx'
2
- import {cloneElement, useEffect, useId, useState} from 'react'
2
+ import {cloneElement, useId, useState, type ToggleEvent} from 'react'
3
3
 
4
+ import {useIsClient} from '../../hooks/useIsClient'
4
5
  import {getProps} from '../../utils/getProps'
6
+ import {mergeTriggerProps} from '../../utils/mergeTriggerProps'
7
+ import {renderPortal} from '../../utils/renderPortal'
5
8
  import {suffixClassName} from '../../utils/suffixClassName'
6
- import {Box} from '../box/Box'
7
9
  import {type TooltipProps, tooltipProps} from './tooltip.props'
8
10
 
9
11
  const tooltipClassName = suffixClassName('sui-Tooltip')
10
- const tooltipDismissedClassName = suffixClassName('sui-Tooltip-Dismissed')
11
12
 
12
- /** @public */
13
- export function Tooltip({placement = 'bottom', ...props}: TooltipProps) {
13
+ function TooltipRoot({
14
+ placement = 'bottom',
15
+ ...props
16
+ }: TooltipProps & {
17
+ triggerProps?: Record<string, unknown>
18
+ }) {
14
19
  const {
15
20
  children,
16
21
  className,
17
22
  style,
18
- disabled,
19
23
  id: idProp,
20
- text,
24
+ anchorName,
25
+ content,
26
+ portal,
27
+ triggerProps: forwardedTriggerProps,
21
28
  ...rest
22
29
  } = getProps({placement, ...props}, tooltipProps)
23
30
  const reactId = useId()
24
31
  const id = idProp || reactId
25
32
  const [dismissed, setDismissed] = useState(false)
33
+ const isClient = useIsClient()
26
34
 
27
- useEffect(() => {
28
- if (dismissed) {
29
- return
30
- }
31
-
32
- const handleKeyDown = (e: KeyboardEvent) => {
33
- if (e.key === 'Escape') {
34
- setDismissed(true)
35
- }
36
- }
37
-
38
- window.addEventListener('keydown', handleKeyDown)
39
- return () => window.removeEventListener('keydown', handleKeyDown)
40
- }, [dismissed])
41
-
42
- const trigger = cloneElement(children, {
35
+ const triggerProps = {
43
36
  'aria-describedby': id,
44
- 'onMouseEnter': (e) => {
37
+ 'interestfor': id,
38
+ 'style': {anchorName: `--anchor-${anchorName || id}`},
39
+ 'onMouseLeave': () => {
45
40
  setDismissed(false)
46
- children.props.onMouseEnter?.(e)
47
41
  },
48
- 'onFocus': (e) => {
42
+ 'onBlur': () => {
49
43
  setDismissed(false)
50
- children.props.onFocus?.(e)
51
44
  },
52
- 'onClick': (e) => {
45
+ 'onClick': () => {
53
46
  setDismissed(true)
54
- children.props.onClick?.(e)
47
+ document.getElementById(id)?.hidePopover()
55
48
  },
56
- 'style': {
57
- ...children.props.style,
58
- anchorName: `--tooltip-anchor-${id}`,
59
- },
60
- })
49
+ }
50
+
51
+ const trigger = children.type.forwardsTriggerProps
52
+ ? cloneElement(children, {triggerProps})
53
+ : cloneElement(children, mergeTriggerProps(children.props, forwardedTriggerProps, triggerProps))
61
54
 
62
- if (disabled) {
63
- return children
55
+ const handleBeforeToggle = (e: ToggleEvent) => {
56
+ if (e.newState === 'open' && dismissed) {
57
+ e.preventDefault()
58
+ }
64
59
  }
65
60
 
66
61
  return (
67
62
  <>
68
63
  {trigger}
69
64
 
70
- <Box
71
- className={clsx(tooltipClassName, dismissed ? tooltipDismissedClassName : '', className)}
72
- role="tooltip"
73
- style={{
74
- ...style,
75
- positionAnchor: `--tooltip-anchor-${id}`,
76
- }}
77
- data-ui="Tooltip"
78
- id={id}
79
- paddingX={2}
80
- paddingY={1}
81
- radius={2}
82
- position="fixed"
83
- zIndex={9999}
84
- shadow={2}
85
- {...rest}
86
- >
87
- {text}
88
- </Box>
65
+ {renderPortal(
66
+ <div
67
+ className={clsx(
68
+ tooltipClassName,
69
+ 'sui-px2 sui-py1 sui-radius2 sui-position-fixed sui-shadow2',
70
+ className,
71
+ )}
72
+ style={{
73
+ ...style,
74
+ positionAnchor: `--anchor-${anchorName || id}`,
75
+ }}
76
+ data-ui="Tooltip"
77
+ role="tooltip"
78
+ popover="hint"
79
+ id={id}
80
+ onBeforeToggle={handleBeforeToggle}
81
+ {...rest}
82
+ >
83
+ {content}
84
+ </div>,
85
+ isClient,
86
+ portal,
87
+ )}
89
88
  </>
90
89
  )
91
90
  }
92
91
 
92
+ /** @beta */
93
+ export const Tooltip = Object.assign(TooltipRoot, {
94
+ forwardsTriggerProps: true,
95
+ }) as typeof TooltipRoot
96
+
93
97
  export type {TooltipProps}