@sanity/ui 5.0.0-alpha.5 → 5.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +92 -47
- package/dist/index.js +327 -209
- package/dist/index.js.map +1 -1
- package/dist/polyfills.d.ts +1 -0
- package/dist/polyfills.js +5 -0
- package/dist/polyfills.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +8 -2
- package/src/components/checkbox/Checkbox.tsx +1 -1
- package/src/components/{modal/Modal.tsx → dialog/Dialog.tsx} +33 -38
- package/src/components/dialog/dialog.css +49 -0
- package/src/components/dialog/dialog.props.ts +29 -0
- package/src/components/index.css +2 -1
- package/src/components/popover/Popover.tsx +85 -0
- package/src/components/popover/popover.css +21 -0
- package/src/components/popover/popover.props.ts +28 -0
- package/src/components/radio/Radio.tsx +1 -1
- package/src/components/switch/Switch.tsx +1 -1
- package/src/components/tooltip/Tooltip.tsx +59 -55
- package/src/components/tooltip/tooltip.css +19 -40
- package/src/components/tooltip/tooltip.props.ts +16 -8
- package/src/components/tooltip-group/TooltipGroup.tsx +2 -5
- package/src/css/classes/generic/placement.css +31 -0
- package/src/css/tokens/semantic.css +4 -0
- package/src/hooks/useIsClient.ts +10 -0
- package/src/index.ts +4 -1
- package/src/polyfills.ts +15 -0
- package/src/utils/mergeTriggerProps.ts +66 -0
- package/src/utils/renderPortal.tsx +14 -0
- package/src/version.ts +2 -2
- package/src/components/modal/modal.css +0 -49
- package/src/components/modal/modal.props.ts +0 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.7",
|
|
4
4
|
"description": "Sanity UI Library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|
|
@@ -30,11 +30,14 @@
|
|
|
30
30
|
],
|
|
31
31
|
"type": "module",
|
|
32
32
|
"sideEffects": [
|
|
33
|
-
"*.css"
|
|
33
|
+
"*.css",
|
|
34
|
+
"./src/polyfills.ts",
|
|
35
|
+
"./dist/polyfills.js"
|
|
34
36
|
],
|
|
35
37
|
"types": "./dist/index.d.ts",
|
|
36
38
|
"exports": {
|
|
37
39
|
".": "./dist/index.js",
|
|
40
|
+
"./polyfills": "./dist/polyfills.js",
|
|
38
41
|
"./package.json": "./package.json",
|
|
39
42
|
"./styles.css": {
|
|
40
43
|
"types": "./src/styles.css.d.ts",
|
|
@@ -49,6 +52,8 @@
|
|
|
49
52
|
"@sanity/icons": "^3.7.4",
|
|
50
53
|
"clsx": "^2.1.1",
|
|
51
54
|
"comment-json": "^5.0.0",
|
|
55
|
+
"dialog-closedby-polyfill": "^1.3.0",
|
|
56
|
+
"interestfor": "^1.0.8",
|
|
52
57
|
"react-refractor": "^4.0.0"
|
|
53
58
|
},
|
|
54
59
|
"devDependencies": {
|
|
@@ -59,6 +64,7 @@
|
|
|
59
64
|
"@types/postcss-import": "^14.0.3",
|
|
60
65
|
"@types/postcss-prefix-selector": "^1.16.3",
|
|
61
66
|
"@types/react": "^19.2.17",
|
|
67
|
+
"@types/react-dom": "^19.2.3",
|
|
62
68
|
"autoprefixer": "^10.4.27",
|
|
63
69
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
64
70
|
"concurrently": "^9.2.1",
|
|
@@ -29,7 +29,7 @@ export function Checkbox(props: CheckboxProps) {
|
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
31
|
<Label
|
|
32
|
-
className={clsx(checkboxClassName, className)}
|
|
32
|
+
className={clsx(checkboxClassName, 'sui-position-relative', className)}
|
|
33
33
|
style={style}
|
|
34
34
|
data-ui="Checkbox"
|
|
35
35
|
disabled={disabled}
|
|
@@ -8,33 +8,28 @@ import {Box} from '../box/Box'
|
|
|
8
8
|
import {Flex} from '../flex/Flex'
|
|
9
9
|
import {Heading} from '../heading/Heading'
|
|
10
10
|
import {IconButton} from '../icon-button/IconButton'
|
|
11
|
-
import {type
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
function
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
onClose,
|
|
25
|
-
...rest
|
|
26
|
-
} = getProps(props, modalProps)
|
|
27
|
-
|
|
28
|
-
const modalRef = useRef<HTMLDialogElement>(null)
|
|
11
|
+
import {type DialogProps, dialogProps} from './dialog.props'
|
|
12
|
+
|
|
13
|
+
const dialogClassName = suffixClassName('sui-Dialog')
|
|
14
|
+
const dialogContentClassName = suffixClassName('sui-DialogContent')
|
|
15
|
+
const dialogFooterClassName = suffixClassName('sui-DialogFooter')
|
|
16
|
+
|
|
17
|
+
function DialogRoot({open = false, size = 0, ...props}: DialogProps) {
|
|
18
|
+
const {children, className, style, header, onClose, ...rest} = getProps(
|
|
19
|
+
{size, ...props},
|
|
20
|
+
dialogProps,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
29
24
|
const headerId = useId()
|
|
30
25
|
|
|
31
|
-
const
|
|
32
|
-
|
|
26
|
+
const dialogClasses = clsx(
|
|
27
|
+
dialogClassName,
|
|
33
28
|
'sui-inset0 sui-m-auto sui-radius5 sui-shadow3 sui-p4 sui-flex-direction-column sui-gap4 sui-overflow-y-auto',
|
|
34
29
|
)
|
|
35
30
|
|
|
36
31
|
useEffect(() => {
|
|
37
|
-
const dialogElement =
|
|
32
|
+
const dialogElement = dialogRef.current
|
|
38
33
|
if (!dialogElement) return
|
|
39
34
|
|
|
40
35
|
// `open` = `open` prop
|
|
@@ -51,7 +46,7 @@ function ModalRoot(props: ModalProps) {
|
|
|
51
46
|
}
|
|
52
47
|
}
|
|
53
48
|
|
|
54
|
-
// Ensure the
|
|
49
|
+
// Ensure the dialog is closed (and thus its `open` attribute updated)
|
|
55
50
|
// when the component is unmounted.
|
|
56
51
|
return () => {
|
|
57
52
|
dialogElement.close()
|
|
@@ -61,13 +56,13 @@ function ModalRoot(props: ModalProps) {
|
|
|
61
56
|
return (
|
|
62
57
|
<dialog
|
|
63
58
|
{...rest}
|
|
64
|
-
ref={
|
|
59
|
+
ref={dialogRef}
|
|
65
60
|
closedby="any"
|
|
66
61
|
aria-labelledby={header ? headerId : undefined}
|
|
67
62
|
onClose={onClose}
|
|
68
|
-
className={clsx(
|
|
63
|
+
className={clsx(dialogClasses, className)}
|
|
69
64
|
style={style}
|
|
70
|
-
data-ui="
|
|
65
|
+
data-ui="Dialog"
|
|
71
66
|
>
|
|
72
67
|
<Flex flexShrink={0} justifyContent="space-between" alignItems="center">
|
|
73
68
|
{header ? (
|
|
@@ -81,7 +76,7 @@ function ModalRoot(props: ModalProps) {
|
|
|
81
76
|
aria-label="Close"
|
|
82
77
|
level="tertiary"
|
|
83
78
|
icon={CloseIcon}
|
|
84
|
-
onClick={() =>
|
|
79
|
+
onClick={() => dialogRef.current?.close()}
|
|
85
80
|
/>
|
|
86
81
|
</Flex>
|
|
87
82
|
{children}
|
|
@@ -89,15 +84,15 @@ function ModalRoot(props: ModalProps) {
|
|
|
89
84
|
)
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
function
|
|
87
|
+
function DialogContent(props: ComponentProps<'div'>) {
|
|
93
88
|
const {children, className, style, ...rest} = getProps(props, {})
|
|
94
89
|
|
|
95
90
|
return (
|
|
96
91
|
<Box
|
|
97
92
|
{...rest}
|
|
98
|
-
className={clsx(
|
|
93
|
+
className={clsx(dialogContentClassName, className)}
|
|
99
94
|
style={style}
|
|
100
|
-
data-ui="
|
|
95
|
+
data-ui="DialogContent"
|
|
101
96
|
flexGrow={1}
|
|
102
97
|
overflowY="auto"
|
|
103
98
|
>
|
|
@@ -106,15 +101,15 @@ function ModalContent(props: ComponentProps<'div'>) {
|
|
|
106
101
|
)
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
function
|
|
104
|
+
function DialogFooter(props: ComponentProps<'div'>) {
|
|
110
105
|
const {children, className, style, ...rest} = getProps(props, {})
|
|
111
106
|
|
|
112
107
|
return (
|
|
113
108
|
<Box
|
|
114
109
|
{...rest}
|
|
115
|
-
className={clsx(
|
|
110
|
+
className={clsx(dialogFooterClassName, className)}
|
|
116
111
|
style={style}
|
|
117
|
-
data-ui="
|
|
112
|
+
data-ui="DialogFooter"
|
|
118
113
|
flexShrink={0}
|
|
119
114
|
>
|
|
120
115
|
{children}
|
|
@@ -122,7 +117,7 @@ function ModalFooter(props: ComponentProps<'div'>) {
|
|
|
122
117
|
)
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
|
|
120
|
+
DialogRoot.displayName = 'Dialog'
|
|
126
121
|
|
|
127
122
|
/**
|
|
128
123
|
* @beta
|
|
@@ -131,14 +126,14 @@ ModalRoot.displayName = 'Modal'
|
|
|
131
126
|
* Modal dialogs make their containing documents inert, and render on the topmost
|
|
132
127
|
* layer within that containing document; they must be dimissed before the containing
|
|
133
128
|
* document and its contents become unblocked. They also trap focus within the bounds
|
|
134
|
-
* of the
|
|
129
|
+
* of the dialog element and its descendants.
|
|
135
130
|
*
|
|
136
131
|
* For more on dialogs and modality, refer to the HTML specification for the dialog element:
|
|
137
132
|
* https://html.spec.whatwg.org/dev/interactive-elements.html#the-dialog-element
|
|
138
133
|
*/
|
|
139
|
-
export const
|
|
134
|
+
export const Dialog = DialogRoot
|
|
140
135
|
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
DialogRoot.Content = DialogContent
|
|
137
|
+
DialogRoot.Footer = DialogFooter
|
|
143
138
|
|
|
144
|
-
export type {
|
|
139
|
+
export type {DialogProps}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.sui-Dialog {
|
|
2
|
+
/* Computed width is moderated by Dialog's size prop, which controls max-width */
|
|
3
|
+
width: calc(100dvw - 2rem);
|
|
4
|
+
max-height: calc(100dvh - 2rem);
|
|
5
|
+
transition:
|
|
6
|
+
display 150ms allow-discrete,
|
|
7
|
+
overlay 150ms allow-discrete,
|
|
8
|
+
opacity 150ms var(--timing-linear),
|
|
9
|
+
scale 150ms var(--timing-curve);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sui-Dialog:not([open]) {
|
|
13
|
+
opacity: 0;
|
|
14
|
+
scale: 0.9;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sui-Dialog[open] {
|
|
18
|
+
display: flex;
|
|
19
|
+
opacity: 1;
|
|
20
|
+
scale: 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sui-Dialog::backdrop {
|
|
24
|
+
--dialog-backdrop: hsl(from var(--separator-high) h s l / var(--overlay-opacity));
|
|
25
|
+
background-color: var(--dialog-backdrop);
|
|
26
|
+
transition:
|
|
27
|
+
display 150ms allow-discrete,
|
|
28
|
+
overlay 150ms allow-discrete,
|
|
29
|
+
opacity 150ms var(--timing-linear);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sui-Dialog:not([open])::backdrop {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sui-Dialog[open]::backdrop {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@starting-style {
|
|
41
|
+
.sui-Dialog[open] {
|
|
42
|
+
opacity: 0;
|
|
43
|
+
scale: 0.9;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sui-Dialog[open]::backdrop {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {CONTAINER_SIZE, type ContainerSize} from '../../types/Container'
|
|
2
|
+
import {type PropDef} from '../../types/PropDef'
|
|
3
|
+
import type {Responsive} from '../../types/Responsive'
|
|
4
|
+
|
|
5
|
+
/** @beta */
|
|
6
|
+
export interface DialogProps extends Omit<React.ComponentProps<'dialog'>, 'open'> {
|
|
7
|
+
/** Text to be displayed as the dialog's heading; optional but recommended for optimal accessibility and presentation */
|
|
8
|
+
header?: string
|
|
9
|
+
/** Used to set the value of open to false. Fires whenever the dialog is closed, either programmatically or by the user (via Esc key, clicking background, clicking the dialog's close button, or any other means.) */
|
|
10
|
+
onClose: React.ReactEventHandler<HTMLDialogElement>
|
|
11
|
+
/** Whether the dialog is open; defaults to false. */
|
|
12
|
+
open?: boolean
|
|
13
|
+
/** Max width of the dialog */
|
|
14
|
+
size?: Responsive<ContainerSize>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const dialogProps: Record<string, PropDef> = {
|
|
18
|
+
header: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
},
|
|
21
|
+
open: {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
type: 'union',
|
|
26
|
+
className: 'container',
|
|
27
|
+
values: CONTAINER_SIZE,
|
|
28
|
+
},
|
|
29
|
+
}
|
package/src/components/index.css
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
@import './checkbox/checkbox.css';
|
|
5
5
|
@import './code/code.css';
|
|
6
6
|
@import './container/container.css';
|
|
7
|
+
@import './dialog/dialog.css';
|
|
7
8
|
@import './eyebrow/eyebrow.css';
|
|
8
9
|
@import './flex/flex.css';
|
|
9
10
|
@import './grid/grid.css';
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
@import './label/label.css';
|
|
16
17
|
@import './link/link.css';
|
|
17
18
|
@import './list/list.css';
|
|
18
|
-
@import './
|
|
19
|
+
@import './popover/popover.css';
|
|
19
20
|
@import './press-area/press-area.css';
|
|
20
21
|
@import './radio/radio.css';
|
|
21
22
|
@import './spinner/spinner.css';
|
|
@@ -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,28 @@
|
|
|
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
|
+
/** Anchor name for positioning */
|
|
8
|
+
anchorName?: React.ReactNode
|
|
9
|
+
/** Focusable trigger element */
|
|
10
|
+
children: React.ReactElement<Record<string, unknown>>
|
|
11
|
+
/** Popover content */
|
|
12
|
+
content?: React.ReactNode
|
|
13
|
+
/** Render tooltip in portal */
|
|
14
|
+
portal?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const popoverProps: Record<string, PropDef> = {
|
|
18
|
+
anchorName: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
},
|
|
21
|
+
content: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
},
|
|
24
|
+
portal: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
},
|
|
27
|
+
...placementProps,
|
|
28
|
+
}
|
|
@@ -1,93 +1,97 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
-
import {cloneElement,
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
37
|
+
'interestfor': id,
|
|
38
|
+
'style': {anchorName: `--anchor-${anchorName || id}`},
|
|
39
|
+
'onMouseLeave': () => {
|
|
45
40
|
setDismissed(false)
|
|
46
|
-
children.props.onMouseEnter?.(e)
|
|
47
41
|
},
|
|
48
|
-
'
|
|
42
|
+
'onBlur': () => {
|
|
49
43
|
setDismissed(false)
|
|
50
|
-
children.props.onFocus?.(e)
|
|
51
44
|
},
|
|
52
|
-
'onClick': (
|
|
45
|
+
'onClick': () => {
|
|
53
46
|
setDismissed(true)
|
|
54
|
-
|
|
47
|
+
document.getElementById(id)?.hidePopover()
|
|
55
48
|
},
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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}
|
|
@@ -1,52 +1,31 @@
|
|
|
1
|
+
[interestfor] {
|
|
2
|
+
--tooltip-delay: var(--tooltip-delay-group, 500ms);
|
|
3
|
+
/* The interestfor polyfill looks for intereset delay variables */
|
|
4
|
+
--interest-delay-start: var(--tooltip-delay);
|
|
5
|
+
--interest-delay-end: 0ms;
|
|
6
|
+
interest-delay-start: var(--tooltip-delay);
|
|
7
|
+
interest-delay-end: 0ms;
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
.sui-Tooltip {
|
|
2
|
-
--tooltip-delay-open: var(--tooltip-delay-group, 500ms);
|
|
3
|
-
--tooltip-delay-close: var(--tooltip-delay-group, 100ms);
|
|
4
11
|
background: var(--backdrop);
|
|
12
|
+
visibility: hidden;
|
|
5
13
|
opacity: 0;
|
|
6
14
|
scale: 0.9;
|
|
7
|
-
|
|
8
|
-
transition:
|
|
9
|
-
opacity 200ms cubic-bezier(0.34, 1.2, 0.64, 1) var(--tooltip-delay-close),
|
|
10
|
-
scale 200ms cubic-bezier(0.34, 1.2, 0.64, 1) var(--tooltip-delay-close),
|
|
11
|
-
visibility 0s linear calc(var(--tooltip-delay-close) + 200ms);
|
|
15
|
+
transition: none;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
.sui-Tooltip:popover-open {
|
|
15
19
|
visibility: visible;
|
|
16
20
|
opacity: 1;
|
|
17
21
|
scale: 1;
|
|
18
|
-
transition:
|
|
19
|
-
opacity 200ms cubic-bezier(0.34, 1.2, 0.64, 1) var(--tooltip-delay-open),
|
|
20
|
-
scale 200ms cubic-bezier(0.34, 1.2, 0.64, 1) var(--tooltip-delay-open),
|
|
21
|
-
visibility 0s linear var(--tooltip-delay-open);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
[aria-describedby]:is(:hover, :focus-visible) + .sui-Tooltip-Dismissed[role="tooltip"] {
|
|
25
|
-
transition: none;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.sui-Tooltip[class*='placement-bottom'],
|
|
29
|
-
.sui-Tooltip[class*='placement-top'] {
|
|
30
|
-
margin-block: var(--space-1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.sui-Tooltip[class*='placement-left'],
|
|
34
|
-
.sui-Tooltip[class*='placement-right'] {
|
|
35
|
-
margin-inline: var(--space-1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.sui-Tooltip[class*='bottom'] {
|
|
39
|
-
transform-origin: center top;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.sui-Tooltip[class*='top'] {
|
|
43
|
-
transform-origin: center bottom;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.sui-Tooltip[class*='left'] {
|
|
47
|
-
transform-origin: right center;
|
|
22
|
+
transition: var(--fade-in-transition);
|
|
48
23
|
}
|
|
49
24
|
|
|
50
|
-
|
|
51
|
-
|
|
25
|
+
@starting-style {
|
|
26
|
+
.sui-Tooltip:popover-open {
|
|
27
|
+
visibility: hidden;
|
|
28
|
+
opacity: 0;
|
|
29
|
+
scale: 0.9;
|
|
30
|
+
}
|
|
52
31
|
}
|