@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
@@ -1,7 +1,8 @@
1
1
  import {ThemeFontKey, getTheme_v2} from '@sanity/ui/theme'
2
- import {forwardRef, useMemo} from 'react'
2
+ import {forwardRef} from 'react'
3
3
  import {styled} from 'styled-components'
4
- import {ThemeProps, _getArrayProp, _responsive} from '../../styles'
4
+ import {useArrayProp} from '../../hooks'
5
+ import {ThemeProps, _responsive} from '../../styles'
5
6
  import {Skeleton, SkeletonProps} from './skeleton'
6
7
 
7
8
  const Root = styled(Skeleton)<{$size: number[]; $style: ThemeFontKey}>((
@@ -66,7 +67,7 @@ export const TextSkeleton = forwardRef(function TextSkeleton(
66
67
  ref: React.Ref<HTMLDivElement>,
67
68
  ) {
68
69
  const {size = 2, ...restProps} = props
69
- const $size = useMemo(() => _getArrayProp(size), [size])
70
+ const $size = useArrayProp(size)
70
71
 
71
72
  return <Root {...restProps} $size={$size} ref={ref} $style="text" />
72
73
  })
@@ -81,7 +82,7 @@ export const LabelSkeleton = forwardRef(function TextSkeleton(
81
82
  ref: React.Ref<HTMLDivElement>,
82
83
  ) {
83
84
  const {size = 2, ...restProps} = props
84
- const $size = useMemo(() => _getArrayProp(size), [size])
85
+ const $size = useArrayProp(size)
85
86
 
86
87
  return <Root {...restProps} $size={$size} ref={ref} $style="label" />
87
88
  })
@@ -96,7 +97,7 @@ export const HeadingSkeleton = forwardRef(function TextSkeleton(
96
97
  ref: React.Ref<HTMLDivElement>,
97
98
  ) {
98
99
  const {size = 2, ...restProps} = props
99
- const $size = useMemo(() => _getArrayProp(size), [size])
100
+ const $size = useArrayProp(size)
100
101
 
101
102
  return <Root {...restProps} $size={$size} ref={ref} $style="heading" />
102
103
  })
@@ -111,7 +112,7 @@ export const CodeSkeleton = forwardRef(function TextSkeleton(
111
112
  ref: React.Ref<HTMLDivElement>,
112
113
  ) {
113
114
  const {size = 2, ...restProps} = props
114
- const $size = useMemo(() => _getArrayProp(size), [size])
115
+ const $size = useArrayProp(size)
115
116
 
116
117
  return <Root {...restProps} $size={$size} ref={ref} $style="code" />
117
118
  })
@@ -0,0 +1,61 @@
1
+ import {styled, keyframes, css} from 'styled-components'
2
+ import {ThemeColorStateToneKey, getTheme_v2} from '../../../theme'
3
+ import {POPOVER_MOTION_CONTENT_OPACITY_PROPERTY} from '../../constants'
4
+ import {Flex} from '../../primitives'
5
+ import {ThemeProps} from '../../styles'
6
+
7
+ export const TextBox = styled(Flex)`
8
+ overflow-x: auto;
9
+ `
10
+
11
+ const loadingAnimation = keyframes`
12
+ 0% {
13
+ width: 0;
14
+ }
15
+ 100% {
16
+ width: 100%;
17
+ }
18
+ `
19
+
20
+ const LOADING_BAR_HEIGHT = 2
21
+
22
+ export function rootStyles(
23
+ props: {$duration?: number; tone: ThemeColorStateToneKey} & ThemeProps,
24
+ ): ReturnType<typeof css> {
25
+ const {color} = getTheme_v2(props.theme)
26
+
27
+ const loadingBarColor = color.button.default[props.tone].enabled.bg
28
+
29
+ if (!props.$duration)
30
+ return css`
31
+ pointer-events: all;
32
+ & > * {
33
+ opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1);
34
+ will-change: opacity;
35
+ }
36
+ `
37
+
38
+ return css`
39
+ pointer-events: all;
40
+ width: 100%;
41
+ position: relative;
42
+ overflow: hidden;
43
+ overflow: clip;
44
+ padding-bottom: ${LOADING_BAR_HEIGHT}px;
45
+ &::before {
46
+ content: '';
47
+ position: absolute;
48
+ bottom: 0px;
49
+ height: ${LOADING_BAR_HEIGHT}px;
50
+ background: ${loadingBarColor};
51
+ animation-name: ${loadingAnimation};
52
+ animation-duration: ${props.$duration}ms;
53
+ animation-fill-mode: both;
54
+ }
55
+
56
+ & > * {
57
+ opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1);
58
+ will-change: opacity;
59
+ }
60
+ `
61
+ }
@@ -1,9 +1,10 @@
1
1
  import {CloseIcon} from '@sanity/icons'
2
- import {ThemeColorToneKey} from '@sanity/ui/theme'
2
+ import {ThemeColorStateToneKey} from '@sanity/ui/theme'
3
3
  import {styled} from 'styled-components'
4
- import {POPOVER_MOTION_CONTENT_OPACITY_PROPERTY} from '../../constants'
5
- import {Box, Button, Card, Flex, Stack, Text} from '../../primitives'
4
+ import {Box, Button, Flex, Stack, Text, Card} from '../../primitives'
5
+ import {ThemeProps} from '../../styles'
6
6
  import type {ButtonTone} from '../../types'
7
+ import {rootStyles, TextBox} from './styles'
7
8
 
8
9
  /**
9
10
  * @public
@@ -15,9 +16,10 @@ export interface ToastProps {
15
16
  radius?: number | number[]
16
17
  title?: React.ReactNode
17
18
  status?: 'error' | 'warning' | 'success' | 'info'
19
+ duration?: number
18
20
  }
19
21
 
20
- const STATUS_CARD_TONE: {[key: string]: ThemeColorToneKey} = {
22
+ const STATUS_CARD_TONE: {[key: string]: ThemeColorStateToneKey} = {
21
23
  error: 'critical',
22
24
  warning: 'caution',
23
25
  success: 'positive',
@@ -38,17 +40,9 @@ const ROLES = {
38
40
  info: 'alert',
39
41
  } as const
40
42
 
41
- const Root = styled(Card)`
42
- pointer-events: all;
43
- & > * {
44
- opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1);
45
- will-change: opacity;
46
- }
47
- `
48
-
49
- const TextBox = styled(Flex)`
50
- overflow-x: auto;
51
- `
43
+ const Root = styled(Card)<{$duration?: number; tone: ThemeColorStateToneKey} & ThemeProps>(
44
+ rootStyles,
45
+ )
52
46
 
53
47
  /**
54
48
  * The `Toast` component gives feedback to users when an action has taken place.
@@ -60,7 +54,7 @@ const TextBox = styled(Flex)`
60
54
  export function Toast(
61
55
  props: ToastProps & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'height' | 'ref' | 'title'>,
62
56
  ): React.ReactElement {
63
- const {closable, description, onClose, radius = 3, title, status, ...restProps} = props
57
+ const {closable, description, duration, onClose, radius = 3, title, status, ...restProps} = props
64
58
  const cardTone = status ? STATUS_CARD_TONE[status] : 'default'
65
59
  const buttonTone = status ? BUTTON_TONE[status] : 'default'
66
60
  const role = status ? ROLES[status] : 'status'
@@ -74,6 +68,7 @@ export function Toast(
74
68
  radius={radius}
75
69
  shadow={2}
76
70
  tone={cardTone}
71
+ $duration={duration}
77
72
  >
78
73
  <Flex align="flex-start">
79
74
  <TextBox flex={1} padding={3}>
@@ -45,6 +45,8 @@ const ToastContainer = styled.div`
45
45
  width: 100%;
46
46
  `
47
47
 
48
+ let toastId = 0
49
+
48
50
  /**
49
51
  * @public
50
52
  */
@@ -55,7 +57,27 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
55
57
  const mounted = useMounted()
56
58
  const prefersReducedMotion = usePrefersReducedMotion()
57
59
  const variants = useMemo<Variants>(
58
- () => getVariants(prefersReducedMotion),
60
+ () => ({
61
+ initial: {
62
+ opacity: 0,
63
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: 0,
64
+ y: 32,
65
+ scale: 0.25,
66
+ willChange: 'transform',
67
+ },
68
+ animate: {
69
+ opacity: [0, 1, 1],
70
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [0, 0, 1],
71
+ y: 0,
72
+ scale: 1,
73
+ },
74
+ exit: {
75
+ opacity: [1, 1, 0],
76
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [1, 0, 0],
77
+ scale: 0.5,
78
+ transition: prefersReducedMotion ? {duration: 0} : {duration: 0.2},
79
+ },
80
+ }),
59
81
  [prefersReducedMotion],
60
82
  )
61
83
 
@@ -64,7 +86,7 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
64
86
  // Wrap setState in startTransition to allow React to give input state updates higher priority
65
87
  const setState: typeof _setState = (state) => startTransition(() => _setState(state))
66
88
 
67
- const id = params.id || crypto.randomUUID()
89
+ const id = params.id || String(toastId++)
68
90
  const duration = params.duration || 5000
69
91
 
70
92
  const dismiss = () => {
@@ -155,6 +177,7 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
155
177
  onClose={dismiss}
156
178
  status={params.status}
157
179
  title={params.title}
180
+ duration={params.duration}
158
181
  />
159
182
  </motion.div>
160
183
  ))}
@@ -166,27 +189,3 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
166
189
  </ToastContext.Provider>
167
190
  )
168
191
  }
169
-
170
- function getVariants(prefersReducedMotion: boolean) {
171
- return {
172
- initial: {
173
- opacity: 0,
174
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: 0,
175
- y: 32,
176
- scale: 0.25,
177
- willChange: 'transform',
178
- },
179
- animate: {
180
- opacity: [0, 1, 1],
181
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [0, 0, 1],
182
- y: 0,
183
- scale: 1,
184
- },
185
- exit: {
186
- opacity: [1, 1, 0],
187
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [1, 0, 0],
188
- scale: 0.5,
189
- transition: {duration: prefersReducedMotion ? 0 : 0.2},
190
- },
191
- }
192
- }
@@ -1,10 +1,16 @@
1
1
  import {ToggleArrowRightIcon} from '@sanity/icons'
2
2
  import {ThemeFontWeightKey} from '@sanity/ui/theme'
3
3
  import {memo, useCallback, useEffect, useId, useMemo, useRef} from 'react'
4
+ import {styled} from 'styled-components'
4
5
  import {Box, BoxProps, Flex, Text} from '../../primitives'
6
+ import {
7
+ treeItemRootStyle,
8
+ treeItemRootColorStyle,
9
+ treeItemBoxStyle,
10
+ TreeItemBoxStyleProps,
11
+ } from './style'
5
12
  import {TreeContext} from './treeContext'
6
13
  import {TreeGroup} from './treeGroup'
7
- import {Root, ToggleArrowText, TreeItemBox} from './treeItem.styles'
8
14
  import {useTree} from './useTree'
9
15
 
10
16
  /**
@@ -24,6 +30,16 @@ export interface TreeItemProps {
24
30
  weight?: ThemeFontWeightKey
25
31
  }
26
32
 
33
+ const Root = memo(styled.li(treeItemRootStyle, treeItemRootColorStyle))
34
+
35
+ const TreeItemBox = styled(Box).attrs({forwardedAs: 'a'})<TreeItemBoxStyleProps>(treeItemBoxStyle)
36
+
37
+ const ToggleArrowText = styled(Text)`
38
+ & > svg {
39
+ transition: transform 100ms;
40
+ }
41
+ `
42
+
27
43
  /**
28
44
  * This API might change. DO NOT USE IN PRODUCTION.
29
45
  * @beta
@@ -54,7 +70,7 @@ export const TreeItem = memo(function TreeItem(
54
70
  const {path, registerItem, setExpanded, setFocusedElement} = tree
55
71
  const _id = useId()
56
72
  const id = idProp || _id
57
- const itemPath = useMemo(() => [...path, id || ''], [id, path])
73
+ const itemPath = useMemo(() => path.concat([id || '']), [id, path])
58
74
  const itemKey = itemPath.join('/')
59
75
  const itemState = tree.state[itemKey]
60
76
  const focused = tree.focusedElement === rootRef.current
@@ -67,7 +83,7 @@ export const TreeItem = memo(function TreeItem(
67
83
 
68
84
  const handleClick = useCallback(
69
85
  (event: React.MouseEvent<HTMLLIElement>) => {
70
- onClick?.(event)
86
+ if (onClick) onClick(event)
71
87
 
72
88
  const target = event.target
73
89
 
@@ -101,35 +117,32 @@ export const TreeItem = memo(function TreeItem(
101
117
  return registerItem(rootRef.current, itemPath.join('/'), expanded, selected)
102
118
  }, [expanded, itemPath, registerItem, selected])
103
119
 
104
- const content = useMemo(
105
- () => (
106
- <Flex padding={padding}>
107
- <Box
108
- marginRight={space}
109
- style={{
110
- visibility: IconComponent || children ? 'visible' : 'hidden',
111
- pointerEvents: 'none',
112
- }}
113
- >
114
- {IconComponent && (
115
- <Text muted={muted} size={fontSize} weight={weight}>
116
- <IconComponent />
117
- </Text>
118
- )}
119
- {!IconComponent && (
120
- <ToggleArrowText muted={muted} size={fontSize} weight={weight}>
121
- <ToggleArrowRightIcon style={{transform: expanded ? 'rotate(90deg)' : undefined}} />
122
- </ToggleArrowText>
123
- )}
124
- </Box>
125
- <Box flex={1}>
126
- <Text muted={muted} size={fontSize} textOverflow="ellipsis" weight={weight}>
127
- {text}
120
+ const content = (
121
+ <Flex padding={padding}>
122
+ <Box
123
+ marginRight={space}
124
+ style={{
125
+ visibility: IconComponent || children ? 'visible' : 'hidden',
126
+ pointerEvents: 'none',
127
+ }}
128
+ >
129
+ {IconComponent && (
130
+ <Text muted={muted} size={fontSize} weight={weight}>
131
+ <IconComponent />
128
132
  </Text>
129
- </Box>
130
- </Flex>
131
- ),
132
- [IconComponent, children, expanded, fontSize, muted, padding, space, text, weight],
133
+ )}
134
+ {!IconComponent && (
135
+ <ToggleArrowText muted={muted} size={fontSize} weight={weight}>
136
+ <ToggleArrowRightIcon style={{transform: expanded ? 'rotate(90deg)' : undefined}} />
137
+ </ToggleArrowText>
138
+ )}
139
+ </Box>
140
+ <Box flex={1}>
141
+ <Text muted={muted} size={fontSize} textOverflow="ellipsis" weight={weight}>
142
+ {text}
143
+ </Text>
144
+ </Box>
145
+ </Flex>
133
146
  )
134
147
 
135
148
  if (href) {
@@ -7,20 +7,19 @@ export type ArrayPropPrimitive = string | number | boolean | undefined | null
7
7
  /**
8
8
  * This API might change. DO NOT USE IN PRODUCTION.
9
9
  * @beta
10
- * @deprecated use `_getArrayProp` and `useMemo` directly instead
11
- * @example
12
- * ```diff
13
- * -import {useArrayProp} from '@sanity/ui'
14
- * +import {_getArrayProp} from '@sanity/ui'
15
- * +import {useMemo} from 'react'
16
- *
17
- * -const width = useArrayProp(props.width)
18
- * +const width = useMemo(() => _getArrayProp(props.width), [props.width])
19
- * ```
20
10
  */
21
11
  export function useArrayProp<T extends ArrayPropPrimitive = ArrayPropPrimitive>(
22
12
  val: T | T[] | undefined,
23
13
  defaultVal?: T[],
24
14
  ): T[] {
25
- return useMemo(() => _getArrayProp(val, defaultVal), [defaultVal, val])
15
+ // JSON.stringify is fast, but it's not faster than useMemo's referencial equality check
16
+ const __perf_hash__ = useMemo(() => JSON.stringify(val ?? defaultVal), [defaultVal, val])
17
+
18
+ return useMemo(
19
+ () => _getArrayProp(val, defaultVal),
20
+
21
+ // Improve performance: Keep object identify for a given hash of the value
22
+ // eslint-disable-next-line react-hooks/exhaustive-deps
23
+ [__perf_hash__],
24
+ )
26
25
  }
@@ -1,8 +1,8 @@
1
1
  import {ThemeColorAvatarColorKey} from '@sanity/ui/theme'
2
- import {forwardRef, useCallback, useEffect, useId, useState} from 'react'
3
- import {isValidElementType} from 'react-is'
2
+ import {forwardRef, useCallback, useEffect, useId, useMemo, useState} from 'react'
3
+ import ReactIs from 'react-is'
4
4
  import {styled} from 'styled-components'
5
- import {_getArrayProp} from '../../styles'
5
+ import {useArrayProp} from '../../hooks'
6
6
  import {useTheme_v2} from '../../theme'
7
7
  import {AvatarPosition, AvatarSize, AvatarStatus} from '../../types'
8
8
  import {Label} from '../label'
@@ -73,8 +73,8 @@ export const Avatar = forwardRef(function Avatar(
73
73
  ...restProps
74
74
  } = props
75
75
  const {avatar} = useTheme_v2()
76
- const as = isValidElementType(asProp) ? asProp : 'div'
77
- const size = _getArrayProp(sizeProp)
76
+ const as = ReactIs.isValidElementType(asProp) ? asProp : 'div'
77
+ const size = useArrayProp(sizeProp)
78
78
 
79
79
  // @todo: remove this
80
80
  const avatarSize = avatar.sizes[size[0]] || avatar.sizes[0]
@@ -111,13 +111,17 @@ export const Avatar = forwardRef(function Avatar(
111
111
  }
112
112
  }, [onImageLoadError])
113
113
 
114
- const initialsSize = size.map((s) => {
115
- if (s === 1) return 1
116
- if (s === 2) return 3
117
- if (s === 3) return 5
114
+ const initialsSize = useMemo(
115
+ () =>
116
+ size.map((s) => {
117
+ if (s === 1) return 1
118
+ if (s === 2) return 3
119
+ if (s === 3) return 5
118
120
 
119
- return 0
120
- })
121
+ return 0
122
+ }),
123
+ [size],
124
+ )
121
125
 
122
126
  return (
123
127
  <Root
@@ -1,8 +1,9 @@
1
1
  import {getTheme_v2} from '@sanity/ui/theme'
2
- import {forwardRef} from 'react'
2
+ import {forwardRef, useMemo} from 'react'
3
3
  import {styled, css} from 'styled-components'
4
4
  import {EMPTY_RECORD} from '../../constants'
5
- import {rem, _responsive, ThemeProps, _getArrayProp} from '../../styles'
5
+ import {useArrayProp} from '../../hooks'
6
+ import {rem, _responsive, ThemeProps} from '../../styles'
6
7
  import {AvatarSize} from '../../types'
7
8
  import {Label} from '../label'
8
9
 
@@ -66,18 +67,23 @@ export const AvatarCounter = forwardRef(function AvatarCounter(
66
67
  props: AvatarCounterProps,
67
68
  ref: React.Ref<HTMLDivElement>,
68
69
  ) {
69
- const {count, size} = props
70
- const $size = _getArrayProp(size ?? 1)
71
- const fontSize = $size.map((s) => {
72
- if (s === 1) return 1
73
- if (s === 2) return 3
74
- if (s === 3) return 5
70
+ const {count, size: sizeProp = 1} = props
71
+ const size = useArrayProp(sizeProp)
75
72
 
76
- return 0
77
- })
73
+ const fontSize = useMemo(
74
+ () =>
75
+ size.map((s) => {
76
+ if (s === 1) return 1
77
+ if (s === 2) return 3
78
+ if (s === 3) return 5
79
+
80
+ return 0
81
+ }),
82
+ [size],
83
+ )
78
84
 
79
85
  return (
80
- <Root $size={$size} data-ui="AvatarCounter" ref={ref}>
86
+ <Root $size={size} data-ui="AvatarCounter" ref={ref}>
81
87
  <Label as="span" size={fontSize} weight="medium">
82
88
  {count}
83
89
  </Label>
@@ -2,7 +2,8 @@ import {getTheme_v2} from '@sanity/ui/theme'
2
2
  import {Children, cloneElement, forwardRef, isValidElement, useMemo} from 'react'
3
3
  import {styled, css} from 'styled-components'
4
4
  import {EMPTY_RECORD} from '../../constants'
5
- import {rem, _responsive, ThemeProps, _getArrayProp} from '../../styles'
5
+ import {useArrayProp} from '../../hooks'
6
+ import {rem, _responsive, ThemeProps} from '../../styles'
6
7
  import {AvatarSize} from '../../types'
7
8
  import {AvatarCounter} from './avatarCounter'
8
9
 
@@ -64,9 +65,12 @@ export const AvatarStack = forwardRef(function AvatarStack(
64
65
  size: sizeProp = 1,
65
66
  ...restProps
66
67
  } = props
67
- const children: React.ReactElement[] = Children.toArray(childrenProp).filter(isValidElement)
68
+ const children = useMemo<React.ReactElement[]>(
69
+ () => Children.toArray(childrenProp).filter(isValidElement),
70
+ [childrenProp],
71
+ )
68
72
  const maxLength = Math.max(maxLengthProp, 0)
69
- const size = useMemo(() => _getArrayProp(sizeProp), [sizeProp])
73
+ const size = useArrayProp(sizeProp)
70
74
 
71
75
  const len = children.length
72
76
  const visibleCount = maxLength - 1
@@ -1,6 +1,6 @@
1
- import {forwardRef, useMemo} from 'react'
1
+ import {forwardRef} from 'react'
2
2
  import {styled} from 'styled-components'
3
- import {_getArrayProp} from '../../styles'
3
+ import {useArrayProp} from '../../hooks'
4
4
  import {responsiveRadiusStyle, ResponsiveRadiusStyleProps} from '../../styles/internal'
5
5
  import {BadgeMode, BadgeTone} from '../../types'
6
6
  import {Box, BoxProps} from '../box'
@@ -45,16 +45,13 @@ export const Badge = forwardRef(function Badge(
45
45
  ...restProps
46
46
  } = props
47
47
 
48
- const $radius = useMemo(() => _getArrayProp(radius), [radius])
49
- const $padding = useMemo(() => _getArrayProp(padding), [padding])
50
-
51
48
  return (
52
49
  <Root
53
50
  data-ui="Badge"
54
51
  {...restProps}
55
52
  $tone={tone}
56
- $radius={$radius}
57
- padding={$padding}
53
+ $radius={useArrayProp(radius)}
54
+ padding={useArrayProp(padding)}
58
55
  ref={ref}
59
56
  >
60
57
  <Text size={fontSize}>{children}</Text>
@@ -1,6 +1,6 @@
1
- import {forwardRef, useMemo} from 'react'
1
+ import {forwardRef} from 'react'
2
2
  import {styled} from 'styled-components'
3
- import {_getArrayProp} from '../../styles'
3
+ import {useArrayProp} from '../../hooks'
4
4
  import {
5
5
  boxStyle,
6
6
  flexItemStyle,
@@ -90,62 +90,36 @@ export const Box = forwardRef(function Box(
90
90
  ...restProps
91
91
  } = props
92
92
 
93
- const $column = useMemo(() => _getArrayProp(column), [column])
94
- const $columnStart = useMemo(() => _getArrayProp(columnStart), [columnStart])
95
- const $columnEnd = useMemo(() => _getArrayProp(columnEnd), [columnEnd])
96
- const $display = useMemo(() => _getArrayProp(display), [display])
97
- const $flex = useMemo(() => _getArrayProp(flex), [flex])
98
- const $height = useMemo(() => _getArrayProp(height), [height])
99
- const $margin = useMemo(() => _getArrayProp(margin), [margin])
100
- const $marginX = useMemo(() => _getArrayProp(marginX), [marginX])
101
- const $marginY = useMemo(() => _getArrayProp(marginY), [marginY])
102
- const $marginTop = useMemo(() => _getArrayProp(marginTop), [marginTop])
103
- const $marginRight = useMemo(() => _getArrayProp(marginRight), [marginRight])
104
- const $marginBottom = useMemo(() => _getArrayProp(marginBottom), [marginBottom])
105
- const $marginLeft = useMemo(() => _getArrayProp(marginLeft), [marginLeft])
106
- const $overflow = useMemo(() => _getArrayProp(overflow), [overflow])
107
- const $padding = useMemo(() => _getArrayProp(padding), [padding])
108
- const $paddingX = useMemo(() => _getArrayProp(paddingX), [paddingX])
109
- const $paddingY = useMemo(() => _getArrayProp(paddingY), [paddingY])
110
- const $paddingTop = useMemo(() => _getArrayProp(paddingTop), [paddingTop])
111
- const $paddingRight = useMemo(() => _getArrayProp(paddingRight), [paddingRight])
112
- const $paddingBottom = useMemo(() => _getArrayProp(paddingBottom), [paddingBottom])
113
- const $paddingLeft = useMemo(() => _getArrayProp(paddingLeft), [paddingLeft])
114
- const $row = useMemo(() => _getArrayProp(row), [row])
115
- const $rowStart = useMemo(() => _getArrayProp(rowStart), [rowStart])
116
- const $rowEnd = useMemo(() => _getArrayProp(rowEnd), [rowEnd])
117
- const $sizing = useMemo(() => _getArrayProp(sizing), [sizing])
118
-
119
93
  return (
120
94
  <Root
121
95
  data-as={typeof asProp === 'string' ? asProp : undefined}
122
96
  data-ui="Box"
123
97
  {...restProps}
124
- $column={$column}
125
- $columnStart={$columnStart}
126
- $columnEnd={$columnEnd}
127
- $display={$display}
128
- $flex={$flex}
129
- $height={$height}
130
- $margin={$margin}
131
- $marginX={$marginX}
132
- $marginY={$marginY}
133
- $marginTop={$marginTop}
134
- $marginRight={$marginRight}
135
- $marginBottom={$marginBottom}
136
- $marginLeft={$marginLeft}
137
- $overflow={$overflow}
138
- $padding={$padding}
139
- $paddingX={$paddingX}
140
- $paddingY={$paddingY}
141
- $paddingTop={$paddingTop}
142
- $paddingRight={$paddingRight}
143
- $paddingBottom={$paddingBottom}
144
- $paddingLeft={$paddingLeft}
145
- $row={$row}
146
- $rowStart={$rowStart}
147
- $rowEnd={$rowEnd}
148
- $sizing={$sizing}
98
+ $column={useArrayProp(column)}
99
+ $columnStart={useArrayProp(columnStart)}
100
+ $columnEnd={useArrayProp(columnEnd)}
101
+ $display={useArrayProp(display)}
102
+ $flex={useArrayProp(flex)}
103
+ $height={useArrayProp(height)}
104
+ $margin={useArrayProp(margin)}
105
+ $marginX={useArrayProp(marginX)}
106
+ $marginY={useArrayProp(marginY)}
107
+ $marginTop={useArrayProp(marginTop)}
108
+ $marginRight={useArrayProp(marginRight)}
109
+ $marginBottom={useArrayProp(marginBottom)}
110
+ $marginLeft={useArrayProp(marginLeft)}
111
+ $overflow={useArrayProp(overflow)}
112
+ $padding={useArrayProp(padding)}
113
+ $paddingX={useArrayProp(paddingX)}
114
+ $paddingY={useArrayProp(paddingY)}
115
+ $paddingTop={useArrayProp(paddingTop)}
116
+ $paddingRight={useArrayProp(paddingRight)}
117
+ $paddingBottom={useArrayProp(paddingBottom)}
118
+ $paddingLeft={useArrayProp(paddingLeft)}
119
+ $row={useArrayProp(row)}
120
+ $rowStart={useArrayProp(rowStart)}
121
+ $rowEnd={useArrayProp(rowEnd)}
122
+ $sizing={useArrayProp(sizing)}
149
123
  as={asProp}
150
124
  ref={ref}
151
125
  >