@sanity/ui 2.9.0-canary.2 → 2.9.0-canary.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "2.9.0-canary.2",
3
+ "version": "2.9.0-canary.4",
4
4
  "keywords": [
5
5
  "sanity",
6
6
  "ui",
@@ -1,13 +1,4 @@
1
- import {
2
- Children,
3
- forwardRef,
4
- Fragment,
5
- isValidElement,
6
- useCallback,
7
- useMemo,
8
- useRef,
9
- useState,
10
- } from 'react'
1
+ import {Children, forwardRef, Fragment, isValidElement, useCallback, useRef, useState} from 'react'
11
2
  import {useArrayProp, useClickOutsideEvent} from '../../hooks'
12
3
  import {Box, Popover, Stack, Text} from '../../primitives'
13
4
  import {ExpandButton, Root} from './breadcrumbs.styles'
@@ -39,46 +30,43 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
39
30
 
40
31
  useClickOutsideEvent(collapse, () => [expandElementRef.current, popoverElementRef.current])
41
32
 
42
- const rawItems = useMemo(() => Children.toArray(children).filter(isValidElement), [children])
33
+ const rawItems = Children.toArray(children).filter(isValidElement)
43
34
 
44
- const items = useMemo(() => {
45
- const len = rawItems.length
35
+ let items = rawItems
36
+ const len = rawItems.length
46
37
 
47
- if (maxLength && len > maxLength) {
48
- const beforeLength = Math.ceil(maxLength / 2)
49
- const afterLength = Math.floor(maxLength / 2)
38
+ if (maxLength && rawItems.length > maxLength) {
39
+ const beforeLength = Math.ceil(maxLength / 2)
40
+ const afterLength = Math.floor(maxLength / 2)
50
41
 
51
- return [
52
- ...rawItems.slice(0, beforeLength - 1),
53
- <Popover
54
- constrainSize
55
- content={
56
- <Stack as="ol" overflow="auto" padding={space} space={space}>
57
- {rawItems.slice(beforeLength - 1, len - afterLength)}
58
- </Stack>
59
- }
60
- key="button"
61
- open={open}
62
- placement="top"
63
- portal
64
- ref={popoverElementRef}
65
- >
66
- <ExpandButton
67
- fontSize={1}
68
- mode="bleed"
69
- onClick={open ? collapse : expand}
70
- padding={1}
71
- ref={expandElementRef}
72
- selected={open}
73
- text="…"
74
- />
75
- </Popover>,
76
- ...rawItems.slice(len - afterLength),
77
- ]
78
- }
79
-
80
- return rawItems
81
- }, [collapse, expand, maxLength, open, rawItems, space])
42
+ items = [
43
+ ...rawItems.slice(0, beforeLength - 1),
44
+ <Popover
45
+ constrainSize
46
+ content={
47
+ <Stack as="ol" overflow="auto" padding={space} space={space}>
48
+ {rawItems.slice(beforeLength - 1, len - afterLength)}
49
+ </Stack>
50
+ }
51
+ key="button"
52
+ open={open}
53
+ placement="top"
54
+ portal
55
+ ref={popoverElementRef}
56
+ >
57
+ <ExpandButton
58
+ fontSize={1}
59
+ mode="bleed"
60
+ onClick={open ? collapse : expand}
61
+ padding={1}
62
+ ref={expandElementRef}
63
+ selected={open}
64
+ text="…"
65
+ />
66
+ </Popover>,
67
+ ...rawItems.slice(len - afterLength),
68
+ ]
69
+ }
82
70
 
83
71
  return (
84
72
  <Root data-ui="Breadcrumbs" {...restProps} ref={ref}>
@@ -14,19 +14,21 @@ export interface MenuProps extends ResponsivePaddingProps {
14
14
  /**
15
15
  * @deprecated Use `shouldFocus="first"` instead.
16
16
  */
17
- focusFirst?: boolean
17
+ 'focusFirst'?: boolean
18
18
  /**
19
19
  * @deprecated Use `shouldFocus="last"` instead.
20
20
  */
21
- focusLast?: boolean
22
- onClickOutside?: (event: MouseEvent) => void
23
- onEscape?: () => void
24
- onItemClick?: () => void
25
- onItemSelect?: (index: number) => void
26
- originElement?: HTMLElement | null
27
- registerElement?: (el: HTMLElement) => () => void
28
- shouldFocus?: 'first' | 'last' | null
29
- space?: number | number[]
21
+ 'focusLast'?: boolean
22
+ 'onClickOutside'?: (event: MouseEvent) => void
23
+ 'onEscape'?: () => void
24
+ 'onItemClick'?: () => void
25
+ 'onItemSelect'?: (index: number) => void
26
+ 'originElement'?: HTMLElement | null
27
+ 'registerElement'?: (el: HTMLElement) => () => void
28
+ 'shouldFocus'?: 'first' | 'last' | null
29
+ 'space'?: number | number[]
30
+ 'aria-labelledby'?: string
31
+ 'onBlurCapture'?: (event: FocusEvent) => void
30
32
  }
31
33
 
32
34
  const Root = styled(Box)`
@@ -169,7 +169,7 @@ export const MenuButton = forwardRef(function MenuButton(
169
169
  }, [buttonElement, disableRestoreFocusOnClose])
170
170
 
171
171
  const handleBlur = useCallback(
172
- (event: React.FocusEvent<HTMLButtonElement>) => {
172
+ (event: FocusEvent) => {
173
173
  const target = event.relatedTarget
174
174
 
175
175
  if (!(target instanceof Node)) {
@@ -199,32 +199,19 @@ export const MenuButton = forwardRef(function MenuButton(
199
199
  return () => setChildMenuElements((els) => els.filter((_el) => _el !== el))
200
200
  }, [])
201
201
 
202
- const menuProps: MenuProps = useMemo(
203
- () => ({
204
- 'aria-labelledby': id,
205
- 'onBlurCapture': handleBlur,
206
- 'onClickOutside': handleMenuClickOutside,
207
- 'onEscape': handleMenuEscape,
208
- 'onItemClick': handleItemClick,
209
- 'originElement': buttonElement,
210
- registerElement,
211
- shouldFocus,
212
- }),
213
- [
214
- buttonElement,
215
- handleMenuClickOutside,
216
- handleMenuEscape,
217
- handleItemClick,
218
- id,
219
- handleBlur,
220
- registerElement,
221
- shouldFocus,
222
- ],
223
- )
202
+ const menuProps: MenuProps = {
203
+ 'aria-labelledby': id,
204
+ 'onBlurCapture': handleBlur,
205
+ 'onClickOutside': handleMenuClickOutside,
206
+ 'onEscape': handleMenuEscape,
207
+ 'onItemClick': handleItemClick,
208
+ 'originElement': buttonElement,
209
+ registerElement,
210
+ shouldFocus,
211
+ }
224
212
 
225
213
  const menu = menuProp && cloneElement(menuProp, menuProps)
226
214
 
227
- const ref = useRef<HTMLButtonElement | null>(null)
228
215
  const button = useMemo(
229
216
  () =>
230
217
  buttonProp &&
@@ -236,7 +223,7 @@ export const MenuButton = forwardRef(function MenuButton(
236
223
  'onMouseDown': handleMouseDown,
237
224
  'aria-haspopup': true,
238
225
  'aria-expanded': open,
239
- 'ref': ref,
226
+ 'ref': setButtonElement,
240
227
  'selected': buttonProp.props.selected ?? open,
241
228
  }),
242
229
  [buttonProp, handleButtonClick, handleButtonKeyDown, handleMouseDown, id, open],
@@ -245,19 +232,10 @@ export const MenuButton = forwardRef(function MenuButton(
245
232
  // Forward button ref to parent
246
233
  useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
247
234
  forwardedRef,
248
- () => ref.current,
235
+ () => buttonElement,
236
+ [buttonElement],
249
237
  )
250
238
 
251
- // If there's a button then we need to set the reference element to the cloned button ref
252
- // and if button changes we make sure to update or remove the reference element.
253
- useEffect(() => {
254
- if (!button) return undefined
255
-
256
- setButtonElement(ref.current)
257
-
258
- return () => setButtonElement(null)
259
- }, [button])
260
-
261
239
  const popoverProps: MenuButtonProps['popover'] = useMemo(
262
240
  () => ({
263
241
  boundaryElement: deprecated_boundaryElement,
@@ -1,4 +1,4 @@
1
- import {cloneElement, forwardRef, useCallback, useMemo, useState} from 'react'
1
+ import {cloneElement, forwardRef, useCallback, useState, Children, isValidElement} from 'react'
2
2
  import {styled} from 'styled-components'
3
3
  import {Inline, InlineProps} from '../../primitives'
4
4
 
@@ -9,10 +9,6 @@ export interface TabListProps extends Omit<InlineProps, 'as' | 'height'> {
9
9
  children: Array<React.ReactElement | null | undefined | false>
10
10
  }
11
11
 
12
- function _isReactElement(node: unknown): node is React.ReactElement {
13
- return Boolean(node)
14
- }
15
-
16
12
  //Limits the width of tabs in tablist
17
13
  const CustomInline = styled(Inline)`
18
14
  & > div {
@@ -33,22 +29,18 @@ export const TabList = forwardRef(function TabList(
33
29
  const {children: childrenProp, ...restProps} = props
34
30
  const [focusedIndex, setFocusedIndex] = useState(-1)
35
31
 
36
- const children = useMemo(() => childrenProp.filter(_isReactElement), [childrenProp])
32
+ const children: React.ReactElement[] = Children.toArray(childrenProp).filter(isValidElement)
37
33
 
38
34
  const tabs = children.map((child, childIndex) =>
39
35
  cloneElement(child, {
40
36
  focused: focusedIndex === childIndex,
41
37
  key: childIndex,
42
- onFocus: () => handleTabFocus(childIndex),
38
+ onFocus: () => setFocusedIndex(childIndex),
43
39
  }),
44
40
  )
45
41
 
46
42
  const numTabs = tabs.length
47
43
 
48
- const handleTabFocus = useCallback((tabIdx: number) => {
49
- setFocusedIndex(tabIdx)
50
- }, [])
51
-
52
44
  const handleKeyDown = useCallback(
53
45
  (event: React.KeyboardEvent<HTMLDivElement>) => {
54
46
  if (event.key === 'ArrowLeft') {
@@ -8,6 +8,7 @@ import {Box} from '../../primitives'
8
8
  import {Layer} from '../../utils'
9
9
  import {Toast} from './toast'
10
10
  import {ToastContext} from './toastContext'
11
+ import {generateToastId} from './toastState'
11
12
  import {ToastContextValue, ToastParams} from './types'
12
13
 
13
14
  type ToastState = {
@@ -45,8 +46,6 @@ const ToastContainer = styled.div`
45
46
  width: 100%;
46
47
  `
47
48
 
48
- let toastId = 0
49
-
50
49
  /**
51
50
  * @public
52
51
  */
@@ -60,20 +59,20 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
60
59
  () => ({
61
60
  initial: {
62
61
  opacity: 0,
63
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: 0,
62
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: 0,
64
63
  y: 32,
65
64
  scale: 0.25,
66
65
  willChange: 'transform',
67
66
  },
68
67
  animate: {
69
68
  opacity: [0, 1, 1],
70
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [0, 0, 1],
69
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: [0, 0, 1],
71
70
  y: 0,
72
71
  scale: 1,
73
72
  },
74
73
  exit: {
75
74
  opacity: [1, 1, 0],
76
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [1, 0, 0],
75
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: [1, 0, 0],
77
76
  scale: 0.5,
78
77
  transition: prefersReducedMotion ? {duration: 0} : {duration: 0.2},
79
78
  },
@@ -86,7 +85,7 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
86
85
  // Wrap setState in startTransition to allow React to give input state updates higher priority
87
86
  const setState: typeof _setState = (state) => startTransition(() => _setState(state))
88
87
 
89
- const id = params.id || String(toastId++)
88
+ const id = params.id || generateToastId()
90
89
  const duration = params.duration || 5000
91
90
 
92
91
  const dismiss = () => {
@@ -0,0 +1,6 @@
1
+ let toastId = 0
2
+
3
+ /** @internal */
4
+ export function generateToastId(): string {
5
+ return String(toastId++)
6
+ }
@@ -70,8 +70,11 @@ export const TreeItem = memo(function TreeItem(
70
70
  const {path, registerItem, setExpanded, setFocusedElement} = tree
71
71
  const _id = useId()
72
72
  const id = idProp || _id
73
- const itemPath = useMemo(() => path.concat([id || '']), [id, path])
74
- const itemKey = itemPath.join('/')
73
+ const [itemPath, itemKey] = useMemo(() => {
74
+ const result = path.concat([id || ''])
75
+
76
+ return [result, result.join('/')]
77
+ }, [id, path])
75
78
  const itemState = tree.state[itemKey]
76
79
  const focused = tree.focusedElement === rootElement
77
80
  const expanded = itemState?.expanded === undefined ? expandedProp : itemState?.expanded || false
@@ -13,7 +13,7 @@ export const EMPTY_RECORD: Record<string, never> = {}
13
13
  /**
14
14
  * @internal
15
15
  */
16
- export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity'
16
+ export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity' as string
17
17
 
18
18
  /**
19
19
  * Shared `framer-motion` variants used by `Popover` and `Tooltip` components.
@@ -171,7 +171,7 @@ export const Popover = memo(
171
171
  const widthArrayProp = useArrayProp(widthProp)
172
172
  const zOffset = useArrayProp(zOffsetProp)
173
173
  const ref = useRef<HTMLDivElement | null>(null)
174
- const arrowRef = useRef<HTMLDivElement | null>(null)
174
+ const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
175
175
  const rootBoundary: RootBoundary = 'viewport'
176
176
 
177
177
  useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(
@@ -299,7 +299,7 @@ export const Popover = memo(
299
299
  if (arrowProp) {
300
300
  ret.push(
301
301
  arrow({
302
- element: arrowRef,
302
+ element: arrowElement,
303
303
  padding: DEFAULT_POPOVER_PADDING,
304
304
  }),
305
305
  )
@@ -322,6 +322,7 @@ export const Popover = memo(
322
322
  return ret
323
323
  }, [
324
324
  animate,
325
+ arrowElement,
325
326
  arrowProp,
326
327
  constrainSize,
327
328
  fallbackPlacements,
@@ -346,10 +347,6 @@ export const Popover = memo(
346
347
  const originX = middlewareData['@sanity/ui/origin']?.originX
347
348
  const originY = middlewareData['@sanity/ui/origin']?.originY
348
349
 
349
- const setArrow = useCallback((arrowEl: HTMLDivElement | null) => {
350
- arrowRef.current = arrowEl
351
- }, [])
352
-
353
350
  const setFloating = useCallback(
354
351
  (node: HTMLDivElement | null) => {
355
352
  ref.current = node
@@ -393,7 +390,7 @@ export const Popover = memo(
393
390
  __unstable_margins={margins}
394
391
  animate={animate}
395
392
  arrow={arrowProp}
396
- arrowRef={setArrow}
393
+ arrowRef={setArrowElement}
397
394
  arrowX={arrowX}
398
395
  arrowY={arrowY}
399
396
  hidden={referenceHidden}
@@ -1,4 +1,4 @@
1
- import {forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react'
1
+ import {forwardRef, useImperativeHandle, useMemo, useState} from 'react'
2
2
  import {useElementSize} from '../../hooks'
3
3
  import {useTheme_v2} from '../../theme'
4
4
  import {findMaxBreakpoints, findMinBreakpoints} from './helpers'
@@ -24,29 +24,27 @@ export const ElementQuery = forwardRef(function ElementQuery(
24
24
  const {children, media: _media, ...restProps} = props
25
25
  const media = _media ?? theme.media
26
26
 
27
- const ref = useRef<HTMLDivElement | null>(null)
28
27
  const [element, setElement] = useState<HTMLDivElement | null>(null)
29
28
  const elementSize = useElementSize(element)
30
29
  const width = useMemo(() => elementSize?.border.width ?? window.innerWidth, [elementSize])
31
30
 
32
- const max = useMemo(() => findMaxBreakpoints(media, width), [media, width])
33
- const min = useMemo(() => findMinBreakpoints(media, width), [media, width])
31
+ const max = useMemo(() => {
32
+ const eq = findMaxBreakpoints(media, width)
34
33
 
35
- useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(forwardedRef, () => ref.current)
34
+ return eq.length ? eq.join(' ') : undefined
35
+ }, [media, width])
36
+ const min = useMemo(() => {
37
+ const eq = findMinBreakpoints(media, width)
36
38
 
37
- const setRef = useCallback((el: HTMLDivElement | null) => {
38
- ref.current = el
39
- setElement(el)
40
- }, [])
39
+ return eq.length ? eq.join(' ') : undefined
40
+ }, [media, width])
41
+
42
+ useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(forwardedRef, () => element, [
43
+ element,
44
+ ])
41
45
 
42
46
  return (
43
- <div
44
- data-ui="ElementQuery"
45
- {...restProps}
46
- data-eq-max={max.length ? max.join(' ') : undefined}
47
- data-eq-min={min.length ? min.join(' ') : undefined}
48
- ref={setRef}
49
- >
47
+ <div data-ui="ElementQuery" {...restProps} data-eq-max={max} data-eq-min={min} ref={setElement}>
50
48
  {children}
51
49
  </div>
52
50
  )
@@ -85,7 +85,7 @@ export function LayerProvider(props: LayerProviderProps): React.ReactElement {
85
85
  parentDispose?.()
86
86
  }
87
87
  },
88
- [parentRegisterChild],
88
+ [parentRegisterChild, setSize, setChildLayers],
89
89
  )
90
90
 
91
91
  // Register this layer on mount