@sanity/ui 4.0.0-static.30 → 4.0.0-static.32

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 (47) hide show
  1. package/dist/index.d.ts +8 -1
  2. package/dist/index.js +1076 -510
  3. package/dist/index.js.map +1 -1
  4. package/package.json +10 -11
  5. package/src/core/components/autocomplete/Autocomplete.tsx +80 -48
  6. package/src/core/components/breadcrumbs/Breadcrumbs.tsx +74 -41
  7. package/src/core/components/dialog/DialogContext.ts +1 -1
  8. package/src/core/components/dialog/__workshop__/Panes.tsx +5 -21
  9. package/src/core/components/dialog/__workshop__/styles.css.ts +18 -0
  10. package/src/core/components/menu/Menu.test.tsx +2 -6
  11. package/src/core/components/menu/Menu.tsx +13 -14
  12. package/src/core/components/menu/MenuContext.ts +2 -3
  13. package/src/core/components/menu/MenuGroup.tsx +2 -0
  14. package/src/core/components/menu/useMenu.ts +2 -2
  15. package/src/core/components/menu/useMenuController.ts +5 -5
  16. package/src/core/components/toast/ToastContext.ts +1 -1
  17. package/src/core/components/tree/Tree.tsx +1 -0
  18. package/src/core/components/tree/TreeContext.ts +1 -1
  19. package/src/core/components/tree/TreeItem.tsx +17 -14
  20. package/src/core/components/virtualList/VirtualList.tsx +86 -44
  21. package/src/core/hooks/useGlobalKeyDown.ts +11 -4
  22. package/src/core/lib/createGlobalScopedContext.ts +2 -2
  23. package/src/core/primitives/avatar/Avatar.tsx +1 -0
  24. package/src/core/primitives/button/Button.tsx +2 -1
  25. package/src/core/primitives/button/__workshop__/SanityUploadButton.tsx +10 -25
  26. package/src/core/primitives/button/__workshop__/Styled1.tsx +2 -8
  27. package/src/core/primitives/button/__workshop__/index.ts +0 -5
  28. package/src/core/primitives/button/__workshop__/styles.css.ts +30 -0
  29. package/src/core/primitives/card/CardContext.ts +1 -1
  30. package/src/core/primitives/card/__workshop__/Styled.tsx +2 -5
  31. package/src/core/primitives/flex/__workshop__/Example.tsx +7 -15
  32. package/src/core/primitives/flex/__workshop__/styles.css.ts +12 -0
  33. package/src/core/primitives/inline/Inline.tsx +1 -0
  34. package/src/core/primitives/layer/LayerContext.ts +1 -1
  35. package/src/core/primitives/popover/Popover.tsx +146 -130
  36. package/src/core/primitives/popover/floating-ui/size.ts +17 -16
  37. package/src/core/primitives/skeleton/Skeleton.tsx +2 -2
  38. package/src/core/primitives/text/__workshop__/Colored.tsx +5 -10
  39. package/src/core/primitives/tooltip/Tooltip.tsx +69 -41
  40. package/src/core/primitives/tooltip/tooltipDelayGroup/TooltipDelayGroupContext.tsx +1 -1
  41. package/src/core/utils/boundaryElement/BoundaryElementContext.ts +1 -1
  42. package/src/core/utils/elementQuery/__workshop__/CustomMedia.tsx +3 -25
  43. package/src/core/utils/elementQuery/__workshop__/styles.css.ts +31 -0
  44. package/src/core/utils/portal/Portal.test.tsx +6 -6
  45. package/src/core/utils/portal/PortalContext.ts +1 -1
  46. package/src/core/utils/portal/PortalProvider.tsx +5 -37
  47. package/src/core/primitives/button/__workshop__/Styled2.tsx +0 -19
@@ -19,7 +19,6 @@ import {
19
19
  type ReactNode,
20
20
  type Ref,
21
21
  useCallback,
22
- useEffect,
23
22
  useImperativeHandle,
24
23
  useMemo,
25
24
  useRef,
@@ -166,10 +165,9 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
166
165
  constrainSize = false,
167
166
  content,
168
167
  disabled,
169
- fallbackPlacements = props.fallbackPlacements ??
170
- DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom'],
168
+ fallbackPlacements: _fallbackPlacements,
171
169
  matchReferenceWidth,
172
- floatingBoundary = boundaryElementContext.element,
170
+ floatingBoundary: _floatingBoundary,
173
171
  modal,
174
172
  onActivate,
175
173
  open,
@@ -181,7 +179,7 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
181
179
  preventOverflow = true,
182
180
  radius = 3,
183
181
  ref: forwardedRef,
184
- referenceBoundary = boundaryElementContext.element,
182
+ referenceBoundary: _referenceBoundary,
185
183
  referenceElement,
186
184
  scheme,
187
185
  shadow = 3,
@@ -191,6 +189,10 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
191
189
  updateRef,
192
190
  ...rest
193
191
  } = props as PopoverProps<typeof DEFAULT_POPOVER_ELEMENT>
192
+ const fallbackPlacements =
193
+ _fallbackPlacements ?? DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom']
194
+ const floatingBoundary = _floatingBoundary ?? boundaryElementContext.element
195
+ const referenceBoundary = _referenceBoundary ?? boundaryElementContext.element
194
196
 
195
197
  const card = useCard()
196
198
 
@@ -206,108 +208,22 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
206
208
  // Keep track of reference element width (see `size` middleware below)
207
209
  const referenceWidthRef = useRef<number>(undefined)
208
210
 
209
- const middleware = useMemo(() => {
210
- const ret: Middleware[] = []
211
-
212
- // Flip the floating element when leaving the boundary box
213
- if (constrainSize || preventOverflow) {
214
- if (placementStrategy === 'autoPlacement') {
215
- ret.push(
216
- autoPlacement({
217
- allowedPlacements: [placementProp].concat(fallbackPlacements),
218
- }),
219
- )
220
- } else {
221
- ret.push(
222
- flip({
223
- boundary: floatingBoundary || undefined,
224
- fallbackPlacements,
225
- padding: DEFAULT_POPOVER_PADDING,
226
- rootBoundary,
227
- }),
228
- )
229
- }
230
- }
231
-
232
- // Define distance between reference and floating element
233
- ret.push(offset({mainAxis: DEFAULT_POPOVER_DISTANCE}))
234
-
235
- // Track sizes
236
- if (constrainSize || matchReferenceWidth) {
237
- ret.push(
238
- size({
239
- apply({availableWidth, availableHeight, elements, referenceWidth}) {
240
- // not fresh, so use refs
241
-
242
- referenceWidthRef.current = referenceWidth
243
-
244
- if (matchReferenceWidth) {
245
- elements.floating.style.width = `${referenceWidth}px`
246
- }
247
-
248
- if (constrainSize) {
249
- elements.floating.style.maxWidth = `${availableWidth}px`
250
- elements.floating.style.maxHeight = `${availableHeight}px`
251
- }
252
- },
253
- boundaryElement: floatingBoundary || undefined,
254
- constrainSize,
255
- margins,
256
- matchReferenceWidth,
257
- padding: DEFAULT_POPOVER_PADDING,
258
- }),
259
- )
260
- }
261
-
262
- // Shift the popover so its sits within the boundary element
263
- if (preventOverflow) {
264
- ret.push(
265
- shift({
266
- boundary: floatingBoundary || undefined,
267
- rootBoundary,
268
- padding: DEFAULT_POPOVER_PADDING,
269
- }),
270
- )
271
- }
272
-
273
- // Place arrow
274
- if (arrowProp) {
275
- ret.push(
276
- arrow({
277
- element: arrowRef,
278
- padding: DEFAULT_POPOVER_PADDING,
279
- }),
280
- )
281
- }
282
-
283
- // Determine the origin to scale from.
284
- // Must be placed after `@sanity/ui/size` and `shift` middleware.
285
- if (animate) {
286
- ret.push(origin)
287
- }
288
-
289
- ret.push(
290
- hide({
291
- boundary: referenceBoundary || undefined,
292
- padding: DEFAULT_POPOVER_PADDING,
293
- strategy: 'referenceHidden',
294
- }),
295
- )
296
-
297
- return ret
298
- }, [
211
+ const middleware = useMiddleware({
299
212
  animate,
300
213
  arrowProp,
214
+ arrowRef,
301
215
  constrainSize,
302
216
  fallbackPlacements,
303
217
  floatingBoundary,
304
- placementProp,
305
- placementStrategy,
306
218
  margins,
307
219
  matchReferenceWidth,
220
+ placementProp,
221
+ placementStrategy,
308
222
  preventOverflow,
309
223
  referenceBoundary,
310
- ])
224
+ referenceWidthRef,
225
+ rootBoundary,
226
+ })
311
227
 
312
228
  const {x, y, middlewareData, placement, refs, strategy, update} = useFloating({
313
229
  middleware,
@@ -328,10 +244,6 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
328
244
  const originX = middlewareData['@sanity/ui/origin']?.originX
329
245
  const originY = middlewareData['@sanity/ui/origin']?.originY
330
246
 
331
- const setArrow = useCallback((arrowEl: HTMLDivElement | null) => {
332
- arrowRef.current = arrowEl
333
- }, [])
334
-
335
247
  const setFloating = useCallback(
336
248
  (node: HTMLDivElement | null) => {
337
249
  ref.current = node
@@ -340,22 +252,9 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
340
252
  [refs],
341
253
  )
342
254
 
343
- const setReference = useCallback(
344
- (node: HTMLElement | null) => {
345
- refs.setReference(node)
346
-
347
- if (!childProp) return
348
-
349
- const childRef = getElementRef(childProp)
350
-
351
- if (typeof childRef === 'function') {
352
- childRef(node)
353
- } else if (childRef) {
354
- childRef.current = node
355
- }
356
- },
357
- [childProp, refs],
358
- )
255
+ // If there's a child then we need to set the reference element to the cloned child ref
256
+ // and if child changes we make sure to update or remove the reference element.
257
+ useImperativeHandle(childProp ? getElementRef(childProp) : null, () => refs.reference.current)
359
258
 
360
259
  const child = useMemo(() => {
361
260
  // If a reference element is defined, we don't need to clone the child
@@ -363,18 +262,10 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
363
262
 
364
263
  if (!childProp) return null
365
264
 
366
- return cloneElement(childProp, {ref: setReference})
367
- }, [childProp, referenceElement, setReference])
265
+ return cloneElement(childProp, {ref: refs.setReference})
266
+ }, [childProp, referenceElement, refs.setReference])
368
267
 
369
- useEffect(() => {
370
- if (updateRef) {
371
- if (typeof updateRef === 'function') {
372
- updateRef(update)
373
- } else if (updateRef) {
374
- updateRef.current = update
375
- }
376
- }
377
- }, [update, updateRef])
268
+ useImperativeHandle(updateRef, () => update, [update])
378
269
 
379
270
  if (disabled) {
380
271
  return childProp || <></>
@@ -391,7 +282,7 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
391
282
  __unstable_margins={margins}
392
283
  animate={animate}
393
284
  arrow={arrowProp}
394
- arrowRef={setArrow}
285
+ arrowRef={arrowRef}
395
286
  arrowX={arrowX}
396
287
  arrowY={arrowY}
397
288
  as={as}
@@ -440,3 +331,128 @@ export function Popover<E extends PopoverElementType = typeof DEFAULT_POPOVER_EL
440
331
  </>
441
332
  )
442
333
  }
334
+
335
+ function useMiddleware({
336
+ animate,
337
+ arrowProp,
338
+ arrowRef,
339
+ constrainSize,
340
+ fallbackPlacements,
341
+ floatingBoundary,
342
+ margins,
343
+ matchReferenceWidth,
344
+ placementProp,
345
+ placementStrategy,
346
+ preventOverflow,
347
+ referenceBoundary,
348
+ referenceWidthRef,
349
+ rootBoundary,
350
+ }: {
351
+ animate: boolean
352
+ arrowProp: boolean
353
+ arrowRef: React.RefObject<HTMLDivElement | null>
354
+ constrainSize: boolean
355
+ fallbackPlacements: Placement[]
356
+ floatingBoundary: HTMLElement | null
357
+ margins: PopoverMargins
358
+ matchReferenceWidth: boolean | undefined
359
+ placementProp: Placement
360
+ placementStrategy: 'flip' | 'autoPlacement'
361
+ preventOverflow: boolean
362
+ referenceBoundary: HTMLElement | null
363
+ referenceWidthRef: React.RefObject<number | undefined>
364
+ rootBoundary: RootBoundary
365
+ }) {
366
+ return useMemo(() => {
367
+ const ret: Middleware[] = []
368
+
369
+ // Flip the floating element when leaving the boundary box
370
+ if (constrainSize || preventOverflow) {
371
+ if (placementStrategy === 'autoPlacement') {
372
+ ret.push(
373
+ autoPlacement({
374
+ allowedPlacements: [placementProp].concat(fallbackPlacements),
375
+ }),
376
+ )
377
+ } else {
378
+ ret.push(
379
+ flip({
380
+ boundary: floatingBoundary || undefined,
381
+ fallbackPlacements,
382
+ padding: DEFAULT_POPOVER_PADDING,
383
+ rootBoundary,
384
+ }),
385
+ )
386
+ }
387
+ }
388
+
389
+ // Define distance between reference and floating element
390
+ ret.push(offset({mainAxis: DEFAULT_POPOVER_DISTANCE}))
391
+
392
+ // Track sizes
393
+ if (constrainSize || matchReferenceWidth) {
394
+ ret.push(
395
+ size({
396
+ boundaryElement: floatingBoundary || undefined,
397
+ constrainSize,
398
+ margins,
399
+ matchReferenceWidth,
400
+ padding: DEFAULT_POPOVER_PADDING,
401
+ referenceWidthRef,
402
+ }),
403
+ )
404
+ }
405
+
406
+ // Shift the popover so its sits within the boundary element
407
+ if (preventOverflow) {
408
+ ret.push(
409
+ shift({
410
+ boundary: floatingBoundary || undefined,
411
+ rootBoundary,
412
+ padding: DEFAULT_POPOVER_PADDING,
413
+ }),
414
+ )
415
+ }
416
+
417
+ // Place arrow
418
+ if (arrowProp) {
419
+ ret.push(
420
+ arrow({
421
+ element: arrowRef,
422
+ padding: DEFAULT_POPOVER_PADDING,
423
+ }),
424
+ )
425
+ }
426
+
427
+ // Determine the origin to scale from.
428
+ // Must be placed after `@sanity/ui/size` and `shift` middleware.
429
+ if (animate) {
430
+ ret.push(origin)
431
+ }
432
+
433
+ ret.push(
434
+ hide({
435
+ boundary: referenceBoundary || undefined,
436
+ padding: DEFAULT_POPOVER_PADDING,
437
+ strategy: 'referenceHidden',
438
+ }),
439
+ )
440
+
441
+ return ret
442
+ }, [
443
+ animate,
444
+ arrowProp,
445
+ arrowRef,
446
+ constrainSize,
447
+ fallbackPlacements,
448
+ floatingBoundary,
449
+ margins,
450
+ matchReferenceWidth,
451
+ placementProp,
452
+ placementStrategy,
453
+ preventOverflow,
454
+ referenceBoundary,
455
+ referenceWidthRef,
456
+ rootBoundary,
457
+ ])
458
+ }
@@ -1,23 +1,16 @@
1
- import {detectOverflow, type Elements, type Middleware} from '@floating-ui/react-dom'
1
+ import {detectOverflow, type Middleware} from '@floating-ui/react-dom'
2
2
 
3
3
  import type {PopoverMargins} from '../types'
4
4
 
5
- export interface SizeMiddlewareApplyOptions {
6
- availableWidth: number
7
- availableHeight: number
8
- elements: Elements
9
- referenceWidth: number
10
- }
11
-
12
5
  export function size(options: {
13
- apply: (args: SizeMiddlewareApplyOptions) => void
14
6
  boundaryElement?: HTMLElement | null
15
7
  constrainSize: boolean
16
8
  margins: PopoverMargins
17
9
  matchReferenceWidth?: boolean
18
10
  padding?: number
11
+ referenceWidthRef: React.RefObject<number | undefined>
19
12
  }): Middleware {
20
- const {apply, margins, padding = 0} = options
13
+ const {margins, padding = 0, constrainSize, matchReferenceWidth, referenceWidthRef} = options
21
14
 
22
15
  return {
23
16
  name: '@sanity/ui/size',
@@ -61,12 +54,20 @@ export function size(options: {
61
54
 
62
55
  // IMPORTANT – APPLY ELEMENT STYLES HERE
63
56
  // Elements need to be resized BEFORE the `platform.getDimensions` call below
64
- apply({
65
- availableWidth: maxWidth - margins[1] - margins[3],
66
- availableHeight: maxHeight - margins[0] - margins[2],
67
- elements,
68
- referenceWidth: reference.width - margins[1] - margins[3],
69
- })
57
+ const availableWidth = maxWidth - margins[1] - margins[3]
58
+ const availableHeight = maxHeight - margins[0] - margins[2]
59
+ const referenceWidth = reference.width - margins[1] - margins[3]
60
+
61
+ referenceWidthRef.current = referenceWidth
62
+
63
+ if (matchReferenceWidth) {
64
+ elements.floating.style.width = `${referenceWidth}px`
65
+ }
66
+
67
+ if (constrainSize) {
68
+ elements.floating.style.maxWidth = `${availableWidth}px`
69
+ elements.floating.style.maxHeight = `${availableHeight}px`
70
+ }
70
71
 
71
72
  const nextDimensions = await platform.getDimensions(elements.floating)
72
73
 
@@ -43,7 +43,7 @@ export function Skeleton<E extends SkeletonElementType = typeof DEFAULT_SKELETON
43
43
 
44
44
  useEffect(() => {
45
45
  if (!delay) {
46
- return setVisible(true)
46
+ return undefined
47
47
  }
48
48
 
49
49
  const timeout = setTimeout(() => {
@@ -64,7 +64,7 @@ export function Skeleton<E extends SkeletonElementType = typeof DEFAULT_SKELETON
64
64
  ...rest,
65
65
  })}
66
66
  data-animated={animated ? '' : undefined}
67
- data-visible={visible ? '' : undefined}
67
+ data-visible={(delay ? visible : true) ? '' : undefined}
68
68
  />
69
69
  )
70
70
  }
@@ -2,29 +2,24 @@ import {Flex, Text} from '@sanity/ui'
2
2
  import {vars} from '@sanity/ui/css'
3
3
  import type {AvatarColor} from '@sanity/ui/theme'
4
4
  import {useSelect} from '@sanity/ui-workshop'
5
- import {styled} from 'styled-components'
5
+ import {assignInlineVars} from '@vanilla-extract/dynamic'
6
6
 
7
7
  import {WORKSHOP_AVATAR_COLOR_OPTIONS} from '$workshop'
8
8
 
9
- const ColoredText = styled(Text)<{$color: AvatarColor}>`
10
- ${vars.color.fg.slice(4, -1)}: ${({$color}) => vars.color.avatar[$color].bg};
11
- `
12
-
13
9
  export default function ColoredTextStory(): React.JSX.Element {
14
10
  // @ts-expect-error - TODO: fix this
15
- const color = useSelect('Color', WORKSHOP_AVATAR_COLOR_OPTIONS, 'gray') ?? 'gray'
11
+ const color: AvatarColor = useSelect('Color', WORKSHOP_AVATAR_COLOR_OPTIONS, 'gray') ?? 'gray'
16
12
 
17
13
  return (
18
14
  <Flex align="center" height="fill" justify="center" padding={[4, 5, 6]} sizing="border">
19
- <ColoredText
15
+ <Text
20
16
  align="center"
21
- // @ts-expect-error - TODO: fix this
22
- $color={color}
23
17
  size={4}
24
18
  weight="bold"
19
+ style={assignInlineVars({[vars.color.fg]: vars.color.avatar[color].bg})}
25
20
  >
26
21
  {color}
27
- </ColoredText>
22
+ </Text>
28
23
  </Flex>
29
24
  )
30
25
  }
@@ -112,14 +112,13 @@ export function Tooltip<E extends TooltipElementType = typeof DEFAULT_TOOLTIP_EL
112
112
  animate: _animate = false,
113
113
  arrow: arrowProp = false,
114
114
  as = DEFAULT_TOOLTIP_ELEMENT,
115
- boundaryElement = boundaryElementContext?.element,
115
+ boundaryElement: _boundaryElement,
116
116
  children: childProp,
117
117
  className,
118
118
  content,
119
119
  delay,
120
120
  disabled,
121
- fallbackPlacements = props.fallbackPlacements ??
122
- DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom'],
121
+ fallbackPlacements: _fallbackPlacements,
123
122
  padding = 2,
124
123
  placement: placementProp = 'bottom',
125
124
  portal: portalProp,
@@ -131,6 +130,9 @@ export function Tooltip<E extends TooltipElementType = typeof DEFAULT_TOOLTIP_EL
131
130
  tone = 'inherit',
132
131
  ...rest
133
132
  } = props as TooltipProps<typeof DEFAULT_TOOLTIP_ELEMENT>
133
+ const boundaryElement = _boundaryElement ?? boundaryElementContext?.element
134
+ const fallbackPlacements =
135
+ _fallbackPlacements ?? DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom']
134
136
 
135
137
  const card = useCard()
136
138
  const prefersReducedMotion = usePrefersReducedMotion()
@@ -147,44 +149,14 @@ export function Tooltip<E extends TooltipElementType = typeof DEFAULT_TOOLTIP_EL
147
149
  const portalElement =
148
150
  typeof portalProp === 'string' ? portal.elements?.[portalProp] || null : portal.element
149
151
 
150
- const middleware = useMemo(() => {
151
- const ret: Middleware[] = []
152
-
153
- // Flip the floating element when leaving the boundary box
154
- ret.push(
155
- flip({
156
- boundary: boundaryElement || undefined,
157
- fallbackPlacements,
158
- padding: DEFAULT_TOOLTIP_PADDING,
159
- rootBoundary,
160
- }),
161
- )
162
-
163
- // Define distance between reference and floating element
164
- ret.push(offset({mainAxis: DEFAULT_TOOLTIP_DISTANCE}))
165
-
166
- // Shift the tooltip so its sits with the boundary element
167
- ret.push(
168
- shift({
169
- boundary: boundaryElement || undefined,
170
- rootBoundary,
171
- padding: DEFAULT_TOOLTIP_PADDING,
172
- }),
173
- )
174
-
175
- // Place arrow
176
- if (arrowProp) {
177
- ret.push(arrow({element: arrowRef, padding: DEFAULT_TOOLTIP_PADDING}))
178
- }
179
-
180
- // Determine the origin to scale from.
181
- // Must be placed after `@sanity/ui/size` and `shift` middleware.
182
- if (animate) {
183
- ret.push(origin)
184
- }
185
-
186
- return ret
187
- }, [animate, arrowProp, boundaryElement, fallbackPlacements])
152
+ const middleware = useMiddleware({
153
+ animate,
154
+ arrowProp,
155
+ arrowRef,
156
+ boundaryElement,
157
+ fallbackPlacements,
158
+ rootBoundary,
159
+ })
188
160
 
189
161
  const {floatingStyles, placement, middlewareData, refs, update} = useFloating({
190
162
  middleware,
@@ -331,6 +303,7 @@ export function Tooltip<E extends TooltipElementType = typeof DEFAULT_TOOLTIP_EL
331
303
  portalElement?.offsetWidth || document.body.offsetWidth,
332
304
  ]
333
305
 
306
+ // eslint-disable-next-line react-hooks/set-state-in-effect
334
307
  setTooltipMaxWidth(Math.min(...availableWidths) - DEFAULT_TOOLTIP_PADDING * 2)
335
308
  }, [boundaryElement, portalElement])
336
309
 
@@ -435,6 +408,61 @@ export function Tooltip<E extends TooltipElementType = typeof DEFAULT_TOOLTIP_EL
435
408
  )
436
409
  }
437
410
 
411
+ function useMiddleware({
412
+ animate,
413
+ arrowProp,
414
+ arrowRef,
415
+ boundaryElement,
416
+ fallbackPlacements,
417
+ rootBoundary,
418
+ }: {
419
+ animate: boolean
420
+ arrowProp: boolean
421
+ arrowRef: React.RefObject<HTMLDivElement | null>
422
+ boundaryElement: HTMLElement | null
423
+ fallbackPlacements: Placement[]
424
+ rootBoundary: RootBoundary
425
+ }) {
426
+ return useMemo(() => {
427
+ const ret: Middleware[] = []
428
+
429
+ // Flip the floating element when leaving the boundary box
430
+ ret.push(
431
+ flip({
432
+ boundary: boundaryElement || undefined,
433
+ fallbackPlacements,
434
+ padding: DEFAULT_TOOLTIP_PADDING,
435
+ rootBoundary,
436
+ }),
437
+ )
438
+
439
+ // Define distance between reference and floating element
440
+ ret.push(offset({mainAxis: DEFAULT_TOOLTIP_DISTANCE}))
441
+
442
+ // Shift the tooltip so its sits with the boundary element
443
+ ret.push(
444
+ shift({
445
+ boundary: boundaryElement || undefined,
446
+ rootBoundary,
447
+ padding: DEFAULT_TOOLTIP_PADDING,
448
+ }),
449
+ )
450
+
451
+ // Place arrow
452
+ if (arrowProp) {
453
+ ret.push(arrow({element: arrowRef, padding: DEFAULT_TOOLTIP_PADDING}))
454
+ }
455
+
456
+ // Determine the origin to scale from.
457
+ // Must be placed after `@sanity/ui/size` and `shift` middleware.
458
+ if (animate) {
459
+ ret.push(origin)
460
+ }
461
+
462
+ return ret
463
+ }, [animate, arrowProp, arrowRef, boundaryElement, fallbackPlacements, rootBoundary])
464
+ }
465
+
438
466
  /**
439
467
  * As `useEffectEvent` should never be passed to other components or hooks, this custom hook groups together the `useEffectEvent` and the `useEffect` hook using it.
440
468
  * @see https://19.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events:~:text=Never%20pass%20them%20to%20other%20components%20or%20Hooks
@@ -8,6 +8,6 @@ import type {TooltipDelayGroupContextValue} from './types'
8
8
  */
9
9
  export const TooltipDelayGroupContext: Context<TooltipDelayGroupContextValue | null> =
10
10
  createGlobalScopedContext<TooltipDelayGroupContextValue | null>(
11
- '@sanity/ui/v3/tooltipDelayGroup',
11
+ '@sanity/ui/v4/tooltipDelayGroup',
12
12
  null,
13
13
  )
@@ -5,6 +5,6 @@ import type {BoundaryElementContextValue} from './types'
5
5
 
6
6
  export const BoundaryElementContext: Context<BoundaryElementContextValue | null> =
7
7
  createGlobalScopedContext<BoundaryElementContextValue | null>(
8
- '@sanity/ui/v3/boundaryElement',
8
+ '@sanity/ui/v4/boundaryElement',
9
9
  null,
10
10
  )
@@ -1,28 +1,6 @@
1
1
  import {Box, Card, ElementQuery, Text} from '@sanity/ui'
2
- import {vars} from '@sanity/ui/css'
3
- import {styled} from 'styled-components'
4
2
 
5
- const TestCard = styled(Card)`
6
- [data-eq-min~='0'] > & {
7
- ${vars.color.fg.slice(4, -1)}: light-dark(${vars.color.palette.orange[600]}, ${vars.color
8
- .palette.orange[400]});
9
- }
10
-
11
- [data-eq-min~='1'] > & {
12
- ${vars.color.fg.slice(4, -1)}: light-dark(${vars.color.palette.red[600]}, ${vars.color.palette
13
- .red[400]});
14
- }
15
-
16
- [data-eq-min~='2'] > & {
17
- ${vars.color.fg.slice(4, -1)}: light-dark(${vars.color.palette.magenta[600]}, ${vars.color
18
- .palette.magenta[400]});
19
- }
20
-
21
- [data-eq-min~='3'] > & {
22
- ${vars.color.fg.slice(4, -1)}: light-dark(${vars.color.palette.purple[600]}, ${vars.color
23
- .palette.purple[400]});
24
- }
25
- `
3
+ import {testCard} from './styles.css'
26
4
 
27
5
  export default function CustomMediaQuery(): React.JSX.Element {
28
6
  return (
@@ -32,9 +10,9 @@ export default function CustomMediaQuery(): React.JSX.Element {
32
10
  </Box>
33
11
 
34
12
  <ElementQuery media={[0, 100, 200, 300]}>
35
- <TestCard padding={3} shadow={1}>
13
+ <Card className={testCard} padding={3} shadow={1}>
36
14
  <Text>This card sits inside an element query.</Text>
37
- </TestCard>
15
+ </Card>
38
16
  </ElementQuery>
39
17
  </Box>
40
18
  )
@@ -0,0 +1,31 @@
1
+ import {vars} from '@sanity/ui/css'
2
+ import {style} from '@vanilla-extract/css'
3
+
4
+ export const testCard: string = style({
5
+ selectors: {
6
+ '[data-eq-min~="0"] > &': {
7
+ vars: {
8
+ [vars.color.fg]:
9
+ `light-dark(${vars.color.palette.orange[600]}, ${vars.color.palette.orange[400]})`,
10
+ },
11
+ },
12
+ '[data-eq-min~="1"] > &': {
13
+ vars: {
14
+ [vars.color.fg]:
15
+ `light-dark(${vars.color.palette.red[600]}, ${vars.color.palette.red[400]})`,
16
+ },
17
+ },
18
+ '[data-eq-min~="2"] > &': {
19
+ vars: {
20
+ [vars.color.fg]:
21
+ `light-dark(${vars.color.palette.magenta[600]}, ${vars.color.palette.magenta[400]})`,
22
+ },
23
+ },
24
+ '[data-eq-min~="3"] > &': {
25
+ vars: {
26
+ [vars.color.fg]:
27
+ `light-dark(${vars.color.palette.purple[600]}, ${vars.color.palette.purple[400]})`,
28
+ },
29
+ },
30
+ },
31
+ })