@sanity/ui 3.1.13 → 3.2.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 (47) hide show
  1. package/dist/_chunks-cjs/_visual-editing.js +276 -252
  2. package/dist/_chunks-cjs/_visual-editing.js.map +1 -1
  3. package/dist/_chunks-es/_visual-editing.mjs +276 -252
  4. package/dist/_chunks-es/_visual-editing.mjs.map +1 -1
  5. package/dist/_visual-editing.d.mts +66 -1
  6. package/dist/_visual-editing.d.ts +66 -1
  7. package/dist/index.d.mts +86 -1
  8. package/dist/index.d.ts +86 -1
  9. package/dist/index.js +97 -93
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +97 -93
  12. package/dist/index.mjs.map +1 -1
  13. package/package.json +1 -1
  14. package/src/core/components/breadcrumbs/breadcrumbs.test.tsx +51 -0
  15. package/src/core/components/breadcrumbs/breadcrumbs.tsx +7 -3
  16. package/src/core/components/hotkeys/hotkeys.test.tsx +42 -0
  17. package/src/core/components/hotkeys/hotkeys.tsx +7 -3
  18. package/src/core/components/menu/menu.spacing.test.tsx +63 -0
  19. package/src/core/components/menu/menu.tsx +8 -2
  20. package/src/core/components/menu/menuGroup.spacing.test.tsx +69 -0
  21. package/src/core/components/menu/menuGroup.tsx +8 -2
  22. package/src/core/components/menu/menuItem.spacing.test.tsx +59 -0
  23. package/src/core/components/menu/menuItem.tsx +8 -2
  24. package/src/core/components/tab/tabList.test.tsx +48 -0
  25. package/src/core/components/tree/tree.test.tsx +55 -0
  26. package/src/core/components/tree/tree.tsx +10 -4
  27. package/src/core/components/tree/treeGroup.tsx +2 -2
  28. package/src/core/components/tree/treeItem.test.tsx +65 -0
  29. package/src/core/components/tree/treeItem.tsx +8 -2
  30. package/src/core/components/tree/types.ts +4 -0
  31. package/src/core/hooks/useDelayed.test.ts +20 -0
  32. package/src/core/hooks/useDelayedState.ts +9 -1
  33. package/src/core/primitives/box/box.test.tsx +134 -0
  34. package/src/core/primitives/box/box.tsx +18 -6
  35. package/src/core/primitives/button/button.test.tsx +38 -0
  36. package/src/core/primitives/button/button.tsx +8 -3
  37. package/src/core/primitives/grid/grid.test.tsx +78 -0
  38. package/src/core/primitives/grid/grid.tsx +17 -4
  39. package/src/core/primitives/inline/inline.test.tsx +51 -0
  40. package/src/core/primitives/inline/inline.tsx +9 -3
  41. package/src/core/primitives/select/select.test.tsx +52 -0
  42. package/src/core/primitives/select/select.tsx +8 -2
  43. package/src/core/primitives/stack/stack.test.tsx +57 -0
  44. package/src/core/primitives/stack/stack.tsx +7 -2
  45. package/src/core/primitives/textInput/textInput.test.tsx +48 -0
  46. package/src/core/primitives/textInput/textInput.tsx +7 -2
  47. package/src/core/primitives/types.ts +33 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "3.1.13",
3
+ "version": "3.2.0",
4
4
  "keywords": [
5
5
  "sanity",
6
6
  "ui",
@@ -0,0 +1,51 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Box} from '../../primitives'
5
+ import {Breadcrumbs} from './breadcrumbs'
6
+
7
+ jest.mock('../../primitives', () => {
8
+ const actual = jest.requireActual('../../primitives')
9
+
10
+ return {
11
+ ...actual,
12
+ Box: jest.fn((props: Record<string, unknown>) => (actual.Box as any).render(props, null)),
13
+ }
14
+ })
15
+
16
+ function renderBreadcrumbs(props: Partial<React.ComponentProps<typeof Breadcrumbs>> = {}) {
17
+ return render(
18
+ <Breadcrumbs {...props}>
19
+ <span>Root</span>
20
+ <span>Section</span>
21
+ </Breadcrumbs>,
22
+ )
23
+ }
24
+
25
+ describe('components/breadcrumbs spacing', () => {
26
+ const mockedBox = jest.mocked(Box)
27
+
28
+ beforeEach(() => {
29
+ mockedBox.mockClear()
30
+ })
31
+
32
+ it('should support `space` and `gap` with the same behavior', () => {
33
+ renderBreadcrumbs({space: 2})
34
+ expect(mockedBox.mock.calls.map(([props]) => props)).toContainEqual(
35
+ expect.objectContaining({paddingX: [2]}),
36
+ )
37
+
38
+ mockedBox.mockClear()
39
+ renderBreadcrumbs({gap: 2})
40
+ expect(mockedBox.mock.calls.map(([props]) => props)).toContainEqual(
41
+ expect.objectContaining({paddingX: [2]}),
42
+ )
43
+ })
44
+
45
+ it('should prefer `gap` over `space` when both are provided', () => {
46
+ renderBreadcrumbs({gap: 3, space: 1})
47
+ const propsList = mockedBox.mock.calls.map(([props]) => props)
48
+ expect(propsList).toContainEqual(expect.objectContaining({paddingX: [3]}))
49
+ expect(propsList).not.toContainEqual(expect.objectContaining({paddingX: [1]}))
50
+ })
51
+ })
@@ -20,6 +20,10 @@ import {ExpandButton, StyledBreadcrumbs} from './breadcrumbs.styles'
20
20
  export interface BreadcrumbsProps {
21
21
  maxLength?: number
22
22
  separator?: React.ReactNode
23
+ gap?: number | number[]
24
+ /**
25
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
26
+ */
23
27
  space?: number | number[]
24
28
  }
25
29
 
@@ -30,8 +34,8 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
30
34
  props: BreadcrumbsProps & Omit<React.HTMLProps<HTMLOListElement>, 'as' | 'ref' | 'type'>,
31
35
  ref: React.ForwardedRef<HTMLOListElement>,
32
36
  ) {
33
- const {children, maxLength, separator, space: spaceRaw = 2, ...restProps} = props
34
- const space = _getArrayProp(spaceRaw)
37
+ const {children, gap, maxLength, separator, space: deprecated_space = 2, ...restProps} = props
38
+ const space = _getArrayProp(gap === undefined ? deprecated_space : gap)
35
39
  const [open, setOpen] = useState(false)
36
40
  const expandElementRef = useRef<HTMLButtonElement | null>(null)
37
41
  const popoverElementRef = useRef<HTMLDivElement | null>(null)
@@ -101,7 +105,7 @@ function useItems({
101
105
  <Popover
102
106
  constrainSize
103
107
  content={
104
- <Stack as="ol" overflow="auto" padding={space} space={space}>
108
+ <Stack as="ol" overflow="auto" padding={space} gap={space}>
105
109
  {rawItems.slice(beforeLength - 1, len - afterLength)}
106
110
  </Stack>
107
111
  }
@@ -0,0 +1,42 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Inline} from '../../primitives'
5
+ import {Hotkeys} from './hotkeys'
6
+
7
+ jest.mock('../../primitives', () => {
8
+ const actual = jest.requireActual('../../primitives')
9
+
10
+ return {
11
+ ...actual,
12
+ Inline: jest.fn((props: Record<string, unknown>) => (actual.Inline as any).render(props, null)),
13
+ }
14
+ })
15
+
16
+ describe('components/hotkeys spacing', () => {
17
+ const mockedInline = jest.mocked(Inline)
18
+
19
+ beforeEach(() => {
20
+ mockedInline.mockClear()
21
+ })
22
+
23
+ it('should support `space` and `gap` with the same behavior', () => {
24
+ render(<Hotkeys keys={['Ctrl', 'S']} space={2} />)
25
+ expect(mockedInline.mock.calls.map(([props]) => props)).toContainEqual(
26
+ expect.objectContaining({gap: [2]}),
27
+ )
28
+
29
+ mockedInline.mockClear()
30
+ render(<Hotkeys gap={2} keys={['Ctrl', 'S']} />)
31
+ expect(mockedInline.mock.calls.map(([props]) => props)).toContainEqual(
32
+ expect.objectContaining({gap: [2]}),
33
+ )
34
+ })
35
+
36
+ it('should prefer `gap` over `space` when both are provided', () => {
37
+ render(<Hotkeys gap={3} keys={['Ctrl', 'S']} space={1} />)
38
+ const propsList = mockedInline.mock.calls.map(([props]) => props)
39
+ expect(propsList).toContainEqual(expect.objectContaining({gap: [3]}))
40
+ expect(propsList).not.toContainEqual(expect.objectContaining({gap: [1]}))
41
+ })
42
+ })
@@ -10,8 +10,12 @@ import {Radius} from '../../types'
10
10
  */
11
11
  export interface HotkeysProps {
12
12
  fontSize?: number | number[]
13
+ gap?: number | number[]
13
14
  padding?: number | number[]
14
15
  radius?: Radius | Radius[]
16
+ /**
17
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
18
+ */
15
19
  space?: number | number[]
16
20
  keys?: string[]
17
21
  }
@@ -40,8 +44,8 @@ export const Hotkeys = forwardRef(function Hotkeys(
40
44
  props: HotkeysProps & Omit<React.HTMLProps<HTMLElement>, 'as' | 'ref' | 'size'>,
41
45
  ref: React.Ref<HTMLElement>,
42
46
  ) {
43
- const {fontSize, keys, padding, radius, space: spaceProp = 0.5, ...restProps} = props
44
- const space = _getArrayProp(spaceProp)
47
+ const {fontSize, gap, keys, padding, radius, space: deprecated_space = 0.5, ...restProps} = props
48
+ const spacing = _getArrayProp(gap === undefined ? deprecated_space : gap)
45
49
 
46
50
  if (!keys || keys.length === 0) {
47
51
  return <></>
@@ -49,7 +53,7 @@ export const Hotkeys = forwardRef(function Hotkeys(
49
53
 
50
54
  return (
51
55
  <StyledHotkeys data-ui="Hotkeys" {...restProps} ref={ref}>
52
- <Inline as="span" space={space}>
56
+ <Inline as="span" gap={spacing}>
53
57
  {keys.map((key, i) => (
54
58
  <Key fontSize={fontSize} key={i} padding={padding} radius={radius}>
55
59
  {key}
@@ -0,0 +1,63 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Stack} from '../../primitives'
5
+ import {Menu} from './menu'
6
+
7
+ jest.mock('../../primitives', () => {
8
+ const actual = jest.requireActual('../../primitives')
9
+
10
+ return {
11
+ ...actual,
12
+ Stack: jest.fn((props: Record<string, unknown>) => (actual.Stack as any).render(props, null)),
13
+ }
14
+ })
15
+
16
+ jest.mock('../../utils', () => {
17
+ const actual = jest.requireActual('../../utils')
18
+
19
+ return {
20
+ ...actual,
21
+ useLayer: () => ({isTopLayer: true}),
22
+ }
23
+ })
24
+
25
+ describe('components/menu spacing', () => {
26
+ const mockedStack = jest.mocked(Stack)
27
+
28
+ beforeEach(() => {
29
+ mockedStack.mockClear()
30
+ })
31
+
32
+ it('should support `space` and `gap` with the same behavior', () => {
33
+ render(
34
+ <Menu space={2}>
35
+ <div>Item</div>
36
+ </Menu>,
37
+ )
38
+ expect(mockedStack.mock.calls.map(([props]) => props)).toContainEqual(
39
+ expect.objectContaining({gap: 2}),
40
+ )
41
+
42
+ mockedStack.mockClear()
43
+ render(
44
+ <Menu gap={2}>
45
+ <div>Item</div>
46
+ </Menu>,
47
+ )
48
+ expect(mockedStack.mock.calls.map(([props]) => props)).toContainEqual(
49
+ expect.objectContaining({gap: 2}),
50
+ )
51
+ })
52
+
53
+ it('should prefer `gap` over `space` when both are provided', () => {
54
+ render(
55
+ <Menu gap={3} space={1}>
56
+ <div>Item</div>
57
+ </Menu>,
58
+ )
59
+ const propsList = mockedStack.mock.calls.map(([props]) => props)
60
+ expect(propsList).toContainEqual(expect.objectContaining({gap: 3}))
61
+ expect(propsList).not.toContainEqual(expect.objectContaining({gap: 1}))
62
+ })
63
+ })
@@ -27,6 +27,10 @@ export interface MenuProps extends ResponsivePaddingProps {
27
27
  'originElement'?: HTMLElement | null
28
28
  'registerElement'?: (el: HTMLElement) => () => void
29
29
  'shouldFocus'?: 'first' | 'last' | null
30
+ 'gap'?: number | number[]
31
+ /**
32
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
33
+ */
30
34
  'space'?: number | number[]
31
35
  'aria-labelledby'?: string
32
36
  'onBlurCapture'?: (event: FocusEvent) => void
@@ -61,9 +65,11 @@ export const Menu = forwardRef(function Menu(
61
65
  padding = 1,
62
66
  registerElement,
63
67
  shouldFocus: _shouldFocus,
64
- space = 1,
68
+ gap,
69
+ space: deprecated_space = 1,
65
70
  ...restProps
66
71
  } = props
72
+ const spacing = gap === undefined ? deprecated_space : gap
67
73
  const shouldFocus =
68
74
  _shouldFocus ?? ((props.focusFirst && 'first') || (props.focusLast && 'last') || null)
69
75
 
@@ -164,7 +170,7 @@ export const Menu = forwardRef(function Menu(
164
170
  role="menu"
165
171
  tabIndex={-1}
166
172
  >
167
- <Stack space={space}>{children}</Stack>
173
+ <Stack gap={spacing}>{children}</Stack>
168
174
  </StyledMenu>
169
175
  </MenuContext.Provider>
170
176
  )
@@ -0,0 +1,69 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Flex} from '../../primitives'
5
+ import {MenuContext, MenuContextValue} from './menuContext'
6
+ import {MenuGroup} from './menuGroup'
7
+ import {MenuItem} from './menuItem'
8
+
9
+ jest.mock('../../primitives', () => {
10
+ const actual = jest.requireActual('../../primitives')
11
+
12
+ return {
13
+ ...actual,
14
+ Flex: jest.fn((props: Record<string, unknown>) => (actual.Flex as any).render(props, null)),
15
+ Popover: jest.fn(
16
+ ({
17
+ children,
18
+ }: {
19
+ children?: React.ReactNode
20
+ }) => children,
21
+ ),
22
+ }
23
+ })
24
+
25
+ const menuContextValue: MenuContextValue = {
26
+ version: 2,
27
+ activeElement: null,
28
+ mount: () => () => undefined,
29
+ onItemMouseEnter: () => undefined,
30
+ onItemMouseLeave: () => undefined,
31
+ }
32
+
33
+ function renderMenuGroup(props: Partial<React.ComponentProps<typeof MenuGroup>> = {}) {
34
+ return render(
35
+ <MenuContext.Provider value={menuContextValue}>
36
+ <MenuGroup id="menu-group" text="Menu group" {...props}>
37
+ <MenuItem id="submenu-item" text="Submenu item" />
38
+ </MenuGroup>
39
+ </MenuContext.Provider>,
40
+ )
41
+ }
42
+
43
+ describe('components/menuGroup spacing', () => {
44
+ const mockedFlex = jest.mocked(Flex)
45
+
46
+ beforeEach(() => {
47
+ mockedFlex.mockClear()
48
+ })
49
+
50
+ it('should support `space` and `gap` with the same behavior', () => {
51
+ renderMenuGroup({space: 2})
52
+ expect(mockedFlex.mock.calls.map(([props]) => props)).toContainEqual(
53
+ expect.objectContaining({gap: 2}),
54
+ )
55
+
56
+ mockedFlex.mockClear()
57
+ renderMenuGroup({gap: 2})
58
+ expect(mockedFlex.mock.calls.map(([props]) => props)).toContainEqual(
59
+ expect.objectContaining({gap: 2}),
60
+ )
61
+ })
62
+
63
+ it('should prefer `gap` over `space` when both are provided', () => {
64
+ renderMenuGroup({gap: 3, space: 1})
65
+ const propsList = mockedFlex.mock.calls.map(([props]) => props)
66
+ expect(propsList).toContainEqual(expect.objectContaining({gap: 3}))
67
+ expect(propsList).not.toContainEqual(expect.objectContaining({gap: 1}))
68
+ })
69
+ })
@@ -31,6 +31,10 @@ export interface MenuGroupProps {
31
31
  padding?: number | number[]
32
32
  popover?: Omit<PopoverProps, 'content' | 'open'>
33
33
  radius?: Radius | Radius[]
34
+ gap?: number | number[]
35
+ /**
36
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
37
+ */
34
38
  space?: number | number[]
35
39
  text: React.ReactNode
36
40
  tone?: SelectableTone
@@ -53,11 +57,13 @@ export function MenuGroup(
53
57
  padding = 3,
54
58
  popover,
55
59
  radius = 2,
56
- space = 3,
60
+ gap,
61
+ space: deprecated_space = 3,
57
62
  text,
58
63
  tone = 'default',
59
64
  ...restProps
60
65
  } = props
66
+ const spacing = gap === undefined ? deprecated_space : gap
61
67
  const menu = useMenu()
62
68
  const {scheme} = useRootTheme()
63
69
  const {
@@ -193,7 +199,7 @@ export function MenuGroup(
193
199
  tabIndex={-1}
194
200
  type={as === 'button' ? 'button' : undefined}
195
201
  >
196
- <Flex gap={space} padding={padding}>
202
+ <Flex gap={spacing} padding={padding}>
197
203
  {IconComponent && (
198
204
  <Text size={fontSize}>
199
205
  {isValidElement(IconComponent) && IconComponent}
@@ -0,0 +1,59 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Flex} from '../../primitives'
5
+ import {MenuContext, MenuContextValue} from './menuContext'
6
+ import {MenuItem} from './menuItem'
7
+
8
+ jest.mock('../../primitives', () => {
9
+ const actual = jest.requireActual('../../primitives')
10
+
11
+ return {
12
+ ...actual,
13
+ Flex: jest.fn((props: Record<string, unknown>) => (actual.Flex as any).render(props, null)),
14
+ }
15
+ })
16
+
17
+ const menuContextValue: MenuContextValue = {
18
+ version: 2,
19
+ activeElement: null,
20
+ mount: () => () => undefined,
21
+ onItemMouseEnter: () => undefined,
22
+ onItemMouseLeave: () => undefined,
23
+ }
24
+
25
+ function renderMenuItem(props: Partial<React.ComponentProps<typeof MenuItem>> = {}) {
26
+ return render(
27
+ <MenuContext.Provider value={menuContextValue}>
28
+ <MenuItem id="menu-item" text="Menu item" {...props} />
29
+ </MenuContext.Provider>,
30
+ )
31
+ }
32
+
33
+ describe('components/menuItem spacing', () => {
34
+ const mockedFlex = jest.mocked(Flex)
35
+
36
+ beforeEach(() => {
37
+ mockedFlex.mockClear()
38
+ })
39
+
40
+ it('should support `space` and `gap` with the same behavior', () => {
41
+ renderMenuItem({space: 2})
42
+ expect(mockedFlex.mock.calls.map(([props]) => props)).toContainEqual(
43
+ expect.objectContaining({gap: 2}),
44
+ )
45
+
46
+ mockedFlex.mockClear()
47
+ renderMenuItem({gap: 2})
48
+ expect(mockedFlex.mock.calls.map(([props]) => props)).toContainEqual(
49
+ expect.objectContaining({gap: 2}),
50
+ )
51
+ })
52
+
53
+ it('should prefer `gap` over `space` when both are provided', () => {
54
+ renderMenuItem({gap: 3, space: 1})
55
+ const propsList = mockedFlex.mock.calls.map(([props]) => props)
56
+ expect(propsList).toContainEqual(expect.objectContaining({gap: 3}))
57
+ expect(propsList).not.toContainEqual(expect.objectContaining({gap: 1}))
58
+ })
59
+ })
@@ -30,6 +30,10 @@ export interface MenuItemProps extends ResponsivePaddingProps, ResponsiveRadiusP
30
30
  iconRight?: React.ElementType | React.ReactNode
31
31
  pressed?: boolean
32
32
  selected?: boolean
33
+ gap?: number | number[]
34
+ /**
35
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
36
+ */
33
37
  space?: number | number[]
34
38
  text?: React.ReactNode
35
39
  tone?: SelectableTone
@@ -62,11 +66,13 @@ export const MenuItem = forwardRef(function MenuItem(
62
66
  pressed,
63
67
  radius = 2,
64
68
  selected: selectedProp,
65
- space = 3,
69
+ gap,
70
+ space: deprecated_space = 3,
66
71
  text,
67
72
  tone = 'default',
68
73
  ...restProps
69
74
  } = props
75
+ const spacing = gap === undefined ? deprecated_space : gap
70
76
  const {scheme} = useRootTheme()
71
77
  const menu = useMenu()
72
78
  const {
@@ -137,7 +143,7 @@ export const MenuItem = forwardRef(function MenuItem(
137
143
  type={as === 'button' ? 'button' : undefined}
138
144
  >
139
145
  {(IconComponent || text || IconRightComponent) && (
140
- <Flex as="span" gap={space} align="center" {...paddingProps}>
146
+ <Flex as="span" gap={spacing} align="center" {...paddingProps}>
141
147
  {IconComponent && (
142
148
  <Text size={fontSize}>
143
149
  {isValidElement(IconComponent) && IconComponent}
@@ -0,0 +1,48 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {inlineSpaceStyle} from '../../primitives/inline/styles'
5
+ import {Tab} from './tab'
6
+ import {TabList} from './tabList'
7
+
8
+ jest.mock('../../primitives/inline/styles', () => {
9
+ const actual = jest.requireActual('../../primitives/inline/styles')
10
+
11
+ return {
12
+ ...actual,
13
+ inlineSpaceStyle: jest.fn(actual.inlineSpaceStyle),
14
+ }
15
+ })
16
+
17
+ describe('components/tabList', () => {
18
+ const mockedInlineSpaceStyle = jest.mocked(inlineSpaceStyle)
19
+
20
+ beforeEach(() => {
21
+ mockedInlineSpaceStyle.mockClear()
22
+ })
23
+
24
+ function renderTabList(
25
+ props: Partial<React.ComponentProps<typeof TabList>> = {},
26
+ ): ReturnType<typeof render> {
27
+ return render(
28
+ <TabList {...props}>
29
+ <Tab aria-controls="tab-panel-a" id="tab-a" label="Tab A" />
30
+ <Tab aria-controls="tab-panel-b" id="tab-b" label="Tab B" />
31
+ </TabList>,
32
+ )
33
+ }
34
+
35
+ it('should support `space` and `gap` with the same behavior', () => {
36
+ renderTabList({space: 2})
37
+ expect(mockedInlineSpaceStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [2]}))
38
+
39
+ mockedInlineSpaceStyle.mockClear()
40
+ renderTabList({gap: 2})
41
+ expect(mockedInlineSpaceStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [2]}))
42
+ })
43
+
44
+ it('should prefer `gap` over `space` when both are provided', () => {
45
+ renderTabList({gap: 3, space: 1})
46
+ expect(mockedInlineSpaceStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [3]}))
47
+ })
48
+ })
@@ -0,0 +1,55 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Stack} from '../../primitives'
5
+ import {Tree} from './tree'
6
+ import {TreeItem} from './treeItem'
7
+
8
+ jest.mock('../../primitives', () => {
9
+ const actual = jest.requireActual('../../primitives')
10
+
11
+ return {
12
+ ...actual,
13
+ Stack: jest.fn((props: Record<string, unknown>) => (actual.Stack as any).render(props, null)),
14
+ }
15
+ })
16
+
17
+ describe('components/tree spacing', () => {
18
+ const mockedStack = jest.mocked(Stack)
19
+
20
+ beforeEach(() => {
21
+ mockedStack.mockClear()
22
+ })
23
+
24
+ it('should support `space` and `gap` with the same behavior', () => {
25
+ render(
26
+ <Tree space={2}>
27
+ <TreeItem text="Item 1" />
28
+ </Tree>,
29
+ )
30
+ expect(mockedStack.mock.calls.map(([props]) => props)).toContainEqual(
31
+ expect.objectContaining({gap: 2}),
32
+ )
33
+
34
+ mockedStack.mockClear()
35
+ render(
36
+ <Tree gap={2}>
37
+ <TreeItem text="Item 1" />
38
+ </Tree>,
39
+ )
40
+ expect(mockedStack.mock.calls.map(([props]) => props)).toContainEqual(
41
+ expect.objectContaining({gap: 2}),
42
+ )
43
+ })
44
+
45
+ it('should prefer `gap` over `space` when both are provided', () => {
46
+ render(
47
+ <Tree gap={3} space={1}>
48
+ <TreeItem text="Item 1" />
49
+ </Tree>,
50
+ )
51
+ const propsList = mockedStack.mock.calls.map(([props]) => props)
52
+ expect(propsList).toContainEqual(expect.objectContaining({gap: 3}))
53
+ expect(propsList).not.toContainEqual(expect.objectContaining({gap: 1}))
54
+ })
55
+ })
@@ -18,6 +18,10 @@ import {TreeContextValue, TreeState} from './types'
18
18
  * @beta
19
19
  */
20
20
  export interface TreeProps {
21
+ gap?: number | number[]
22
+ /**
23
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
24
+ */
21
25
  space?: number | number[]
22
26
  }
23
27
 
@@ -30,7 +34,8 @@ export const Tree = forwardRef(function Tree(
30
34
  Omit<React.HTMLProps<HTMLDivElement>, 'align' | 'as' | 'height' | 'ref' | 'role' | 'wrap'>,
31
35
  forwardedRef: React.ForwardedRef<HTMLDivElement>,
32
36
  ): React.JSX.Element {
33
- const {children, space = 1, onFocus, ...restProps} = props
37
+ const {children, gap, space: deprecated_space = 1, onFocus, ...restProps} = props
38
+ const spacing = gap === undefined ? deprecated_space : gap
34
39
  const ref = useRef<HTMLDivElement | null>(null)
35
40
  const [focusedElement, setFocusedElement] = useState<HTMLElement | null>(null)
36
41
  const focusedElementRef = useRef(focusedElement)
@@ -89,10 +94,11 @@ export const Tree = forwardRef(function Tree(
89
94
  registerItem,
90
95
  setExpanded,
91
96
  setFocusedElement,
92
- space,
97
+ gap: spacing,
98
+ space: spacing,
93
99
  state,
94
100
  }),
95
- [focusedElement, itemElements, path, registerItem, setExpanded, space, state],
101
+ [focusedElement, itemElements, path, registerItem, setExpanded, spacing, state],
96
102
  )
97
103
 
98
104
  const handleKeyDown = useCallback(
@@ -221,7 +227,7 @@ export const Tree = forwardRef(function Tree(
221
227
  onKeyDown={handleKeyDown}
222
228
  ref={ref}
223
229
  role="tree"
224
- space={space}
230
+ gap={spacing}
225
231
  >
226
232
  {children}
227
233
  </Stack>
@@ -18,9 +18,9 @@ export function TreeGroup(
18
18
  data-ui="TreeGroup"
19
19
  {...restProps}
20
20
  hidden={!expanded}
21
- marginTop={tree.space}
21
+ marginTop={tree.gap}
22
22
  role="group"
23
- space={tree.space}
23
+ gap={tree.gap}
24
24
  >
25
25
  {children}
26
26
  </Stack>