@sanity/ui 2.0.0-alpha.2 → 2.0.0-alpha.21
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.cjs.mjs +28 -0
- package/dist/index.d.ts +476 -12
- package/dist/index.esm.js +1326 -524
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1353 -523
- package/dist/index.js.map +1 -1
- package/package.json +6 -8
- package/src/components/autocomplete/autocomplete.tsx +6 -0
- package/src/components/dialog/constants.ts +4 -0
- package/src/components/dialog/dialog.tsx +79 -15
- package/src/components/hotkeys/hotkeys.tsx +2 -0
- package/src/components/menu/menu.tsx +2 -0
- package/src/components/menu/menuButton.tsx +2 -0
- package/src/components/menu/menuGroup.tsx +22 -23
- package/src/components/menu/menuItem.tsx +29 -35
- package/src/components/tab/tab.tsx +4 -1
- package/src/components/toast/toast.tsx +4 -0
- package/src/primitives/_selectable/style.ts +2 -1
- package/src/primitives/avatar/avatar.tsx +16 -9
- package/src/primitives/avatar/avatarCounter.tsx +3 -3
- package/src/primitives/avatar/styles.ts +1 -1
- package/src/primitives/badge/badge.tsx +7 -3
- package/src/primitives/box/box.tsx +3 -0
- package/src/primitives/button/__workshop__/sanityUploadButton.tsx +2 -1
- package/src/primitives/button/button.tsx +7 -3
- package/src/primitives/button/styles.ts +9 -2
- package/src/primitives/card/card.tsx +4 -0
- package/src/primitives/card/styles.ts +2 -0
- package/src/primitives/checkbox/checkbox.tsx +2 -0
- package/src/primitives/code/code.tsx +1 -0
- package/src/primitives/container/container.tsx +2 -0
- package/src/primitives/flex/flex.tsx +2 -0
- package/src/primitives/grid/grid.tsx +2 -0
- package/src/primitives/heading/heading.tsx +2 -0
- package/src/primitives/inline/inline.tsx +3 -0
- package/src/primitives/kbd/kbd.tsx +2 -0
- package/src/primitives/label/label.tsx +2 -0
- package/src/primitives/popover/popover.tsx +6 -1
- package/src/primitives/radio/radio.tsx +2 -0
- package/src/primitives/select/select.tsx +2 -0
- package/src/primitives/select/styles.ts +2 -0
- package/src/primitives/spinner/spinner.tsx +2 -0
- package/src/primitives/stack/stack.tsx +2 -0
- package/src/primitives/switch/switch.tsx +4 -0
- package/src/primitives/text/text.tsx +3 -0
- package/src/primitives/textArea/textArea.tsx +4 -1
- package/src/primitives/textInput/textInput.tsx +10 -1
- package/src/primitives/tooltip/__workshop__/customPortal.tsx +99 -0
- package/src/primitives/tooltip/__workshop__/index.ts +15 -0
- package/src/primitives/tooltip/__workshop__/overflowingBoundary.tsx +73 -0
- package/src/primitives/tooltip/__workshop__/resizableBoundary.tsx +85 -0
- package/src/primitives/tooltip/constants.ts +1 -0
- package/src/primitives/tooltip/tooltip.test.tsx +192 -0
- package/src/primitives/tooltip/tooltip.tsx +109 -33
- package/src/primitives/types.ts +4 -0
- package/src/styles/input/textInputStyle.ts +11 -6
- package/src/theme/_internal/__workshop__/build/story.tsx +288 -0
- package/src/theme/_internal/__workshop__/build/theme.ts +3 -0
- package/src/theme/_internal/__workshop__/index.ts +14 -0
- package/src/theme/_internal/build/buildTheme.test.ts +78 -0
- package/src/theme/_internal/build/buildTheme.ts +16 -0
- package/src/theme/_internal/build/colorTheme/buildColorTheme.test.ts +185 -0
- package/src/theme/_internal/build/colorTheme/buildColorTheme.ts +164 -0
- package/src/theme/_internal/build/colorTheme/renderColorTheme.ts +242 -0
- package/src/theme/_internal/build/colorTheme/resolveColorTokens.ts +121 -0
- package/src/theme/_internal/build/defaults/colorPalette.ts +8 -0
- package/src/theme/_internal/build/defaults/colorTokens.ts +147 -0
- package/src/theme/_internal/build/helpers.ts +65 -0
- package/src/theme/_internal/build/index.ts +1 -0
- package/src/theme/_internal/config/helpers.ts +47 -0
- package/src/theme/_internal/config/index.ts +3 -0
- package/src/theme/_internal/config/token/index.ts +2 -0
- package/src/theme/_internal/config/token/parseTokenKey.test.ts +72 -0
- package/src/theme/_internal/config/token/parseTokenKey.ts +88 -0
- package/src/theme/_internal/config/token/parseTokenValue.test.ts +45 -0
- package/src/theme/_internal/config/token/parseTokenValue.ts +123 -0
- package/src/theme/_internal/config/types.ts +137 -0
- package/src/theme/_internal/constants.ts +48 -0
- package/src/theme/_internal/helpers.ts +19 -0
- package/src/theme/_internal/index.ts +5 -0
- package/src/theme/_internal/types.ts +91 -0
- package/src/theme/index.ts +1 -0
- package/src/theme/lib/color-fns/blend/multiply.ts +2 -2
- package/src/theme/lib/color-fns/blend/screen.ts +2 -2
- package/src/theme/lib/color-fns/convert.ts +15 -1
- package/src/theme/lib/color-fns/parse.ts +7 -3
- package/src/theme/lib/color-fns/types.ts +11 -0
- package/src/theme/lib/theme/avatar.ts +2 -0
- package/src/theme/lib/theme/color/_legacy/createColorTheme.ts +7 -0
- package/src/theme/lib/theme/color/_legacy/index.ts +1 -0
- package/src/theme/lib/theme/color/_legacy/legacyColor.ts +3997 -0
- package/src/theme/studioTheme/color.ts +98 -82
- package/src/theme/studioTheme/helpers.ts +15 -1
- package/src/theme/studioTheme/theme.ts +5 -5
- package/src/theme/studioTheme/tints.ts +244 -84
- package/src/types/button.ts +5 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {Placement} from '@floating-ui/react-dom'
|
|
2
2
|
|
|
3
|
+
export const DEFAULT_TOOLTIP_PADDING = 4
|
|
3
4
|
export const DEFAULT_FALLBACK_PLACEMENTS: Record<Placement, Placement[]> = {
|
|
4
5
|
top: ['bottom', 'left', 'right'],
|
|
5
6
|
'top-start': ['bottom-start', 'left-start', 'right-start'],
|
|
@@ -6,6 +6,17 @@ import {render} from '../../../test'
|
|
|
6
6
|
import {Text, Button} from '../../primitives'
|
|
7
7
|
import {Tooltip, TooltipDelayGroupProvider} from '../tooltip'
|
|
8
8
|
|
|
9
|
+
// Fake timers using Jest
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.useFakeTimers()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// Running all pending timers and switching to real timers using Jest
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.runOnlyPendingTimers()
|
|
17
|
+
jest.useRealTimers()
|
|
18
|
+
})
|
|
19
|
+
|
|
9
20
|
describe('Tooltip', () => {
|
|
10
21
|
describe('Using same delay for open and close', () => {
|
|
11
22
|
it('should hide and show the tooltip content when hovered, with no delay', () => {
|
|
@@ -275,4 +286,185 @@ describe('Tooltip', () => {
|
|
|
275
286
|
jest.clearAllMocks()
|
|
276
287
|
})
|
|
277
288
|
})
|
|
289
|
+
|
|
290
|
+
describe('Closing the <Tooltip /> with the Escape key', () => {
|
|
291
|
+
it('Standalone tooltip closes immediately with Escape key', () => {
|
|
292
|
+
const delay = 150
|
|
293
|
+
|
|
294
|
+
jest.useFakeTimers()
|
|
295
|
+
|
|
296
|
+
render(
|
|
297
|
+
<Tooltip
|
|
298
|
+
content={<Text size={1}>{'Tooltip content'}</Text>}
|
|
299
|
+
placement={'top'}
|
|
300
|
+
delay={delay}
|
|
301
|
+
>
|
|
302
|
+
<Button mode="bleed" text="Hover me" />
|
|
303
|
+
</Tooltip>,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
const button = screen.getByText('Hover me')
|
|
307
|
+
|
|
308
|
+
// Validate tooltip content is not rendered
|
|
309
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
310
|
+
fireEvent.focus(button)
|
|
311
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
312
|
+
|
|
313
|
+
// Validate tooltip content is rendered
|
|
314
|
+
screen.getByText('Tooltip content')
|
|
315
|
+
|
|
316
|
+
act(() => {
|
|
317
|
+
fireEvent.keyDown(button, {key: 'Escape', code: 'Escape'})
|
|
318
|
+
})
|
|
319
|
+
// Validate tooltip content is not rendered anymore
|
|
320
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
321
|
+
})
|
|
322
|
+
it('With <TooltipDelayGroupProvider /> closes immediately with Escape key', () => {
|
|
323
|
+
const delay = 150
|
|
324
|
+
|
|
325
|
+
jest.useFakeTimers()
|
|
326
|
+
|
|
327
|
+
render(
|
|
328
|
+
<TooltipDelayGroupProvider delay={{close: delay}}>
|
|
329
|
+
<Tooltip
|
|
330
|
+
content={<Text size={1}>{'Tooltip content'}</Text>}
|
|
331
|
+
placement={'top'}
|
|
332
|
+
delay={{close: delay}}
|
|
333
|
+
>
|
|
334
|
+
<Button mode="bleed" text="Hover me" />
|
|
335
|
+
</Tooltip>
|
|
336
|
+
</TooltipDelayGroupProvider>,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
const button = screen.getByText('Hover me')
|
|
340
|
+
|
|
341
|
+
// Validate tooltip content is not rendered
|
|
342
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
343
|
+
fireEvent.focus(button)
|
|
344
|
+
|
|
345
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
346
|
+
|
|
347
|
+
// Validate tooltip content is rendered
|
|
348
|
+
screen.getByText('Tooltip content')
|
|
349
|
+
|
|
350
|
+
act(() => {
|
|
351
|
+
fireEvent.keyDown(button, {key: 'Escape', code: 'Escape'})
|
|
352
|
+
})
|
|
353
|
+
// Validate tooltip content is not rendered anymore
|
|
354
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
describe('Clicking the <Tooltip /> child should close the tooltip', () => {
|
|
359
|
+
it('Should close the tooltip when clicked', () => {
|
|
360
|
+
const delay = 150
|
|
361
|
+
|
|
362
|
+
render(
|
|
363
|
+
<Tooltip content={<Text size={1}>{'Tooltip content'}</Text>} delay={delay}>
|
|
364
|
+
<Button mode="bleed" text="Hover me" />
|
|
365
|
+
</Tooltip>,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
const button = screen.getByText('Hover me')
|
|
369
|
+
|
|
370
|
+
// Assertion: tooltip does not exist in the document
|
|
371
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
372
|
+
fireEvent.focus(button)
|
|
373
|
+
|
|
374
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
375
|
+
|
|
376
|
+
// Assertion: the tooltip is not visible
|
|
377
|
+
expect(screen.queryByText('Tooltip content')).toBeVisible()
|
|
378
|
+
|
|
379
|
+
act(() => fireEvent.click(button))
|
|
380
|
+
|
|
381
|
+
// Assertion: tooltip does not exist in the document
|
|
382
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('Should close the tooltip when the context menu is opened (right click)', () => {
|
|
386
|
+
const delay = 150
|
|
387
|
+
|
|
388
|
+
render(
|
|
389
|
+
<Tooltip content={<Text size={1}>{'Tooltip content'}</Text>} delay={delay}>
|
|
390
|
+
<Button mode="bleed" text="Hover me" />
|
|
391
|
+
</Tooltip>,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
const button = screen.getByText('Hover me')
|
|
395
|
+
|
|
396
|
+
// Assertion: tooltip does not exist in the document
|
|
397
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
398
|
+
fireEvent.focus(button)
|
|
399
|
+
|
|
400
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
401
|
+
|
|
402
|
+
// Assertion: the tooltip is not visible
|
|
403
|
+
expect(screen.queryByText('Tooltip content')).toBeVisible()
|
|
404
|
+
|
|
405
|
+
act(() => fireEvent.contextMenu(button))
|
|
406
|
+
|
|
407
|
+
// Assertion: tooltip does not exist in the document
|
|
408
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('Used defined events on <Tooltip /> child should fire correctly', () => {
|
|
413
|
+
const handleBlur = jest.fn()
|
|
414
|
+
const handleClick = jest.fn()
|
|
415
|
+
const handleContextMenu = jest.fn()
|
|
416
|
+
const handleFocus = jest.fn()
|
|
417
|
+
const handleMouseEnter = jest.fn()
|
|
418
|
+
const handleMouseLeave = jest.fn()
|
|
419
|
+
|
|
420
|
+
beforeEach(() => {
|
|
421
|
+
render(
|
|
422
|
+
<Tooltip content={<Text size={1}>{'Tooltip content'}</Text>}>
|
|
423
|
+
<Button
|
|
424
|
+
data-testid="btn"
|
|
425
|
+
mode="bleed"
|
|
426
|
+
onBlur={handleBlur}
|
|
427
|
+
onClick={handleClick}
|
|
428
|
+
onContextMenu={handleContextMenu}
|
|
429
|
+
onFocus={handleFocus}
|
|
430
|
+
onMouseEnter={handleMouseEnter}
|
|
431
|
+
onMouseLeave={handleMouseLeave}
|
|
432
|
+
text="Hover me"
|
|
433
|
+
/>
|
|
434
|
+
</Tooltip>,
|
|
435
|
+
)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
afterEach(() => jest.clearAllMocks())
|
|
439
|
+
|
|
440
|
+
it('should fire the onBlur event', () => {
|
|
441
|
+
fireEvent.blur(screen.getByTestId('btn'))
|
|
442
|
+
expect(handleBlur).toHaveBeenCalledTimes(1)
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should fire the onClick event', () => {
|
|
446
|
+
fireEvent.click(screen.getByTestId('btn'))
|
|
447
|
+
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it('should fire the onContextMenu event', () => {
|
|
451
|
+
fireEvent.contextMenu(screen.getByTestId('btn'))
|
|
452
|
+
expect(handleContextMenu).toHaveBeenCalledTimes(1)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('should fire the onFocus event', () => {
|
|
456
|
+
fireEvent.focus(screen.getByTestId('btn'))
|
|
457
|
+
expect(handleFocus).toHaveBeenCalledTimes(1)
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
it('should fire the onMouseEnter event', () => {
|
|
461
|
+
fireEvent.mouseEnter(screen.getByTestId('btn'))
|
|
462
|
+
expect(handleMouseEnter).toHaveBeenCalledTimes(1)
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('should fire the onMouseLeave event', () => {
|
|
466
|
+
fireEvent.mouseLeave(screen.getByTestId('btn'))
|
|
467
|
+
expect(handleMouseLeave).toHaveBeenCalledTimes(1)
|
|
468
|
+
})
|
|
469
|
+
})
|
|
278
470
|
})
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
useFloating,
|
|
9
9
|
Middleware,
|
|
10
10
|
RootBoundary,
|
|
11
|
-
size,
|
|
12
11
|
} from '@floating-ui/react-dom'
|
|
13
12
|
import {
|
|
14
13
|
cloneElement,
|
|
@@ -28,10 +27,10 @@ import {useArrayProp, useForwardedRef} from '../../hooks'
|
|
|
28
27
|
import {useDelayedState} from '../../hooks/useDelayedState'
|
|
29
28
|
import {ThemeColorSchemeKey, useTheme} from '../../theme'
|
|
30
29
|
import {Placement} from '../../types'
|
|
31
|
-
import {Layer, LayerProps, Portal, useBoundaryElement} from '../../utils'
|
|
30
|
+
import {Layer, LayerProps, Portal, useBoundaryElement, usePortal} from '../../utils'
|
|
32
31
|
import {Card} from '../card'
|
|
33
32
|
import {Delay} from '../types'
|
|
34
|
-
import {DEFAULT_FALLBACK_PLACEMENTS} from './constants'
|
|
33
|
+
import {DEFAULT_FALLBACK_PLACEMENTS, DEFAULT_TOOLTIP_PADDING} from './constants'
|
|
35
34
|
import {TooltipArrow} from './tooltipArrow'
|
|
36
35
|
import {useTooltipDelayGroup} from './tooltipDelayGroup'
|
|
37
36
|
|
|
@@ -48,6 +47,7 @@ export interface TooltipProps extends Omit<LayerProps, 'as'> {
|
|
|
48
47
|
fallbackPlacements?: Placement[]
|
|
49
48
|
padding?: number | number[]
|
|
50
49
|
placement?: Placement
|
|
50
|
+
/** Whether or not to render the tooltip in a portal element. */
|
|
51
51
|
portal?: boolean | string
|
|
52
52
|
scheme?: ThemeColorSchemeKey
|
|
53
53
|
shadow?: number | number[]
|
|
@@ -61,11 +61,14 @@ export interface TooltipProps extends Omit<LayerProps, 'as'> {
|
|
|
61
61
|
delay?: Delay
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const Root = styled(Layer)
|
|
64
|
+
const Root = styled(Layer)<{$maxWidth: number}>`
|
|
65
65
|
pointer-events: none;
|
|
66
|
+
max-width: ${({$maxWidth}) => $maxWidth}px;
|
|
66
67
|
`
|
|
67
68
|
|
|
68
69
|
/**
|
|
70
|
+
* Tooltips display information when hovering, focusing or tapping.
|
|
71
|
+
*
|
|
69
72
|
* @public
|
|
70
73
|
*/
|
|
71
74
|
export const Tooltip = forwardRef(function Tooltip(
|
|
@@ -83,7 +86,7 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
83
86
|
DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom'],
|
|
84
87
|
padding = 3,
|
|
85
88
|
placement: placementProp = 'bottom',
|
|
86
|
-
portal,
|
|
89
|
+
portal: portalProp,
|
|
87
90
|
scheme,
|
|
88
91
|
shadow = 2,
|
|
89
92
|
zOffset = theme.sanity.layer?.tooltip.zOffset,
|
|
@@ -96,6 +99,22 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
96
99
|
const arrowRef = useRef<HTMLDivElement | null>(null)
|
|
97
100
|
const rootBoundary: RootBoundary = 'viewport'
|
|
98
101
|
|
|
102
|
+
const portal = usePortal()
|
|
103
|
+
const portalElement =
|
|
104
|
+
typeof portalProp === 'string' ? portal.elements?.[portalProp] || null : portal.element
|
|
105
|
+
|
|
106
|
+
// Get the maximum tooltip width (sans tooltip padding)
|
|
107
|
+
// Tooltip width should never exceed the width of either any supplied boundary or portal element.
|
|
108
|
+
// If both portal and boundary elements are provided, use the smaller width of the two.
|
|
109
|
+
const tooltipWidth = useMemo(() => {
|
|
110
|
+
const availableWidths = [
|
|
111
|
+
...(boundaryElement ? [boundaryElement.offsetWidth] : []),
|
|
112
|
+
portalElement?.offsetWidth || document.body.offsetWidth,
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
return Math.min(...availableWidths) - DEFAULT_TOOLTIP_PADDING * 2
|
|
116
|
+
}, [boundaryElement, portalElement?.offsetWidth])
|
|
117
|
+
|
|
99
118
|
const middleware = useMemo(() => {
|
|
100
119
|
const ret: Middleware[] = []
|
|
101
120
|
|
|
@@ -104,33 +123,20 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
104
123
|
flip({
|
|
105
124
|
boundary: boundaryElement || undefined,
|
|
106
125
|
fallbackPlacements,
|
|
107
|
-
padding:
|
|
126
|
+
padding: DEFAULT_TOOLTIP_PADDING,
|
|
108
127
|
rootBoundary,
|
|
109
|
-
mainAxis: false,
|
|
110
128
|
}),
|
|
111
129
|
)
|
|
112
130
|
|
|
113
131
|
// Define distance between reference and floating element
|
|
114
132
|
ret.push(offset({mainAxis: 3}))
|
|
115
133
|
|
|
116
|
-
//
|
|
117
|
-
ret.push(
|
|
118
|
-
size({
|
|
119
|
-
apply({availableWidth, availableHeight, elements}) {
|
|
120
|
-
Object.assign(elements.floating.style, {
|
|
121
|
-
maxWidth: `${availableWidth - 4 * 2}px`, // the padding is `4px`
|
|
122
|
-
maxHeight: `${availableHeight - 4 * 2}px`, // the padding is `4px`
|
|
123
|
-
})
|
|
124
|
-
},
|
|
125
|
-
}),
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
// Shift the tooltip so its sits with the boundary eleement
|
|
134
|
+
// Shift the tooltip so its sits with the boundary element
|
|
129
135
|
ret.push(
|
|
130
136
|
shift({
|
|
131
137
|
boundary: boundaryElement || undefined,
|
|
132
138
|
rootBoundary,
|
|
133
|
-
padding:
|
|
139
|
+
padding: DEFAULT_TOOLTIP_PADDING,
|
|
134
140
|
}),
|
|
135
141
|
)
|
|
136
142
|
|
|
@@ -177,32 +183,74 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
177
183
|
const closeDelay = isInsideGroup ? delayGroupContext.closeDelay : closeDelayProp
|
|
178
184
|
|
|
179
185
|
const handleIsOpenChange = useCallback(
|
|
180
|
-
(open: boolean) => {
|
|
186
|
+
(open: boolean, immediate?: boolean) => {
|
|
181
187
|
if (isInsideGroup) {
|
|
182
188
|
// When it's inside a group, the open or close status will be handled by the group.
|
|
183
189
|
if (open) {
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
const groupedOpenDelay = immediate ? 0 : openDelay
|
|
191
|
+
|
|
192
|
+
delayGroupContext.setIsGroupActive(open, groupedOpenDelay)
|
|
193
|
+
delayGroupContext.setOpenTooltipId(tooltipId, groupedOpenDelay)
|
|
186
194
|
} else {
|
|
187
195
|
const minimumGroupDeactivateDelay = 200 // We should provide some delay to allow the user to reach the next tooltip.
|
|
188
196
|
const groupDeactivateDelay =
|
|
189
197
|
closeDelay > minimumGroupDeactivateDelay ? closeDelay : minimumGroupDeactivateDelay
|
|
190
198
|
|
|
191
199
|
delayGroupContext.setIsGroupActive(open, groupDeactivateDelay)
|
|
192
|
-
delayGroupContext.setOpenTooltipId(null, closeDelay)
|
|
200
|
+
delayGroupContext.setOpenTooltipId(null, immediate ? 0 : closeDelay)
|
|
193
201
|
}
|
|
194
202
|
} else {
|
|
203
|
+
const standaloneDelay = immediate ? 0 : open ? openDelay : closeDelay
|
|
204
|
+
|
|
195
205
|
// When it's not inside a group, the open or close status will be handled by the tooltip itself.
|
|
196
|
-
setIsOpen(open,
|
|
206
|
+
setIsOpen(open, standaloneDelay)
|
|
197
207
|
}
|
|
198
208
|
},
|
|
199
209
|
[isInsideGroup, delayGroupContext, openDelay, tooltipId, closeDelay, setIsOpen],
|
|
200
210
|
)
|
|
201
211
|
|
|
202
|
-
const handleBlur = useCallback(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
212
|
+
const handleBlur = useCallback(
|
|
213
|
+
(e: FocusEvent) => {
|
|
214
|
+
handleIsOpenChange(false)
|
|
215
|
+
childProp?.props?.onBlur?.(e)
|
|
216
|
+
},
|
|
217
|
+
[childProp?.props, handleIsOpenChange],
|
|
218
|
+
)
|
|
219
|
+
const handleClick = useCallback(
|
|
220
|
+
(e: MouseEvent) => {
|
|
221
|
+
handleIsOpenChange(false, true), [handleIsOpenChange]
|
|
222
|
+
childProp?.props.onClick?.(e)
|
|
223
|
+
},
|
|
224
|
+
[childProp?.props, handleIsOpenChange],
|
|
225
|
+
)
|
|
226
|
+
const handleContextMenu = useCallback(
|
|
227
|
+
(e: MouseEvent) => {
|
|
228
|
+
handleIsOpenChange(false, true), [handleIsOpenChange]
|
|
229
|
+
childProp?.props.onContextMenu?.(e)
|
|
230
|
+
},
|
|
231
|
+
[childProp?.props, handleIsOpenChange],
|
|
232
|
+
)
|
|
233
|
+
const handleFocus = useCallback(
|
|
234
|
+
(e: FocusEvent) => {
|
|
235
|
+
handleIsOpenChange(true)
|
|
236
|
+
childProp?.props?.onFocus?.(e)
|
|
237
|
+
},
|
|
238
|
+
[childProp?.props, handleIsOpenChange],
|
|
239
|
+
)
|
|
240
|
+
const handleMouseEnter = useCallback(
|
|
241
|
+
(e: MouseEvent) => {
|
|
242
|
+
handleIsOpenChange(true)
|
|
243
|
+
childProp?.props?.onMouseEnter?.(e)
|
|
244
|
+
},
|
|
245
|
+
[childProp?.props, handleIsOpenChange],
|
|
246
|
+
)
|
|
247
|
+
const handleMouseLeave = useCallback(
|
|
248
|
+
(e: MouseEvent) => {
|
|
249
|
+
handleIsOpenChange(false)
|
|
250
|
+
childProp?.props?.onMouseLeave?.(e)
|
|
251
|
+
},
|
|
252
|
+
[childProp?.props, handleIsOpenChange],
|
|
253
|
+
)
|
|
206
254
|
|
|
207
255
|
// Detect whether the mouse is moving outside of the reference element. This is sometimes
|
|
208
256
|
// necessary, because the tooltip might not always close as it should (e.g. when clicking
|
|
@@ -243,6 +291,22 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
243
291
|
// Update reference
|
|
244
292
|
useEffect(() => refs.setReference(referenceElement), [referenceElement, refs])
|
|
245
293
|
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
// If the user clicks on escape key, close the tooltip.
|
|
296
|
+
if (!showTooltip) return
|
|
297
|
+
|
|
298
|
+
function handleWindowKeyDown(event: KeyboardEvent) {
|
|
299
|
+
if (event.key === 'Escape') {
|
|
300
|
+
handleIsOpenChange(false, true)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
window.addEventListener('keydown', handleWindowKeyDown)
|
|
305
|
+
|
|
306
|
+
return () => {
|
|
307
|
+
window.removeEventListener('keydown', handleWindowKeyDown)
|
|
308
|
+
}
|
|
309
|
+
}, [handleIsOpenChange, showTooltip])
|
|
246
310
|
const setArrow = useCallback(
|
|
247
311
|
(arrowEl: HTMLDivElement | null) => {
|
|
248
312
|
arrowRef.current = arrowEl
|
|
@@ -283,9 +347,20 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
283
347
|
onFocus: handleFocus,
|
|
284
348
|
onMouseEnter: handleMouseEnter,
|
|
285
349
|
onMouseLeave: handleMouseLeave,
|
|
350
|
+
onClick: handleClick,
|
|
351
|
+
onContextMenu: handleContextMenu,
|
|
286
352
|
ref: setReference,
|
|
287
353
|
})
|
|
288
|
-
}, [
|
|
354
|
+
}, [
|
|
355
|
+
childProp,
|
|
356
|
+
handleBlur,
|
|
357
|
+
handleClick,
|
|
358
|
+
handleContextMenu,
|
|
359
|
+
handleFocus,
|
|
360
|
+
handleMouseEnter,
|
|
361
|
+
handleMouseLeave,
|
|
362
|
+
setReference,
|
|
363
|
+
])
|
|
289
364
|
|
|
290
365
|
if (!child) return <></>
|
|
291
366
|
|
|
@@ -298,6 +373,7 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
298
373
|
ref={setFloating}
|
|
299
374
|
style={floatingStyles}
|
|
300
375
|
zOffset={zOffset}
|
|
376
|
+
$maxWidth={tooltipWidth}
|
|
301
377
|
>
|
|
302
378
|
<Card
|
|
303
379
|
data-ui="Tooltip__card"
|
|
@@ -319,8 +395,8 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
319
395
|
|
|
320
396
|
{showTooltip && (
|
|
321
397
|
<>
|
|
322
|
-
{
|
|
323
|
-
<Portal __unstable_name={typeof
|
|
398
|
+
{portalProp ? (
|
|
399
|
+
<Portal __unstable_name={typeof portalProp === 'string' ? portalProp : undefined}>
|
|
324
400
|
{root}
|
|
325
401
|
</Portal>
|
|
326
402
|
) : (
|
package/src/primitives/types.ts
CHANGED
|
@@ -55,6 +55,7 @@ export interface ResponsiveFlexProps {
|
|
|
55
55
|
* @public
|
|
56
56
|
*/
|
|
57
57
|
export interface ResponsiveFlexItemProps {
|
|
58
|
+
/** Sets the flex CSS attribute. The property is responsive. */
|
|
58
59
|
flex?: FlexValue | FlexValue[]
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -88,8 +89,11 @@ export interface ResponsiveGridItemProps {
|
|
|
88
89
|
* @public
|
|
89
90
|
*/
|
|
90
91
|
export interface ResponsiveMarginProps {
|
|
92
|
+
/** Applies margins to all sides. The property is responsive. */
|
|
91
93
|
margin?: number | number[]
|
|
94
|
+
/** Applies margins to the left and right sides. The property is responsive. */
|
|
92
95
|
marginX?: number | number[]
|
|
96
|
+
/** Applies margins to the top and bottom sides. The property is responsive. */
|
|
93
97
|
marginY?: number | number[]
|
|
94
98
|
marginTop?: number | number[]
|
|
95
99
|
marginRight?: number | number[]
|
|
@@ -24,6 +24,7 @@ export interface TextInputRepresentationStyleProps {
|
|
|
24
24
|
$hasSuffix?: boolean
|
|
25
25
|
$scheme: ThemeColorSchemeKey
|
|
26
26
|
$tone: CardTone
|
|
27
|
+
$unstableDisableFocusRing?: boolean
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const ROOT_STYLE = css`
|
|
@@ -129,7 +130,7 @@ export function textInputFontSizeStyle(props: TextInputInputStyleProps & ThemePr
|
|
|
129
130
|
export function textInputRepresentationStyle(
|
|
130
131
|
props: TextInputRepresentationStyleProps & ThemeProps,
|
|
131
132
|
): ReturnType<typeof css> {
|
|
132
|
-
const {$hasPrefix, $hasSuffix, $scheme, $tone, theme} = props
|
|
133
|
+
const {$hasPrefix, $hasSuffix, $scheme, $tone, $unstableDisableFocusRing, theme} = props
|
|
133
134
|
const {input} = theme.sanity
|
|
134
135
|
const {focusRing} = input.text
|
|
135
136
|
const color = theme.sanity.color.input
|
|
@@ -182,14 +183,18 @@ export function textInputRepresentationStyle(
|
|
|
182
183
|
/* focused */
|
|
183
184
|
*:not(:disabled):focus + & {
|
|
184
185
|
&[data-border] {
|
|
185
|
-
--input-box-shadow: ${
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
--input-box-shadow: ${$unstableDisableFocusRing
|
|
187
|
+
? undefined
|
|
188
|
+
: focusRingStyle({
|
|
189
|
+
border: {color: color.default.enabled.border, width: input.border.width},
|
|
190
|
+
focusRing,
|
|
191
|
+
})};
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
&:not([data-border]) {
|
|
192
|
-
--input-box-shadow: ${
|
|
195
|
+
--input-box-shadow: ${$unstableDisableFocusRing
|
|
196
|
+
? undefined
|
|
197
|
+
: focusRingStyle({focusRing})};
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
|