@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.
Files changed (96) hide show
  1. package/dist/index.cjs.mjs +28 -0
  2. package/dist/index.d.ts +476 -12
  3. package/dist/index.esm.js +1326 -524
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +1353 -523
  6. package/dist/index.js.map +1 -1
  7. package/package.json +6 -8
  8. package/src/components/autocomplete/autocomplete.tsx +6 -0
  9. package/src/components/dialog/constants.ts +4 -0
  10. package/src/components/dialog/dialog.tsx +79 -15
  11. package/src/components/hotkeys/hotkeys.tsx +2 -0
  12. package/src/components/menu/menu.tsx +2 -0
  13. package/src/components/menu/menuButton.tsx +2 -0
  14. package/src/components/menu/menuGroup.tsx +22 -23
  15. package/src/components/menu/menuItem.tsx +29 -35
  16. package/src/components/tab/tab.tsx +4 -1
  17. package/src/components/toast/toast.tsx +4 -0
  18. package/src/primitives/_selectable/style.ts +2 -1
  19. package/src/primitives/avatar/avatar.tsx +16 -9
  20. package/src/primitives/avatar/avatarCounter.tsx +3 -3
  21. package/src/primitives/avatar/styles.ts +1 -1
  22. package/src/primitives/badge/badge.tsx +7 -3
  23. package/src/primitives/box/box.tsx +3 -0
  24. package/src/primitives/button/__workshop__/sanityUploadButton.tsx +2 -1
  25. package/src/primitives/button/button.tsx +7 -3
  26. package/src/primitives/button/styles.ts +9 -2
  27. package/src/primitives/card/card.tsx +4 -0
  28. package/src/primitives/card/styles.ts +2 -0
  29. package/src/primitives/checkbox/checkbox.tsx +2 -0
  30. package/src/primitives/code/code.tsx +1 -0
  31. package/src/primitives/container/container.tsx +2 -0
  32. package/src/primitives/flex/flex.tsx +2 -0
  33. package/src/primitives/grid/grid.tsx +2 -0
  34. package/src/primitives/heading/heading.tsx +2 -0
  35. package/src/primitives/inline/inline.tsx +3 -0
  36. package/src/primitives/kbd/kbd.tsx +2 -0
  37. package/src/primitives/label/label.tsx +2 -0
  38. package/src/primitives/popover/popover.tsx +6 -1
  39. package/src/primitives/radio/radio.tsx +2 -0
  40. package/src/primitives/select/select.tsx +2 -0
  41. package/src/primitives/select/styles.ts +2 -0
  42. package/src/primitives/spinner/spinner.tsx +2 -0
  43. package/src/primitives/stack/stack.tsx +2 -0
  44. package/src/primitives/switch/switch.tsx +4 -0
  45. package/src/primitives/text/text.tsx +3 -0
  46. package/src/primitives/textArea/textArea.tsx +4 -1
  47. package/src/primitives/textInput/textInput.tsx +10 -1
  48. package/src/primitives/tooltip/__workshop__/customPortal.tsx +99 -0
  49. package/src/primitives/tooltip/__workshop__/index.ts +15 -0
  50. package/src/primitives/tooltip/__workshop__/overflowingBoundary.tsx +73 -0
  51. package/src/primitives/tooltip/__workshop__/resizableBoundary.tsx +85 -0
  52. package/src/primitives/tooltip/constants.ts +1 -0
  53. package/src/primitives/tooltip/tooltip.test.tsx +192 -0
  54. package/src/primitives/tooltip/tooltip.tsx +109 -33
  55. package/src/primitives/types.ts +4 -0
  56. package/src/styles/input/textInputStyle.ts +11 -6
  57. package/src/theme/_internal/__workshop__/build/story.tsx +288 -0
  58. package/src/theme/_internal/__workshop__/build/theme.ts +3 -0
  59. package/src/theme/_internal/__workshop__/index.ts +14 -0
  60. package/src/theme/_internal/build/buildTheme.test.ts +78 -0
  61. package/src/theme/_internal/build/buildTheme.ts +16 -0
  62. package/src/theme/_internal/build/colorTheme/buildColorTheme.test.ts +185 -0
  63. package/src/theme/_internal/build/colorTheme/buildColorTheme.ts +164 -0
  64. package/src/theme/_internal/build/colorTheme/renderColorTheme.ts +242 -0
  65. package/src/theme/_internal/build/colorTheme/resolveColorTokens.ts +121 -0
  66. package/src/theme/_internal/build/defaults/colorPalette.ts +8 -0
  67. package/src/theme/_internal/build/defaults/colorTokens.ts +147 -0
  68. package/src/theme/_internal/build/helpers.ts +65 -0
  69. package/src/theme/_internal/build/index.ts +1 -0
  70. package/src/theme/_internal/config/helpers.ts +47 -0
  71. package/src/theme/_internal/config/index.ts +3 -0
  72. package/src/theme/_internal/config/token/index.ts +2 -0
  73. package/src/theme/_internal/config/token/parseTokenKey.test.ts +72 -0
  74. package/src/theme/_internal/config/token/parseTokenKey.ts +88 -0
  75. package/src/theme/_internal/config/token/parseTokenValue.test.ts +45 -0
  76. package/src/theme/_internal/config/token/parseTokenValue.ts +123 -0
  77. package/src/theme/_internal/config/types.ts +137 -0
  78. package/src/theme/_internal/constants.ts +48 -0
  79. package/src/theme/_internal/helpers.ts +19 -0
  80. package/src/theme/_internal/index.ts +5 -0
  81. package/src/theme/_internal/types.ts +91 -0
  82. package/src/theme/index.ts +1 -0
  83. package/src/theme/lib/color-fns/blend/multiply.ts +2 -2
  84. package/src/theme/lib/color-fns/blend/screen.ts +2 -2
  85. package/src/theme/lib/color-fns/convert.ts +15 -1
  86. package/src/theme/lib/color-fns/parse.ts +7 -3
  87. package/src/theme/lib/color-fns/types.ts +11 -0
  88. package/src/theme/lib/theme/avatar.ts +2 -0
  89. package/src/theme/lib/theme/color/_legacy/createColorTheme.ts +7 -0
  90. package/src/theme/lib/theme/color/_legacy/index.ts +1 -0
  91. package/src/theme/lib/theme/color/_legacy/legacyColor.ts +3997 -0
  92. package/src/theme/studioTheme/color.ts +98 -82
  93. package/src/theme/studioTheme/helpers.ts +15 -1
  94. package/src/theme/studioTheme/theme.ts +5 -5
  95. package/src/theme/studioTheme/tints.ts +244 -84
  96. 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: 4,
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
- // Set width and height on the floating element
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: 4,
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
- delayGroupContext.setIsGroupActive(open, openDelay)
185
- delayGroupContext.setOpenTooltipId(tooltipId, openDelay)
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, open ? openDelay : closeDelay)
206
+ setIsOpen(open, standaloneDelay)
197
207
  }
198
208
  },
199
209
  [isInsideGroup, delayGroupContext, openDelay, tooltipId, closeDelay, setIsOpen],
200
210
  )
201
211
 
202
- const handleBlur = useCallback(() => handleIsOpenChange(false), [handleIsOpenChange])
203
- const handleFocus = useCallback(() => handleIsOpenChange(true), [handleIsOpenChange])
204
- const handleMouseEnter = useCallback(() => handleIsOpenChange(true), [handleIsOpenChange])
205
- const handleMouseLeave = useCallback(() => handleIsOpenChange(false), [handleIsOpenChange])
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
- }, [childProp, handleBlur, handleFocus, handleMouseEnter, handleMouseLeave, setReference])
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
- {portal ? (
323
- <Portal __unstable_name={typeof portal === 'string' ? portal : undefined}>
398
+ {portalProp ? (
399
+ <Portal __unstable_name={typeof portalProp === 'string' ? portalProp : undefined}>
324
400
  {root}
325
401
  </Portal>
326
402
  ) : (
@@ -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: ${focusRingStyle({
186
- border: {color: color.default.enabled.border, width: input.border.width},
187
- focusRing,
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: ${focusRingStyle({focusRing})};
195
+ --input-box-shadow: ${$unstableDisableFocusRing
196
+ ? undefined
197
+ : focusRingStyle({focusRing})};
193
198
  }
194
199
  }
195
200