@sanity/ui 2.9.0-canary.6 → 2.9.0-corel.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 (75) hide show
  1. package/dist/_chunks-cjs/getTheme_v2.js +54 -84
  2. package/dist/_chunks-cjs/getTheme_v2.js.map +1 -1
  3. package/dist/_chunks-es/getTheme_v2.mjs +54 -84
  4. package/dist/_chunks-es/getTheme_v2.mjs.map +1 -1
  5. package/dist/_legacy/getTheme_v2.esm.js +54 -84
  6. package/dist/_legacy/getTheme_v2.esm.js.map +1 -1
  7. package/dist/index.d.mts +21 -14
  8. package/dist/index.d.ts +21 -14
  9. package/dist/index.esm.js +3150 -3805
  10. package/dist/index.esm.js.map +1 -1
  11. package/dist/index.js +3148 -3802
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +3150 -3805
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/theme.d.mts +22 -28
  16. package/dist/theme.d.ts +22 -28
  17. package/dist/theme.esm.js +630 -1318
  18. package/dist/theme.esm.js.map +1 -1
  19. package/dist/theme.js +630 -1318
  20. package/dist/theme.js.map +1 -1
  21. package/dist/theme.mjs +630 -1318
  22. package/dist/theme.mjs.map +1 -1
  23. package/package.json +1 -2
  24. package/src/core/__workshop__/constants.ts +13 -13
  25. package/src/core/components/autocomplete/autocomplete.tsx +31 -42
  26. package/src/core/components/breadcrumbs/__workshop__/buttons.tsx +58 -0
  27. package/src/core/components/breadcrumbs/__workshop__/example.tsx +3 -1
  28. package/src/core/components/breadcrumbs/__workshop__/index.ts +5 -0
  29. package/src/core/components/breadcrumbs/breadcrumbs.styles.ts +1 -13
  30. package/src/core/components/breadcrumbs/breadcrumbs.tsx +70 -50
  31. package/src/core/components/dialog/dialog.tsx +3 -5
  32. package/src/core/components/menu/menu.tsx +11 -15
  33. package/src/core/components/menu/menuButton.tsx +36 -14
  34. package/src/core/components/menu/menuGroup.tsx +1 -2
  35. package/src/core/components/menu/menuItem.tsx +2 -4
  36. package/src/core/components/menu/useMenuController.ts +21 -18
  37. package/src/core/components/tab/tabList.tsx +11 -3
  38. package/src/core/components/toast/toastProvider.tsx +6 -5
  39. package/src/core/components/tree/treeItem.tsx +15 -18
  40. package/src/core/constants.ts +1 -1
  41. package/src/core/hooks/useArrayProp.ts +9 -14
  42. package/src/core/hooks/useMatchMedia.ts +22 -12
  43. package/src/core/primitives/avatar/avatarStack.tsx +5 -2
  44. package/src/core/primitives/button/__workshop__/props.tsx +1 -1
  45. package/src/core/primitives/button/button.tsx +11 -6
  46. package/src/core/primitives/button/styles.ts +1 -1
  47. package/src/core/primitives/flex/flex.tsx +5 -1
  48. package/src/core/primitives/heading/heading.tsx +1 -1
  49. package/src/core/primitives/popover/popover.tsx +139 -74
  50. package/src/core/primitives/popover/popoverCard.tsx +4 -1
  51. package/src/core/primitives/text/styles.ts +1 -0
  52. package/src/core/primitives/text/text.tsx +4 -2
  53. package/src/core/primitives/tooltip/tooltip.tsx +17 -47
  54. package/src/core/styles/card/_cardColorStyle.ts +8 -0
  55. package/src/core/styles/flex/flexStyle.ts +18 -0
  56. package/src/core/styles/flex/types.ts +2 -0
  57. package/src/core/theme/themeProvider.tsx +6 -4
  58. package/src/core/utils/elementQuery/elementQuery.tsx +17 -16
  59. package/src/core/utils/layer/layerProvider.tsx +1 -1
  60. package/src/core/utils/portal/portalProvider.tsx +5 -5
  61. package/src/theme/build/_deprecated/color/factory.ts +1 -1
  62. package/src/theme/build/_deprecated/color/muted/createMuted.ts +2 -2
  63. package/src/theme/build/buildColorTheme.ts +45 -39
  64. package/src/theme/build/renderColorTheme.ts +239 -65
  65. package/src/theme/build/resolveColorTokens.ts +51 -115
  66. package/src/theme/config/tokens/color/types.ts +4 -18
  67. package/src/theme/defaults/colorTokens.ts +19 -11
  68. package/src/theme/getScopedTheme.ts +9 -2
  69. package/src/theme/system/color/_constants.ts +8 -0
  70. package/src/theme/system/color/badge.ts +3 -7
  71. package/src/theme/system/v0/color/_system.ts +8 -4
  72. package/src/theme/system/v0/color/color.ts +3 -2
  73. package/src/theme/versioning/themeColor_v0_v2.ts +26 -0
  74. package/src/theme/versioning/v0_v2.ts +4 -0
  75. package/src/core/components/toast/toastState.ts +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "2.9.0-canary.6",
3
+ "version": "2.9.0-corel.0",
4
4
  "keywords": [
5
5
  "sanity",
6
6
  "ui",
@@ -84,7 +84,6 @@
84
84
  "@sanity/icons": "^3.4.0",
85
85
  "csstype": "^3.1.3",
86
86
  "framer-motion": "11.0.8",
87
- "react-compiler-runtime": "19.0.0-beta-6fc168f-20241025",
88
87
  "react-refractor": "^2.2.0",
89
88
  "use-effect-event": "^1.0.2"
90
89
  },
@@ -18,7 +18,7 @@ import {ThemeColorSpotKey, ThemeColorToneKey, ThemeFontWeightKey} from '@sanity/
18
18
 
19
19
  export const WORKSHOP_AVATAR_SIZE_OPTIONS: {[key: string]: AvatarSize} = {
20
20
  '0': 0,
21
- '1 (default)': 1,
21
+ '1': 1,
22
22
  '2': 2,
23
23
  '3': 3,
24
24
  }
@@ -60,12 +60,12 @@ export const WORKSHOP_BUTTON_TONE_OPTIONS: {[key: string]: ButtonTone} = {
60
60
  export const WORKSHOP_CARD_AS_OPTIONS: {
61
61
  [key: string]: 'div' | 'button' | 'span' | 'ol' | 'pre' | 'ul'
62
62
  } = {
63
- 'DIV (default)': 'div',
64
- 'BUTTON': 'button',
65
- 'SPAN': 'span',
66
- 'OL': 'ol',
67
- 'PRE': 'pre',
68
- 'UL': 'ul',
63
+ DIV: 'div',
64
+ BUTTON: 'button',
65
+ SPAN: 'span',
66
+ OL: 'ol',
67
+ PRE: 'pre',
68
+ UL: 'ul',
69
69
  }
70
70
 
71
71
  export const WORKSHOP_CARD_TONE_OPTIONS: {[key: string]: CardTone} = {
@@ -132,7 +132,7 @@ export const WORKSHOP_FONT_WEIGHT_OPTIONS: {[key: string]: ThemeFontWeightKey} =
132
132
  export const WORKSHOP_HEADING_FONT_SIZE_OPTIONS = {
133
133
  '0': 0,
134
134
  '1': 1,
135
- '2 (default)': 2,
135
+ '2': 2,
136
136
  '3': 3,
137
137
  '4': 4,
138
138
  }
@@ -254,16 +254,16 @@ export const WORKSHOP_TEXT_OVERFLOW_OPTIONS: {[key: string]: 'ellipsis' | ''} =
254
254
  export const WORKSHOP_TEXT_SIZE_OPTIONS = {
255
255
  '0': 0,
256
256
  '1': 1,
257
- '2 (default)': 2,
257
+ '2': 2,
258
258
  '3': 3,
259
259
  '4': 4,
260
260
  }
261
261
 
262
262
  export const WORKSHOP_TEXT_WEIGHT_OPTIONS: {[key: string]: ThemeFontWeightKey | ''} = {
263
- 'Regular (default)': '',
264
- 'Medium': 'medium',
265
- 'Semibold': 'semibold',
266
- 'Bold': 'bold',
263
+ Regular: 'regular',
264
+ Medium: 'medium',
265
+ Semibold: 'semibold',
266
+ Bold: 'bold',
267
267
  }
268
268
 
269
269
  export const WORKSHOP_THEME_COLOR_TONE_OPTIONS: {[key: string]: ThemeColorToneKey} = {
@@ -17,7 +17,6 @@ import {
17
17
  useMemo,
18
18
  useReducer,
19
19
  useRef,
20
- useState,
21
20
  } from 'react'
22
21
  import {EMPTY_ARRAY, EMPTY_RECORD} from '../../constants'
23
22
  import {_hasFocus, _raf, focusFirstDescendant} from '../../helpers'
@@ -185,22 +184,21 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
185
184
  typeof filterOptionProp === 'function' ? filterOptionProp : DEFAULT_FILTER_OPTION
186
185
 
187
186
  // Element refs
188
- const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
189
- const [resultsPopoverElement, setResultsPopoverElement] = useState<HTMLDivElement | null>(null)
190
- const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null)
187
+ const rootElementRef = useRef<HTMLDivElement | null>(null)
188
+ const resultsPopoverElementRef = useRef<HTMLDivElement | null>(null)
189
+ const inputElementRef = useRef<HTMLInputElement | null>(null)
191
190
  const listBoxElementRef = useRef<HTMLDivElement | null>(null)
192
191
 
193
192
  // Value refs
194
193
  const listFocusedRef = useRef(false)
195
194
  const valueRef = useRef(value)
196
195
  const valuePropRef = useRef(valueProp)
197
- const [popoverMouseWithin, setPopoverMouseWithin] = useState(false)
196
+ const popoverMouseWithinRef = useRef(false)
198
197
 
199
198
  // Forward ref to parent
200
199
  useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
201
200
  forwardedRef,
202
- () => inputElement,
203
- [inputElement],
201
+ () => inputElementRef.current,
204
202
  )
205
203
 
206
204
  const listBoxId = `${id}-listbox`
@@ -224,13 +222,13 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
224
222
  // NOTE: This is a workaround for a bug that may happen in Chrome (clicking the scrollbar
225
223
  // closes the results in certain situations):
226
224
  // - Do not handle blur if the mouse is within the popover
227
- if (popoverMouseWithin) {
225
+ if (popoverMouseWithinRef.current) {
228
226
  return
229
227
  }
230
228
 
231
229
  const elements: HTMLElement[] = (relatedElements || []).concat(
232
- rootElement ? [rootElement] : [],
233
- resultsPopoverElement ? [resultsPopoverElement] : [],
230
+ rootElementRef.current ? [rootElementRef.current] : [],
231
+ resultsPopoverElementRef.current ? [resultsPopoverElementRef.current] : [],
234
232
  )
235
233
 
236
234
  let focusInside = false
@@ -246,20 +244,13 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
246
244
 
247
245
  if (focusInside === false) {
248
246
  dispatch({type: 'root/blur'})
249
- setPopoverMouseWithin(false)
247
+ popoverMouseWithinRef.current = false
250
248
  if (onQueryChange) onQueryChange(null)
251
249
  if (onBlur) onBlur(event)
252
250
  }
253
251
  }, 0)
254
252
  },
255
- [
256
- onBlur,
257
- onQueryChange,
258
- popoverMouseWithin,
259
- relatedElements,
260
- resultsPopoverElement,
261
- rootElement,
262
- ],
253
+ [onBlur, onQueryChange, relatedElements],
263
254
  )
264
255
 
265
256
  const handleRootFocus = useCallback((event: FocusEvent<HTMLDivElement>) => {
@@ -278,7 +269,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
278
269
  (v: string) => {
279
270
  dispatch({type: 'value/change', value: v})
280
271
 
281
- setPopoverMouseWithin(false)
272
+ popoverMouseWithinRef.current = false
282
273
 
283
274
  if (onSelect) onSelect(v)
284
275
 
@@ -287,9 +278,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
287
278
  if (onChange) onChange(v)
288
279
  if (onQueryChange) onQueryChange(null)
289
280
 
290
- inputElement?.focus()
281
+ inputElementRef.current?.focus()
291
282
  },
292
- [onSelect, onChange, onQueryChange, inputElement],
283
+ [onChange, onSelect, onQueryChange],
293
284
  )
294
285
 
295
286
  const handleRootKeyDown = useCallback(
@@ -333,9 +324,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
333
324
 
334
325
  if (event.key === 'Escape') {
335
326
  dispatch({type: 'root/escape'})
336
- setPopoverMouseWithin(false)
327
+ popoverMouseWithinRef.current = false
337
328
  if (onQueryChange) onQueryChange(null)
338
- inputElement?.focus()
329
+ inputElementRef.current?.focus()
339
330
 
340
331
  return
341
332
  }
@@ -347,12 +338,12 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
347
338
  (listEl === target || listEl?.contains(target)) &&
348
339
  !AUTOCOMPLETE_LISTBOX_IGNORE_KEYS.includes(event.key)
349
340
  ) {
350
- inputElement?.focus()
341
+ inputElementRef.current?.focus()
351
342
 
352
343
  return
353
344
  }
354
345
  },
355
- [activeValue, filteredOptions, filteredOptionsLen, inputElement, onQueryChange],
346
+ [activeValue, filteredOptions, filteredOptionsLen, onQueryChange],
356
347
  )
357
348
 
358
349
  const handleInputChange = useCallback(
@@ -386,11 +377,11 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
386
377
  )
387
378
 
388
379
  const handlePopoverMouseEnter = useCallback(() => {
389
- setPopoverMouseWithin(true)
380
+ popoverMouseWithinRef.current = true
390
381
  }, [])
391
382
 
392
383
  const handlePopoverMouseLeave = useCallback(() => {
393
- setPopoverMouseWithin(false)
384
+ popoverMouseWithinRef.current = false
394
385
  }, [])
395
386
 
396
387
  const handleClearButtonClick = useCallback(() => {
@@ -398,8 +389,8 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
398
389
  valueRef.current = ''
399
390
  if (onChange) onChange('')
400
391
  if (onQueryChange) onQueryChange(null)
401
- inputElement?.focus()
402
- }, [inputElement, onChange, onQueryChange])
392
+ inputElementRef.current?.focus()
393
+ }, [onChange, onQueryChange])
403
394
 
404
395
  const handleClearButtonFocus = useCallback(() => {
405
396
  dispatch({type: 'input/focus'})
@@ -491,9 +482,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
491
482
 
492
483
  if (openButtonProps.onClick) openButtonProps.onClick(event)
493
484
 
494
- _raf(() => inputElement?.focus())
485
+ _raf(() => inputElementRef.current?.focus())
495
486
  },
496
- [dispatchOpen, openButtonProps, inputElement],
487
+ [openButtonProps, dispatchOpen],
497
488
  )
498
489
 
499
490
  const openButtonNode = useMemo(
@@ -563,7 +554,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
563
554
  prefix={prefix}
564
555
  radius={radius}
565
556
  readOnly={readOnly}
566
- ref={setInputElement}
557
+ ref={inputElementRef}
567
558
  role="combobox"
568
559
  spellCheck={false}
569
560
  suffix={suffix || openButtonNode}
@@ -575,10 +566,10 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
575
566
  (event: KeyboardEvent<HTMLDivElement>) => {
576
567
  // If the focus is currently in the list, move focus to the input element
577
568
  if (event.key === 'Tab') {
578
- if (listFocused) inputElement?.focus()
569
+ if (listFocused) inputElementRef.current?.focus()
579
570
  }
580
571
  },
581
- [inputElement, listFocused],
572
+ [listFocused],
582
573
  )
583
574
 
584
575
  const content = useMemo(() => {
@@ -644,11 +635,11 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
644
635
  {
645
636
  content,
646
637
  hidden: !expanded,
647
- inputElement,
638
+ inputElement: inputElementRef.current,
648
639
  onMouseEnter: handlePopoverMouseEnter,
649
640
  onMouseLeave: handlePopoverMouseLeave,
650
641
  },
651
- {current: resultsPopoverElement},
642
+ resultsPopoverElementRef,
652
643
  )
653
644
  }
654
645
 
@@ -670,8 +661,8 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
670
661
  placement={AUTOCOMPLETE_POPOVER_PLACEMENT}
671
662
  portal
672
663
  radius={radius}
673
- ref={setResultsPopoverElement}
674
- referenceElement={inputElement}
664
+ ref={resultsPopoverElementRef}
665
+ referenceElement={inputElementRef.current}
675
666
  {...popover}
676
667
  />
677
668
  )
@@ -681,11 +672,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
681
672
  filteredOptionsLen,
682
673
  handlePopoverMouseEnter,
683
674
  handlePopoverMouseLeave,
684
- inputElement,
685
675
  popover,
686
676
  radius,
687
677
  renderPopover,
688
- resultsPopoverElement,
689
678
  ])
690
679
 
691
680
  return (
@@ -694,7 +683,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
694
683
  onBlur={handleRootBlur}
695
684
  onFocus={handleRootFocus}
696
685
  onKeyDown={handleRootKeyDown}
697
- ref={setRootElement}
686
+ ref={rootElementRef}
698
687
  >
699
688
  {input}
700
689
  {results}
@@ -0,0 +1,58 @@
1
+ import {ChevronRightIcon} from '@sanity/icons'
2
+ import {Box, Breadcrumbs, Button, Card, Flex, Text} from '@sanity/ui'
3
+ import {useSelect} from '@sanity/ui-workshop'
4
+
5
+ const BREADCRUMBS_MAX_LENGTH_OPTIONS = {
6
+ '(none)': 0,
7
+ '1': 1,
8
+ '2': 2,
9
+ '3': 3,
10
+ '4': 4,
11
+ '5': 5,
12
+ '6': 6,
13
+ '7': 7,
14
+ }
15
+
16
+ export default function Example() {
17
+ const maxLength =
18
+ useSelect('Max. length', BREADCRUMBS_MAX_LENGTH_OPTIONS, 0, 'Props') || undefined
19
+
20
+ return (
21
+ <Flex align="center" height="fill" justify="center" padding={4} sizing="border">
22
+ <Card padding={1} radius={3} shadow={1}>
23
+ <Breadcrumbs
24
+ gapX={0}
25
+ expandButton={{padding: 2}}
26
+ maxLength={maxLength}
27
+ separator={
28
+ <Box paddingY={2} style={{opacity: 0.5}}>
29
+ <Text muted size={1}>
30
+ <ChevronRightIcon />
31
+ </Text>
32
+ </Box>
33
+ }
34
+ // space={0}
35
+ >
36
+ <Button href="#" mode="bleed" padding={2} text="Root" />
37
+ <Button href="#" mode="bleed" padding={2} text="Category A of some length" />
38
+ <Button href="#" mode="bleed" padding={2} text="Category B" />
39
+ <Button href="#" mode="bleed" padding={2} text="Category C" />
40
+ <Button
41
+ href="#"
42
+ mode="bleed"
43
+ padding={2}
44
+ text="Category D of an every larger and more extended length"
45
+ textOverflow="none"
46
+ />
47
+ <Button href="#" mode="bleed" padding={2} text="Category E" />
48
+ <Button href="#" mode="bleed" padding={2} text="Category F" />
49
+ <Box padding={2}>
50
+ <Text size={1} weight="semibold">
51
+ Item
52
+ </Text>
53
+ </Box>
54
+ </Breadcrumbs>
55
+ </Card>
56
+ </Flex>
57
+ )
58
+ }
@@ -19,13 +19,15 @@ export default function Example() {
19
19
  return (
20
20
  <Flex align="center" height="fill" justify="center">
21
21
  <Breadcrumbs
22
+ gapX={1}
23
+ gapY={3}
22
24
  maxLength={maxLength}
23
25
  separator={
24
26
  <Text muted size={1}>
25
27
  /
26
28
  </Text>
27
29
  }
28
- space={2}
30
+ // space={2}
29
31
  >
30
32
  <Text size={1}>
31
33
  <a href="#">Root</a>
@@ -10,5 +10,10 @@ export default defineScope({
10
10
  title: 'Example',
11
11
  component: lazy(() => import('./example')),
12
12
  },
13
+ {
14
+ name: 'buttons',
15
+ title: 'Buttons',
16
+ component: lazy(() => import('./buttons')),
17
+ },
13
18
  ],
14
19
  })
@@ -1,17 +1,5 @@
1
1
  import {styled} from 'styled-components'
2
- import {Button} from '../../primitives'
3
2
 
4
- export const Root = styled.ol`
5
- margin: 0;
6
- padding: 0;
7
- display: flex;
8
- list-style: none;
9
- align-items: center;
10
- white-space: nowrap;
3
+ export const Root = styled.nav`
11
4
  line-height: 0;
12
5
  `
13
-
14
- export const ExpandButton = styled(Button)`
15
- appearance: none;
16
- margin: -4px;
17
- `
@@ -1,14 +1,19 @@
1
- import {Children, forwardRef, Fragment, isValidElement, useCallback, useRef, useState} from 'react'
2
- import {useArrayProp, useClickOutsideEvent} from '../../hooks'
3
- import {Box, Popover, Stack, Text} from '../../primitives'
4
- import {ExpandButton, Root} from './breadcrumbs.styles'
1
+ import {Children, forwardRef, isValidElement, useCallback, useMemo, useRef, useState} from 'react'
2
+ import {useClickOutsideEvent} from '../../hooks'
3
+ import {Box, Button, ButtonProps, Flex, Popover, Stack, Text} from '../../primitives'
4
+ import {Root} from './breadcrumbs.styles'
5
5
 
6
6
  /**
7
7
  * @beta
8
8
  */
9
9
  export interface BreadcrumbsProps {
10
+ expandButton?: Omit<ButtonProps, 'onClick' | 'selected'>
11
+ gap?: number | number[]
12
+ gapX?: number | number[]
13
+ gapY?: number | number[]
10
14
  maxLength?: number
11
15
  separator?: React.ReactNode
16
+ /** @deprecated - Use `gap`, `gapX`, `gapY` instead */
12
17
  space?: number | number[]
13
18
  }
14
19
 
@@ -19,8 +24,19 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
19
24
  props: BreadcrumbsProps & Omit<React.HTMLProps<HTMLOListElement>, 'as' | 'ref' | 'type'>,
20
25
  ref: React.ForwardedRef<HTMLOListElement>,
21
26
  ) {
22
- const {children, maxLength, separator, space: spaceRaw = 2, ...restProps} = props
23
- const space = useArrayProp(spaceRaw)
27
+ const {
28
+ children,
29
+ expandButton: expandButtonProps,
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
+ gap: _gapProp,
32
+ gapX = props.gap ?? props.space ?? 2,
33
+ gapY = props.gap ?? props.space ?? 2,
34
+ maxLength,
35
+ separator,
36
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
+ space: _spaceProp,
38
+ ...restProps
39
+ } = props
24
40
  const [open, setOpen] = useState(false)
25
41
  const expandElementRef = useRef<HTMLButtonElement | null>(null)
26
42
  const popoverElementRef = useRef<HTMLDivElement | null>(null)
@@ -30,56 +46,60 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
30
46
 
31
47
  useClickOutsideEvent(collapse, () => [expandElementRef.current, popoverElementRef.current])
32
48
 
33
- const rawItems = Children.toArray(children).filter(isValidElement)
49
+ const rawItems = useMemo(() => Children.toArray(children).filter(isValidElement), [children])
34
50
 
35
- let items = rawItems
36
- const len = rawItems.length
51
+ const items = useMemo(() => {
52
+ const len = rawItems.length
37
53
 
38
- if (maxLength && rawItems.length > maxLength) {
39
- const beforeLength = Math.ceil(maxLength / 2)
40
- const afterLength = Math.floor(maxLength / 2)
54
+ if (maxLength && len > maxLength) {
55
+ const beforeLength = Math.ceil(maxLength / 2)
56
+ const afterLength = Math.floor(maxLength / 2)
41
57
 
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
- }
58
+ return [
59
+ ...rawItems.slice(0, beforeLength - 1),
60
+ <Popover
61
+ constrainSize
62
+ content={
63
+ <Stack as="ol" overflow="auto" space={gapY}>
64
+ {rawItems.slice(beforeLength - 1, len - afterLength)}
65
+ </Stack>
66
+ }
67
+ key="button"
68
+ open={open}
69
+ padding={2}
70
+ placement="top"
71
+ portal
72
+ ref={popoverElementRef}
73
+ >
74
+ <Button
75
+ mode="bleed"
76
+ padding={0}
77
+ text="…"
78
+ {...expandButtonProps}
79
+ onClick={open ? collapse : expand}
80
+ ref={expandElementRef}
81
+ selected={open}
82
+ />
83
+ </Popover>,
84
+ ...rawItems.slice(len - afterLength),
85
+ ]
86
+ }
87
+
88
+ return rawItems
89
+ }, [collapse, expand, expandButtonProps, gapY, maxLength, open, rawItems])
90
+
91
+ const len = items.length
70
92
 
71
93
  return (
72
94
  <Root data-ui="Breadcrumbs" {...restProps} ref={ref}>
73
- {items.map((item, itemIndex) => (
74
- <Fragment key={itemIndex}>
75
- {itemIndex > 0 && (
76
- <Box aria-hidden as="li" paddingX={space}>
77
- {separator || <Text muted>/</Text>}
78
- </Box>
79
- )}
80
- <Box as="li">{item}</Box>
81
- </Fragment>
82
- ))}
95
+ <Flex align="flex-start" as="ol" gapX={gapX} gapY={gapY} wrap="wrap">
96
+ {items.map((item, itemIndex) => (
97
+ <Flex align="flex-start" as="li" gapX={gapX} gapY={gapY} key={itemIndex}>
98
+ <Box>{item}</Box>
99
+ {itemIndex < len - 1 && <Box aria-hidden>{separator || <Text muted>/</Text>}</Box>}
100
+ </Flex>
101
+ ))}
102
+ </Flex>
83
103
  </Root>
84
104
  )
85
105
  })
@@ -241,7 +241,7 @@ const DialogCard = forwardRef(function DialogCard(
241
241
  <DialogLayout direction="column">
242
242
  {showHeader && (
243
243
  <DialogHeader>
244
- <Flex align="center" padding={3}>
244
+ <Flex align="flex-start" padding={3}>
245
245
  <Box flex={1} padding={2}>
246
246
  {header && (
247
247
  <Text id={labelId} size={1} weight="semibold">
@@ -305,15 +305,13 @@ export const Dialog = forwardRef(function Dialog(
305
305
  onFocus,
306
306
  padding: paddingProp = 3,
307
307
  portal: portalProp,
308
- position: _positionProp,
308
+ position: positionProp = dialog.position || 'fixed',
309
309
  scheme,
310
310
  width: widthProp = 0,
311
- zOffset: _zOffsetProp,
311
+ zOffset: zOffsetProp = dialog.zOffset || layer.dialog.zOffset,
312
312
  animate: _animate = false,
313
313
  ...restProps
314
314
  } = props
315
- const positionProp = _positionProp ?? (dialog.position || 'fixed')
316
- const zOffsetProp = _zOffsetProp ?? (dialog.zOffset || layer.dialog.zOffset)
317
315
  const prefersReducedMotion = usePrefersReducedMotion()
318
316
  const animate = prefersReducedMotion ? false : _animate
319
317
  const portal = usePortal()
@@ -14,21 +14,19 @@ 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[]
30
- 'aria-labelledby'?: string
31
- 'onBlurCapture'?: (event: FocusEvent) => void
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[]
32
30
  }
33
31
 
34
32
  const Root = styled(Box)`
@@ -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