@sanity/ui 2.3.7-canary.0 → 2.5.0

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 (67) hide show
  1. package/dist/_chunks-cjs/getTheme_v2.js +319 -0
  2. package/dist/_chunks-cjs/getTheme_v2.js.map +1 -0
  3. package/dist/_chunks-es/getTheme_v2.mjs +320 -0
  4. package/dist/_chunks-es/getTheme_v2.mjs.map +1 -0
  5. package/dist/_legacy/getTheme_v2.esm.js +320 -0
  6. package/dist/_legacy/getTheme_v2.esm.js.map +1 -0
  7. package/dist/index.d.mts +4 -13
  8. package/dist/index.d.ts +4 -13
  9. package/dist/index.esm.js +2311 -2244
  10. package/dist/index.esm.js.map +1 -1
  11. package/dist/index.js +2314 -2248
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +2311 -2244
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/theme.esm.js +4 -315
  16. package/dist/theme.esm.js.map +1 -1
  17. package/dist/theme.js +36 -349
  18. package/dist/theme.js.map +1 -1
  19. package/dist/theme.mjs +4 -315
  20. package/dist/theme.mjs.map +1 -1
  21. package/package.json +35 -31
  22. package/src/core/components/autocomplete/autocomplete.tsx +48 -37
  23. package/src/core/components/breadcrumbs/breadcrumbs.tsx +2 -3
  24. package/src/core/components/dialog/dialog.tsx +12 -23
  25. package/src/core/components/hotkeys/hotkeys.tsx +3 -3
  26. package/src/core/components/menu/menu.tsx +1 -5
  27. package/src/core/components/menu/menuButton.tsx +32 -17
  28. package/src/core/components/menu/menuGroup.tsx +5 -7
  29. package/src/core/components/menu/menuItem.tsx +6 -10
  30. package/src/core/components/skeleton/skeleton.tsx +11 -5
  31. package/src/core/components/skeleton/textSkeleton.tsx +7 -6
  32. package/src/core/components/toast/styles.ts +61 -0
  33. package/src/core/components/toast/toast.tsx +11 -16
  34. package/src/core/components/toast/toastProvider.tsx +25 -26
  35. package/src/core/components/tree/treeItem.tsx +44 -31
  36. package/src/core/hooks/useArrayProp.ts +10 -11
  37. package/src/core/primitives/avatar/avatar.tsx +15 -11
  38. package/src/core/primitives/avatar/avatarCounter.tsx +17 -11
  39. package/src/core/primitives/avatar/avatarStack.tsx +7 -3
  40. package/src/core/primitives/badge/badge.tsx +4 -7
  41. package/src/core/primitives/box/box.tsx +27 -53
  42. package/src/core/primitives/button/button.tsx +12 -11
  43. package/src/core/primitives/card/card.tsx +9 -17
  44. package/src/core/primitives/code/code.tsx +3 -4
  45. package/src/core/primitives/container/container.tsx +11 -4
  46. package/src/core/primitives/flex/flex.tsx +7 -13
  47. package/src/core/primitives/grid/grid.tsx +10 -19
  48. package/src/core/primitives/heading/heading.tsx +4 -7
  49. package/src/core/primitives/inline/inline.tsx +2 -3
  50. package/src/core/primitives/kbd/kbd.tsx +3 -4
  51. package/src/core/primitives/label/label.tsx +4 -7
  52. package/src/core/primitives/popover/popover.tsx +49 -49
  53. package/src/core/primitives/select/select.tsx +6 -12
  54. package/src/core/primitives/stack/stack.tsx +3 -4
  55. package/src/core/primitives/text/text.tsx +4 -7
  56. package/src/core/primitives/textArea/textArea.tsx +6 -12
  57. package/src/core/primitives/textInput/textInput.tsx +5 -6
  58. package/src/core/primitives/tooltip/tooltip.tsx +7 -14
  59. package/src/core/theme/themeProvider.tsx +6 -4
  60. package/src/core/utils/elementQuery/elementQuery.tsx +3 -4
  61. package/src/core/utils/layer/getLayerContext.test.ts +4 -0
  62. package/src/core/utils/layer/layer.test.tsx +2 -0
  63. package/src/core/utils/layer/layerProvider.tsx +43 -35
  64. package/src/core/utils/layer/types.ts +1 -0
  65. package/dist/index.compiled.mjs +0 -7488
  66. package/dist/index.compiled.mjs.map +0 -1
  67. package/src/core/components/tree/treeItem.styles.ts +0 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "2.3.7-canary.0",
3
+ "version": "2.5.0",
4
4
  "keywords": [
5
5
  "sanity",
6
6
  "ui",
@@ -23,10 +23,6 @@
23
23
  "exports": {
24
24
  ".": {
25
25
  "source": "./exports/index.ts",
26
- "react-compiler": {
27
- "source": "./exports/index.ts",
28
- "default": "./dist/index.compiled.mjs"
29
- },
30
26
  "import": "./dist/index.mjs",
31
27
  "require": "./dist/index.js",
32
28
  "default": "./dist/index.js"
@@ -55,6 +51,32 @@
55
51
  "src",
56
52
  "theme.js"
57
53
  ],
54
+ "scripts": {
55
+ "build": "run-s clean pkg:build pkg:check figma:pkg:build",
56
+ "clean": "rimraf .workshop dist",
57
+ "commit": "cz",
58
+ "cypress:dev": "run-p dev cypress:open",
59
+ "cypress:open": "cypress open",
60
+ "cypress:run": "cypress run",
61
+ "dev": "run-p storybook:dev workshop:dev",
62
+ "figma:pkg:build": "cd figma && pnpm build",
63
+ "format": "prettier --write --cache --ignore-unknown .",
64
+ "lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
65
+ "pkg:build": "pkg build --strict",
66
+ "pkg:check": "pkg --strict",
67
+ "prepare": "husky install",
68
+ "prepublishOnly": "pnpm build",
69
+ "release": "semantic-release",
70
+ "storybook:build": "storybook build",
71
+ "storybook:dev": "storybook dev -p 6006",
72
+ "test": "jest",
73
+ "test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
74
+ "ts:check": "tsc",
75
+ "watch": "pkg watch --strict",
76
+ "workshop:build": "workshop build",
77
+ "workshop:dev": "workshop dev",
78
+ "workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
79
+ },
58
80
  "commitlint": {
59
81
  "extends": [
60
82
  "@commitlint/config-conventional"
@@ -84,7 +106,7 @@
84
106
  "dependencies": {
85
107
  "@floating-ui/react-dom": "^2.1.0",
86
108
  "@sanity/color": "^3.0.6",
87
- "@sanity/icons": "3.2.1-canary.0",
109
+ "@sanity/icons": "^3.2.0",
88
110
  "csstype": "^3.1.3",
89
111
  "framer-motion": "11.0.8",
90
112
  "react-refractor": "^2.2.0"
@@ -97,7 +119,7 @@
97
119
  "@commitlint/cli": "^19.3.0",
98
120
  "@commitlint/config-conventional": "^19.2.2",
99
121
  "@juggle/resize-observer": "^3.4.0",
100
- "@sanity/pkg-utils": "6.9.4-canary.1",
122
+ "@sanity/pkg-utils": "^6.9.3",
101
123
  "@sanity/semantic-release-preset": "^4.1.8",
102
124
  "@sanity/ui-workshop": "^2.0.15",
103
125
  "@storybook/addon-a11y": "^8.0.8",
@@ -171,6 +193,7 @@
171
193
  "react-is": "^18",
172
194
  "styled-components": "^5.2 || ^6"
173
195
  },
196
+ "packageManager": "pnpm@9.2.0",
174
197
  "engines": {
175
198
  "node": ">=14.0.0"
176
199
  },
@@ -180,28 +203,9 @@
180
203
  "esm.sh": {
181
204
  "bundle": false
182
205
  },
183
- "scripts": {
184
- "build": "run-s clean pkg:build pkg:check figma:pkg:build",
185
- "clean": "rimraf .workshop dist",
186
- "commit": "cz",
187
- "cypress:dev": "run-p dev cypress:open",
188
- "cypress:open": "cypress open",
189
- "cypress:run": "cypress run",
190
- "dev": "run-p storybook:dev workshop:dev",
191
- "figma:pkg:build": "cd figma && pnpm build",
192
- "format": "prettier --write --cache --ignore-unknown .",
193
- "lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
194
- "pkg:build": "pkg build --strict",
195
- "pkg:check": "pkg --strict",
196
- "release": "semantic-release",
197
- "storybook:build": "storybook build",
198
- "storybook:dev": "storybook dev -p 6006",
199
- "test": "jest",
200
- "test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
201
- "ts:check": "tsc",
202
- "watch": "pkg watch --strict",
203
- "workshop:build": "workshop build",
204
- "workshop:dev": "workshop dev",
205
- "workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
206
+ "pnpm": {
207
+ "overrides": {
208
+ "conventional-changelog-conventionalcommits": ">= 8.0.0"
209
+ }
206
210
  }
207
- }
211
+ }
@@ -20,6 +20,7 @@ import {
20
20
  } from 'react'
21
21
  import {EMPTY_ARRAY, EMPTY_RECORD} from '../../constants'
22
22
  import {_hasFocus, _raf, focusFirstDescendant} from '../../helpers'
23
+ import {useArrayProp} from '../../hooks'
23
24
  import {
24
25
  Box,
25
26
  BoxProps,
@@ -31,7 +32,6 @@ import {
31
32
  Text,
32
33
  TextInput,
33
34
  } from '../../primitives'
34
- import {_getArrayProp} from '../../styles'
35
35
  import {Radius} from '../../types'
36
36
  import {AnimatedSpinnerIcon, ListBox, Root} from './autocomplete.styles'
37
37
  import {AutocompleteOption} from './autocompleteOption'
@@ -61,6 +61,8 @@ export interface AutocompleteProps<Option extends BaseAutocompleteOption = BaseA
61
61
  onSelect?: (value: string) => void
62
62
  /** @beta */
63
63
  openButton?: boolean | AutocompleteOpenButtonProps
64
+ /** @beta */
65
+ openOnFocus?: boolean
64
66
  /** The options to render. */
65
67
  options?: Option[]
66
68
  padding?: number | number[]
@@ -138,6 +140,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
138
140
  onQueryChange,
139
141
  onSelect,
140
142
  openButton,
143
+ openOnFocus,
141
144
  options: optionsProp,
142
145
  padding: paddingProp = 3,
143
146
  popover = EMPTY_RECORD,
@@ -200,7 +203,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
200
203
 
201
204
  const listBoxId = `${id}-listbox`
202
205
  const options = Array.isArray(optionsProp) ? optionsProp : EMPTY_ARRAY
203
- const padding = _getArrayProp(paddingProp)
206
+ const padding = useArrayProp(paddingProp)
204
207
  const currentOption = useMemo(
205
208
  () => (value !== null ? options.find((o) => o.value === value) : undefined),
206
209
  [options, value],
@@ -213,41 +216,44 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
213
216
  const activeItemId = activeValue ? `${id}-option-${activeValue}` : undefined
214
217
  const expanded = (query !== null && loading) || (focused && query !== null)
215
218
 
216
- const handleRootBlur = (event: FocusEvent<HTMLInputElement>) => {
217
- setTimeout(() => {
218
- // NOTE: This is a workaround for a bug that may happen in Chrome (clicking the scrollbar
219
- // closes the results in certain situations):
220
- // - Do not handle blur if the mouse is within the popover
221
- if (popoverMouseWithinRef.current) {
222
- return
223
- }
219
+ const handleRootBlur = useCallback(
220
+ (event: FocusEvent<HTMLInputElement>) => {
221
+ setTimeout(() => {
222
+ // NOTE: This is a workaround for a bug that may happen in Chrome (clicking the scrollbar
223
+ // closes the results in certain situations):
224
+ // - Do not handle blur if the mouse is within the popover
225
+ if (popoverMouseWithinRef.current) {
226
+ return
227
+ }
224
228
 
225
- const elements: HTMLElement[] = (relatedElements || []).concat(
226
- rootElementRef.current ? [rootElementRef.current] : [],
227
- resultsPopoverElementRef.current ? [resultsPopoverElementRef.current] : [],
228
- )
229
+ const elements: HTMLElement[] = (relatedElements || []).concat(
230
+ rootElementRef.current ? [rootElementRef.current] : [],
231
+ resultsPopoverElementRef.current ? [resultsPopoverElementRef.current] : [],
232
+ )
229
233
 
230
- let focusInside = false
234
+ let focusInside = false
231
235
 
232
- if (document.activeElement) {
233
- for (const e of elements) {
234
- if (e === document.activeElement || e.contains(document.activeElement)) {
235
- focusInside = true
236
- break
236
+ if (document.activeElement) {
237
+ for (const e of elements) {
238
+ if (e === document.activeElement || e.contains(document.activeElement)) {
239
+ focusInside = true
240
+ break
241
+ }
237
242
  }
238
243
  }
239
- }
240
244
 
241
- if (focusInside === false) {
242
- dispatch({type: 'root/blur'})
243
- popoverMouseWithinRef.current = false
244
- if (onQueryChange) onQueryChange(null)
245
- if (onBlur) onBlur(event)
246
- }
247
- }, 0)
248
- }
245
+ if (focusInside === false) {
246
+ dispatch({type: 'root/blur'})
247
+ popoverMouseWithinRef.current = false
248
+ if (onQueryChange) onQueryChange(null)
249
+ if (onBlur) onBlur(event)
250
+ }
251
+ }, 0)
252
+ },
253
+ [onBlur, onQueryChange, relatedElements],
254
+ )
249
255
 
250
- const handleRootFocus = (event: FocusEvent<HTMLDivElement>) => {
256
+ const handleRootFocus = useCallback((event: FocusEvent<HTMLDivElement>) => {
251
257
  const listBoxElement = listBoxElementRef.current
252
258
  const focusedElement = event.target instanceof HTMLElement ? event.target : null
253
259
  const listFocused = listBoxElement?.contains(focusedElement) || false
@@ -257,7 +263,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
257
263
 
258
264
  dispatch({type: 'root/setListFocused', listFocused})
259
265
  }
260
- }
266
+ }, [])
261
267
 
262
268
  const handleOptionSelect = useCallback(
263
269
  (v: string) => {
@@ -351,15 +357,23 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
351
357
  [onQueryChange],
352
358
  )
353
359
 
360
+ const dispatchOpen = useCallback(() => {
361
+ dispatch({
362
+ type: 'root/open',
363
+ query: value ? renderValue(value, currentOption) : '',
364
+ })
365
+ }, [currentOption, renderValue, value])
366
+
354
367
  const handleInputFocus = useCallback(
355
368
  (event: FocusEvent<HTMLInputElement>) => {
356
369
  if (!focused) {
357
370
  dispatch({type: 'input/focus'})
358
371
 
359
372
  if (onFocus) onFocus(event)
373
+ if (openOnFocus) dispatchOpen()
360
374
  }
361
375
  },
362
- [focused, onFocus],
376
+ [focused, onFocus, openOnFocus, dispatchOpen],
363
377
  )
364
378
 
365
379
  const handlePopoverMouseEnter = useCallback(() => {
@@ -464,16 +478,13 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
464
478
 
465
479
  const handleOpenClick = useCallback(
466
480
  (event: MouseEvent<HTMLButtonElement>) => {
467
- dispatch({
468
- type: 'root/open',
469
- query: value ? renderValue(value, currentOption) : '',
470
- })
481
+ dispatchOpen()
471
482
 
472
483
  if (openButtonProps.onClick) openButtonProps.onClick(event)
473
484
 
474
485
  _raf(() => inputElementRef.current?.focus())
475
486
  },
476
- [currentOption, openButtonProps, renderValue, value],
487
+ [openButtonProps, dispatchOpen],
477
488
  )
478
489
 
479
490
  const openButtonNode = useMemo(
@@ -1,7 +1,6 @@
1
1
  import {Children, forwardRef, Fragment, isValidElement, useCallback, useMemo, useState} from 'react'
2
- import {useClickOutside} from '../../hooks'
2
+ import {useArrayProp, useClickOutside} from '../../hooks'
3
3
  import {Box, Popover, Stack, Text} from '../../primitives'
4
- import {_getArrayProp} from '../../styles'
5
4
  import {ExpandButton, Root} from './breadcrumbs.styles'
6
5
 
7
6
  /**
@@ -21,7 +20,7 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
21
20
  ref: React.ForwardedRef<HTMLOListElement>,
22
21
  ) {
23
22
  const {children, maxLength, separator, space: spaceRaw = 2, ...restProps} = props
24
- const space = useMemo(() => _getArrayProp(spaceRaw), [spaceRaw])
23
+ const space = useArrayProp(spaceRaw)
25
24
  const [open, setOpen] = useState(false)
26
25
  const [expandElement, setExpandElement] = useState<HTMLButtonElement | null>(null)
27
26
  const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
@@ -1,14 +1,6 @@
1
1
  import {CloseIcon} from '@sanity/icons'
2
2
  import {ThemeColorSchemeKey} from '@sanity/ui/theme'
3
- import {
4
- forwardRef,
5
- useCallback,
6
- useEffect,
7
- useImperativeHandle,
8
- useMemo,
9
- useRef,
10
- useState,
11
- } from 'react'
3
+ import {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'
12
4
  import {styled} from 'styled-components'
13
5
  import {
14
6
  containsOrEqualsElement,
@@ -16,10 +8,9 @@ import {
16
8
  focusLastDescendant,
17
9
  isHTMLElement,
18
10
  } from '../../helpers'
19
- import {useClickOutside, useGlobalKeyDown, usePrefersReducedMotion} from '../../hooks'
11
+ import {useArrayProp, useClickOutside, useGlobalKeyDown, usePrefersReducedMotion} from '../../hooks'
20
12
  import {Box, Button, Card, Container, Flex, Text} from '../../primitives'
21
13
  import {ResponsivePaddingProps, ResponsiveWidthProps} from '../../primitives/types'
22
- import {_getArrayProp} from '../../styles'
23
14
  import {responsivePaddingStyle, ResponsivePaddingStyleProps} from '../../styles/internal'
24
15
  import {useTheme_v2} from '../../theme'
25
16
  import {DialogPosition, Radius} from '../../types'
@@ -175,9 +166,9 @@ const DialogCard = forwardRef(function DialogCard(
175
166
  const portal = usePortal()
176
167
  const portalElement = portalProp ? portal.elements?.[portalProp] || null : portal.element
177
168
  const boundaryElement = useBoundaryElement().element
178
- const radius = useMemo(() => _getArrayProp(radiusProp), [radiusProp])
179
- const shadow = useMemo(() => _getArrayProp(shadowProp), [shadowProp])
180
- const width = useMemo(() => _getArrayProp(widthProp), [widthProp])
169
+ const radius = useArrayProp(radiusProp)
170
+ const shadow = useArrayProp(shadowProp)
171
+ const width = useArrayProp(widthProp)
181
172
  const ref = useRef<HTMLDivElement | null>(null)
182
173
  const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
183
174
  const contentRef = useRef<HTMLDivElement | null>(null)
@@ -316,25 +307,23 @@ export const Dialog = forwardRef(function Dialog(
316
307
  onFocus,
317
308
  padding: paddingProp = 3,
318
309
  portal: portalProp,
319
- position: _position,
310
+ position: positionProp = dialog.position || 'fixed',
320
311
  scheme,
321
312
  width: widthProp = 0,
322
- zOffset: _zOffset,
313
+ zOffset: zOffsetProp = dialog.zOffset || layer.dialog.zOffset,
323
314
  animate: _animate = false,
324
315
  ...restProps
325
316
  } = props
326
- const positionProp = _position ?? (dialog.position || 'fixed')
327
- const zOffsetProp = _zOffset ?? (dialog.zOffset || layer.dialog.zOffset)
328
317
  const prefersReducedMotion = usePrefersReducedMotion()
329
318
  const animate = prefersReducedMotion ? false : _animate
330
319
  const portal = usePortal()
331
320
  const portalElement = portalProp ? portal.elements?.[portalProp] || null : portal.element
332
321
  const boundaryElement = useBoundaryElement().element
333
- const cardRadius = useMemo(() => _getArrayProp(cardRadiusProp), [cardRadiusProp])
334
- const padding = useMemo(() => _getArrayProp(paddingProp), [paddingProp])
335
- const position = useMemo(() => _getArrayProp(positionProp), [positionProp])
336
- const width = useMemo(() => _getArrayProp(widthProp), [widthProp])
337
- const zOffset = useMemo(() => _getArrayProp(zOffsetProp), [zOffsetProp])
322
+ const cardRadius = useArrayProp(cardRadiusProp)
323
+ const padding = useArrayProp(paddingProp)
324
+ const position = useArrayProp(positionProp)
325
+ const width = useArrayProp(widthProp)
326
+ const zOffset = useArrayProp(zOffsetProp)
338
327
  const preDivRef = useRef<HTMLDivElement | null>(null)
339
328
  const postDivRef = useRef<HTMLDivElement | null>(null)
340
329
  const cardRef = useRef<HTMLDivElement | null>(null)
@@ -1,7 +1,7 @@
1
- import {forwardRef, useMemo} from 'react'
1
+ import {forwardRef} from 'react'
2
2
  import {styled} from 'styled-components'
3
+ import {useArrayProp} from '../../hooks'
3
4
  import {Inline, KBD} from '../../primitives'
4
- import {_getArrayProp} from '../../styles'
5
5
  import {Radius} from '../../types'
6
6
 
7
7
  /**
@@ -40,7 +40,7 @@ export const Hotkeys = forwardRef(function Hotkeys(
40
40
  ref: React.Ref<HTMLElement>,
41
41
  ) {
42
42
  const {fontSize, keys, padding, radius, space: spaceProp = 0.5, ...restProps} = props
43
- const space = useMemo(() => _getArrayProp(spaceProp), [spaceProp])
43
+ const space = useArrayProp(spaceProp)
44
44
 
45
45
  if (!keys || keys.length === 0) {
46
46
  return <></>
@@ -11,7 +11,6 @@ import {useMenuController} from './useMenuController'
11
11
  * @public
12
12
  */
13
13
  export interface MenuProps extends ResponsivePaddingProps {
14
- 'aria-labelledby'?: string
15
14
  /**
16
15
  * @deprecated Use `shouldFocus="first"` instead.
17
16
  */
@@ -21,7 +20,6 @@ export interface MenuProps extends ResponsivePaddingProps {
21
20
  */
22
21
  focusLast?: boolean
23
22
  onClickOutside?: (event: MouseEvent) => void
24
- onBlurCapture?: (event: React.FocusEvent<HTMLButtonElement>) => void
25
23
  onEscape?: () => void
26
24
  onItemClick?: () => void
27
25
  onItemSelect?: (index: number) => void
@@ -59,12 +57,10 @@ export const Menu = forwardRef(function Menu(
59
57
  originElement,
60
58
  padding = 1,
61
59
  registerElement,
62
- shouldFocus: _shouldFocus,
60
+ shouldFocus = (props.focusFirst && 'first') || (props.focusLast && 'last') || null,
63
61
  space = 1,
64
62
  ...restProps
65
63
  } = props
66
- const shouldFocus =
67
- _shouldFocus ?? ((props.focusFirst && 'first') || (props.focusLast && 'last') || null)
68
64
 
69
65
  const ref = useRef<HTMLDivElement | null>(null)
70
66
 
@@ -203,9 +203,8 @@ export const MenuButton = forwardRef(function MenuButton(
203
203
  }
204
204
  }, [])
205
205
 
206
- const menu =
207
- menuProp &&
208
- cloneElement<MenuProps>(menuProp, {
206
+ const menuProps: MenuProps = useMemo(
207
+ () => ({
209
208
  'aria-labelledby': id,
210
209
  onBlurCapture: handleBlur,
211
210
  onClickOutside: handleMenuClickOutside,
@@ -214,8 +213,22 @@ export const MenuButton = forwardRef(function MenuButton(
214
213
  originElement: buttonElement,
215
214
  registerElement,
216
215
  shouldFocus,
217
- })
216
+ }),
217
+ [
218
+ buttonElement,
219
+ handleMenuClickOutside,
220
+ handleMenuEscape,
221
+ handleItemClick,
222
+ id,
223
+ handleBlur,
224
+ registerElement,
225
+ shouldFocus,
226
+ ],
227
+ )
228
+
229
+ const menu = menuProp && cloneElement(menuProp, menuProps)
218
230
 
231
+ const ref = useRef<HTMLButtonElement | null>(null)
219
232
  const button = useMemo(
220
233
  () =>
221
234
  buttonProp &&
@@ -227,7 +240,7 @@ export const MenuButton = forwardRef(function MenuButton(
227
240
  onMouseDown: handleMouseDown,
228
241
  'aria-haspopup': true,
229
242
  'aria-expanded': open,
230
- ref: setButtonElement,
243
+ ref: ref,
231
244
  selected: buttonProp.props.selected ?? open,
232
245
  }),
233
246
  [buttonProp, handleButtonClick, handleButtonKeyDown, handleMouseDown, id, open],
@@ -236,10 +249,19 @@ export const MenuButton = forwardRef(function MenuButton(
236
249
  // Forward button ref to parent
237
250
  useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
238
251
  forwardedRef,
239
- () => buttonElement,
240
- [buttonElement],
252
+ () => ref.current,
241
253
  )
242
254
 
255
+ // If there's a button then we need to set the reference element to the cloned button ref
256
+ // and if button changes we make sure to update or remove the reference element.
257
+ useEffect(() => {
258
+ if (!button) return undefined
259
+
260
+ setButtonElement(ref.current)
261
+
262
+ return () => setButtonElement(null)
263
+ }, [button])
264
+
243
265
  const popoverProps: MenuButtonProps['popover'] = useMemo(
244
266
  () => ({
245
267
  boundaryElement: deprecated_boundaryElement,
@@ -263,15 +285,8 @@ export const MenuButton = forwardRef(function MenuButton(
263
285
  )
264
286
 
265
287
  return (
266
- <>
267
- <Popover
268
- data-ui="MenuButton__popover"
269
- referenceElement={buttonElement}
270
- {...popoverProps}
271
- content={menu}
272
- open={open}
273
- />
274
- {button}
275
- </>
288
+ <Popover data-ui="MenuButton__popover" {...popoverProps} content={menu} open={open}>
289
+ {button || <></>}
290
+ </Popover>
276
291
  )
277
292
  })
@@ -1,9 +1,9 @@
1
1
  import {ChevronRightIcon} from '@sanity/icons'
2
- import {isValidElement, useCallback, useEffect, useMemo, useRef, useState} from 'react'
2
+ import {isValidElement, useCallback, useEffect, useRef, useState} from 'react'
3
3
  import {isValidElementType} from 'react-is'
4
+ import {useArrayProp} from '../../hooks'
4
5
  import {Box, Flex, Popover, PopoverProps, Text} from '../../primitives'
5
6
  import {Selectable} from '../../primitives/_selectable'
6
- import {_getArrayProp} from '../../styles'
7
7
  import {useRootTheme} from '../../theme'
8
8
  import {Radius, SelectableTone} from '../../types'
9
9
  import {Menu} from './menu'
@@ -39,7 +39,7 @@ export function MenuGroup(
39
39
  onClick,
40
40
  padding = 3,
41
41
  popover,
42
- radius: radiusProp = 2,
42
+ radius = 2,
43
43
  space = 3,
44
44
  text,
45
45
  tone = 'default',
@@ -53,16 +53,14 @@ export function MenuGroup(
53
53
  onClickOutside,
54
54
  onEscape,
55
55
  onItemClick,
56
- onItemMouseEnter: _onItemMouseEnter,
56
+ onItemMouseEnter = menu.onMouseEnter,
57
57
  registerElement,
58
58
  } = menu
59
- const onItemMouseEnter = _onItemMouseEnter ?? menu.onMouseEnter
60
59
  const [rootElement, setRootElement] = useState<HTMLButtonElement | HTMLDivElement | null>(null)
61
60
  const [open, setOpen] = useState(false)
62
61
  const shouldFocusRef = useRef<'first' | 'last' | null>(null)
63
62
  const active = Boolean(activeElement) && activeElement === rootElement
64
63
  const [withinMenu, setWithinMenu] = useState(false)
65
- const radius = useMemo(() => _getArrayProp(radiusProp), [radiusProp])
66
64
 
67
65
  const handleMouseEnter = useCallback(
68
66
  (event: React.MouseEvent<HTMLElement>) => {
@@ -168,7 +166,7 @@ export function MenuGroup(
168
166
  aria-pressed={as === 'button' ? withinMenu : undefined}
169
167
  data-pressed={as !== 'button' ? withinMenu : undefined}
170
168
  data-selected={!withinMenu && active ? '' : undefined}
171
- $radius={radius}
169
+ $radius={useArrayProp(radius)}
172
170
  $tone={tone}
173
171
  $scheme={scheme}
174
172
  onClick={handleClick}
@@ -9,10 +9,10 @@ import {
9
9
  useState,
10
10
  } from 'react'
11
11
  import {isValidElementType} from 'react-is'
12
+ import {useArrayProp} from '../../hooks'
12
13
  import {Box, Flex, Text} from '../../primitives'
13
14
  import {Selectable} from '../../primitives/_selectable'
14
15
  import {ResponsivePaddingProps, ResponsiveRadiusProps} from '../../primitives/types'
15
- import {_getArrayProp} from '../../styles'
16
16
  import {useRootTheme} from '../../theme'
17
17
  import {SelectableTone} from '../../types/selectable'
18
18
  import {Hotkeys} from '../hotkeys'
@@ -72,11 +72,9 @@ export const MenuItem = forwardRef(function MenuItem(
72
72
  activeElement,
73
73
  mount,
74
74
  onItemClick,
75
- onItemMouseEnter: _onItemMouseEnter,
76
- onItemMouseLeave: _onItemMouseLeave,
75
+ onItemMouseEnter = menu.onMouseEnter,
76
+ onItemMouseLeave = menu.onMouseLeave,
77
77
  } = menu
78
- const onItemMouseEnter = _onItemMouseEnter ?? menu.onMouseEnter
79
- const onItemMouseLeave = _onItemMouseLeave ?? menu.onMouseLeave
80
78
  const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
81
79
  const active = Boolean(activeElement) && activeElement === rootElement
82
80
  const ref = useRef<HTMLDivElement | null>(null)
@@ -107,9 +105,7 @@ export const MenuItem = forwardRef(function MenuItem(
107
105
  [padding, paddingX, paddingY, paddingTop, paddingRight, paddingBottom, paddingLeft],
108
106
  )
109
107
 
110
- const hotkeysFontSize = useMemo(() => _getArrayProp(fontSize).map((s) => s - 1), [fontSize])
111
- const $radius = useMemo(() => _getArrayProp(radius), [radius])
112
- const $padding = useMemo(() => _getArrayProp(0), [])
108
+ const hotkeysFontSize = useArrayProp(fontSize).map((s) => s - 1)
113
109
 
114
110
  const setRef = useCallback((el: HTMLDivElement | null) => {
115
111
  ref.current = el
@@ -125,8 +121,8 @@ export const MenuItem = forwardRef(function MenuItem(
125
121
  data-selected={active ? '' : undefined}
126
122
  data-disabled={disabled ? '' : undefined}
127
123
  forwardedAs={as}
128
- $radius={$radius}
129
- $padding={$padding}
124
+ $radius={useArrayProp(radius)}
125
+ $padding={useArrayProp(0)}
130
126
  $tone={disabled ? 'default' : tone}
131
127
  $scheme={scheme}
132
128
  disabled={disabled}
@@ -1,8 +1,8 @@
1
- import {forwardRef, useEffect, useMemo, useState} from 'react'
1
+ import {forwardRef, useEffect, useState} from 'react'
2
2
  import {styled} from 'styled-components'
3
+ import {useArrayProp} from '../../hooks'
3
4
  import {Box} from '../../primitives'
4
5
  import {BoxProps, ResponsiveRadiusProps} from '../../primitives'
5
- import {_getArrayProp} from '../../styles'
6
6
  import {responsiveRadiusStyle, ResponsiveRadiusStyleProps} from '../../styles/internal'
7
7
  import {skeletonStyle} from './styles'
8
8
 
@@ -45,7 +45,13 @@ export const Skeleton = forwardRef(function Skeleton(
45
45
  }
46
46
  }, [delay])
47
47
 
48
- const $radius = useMemo(() => _getArrayProp(radius), [radius])
49
-
50
- return <Root {...restProps} $animated={animated} $radius={$radius} $visible={visible} ref={ref} />
48
+ return (
49
+ <Root
50
+ {...restProps}
51
+ $animated={animated}
52
+ $radius={useArrayProp(radius)}
53
+ $visible={visible}
54
+ ref={ref}
55
+ />
56
+ )
51
57
  })