@sanity/ui 2.6.7-canary.0 → 2.6.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.
Files changed (59) hide show
  1. package/dist/index.d.mts +15 -51
  2. package/dist/index.d.ts +15 -51
  3. package/dist/index.esm.js +186 -120
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +186 -119
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +186 -120
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/theme.d.mts +14 -14
  10. package/dist/theme.d.ts +14 -14
  11. package/dist/theme.esm.js.map +1 -1
  12. package/dist/theme.js.map +1 -1
  13. package/dist/theme.mjs.map +1 -1
  14. package/package.json +53 -46
  15. package/src/core/__workshop__/constants.ts +36 -36
  16. package/src/core/components/autocomplete/autocomplete.tsx +1 -1
  17. package/src/core/components/breadcrumbs/breadcrumbs.tsx +6 -15
  18. package/src/core/components/dialog/dialog.tsx +21 -12
  19. package/src/core/components/dialog/styles.ts +8 -8
  20. package/src/core/components/menu/menu.tsx +18 -11
  21. package/src/core/components/menu/menuButton.tsx +11 -11
  22. package/src/core/components/menu/menuContext.ts +1 -1
  23. package/src/core/components/menu/useMenuController.ts +17 -11
  24. package/src/core/components/tab/tab.tsx +8 -8
  25. package/src/core/components/tab/tabPanel.tsx +1 -1
  26. package/src/core/components/toast/styles.ts +1 -2
  27. package/src/core/components/toast/useToast.ts +0 -1
  28. package/src/core/components/tree/tree.tsx +0 -1
  29. package/src/core/components/tree/treeItem.tsx +0 -1
  30. package/src/core/helpers/focus.ts +0 -1
  31. package/src/core/helpers/scroll.ts +0 -1
  32. package/src/core/hooks/_internal/index.ts +1 -0
  33. package/src/core/hooks/_internal/useUnique.ts +34 -0
  34. package/src/core/hooks/index.ts +2 -3
  35. package/src/core/hooks/useClickOutside.ts +72 -38
  36. package/src/core/hooks/useElementSize.ts +0 -1
  37. package/src/core/hooks/useMediaIndex/useMediaIndex.ts +10 -2
  38. package/src/core/hooks/usePrefersDark.ts +55 -10
  39. package/src/core/hooks/usePrefersReducedMotion.ts +56 -10
  40. package/src/core/primitives/avatar/styles.ts +42 -42
  41. package/src/core/primitives/badge/styles.ts +2 -2
  42. package/src/core/primitives/button/styles.ts +4 -4
  43. package/src/core/primitives/inline/styles.ts +2 -2
  44. package/src/core/primitives/popover/__workshop__/AlignedStory.tsx +7 -9
  45. package/src/core/primitives/popover/constants.ts +4 -4
  46. package/src/core/primitives/stack/styles.ts +2 -2
  47. package/src/core/primitives/tooltip/__workshop__/customPortal.tsx +7 -9
  48. package/src/core/primitives/tooltip/constants.ts +4 -4
  49. package/src/core/primitives/tooltip/tooltip.tsx +49 -68
  50. package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupProvider.tsx +0 -1
  51. package/src/core/styles/font/responsiveFont.ts +8 -8
  52. package/src/core/theme/__workshop__/debug/story.tsx +11 -11
  53. package/src/core/utils/layer/layerProvider.tsx +0 -1
  54. package/src/core/utils/portal/__workshop__/named.tsx +8 -10
  55. package/src/core/utils/portal/portalProvider.tsx +13 -7
  56. package/src/theme/build/buildColorTheme.test.ts +13 -13
  57. package/src/theme/config/tokens/color/types.ts +14 -14
  58. package/src/theme/defaults/colorTokens.ts +36 -36
  59. package/src/core/hooks/useMatchMedia.ts +0 -46
@@ -1,7 +1,7 @@
1
1
  import {EllipsisVerticalIcon} from '@sanity/icons'
2
2
  import {Button, Card, Flex, Popover, Text, useClickOutside} from '@sanity/ui'
3
3
  import {useBoolean, useSelect} from '@sanity/ui-workshop'
4
- import {useCallback, useRef, useState} from 'react'
4
+ import {useCallback, useState} from 'react'
5
5
  import {
6
6
  WORKSHOP_FLEX_ALIGN_OPTIONS,
7
7
  WORKSHOP_FLEX_JUSTIFY_OPTIONS,
@@ -20,8 +20,8 @@ export default function AlignedStory() {
20
20
 
21
21
  const [open, setOpen] = useState(false)
22
22
  const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
23
- const buttonElementRef = useRef<HTMLButtonElement | null>(null)
24
- const popoverElementRef = useRef<HTMLDivElement | null>(null)
23
+ const [buttonElement, setButtonElement] = useState<HTMLButtonElement | null>(null)
24
+ const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
25
25
 
26
26
  const content = (
27
27
  <Text>
@@ -39,11 +39,9 @@ export default function AlignedStory() {
39
39
  )
40
40
 
41
41
  const handleToggleOpen = useCallback(() => setOpen((v) => !v), [])
42
+ const handleClose = useCallback(() => setOpen(false), [])
42
43
 
43
- useClickOutside(
44
- () => setOpen(false),
45
- () => [buttonElementRef.current, popoverElementRef.current],
46
- )
44
+ useClickOutside(handleClose, [buttonElement, popoverElement])
47
45
 
48
46
  return (
49
47
  <Card height="fill" padding={[4, 5, 6]} sizing="border" tone="transparent">
@@ -58,14 +56,14 @@ export default function AlignedStory() {
58
56
  padding={3}
59
57
  portal={portal}
60
58
  placement={placement}
61
- ref={popoverElementRef}
59
+ ref={setPopoverElement}
62
60
  width={width}
63
61
  >
64
62
  <Button
65
63
  icon={EllipsisVerticalIcon}
66
64
  mode="bleed"
67
65
  onClick={handleToggleOpen}
68
- ref={buttonElementRef}
66
+ ref={setButtonElement}
69
67
  selected={open}
70
68
  />
71
69
  </Popover>
@@ -7,16 +7,16 @@ export const DEFAULT_POPOVER_ARROW_HEIGHT = 8
7
7
  export const DEFAULT_POPOVER_ARROW_RADIUS = 2
8
8
  export const DEFAULT_POPOVER_MARGINS: PopoverMargins = [0, 0, 0, 0]
9
9
  export const DEFAULT_FALLBACK_PLACEMENTS: Record<Placement, Placement[]> = {
10
- 'top': ['bottom', 'left', 'right'],
10
+ top: ['bottom', 'left', 'right'],
11
11
  'top-start': ['bottom-start', 'left-start', 'right-start'],
12
12
  'top-end': ['bottom-end', 'left-end', 'right-end'],
13
- 'bottom': ['top', 'left', 'right'],
13
+ bottom: ['top', 'left', 'right'],
14
14
  'bottom-start': ['top-start', 'left-start', 'right-start'],
15
15
  'bottom-end': ['top-end', 'left-end', 'right-end'],
16
- 'left': ['right', 'top', 'bottom'],
16
+ left: ['right', 'top', 'bottom'],
17
17
  'left-start': ['right-start', 'top-start', 'bottom-start'],
18
18
  'left-end': ['right-end', 'top-end', 'bottom-end'],
19
- 'right': ['left', 'top', 'bottom'],
19
+ right: ['left', 'top', 'bottom'],
20
20
  'right-start': ['left-start', 'top-start', 'bottom-start'],
21
21
  'right-end': ['left-end', 'top-end', 'bottom-end'],
22
22
  }
@@ -12,8 +12,8 @@ const BASE_STYLE: CSSObject = {
12
12
  '&[data-as="ul"],&[data-as="ol"]': {
13
13
  listStyle: 'none',
14
14
  },
15
- 'gridTemplateColumns': 'minmax(0, 1fr)',
16
- 'gridAutoRows': 'min-content',
15
+ gridTemplateColumns: 'minmax(0, 1fr)',
16
+ gridAutoRows: 'min-content',
17
17
  }
18
18
 
19
19
  export function stackBaseStyle(): CSSObject {
@@ -10,13 +10,13 @@ import {
10
10
  Tooltip,
11
11
  } from '@sanity/ui'
12
12
  import {useBoolean, useSelect, useText} from '@sanity/ui-workshop'
13
- import {useMemo, useState} from 'react'
13
+ import {useState} from 'react'
14
14
  import {WORKSHOP_PLACEMENT_OPTIONS} from '../../../__workshop__/constants'
15
15
 
16
16
  const PORTAL_OPTIONS = {
17
17
  '(true)': true,
18
18
  '(false)': false,
19
- 'portal1': 'portal1',
19
+ portal1: 'portal1',
20
20
  }
21
21
 
22
22
  export default function CustomPortalStory() {
@@ -30,15 +30,13 @@ export default function CustomPortalStory() {
30
30
 
31
31
  const [portal1Element, setPortal1Element] = useState<HTMLDivElement | null>(null)
32
32
  const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
33
- const __unstable_elements = useMemo(
34
- () => ({
35
- portal1: portal1Element,
36
- }),
37
- [portal1Element],
38
- )
39
33
 
40
34
  return (
41
- <PortalProvider __unstable_elements={__unstable_elements}>
35
+ <PortalProvider
36
+ __unstable_elements={{
37
+ portal1: portal1Element,
38
+ }}
39
+ >
42
40
  <Flex align="center" height="fill" justify="center">
43
41
  <BoundaryElementProvider element={useBoundaryElement ? boundaryElement : null}>
44
42
  <Card
@@ -8,16 +8,16 @@ export const DEFAULT_TOOLTIP_DISTANCE = 4
8
8
  export const DEFAULT_TOOLTIP_PADDING = 4
9
9
 
10
10
  export const DEFAULT_FALLBACK_PLACEMENTS: Record<Placement, Placement[]> = {
11
- 'top': ['top-end', 'top-start', 'bottom', 'left', 'right'],
11
+ top: ['top-end', 'top-start', 'bottom', 'left', 'right'],
12
12
  'top-start': ['top', 'top-end', 'bottom-start', 'left-start', 'right-start'],
13
13
  'top-end': ['top', 'top-start', 'bottom-end', 'left-end', 'right-end'],
14
- 'bottom': ['bottom-end', 'bottom-start', 'top', 'left', 'right'],
14
+ bottom: ['bottom-end', 'bottom-start', 'top', 'left', 'right'],
15
15
  'bottom-start': ['bottom', 'bottom-end', 'top-start', 'left-start', 'right-start'],
16
16
  'bottom-end': ['bottom', 'bottom-start', 'top-end', 'left-end', 'right-end'],
17
- 'left': ['left-end', 'left-start', 'right', 'top', 'bottom'],
17
+ left: ['left-end', 'left-start', 'right', 'top', 'bottom'],
18
18
  'left-start': ['left', 'left-end', 'right-start', 'top-start', 'bottom-start'],
19
19
  'left-end': ['left', 'left-start', 'right-end', 'top-end', 'bottom-end'],
20
- 'right': ['right-end', 'right-start', 'left', 'top', 'bottom'],
20
+ right: ['right-end', 'right-start', 'left', 'top', 'bottom'],
21
21
  'right-start': ['right', 'right-end', 'left-start', 'top-start', 'bottom-start'],
22
22
  'right-end': ['right', 'right-start', 'left-end', 'top-end', 'bottom-end'],
23
23
  }
@@ -20,11 +20,10 @@ import {
20
20
  useRef,
21
21
  useState,
22
22
  useId,
23
+ useSyncExternalStore,
23
24
  useImperativeHandle,
24
- useLayoutEffect,
25
25
  } from 'react'
26
26
  import {styled} from 'styled-components'
27
- import {useEffectEvent} from 'use-effect-event'
28
27
  import {useArrayProp, usePrefersReducedMotion} from '../../hooks'
29
28
  import {useDelayedState} from '../../hooks/useDelayedState'
30
29
  import {origin} from '../../middleware/origin'
@@ -86,8 +85,9 @@ export interface TooltipProps extends Omit<LayerProps, 'as'> {
86
85
  animate?: boolean
87
86
  }
88
87
 
89
- const Root = styled(Layer)`
88
+ const Root = styled(Layer)<{$maxWidth: number}>`
90
89
  pointer-events: none;
90
+ max-width: ${({$maxWidth}) => $maxWidth}px;
91
91
  `
92
92
 
93
93
  /**
@@ -127,7 +127,6 @@ export const Tooltip = forwardRef(function Tooltip(
127
127
  const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null)
128
128
  const arrowRef = useRef<HTMLDivElement | null>(null)
129
129
  const rootBoundary: RootBoundary = 'viewport'
130
- const [tooltipMaxWidth, setTooltipMaxWidth] = useState(0)
131
130
 
132
131
  useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(forwardedRef, () => ref.current)
133
132
 
@@ -135,6 +134,24 @@ export const Tooltip = forwardRef(function Tooltip(
135
134
  const portalElement =
136
135
  typeof portalProp === 'string' ? portal.elements?.[portalProp] || null : portal.element
137
136
 
137
+ const documentBodyOffsetWidth = useSyncExternalStore(
138
+ emptySubscribe,
139
+ () => document.body.offsetWidth,
140
+ // We don't actually know what the width of the body is during SSR, so we'll just assume it's 800px or larger
141
+ () => 800,
142
+ )
143
+ // Get the maximum tooltip width (sans tooltip padding)
144
+ // Tooltip width should never exceed the width of either any supplied boundary or portal element.
145
+ // If both portal and boundary elements are provided, use the smaller width of the two.
146
+ const tooltipWidth = useMemo(() => {
147
+ const availableWidths = [
148
+ ...(boundaryElement ? [boundaryElement.offsetWidth] : []),
149
+ portalElement?.offsetWidth || documentBodyOffsetWidth,
150
+ ]
151
+
152
+ return Math.min(...availableWidths) - DEFAULT_TOOLTIP_PADDING * 2
153
+ }, [boundaryElement, documentBodyOffsetWidth, portalElement?.offsetWidth])
154
+
138
155
  const middleware = useMemo(() => {
139
156
  const ret: Middleware[] = []
140
157
 
@@ -268,8 +285,31 @@ export const Tooltip = forwardRef(function Tooltip(
268
285
  [childProp?.props, handleIsOpenChange],
269
286
  )
270
287
 
271
- // Handle closing the tooltip when the mouse leaves the referenceElement
272
- useCloseOnMouseLeave({handleIsOpenChange, referenceElement, showTooltip})
288
+ // Detect whether the mouse is moving outside of the reference element. This is sometimes
289
+ // necessary, because the tooltip might not always close as it should (e.g. when clicking
290
+ // the reference element triggers a CPU-heavy operation.)
291
+ useEffect(() => {
292
+ if (!showTooltip) return
293
+
294
+ function handleWindowMouseMove(event: MouseEvent) {
295
+ if (!referenceElement) return
296
+
297
+ const isHoveringReference =
298
+ referenceElement === event.target ||
299
+ (event.target instanceof Node && referenceElement.contains(event.target))
300
+
301
+ if (!isHoveringReference) {
302
+ handleIsOpenChange(false)
303
+ window.removeEventListener('mousemove', handleWindowMouseMove)
304
+ }
305
+ }
306
+
307
+ window.addEventListener('mousemove', handleWindowMouseMove)
308
+
309
+ return () => {
310
+ window.removeEventListener('mousemove', handleWindowMouseMove)
311
+ }
312
+ }, [showTooltip, referenceElement, handleIsOpenChange])
273
313
 
274
314
  // Close when `disabled` changes to `true`
275
315
  useEffect(() => {
@@ -300,20 +340,6 @@ export const Tooltip = forwardRef(function Tooltip(
300
340
  window.removeEventListener('keydown', handleWindowKeyDown)
301
341
  }
302
342
  }, [handleIsOpenChange, showTooltip])
303
-
304
- // // Set the max width of the tooltip based on boundaries and portals
305
- useLayoutEffect(() => {
306
- // Get the maximum tooltip width (sans tooltip padding)
307
- // Tooltip width should never exceed the width of either any supplied boundary or portal element.
308
- // If both portal and boundary elements are provided, use the smaller width of the two.
309
- const availableWidths = [
310
- ...(boundaryElement ? [boundaryElement.offsetWidth] : []),
311
- portalElement?.offsetWidth || document.body.offsetWidth,
312
- ]
313
-
314
- setTooltipMaxWidth(Math.min(...availableWidths) - DEFAULT_TOOLTIP_PADDING * 2)
315
- }, [boundaryElement, portalElement])
316
-
317
343
  const setArrow = useCallback(
318
344
  (arrowEl: HTMLDivElement | null) => {
319
345
  arrowRef.current = arrowEl
@@ -376,11 +402,9 @@ export const Tooltip = forwardRef(function Tooltip(
376
402
  data-ui="Tooltip"
377
403
  {...restProps}
378
404
  ref={setFloating}
379
- style={{
380
- ...floatingStyles,
381
- maxWidth: tooltipMaxWidth > 0 ? `${tooltipMaxWidth}px` : undefined,
382
- }}
405
+ style={floatingStyles}
383
406
  zOffset={zOffset}
407
+ $maxWidth={tooltipWidth}
384
408
  >
385
409
  <TooltipCard
386
410
  {...restProps}
@@ -430,47 +454,4 @@ export const Tooltip = forwardRef(function Tooltip(
430
454
  )
431
455
  })
432
456
 
433
- /**
434
- * As `useEffectEvent` should never be passed to other components or hooks, this custom hook groups together the `useEffectEvent` and the `useEffect` hook using it.
435
- * @see https://19.react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events:~:text=Never%20pass%20them%20to%20other%20components%20or%20Hooks
436
- */
437
- function useCloseOnMouseLeave({
438
- handleIsOpenChange,
439
- referenceElement,
440
- showTooltip,
441
- }: {
442
- handleIsOpenChange: (open: boolean, immediate?: boolean) => void
443
- referenceElement: HTMLElement | null
444
- showTooltip: boolean
445
- }) {
446
- // Since we don't want the `mouseevent` events to be attached and removed if the `referenceElement` is changed
447
- // we use a "effect event" (https://19.react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)
448
- // in order to always see the latest `referenceElement` value inside the event handler itself.
449
- const onMouseMove = useEffectEvent((target: EventTarget | null, teardown: () => void) => {
450
- if (!referenceElement) return
451
-
452
- const isHoveringReference =
453
- referenceElement === target || (target instanceof Node && referenceElement.contains(target))
454
-
455
- if (!isHoveringReference) {
456
- handleIsOpenChange(false)
457
- // Allow removing the event listener eagerly, to avoid race conditions
458
- teardown()
459
- }
460
- })
461
-
462
- // Detect whether the mouse is moving outside of the reference element. This is sometimes
463
- // necessary, because the tooltip might not always close as it should (e.g. when clicking
464
- // the reference element triggers a CPU-heavy operation.)
465
- useEffect(() => {
466
- if (!showTooltip) return
467
-
468
- const handleMouseMove = (event: MouseEvent) => {
469
- onMouseMove(event.target, () => window.removeEventListener('mousemove', handleMouseMove))
470
- }
471
-
472
- window.addEventListener('mousemove', handleMouseMove)
473
-
474
- return () => window.removeEventListener('mousemove', handleMouseMove)
475
- }, [onMouseMove, showTooltip])
476
- }
457
+ const emptySubscribe = () => () => {}
@@ -36,7 +36,6 @@ export function TooltipDelayGroupProvider(
36
36
  const openDelay = typeof delay === 'number' ? delay : delay?.open || 0
37
37
  const closeDelay = typeof delay === 'number' ? delay : delay?.close || 0
38
38
 
39
- // @TODO split out into separate contexts
40
39
  const value: TooltipDelayGroupContextValue = useMemo(
41
40
  () => ({
42
41
  isGroupActive: isGroupActive,
@@ -21,11 +21,11 @@ export function responsiveFont(
21
21
  const defaultSize = sizes[2]
22
22
 
23
23
  const base: CSSObject = {
24
- 'position': 'relative',
25
- 'fontFamily': family,
24
+ position: 'relative',
25
+ fontFamily: family,
26
26
  fontWeight,
27
- 'padding': '1px 0',
28
- 'margin': 0,
27
+ padding: '1px 0',
28
+ margin: 0,
29
29
 
30
30
  '&:before': {
31
31
  content: '""',
@@ -75,10 +75,10 @@ export function fontSize(size: ThemeFontSize): CSSObject {
75
75
  const customIconOffset = (capHeight - customIconSize) / 2
76
76
 
77
77
  return {
78
- 'fontSize': rem(fontSize),
79
- 'lineHeight': `calc(${lineHeight} / ${fontSize})`,
80
- 'letterSpacing': rem(letterSpacing),
81
- 'transform': `translateY(${rem(descenderHeight)})`,
78
+ fontSize: rem(fontSize),
79
+ lineHeight: `calc(${lineHeight} / ${fontSize})`,
80
+ letterSpacing: rem(letterSpacing),
81
+ transform: `translateY(${rem(descenderHeight)})`,
82
82
 
83
83
  '&:before': {
84
84
  marginTop: `calc(${rem(0 - negHeight)} - 1px)`,
@@ -191,7 +191,7 @@ function DebugState() {
191
191
  style={
192
192
  {
193
193
  '--card-fg-color': 'var(--card-avatar-gray-fg-color)',
194
- 'backgroundColor': 'var(--card-avatar-gray-bg-color)',
194
+ backgroundColor: 'var(--card-avatar-gray-bg-color)',
195
195
  } as any
196
196
  }
197
197
  >
@@ -206,7 +206,7 @@ function DebugState() {
206
206
  style={
207
207
  {
208
208
  '--card-fg-color': 'var(--card-avatar-red-fg-color)',
209
- 'backgroundColor': 'var(--card-avatar-red-bg-color)',
209
+ backgroundColor: 'var(--card-avatar-red-bg-color)',
210
210
  } as any
211
211
  }
212
212
  >
@@ -221,7 +221,7 @@ function DebugState() {
221
221
  style={
222
222
  {
223
223
  '--card-fg-color': 'var(--card-avatar-orange-fg-color)',
224
- 'backgroundColor': 'var(--card-avatar-orange-bg-color)',
224
+ backgroundColor: 'var(--card-avatar-orange-bg-color)',
225
225
  } as any
226
226
  }
227
227
  >
@@ -236,7 +236,7 @@ function DebugState() {
236
236
  style={
237
237
  {
238
238
  '--card-fg-color': 'var(--card-avatar-yellow-fg-color)',
239
- 'backgroundColor': 'var(--card-avatar-yellow-bg-color)',
239
+ backgroundColor: 'var(--card-avatar-yellow-bg-color)',
240
240
  } as any
241
241
  }
242
242
  >
@@ -251,7 +251,7 @@ function DebugState() {
251
251
  style={
252
252
  {
253
253
  '--card-fg-color': 'var(--card-avatar-green-fg-color)',
254
- 'backgroundColor': 'var(--card-avatar-green-bg-color)',
254
+ backgroundColor: 'var(--card-avatar-green-bg-color)',
255
255
  } as any
256
256
  }
257
257
  >
@@ -266,7 +266,7 @@ function DebugState() {
266
266
  style={
267
267
  {
268
268
  '--card-fg-color': 'var(--card-avatar-cyan-fg-color)',
269
- 'backgroundColor': 'var(--card-avatar-cyan-bg-color)',
269
+ backgroundColor: 'var(--card-avatar-cyan-bg-color)',
270
270
  } as any
271
271
  }
272
272
  >
@@ -281,7 +281,7 @@ function DebugState() {
281
281
  style={
282
282
  {
283
283
  '--card-fg-color': 'var(--card-avatar-blue-fg-color)',
284
- 'backgroundColor': 'var(--card-avatar-blue-bg-color)',
284
+ backgroundColor: 'var(--card-avatar-blue-bg-color)',
285
285
  } as any
286
286
  }
287
287
  >
@@ -296,7 +296,7 @@ function DebugState() {
296
296
  style={
297
297
  {
298
298
  '--card-fg-color': 'var(--card-avatar-purple-fg-color)',
299
- 'backgroundColor': 'var(--card-avatar-purple-bg-color)',
299
+ backgroundColor: 'var(--card-avatar-purple-bg-color)',
300
300
  } as any
301
301
  }
302
302
  >
@@ -311,7 +311,7 @@ function DebugState() {
311
311
  style={
312
312
  {
313
313
  '--card-fg-color': 'var(--card-avatar-magenta-fg-color)',
314
- 'backgroundColor': 'var(--card-avatar-magenta-bg-color)',
314
+ backgroundColor: 'var(--card-avatar-magenta-bg-color)',
315
315
  } as any
316
316
  }
317
317
  >
@@ -395,7 +395,7 @@ function getStateVars(state: ThemeColorState_v2) {
395
395
  '--card-skeleton-from-color': state.skeleton.from,
396
396
  '--card-skeleton-to-color': state.skeleton.to,
397
397
 
398
- 'backgroundColor': 'var(--card-bg-color)',
399
- 'color': 'var(--card-fg-color)',
398
+ backgroundColor: 'var(--card-bg-color)',
399
+ color: 'var(--card-fg-color)',
400
400
  } as CSSProperties
401
401
  }
@@ -91,7 +91,6 @@ export function LayerProvider(props: LayerProviderProps): React.ReactElement {
91
91
  // Register this layer on mount
92
92
  useEffect(() => parentRegisterChild?.(level), [level, parentRegisterChild])
93
93
 
94
- // @TODO split out into separate contexts
95
94
  const value: LayerContextValue = useMemo(
96
95
  () => ({
97
96
  version: 0.0,
@@ -1,21 +1,19 @@
1
1
  import {Card, Container, Portal, PortalProvider, Stack, Text} from '@sanity/ui'
2
- import {useMemo, useState} from 'react'
2
+ import {useState} from 'react'
3
3
 
4
4
  export default function NamedStory() {
5
5
  const [portal1Element, setPortal1Element] = useState<HTMLDivElement | null>(null)
6
6
  const [portal2Element, setPortal2Element] = useState<HTMLDivElement | null>(null)
7
7
  const [portal3Element, setPortal3Element] = useState<HTMLDivElement | null>(null)
8
- const __unstable_elements = useMemo(
9
- () => ({
10
- portal1: portal1Element,
11
- portal2: portal2Element,
12
- portal3: portal3Element,
13
- }),
14
- [portal1Element, portal2Element, portal3Element],
15
- )
16
8
 
17
9
  return (
18
- <PortalProvider __unstable_elements={__unstable_elements}>
10
+ <PortalProvider
11
+ __unstable_elements={{
12
+ portal1: portal1Element,
13
+ portal2: portal2Element,
14
+ portal3: portal3Element,
15
+ }}
16
+ >
19
17
  <Container width={1}>
20
18
  <Card height="fill" padding={4}>
21
19
  <Stack space={2}>
@@ -1,5 +1,5 @@
1
- import {useMemo} from 'react'
2
- import {useMounted} from '../../hooks/useMounted'
1
+ import {useMemo, useSyncExternalStore} from 'react'
2
+ import {useUnique} from '../../hooks/_internal'
3
3
  import {PortalContext} from './portalContext'
4
4
  import {PortalContextValue} from './types'
5
5
 
@@ -23,18 +23,24 @@ export interface PortalProviderProps {
23
23
  * @public
24
24
  */
25
25
  export function PortalProvider(props: PortalProviderProps): React.ReactElement {
26
- const {boundaryElement, children, element, __unstable_elements: elements} = props
27
- const mounted = useMounted()
26
+ const {boundaryElement, children, element, __unstable_elements: elementsProp} = props
27
+ const elements = useUnique(elementsProp)
28
+ const fallbackElement = useSyncExternalStore(
29
+ emptySubscribe,
30
+ () => document.body,
31
+ () => null,
32
+ )
28
33
 
29
- // @TODO split out into separate contexts
30
34
  const value: PortalContextValue = useMemo(() => {
31
35
  return {
32
36
  version: 0.0,
33
37
  boundaryElement: boundaryElement || null,
34
- element: element || mounted ? document.body : null,
38
+ element: element || fallbackElement,
35
39
  elements,
36
40
  }
37
- }, [boundaryElement, element, elements, mounted])
41
+ }, [boundaryElement, element, elements, fallbackElement])
38
42
 
39
43
  return <PortalContext.Provider value={value}>{children}</PortalContext.Provider>
40
44
  }
45
+
46
+ const emptySubscribe = () => () => {}
@@ -9,23 +9,23 @@ test('buildColorTheme: base', () => {
9
9
  bg: ['50', '900'],
10
10
  fg: ['800', '200'],
11
11
  },
12
- 'transparent': {
12
+ transparent: {
13
13
  bg: ['50', 'black'],
14
14
  },
15
- 'default': {
15
+ default: {
16
16
  bg: ['white', '950'],
17
17
  fg: ['black', '200'],
18
18
  },
19
- 'primary': {
19
+ primary: {
20
20
  _hue: 'purple',
21
21
  },
22
- 'positive': {
22
+ positive: {
23
23
  _hue: 'cyan',
24
24
  },
25
- 'caution': {
25
+ caution: {
26
26
  _hue: 'yellow',
27
27
  },
28
- 'critical': {
28
+ critical: {
29
29
  _hue: 'red',
30
30
  },
31
31
  },
@@ -100,23 +100,23 @@ test('buildColorTheme: button', () => {
100
100
  bg: ['50', '900'],
101
101
  fg: ['800', '200'],
102
102
  },
103
- 'transparent': {
103
+ transparent: {
104
104
  bg: ['50', 'black'],
105
105
  },
106
- 'default': {
106
+ default: {
107
107
  bg: ['white', '950'],
108
108
  fg: ['black', '200'],
109
109
  },
110
- 'primary': {
110
+ primary: {
111
111
  _hue: 'purple',
112
112
  },
113
- 'positive': {
113
+ positive: {
114
114
  _hue: 'cyan',
115
115
  },
116
- 'caution': {
116
+ caution: {
117
117
  _hue: 'yellow',
118
118
  },
119
- 'critical': {
119
+ critical: {
120
120
  _hue: 'red',
121
121
  },
122
122
  },
@@ -127,7 +127,7 @@ test('buildColorTheme: button', () => {
127
127
  bg: ['500', '400'],
128
128
  fg: ['white', 'black'],
129
129
  },
130
- 'hovered': {
130
+ hovered: {
131
131
  bg: ['600', '300'],
132
132
  },
133
133
  },
@@ -21,15 +21,15 @@ export interface ThemeColorAvatarHueTokens {
21
21
  /** @public */
22
22
  export interface ThemeColorAvatarTokens {
23
23
  '*'?: ThemeColorAvatarHueTokens
24
- 'gray'?: ThemeColorAvatarHueTokens
25
- 'blue'?: ThemeColorAvatarHueTokens
26
- 'purple'?: ThemeColorAvatarHueTokens
27
- 'magenta'?: ThemeColorAvatarHueTokens
28
- 'red'?: ThemeColorAvatarHueTokens
29
- 'orange'?: ThemeColorAvatarHueTokens
30
- 'yellow'?: ThemeColorAvatarHueTokens
31
- 'green'?: ThemeColorAvatarHueTokens
32
- 'cyan'?: ThemeColorAvatarHueTokens
24
+ gray?: ThemeColorAvatarHueTokens
25
+ blue?: ThemeColorAvatarHueTokens
26
+ purple?: ThemeColorAvatarHueTokens
27
+ magenta?: ThemeColorAvatarHueTokens
28
+ red?: ThemeColorAvatarHueTokens
29
+ orange?: ThemeColorAvatarHueTokens
30
+ yellow?: ThemeColorAvatarHueTokens
31
+ green?: ThemeColorAvatarHueTokens
32
+ cyan?: ThemeColorAvatarHueTokens
33
33
  }
34
34
 
35
35
  /** @public */
@@ -66,11 +66,11 @@ export interface ThemeColorBadgeToneTokens {
66
66
  /** @public */
67
67
  export interface ThemeColorBadgeTokens {
68
68
  '*'?: ThemeColorBadgeToneTokens
69
- 'default'?: ThemeColorBadgeToneTokens
70
- 'primary'?: ThemeColorBadgeToneTokens
71
- 'positive'?: ThemeColorBadgeToneTokens
72
- 'caution'?: ThemeColorBadgeToneTokens
73
- 'critical'?: ThemeColorBadgeToneTokens
69
+ default?: ThemeColorBadgeToneTokens
70
+ primary?: ThemeColorBadgeToneTokens
71
+ positive?: ThemeColorBadgeToneTokens
72
+ caution?: ThemeColorBadgeToneTokens
73
+ critical?: ThemeColorBadgeToneTokens
74
74
  }
75
75
 
76
76
  /** @public */