@sanity/ui 3.1.14 → 3.3.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 (78) hide show
  1. package/dist/_chunks-cjs/_visual-editing.js +323 -292
  2. package/dist/_chunks-cjs/_visual-editing.js.map +1 -1
  3. package/dist/_chunks-es/_visual-editing.mjs +323 -292
  4. package/dist/_chunks-es/_visual-editing.mjs.map +1 -1
  5. package/dist/_visual-editing.d.mts +195 -76
  6. package/dist/_visual-editing.d.ts +195 -76
  7. package/dist/index.d.mts +308 -140
  8. package/dist/index.d.ts +308 -140
  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/dist/theme.d.mts +10 -4
  14. package/dist/theme.d.ts +10 -4
  15. package/package.json +1 -1
  16. package/src/core/components/autocomplete/autocomplete.tsx +1 -1
  17. package/src/core/components/autocomplete/types.ts +2 -2
  18. package/src/core/components/breadcrumbs/breadcrumbs.test.tsx +51 -0
  19. package/src/core/components/breadcrumbs/breadcrumbs.tsx +7 -3
  20. package/src/core/components/hotkeys/hotkeys.test.tsx +42 -0
  21. package/src/core/components/hotkeys/hotkeys.tsx +7 -3
  22. package/src/core/components/menu/__workshop__/asComponent.tsx +1 -1
  23. package/src/core/components/menu/menu.spacing.test.tsx +63 -0
  24. package/src/core/components/menu/menu.tsx +8 -2
  25. package/src/core/components/menu/menuDivider.ts +16 -5
  26. package/src/core/components/menu/menuGroup.spacing.test.tsx +63 -0
  27. package/src/core/components/menu/menuGroup.tsx +22 -8
  28. package/src/core/components/menu/menuItem.spacing.test.tsx +59 -0
  29. package/src/core/components/menu/menuItem.tsx +25 -8
  30. package/src/core/components/skeleton/skeleton.tsx +2 -2
  31. package/src/core/components/tab/tabList.test.tsx +48 -0
  32. package/src/core/components/tab/tabList.tsx +2 -2
  33. package/src/core/components/tab/tabPanel.tsx +2 -2
  34. package/src/core/components/toast/styles.ts +2 -2
  35. package/src/core/components/tree/__workshop__/basic.perf.ts +1 -1
  36. package/src/core/components/tree/__workshop__/tabFromElement.tsx +1 -1
  37. package/src/core/components/tree/tree.test.tsx +55 -0
  38. package/src/core/components/tree/tree.tsx +19 -10
  39. package/src/core/components/tree/treeGroup.tsx +3 -3
  40. package/src/core/components/tree/treeItem.test.tsx +65 -0
  41. package/src/core/components/tree/treeItem.tsx +11 -4
  42. package/src/core/components/tree/types.ts +4 -0
  43. package/src/core/primitives/avatar/avatar.tsx +19 -11
  44. package/src/core/primitives/badge/badge.tsx +20 -12
  45. package/src/core/primitives/box/__workshop__/asComponent.tsx +31 -0
  46. package/src/core/primitives/box/__workshop__/index.ts +5 -0
  47. package/src/core/primitives/box/box.test.tsx +142 -0
  48. package/src/core/primitives/box/box.tsx +41 -18
  49. package/src/core/primitives/button/__workshop__/disabled.tsx +2 -2
  50. package/src/core/primitives/button/__workshop__/sanityUploadButton.tsx +1 -1
  51. package/src/core/primitives/button/__workshop__/uploadButton.tsx +1 -1
  52. package/src/core/primitives/button/button.test.tsx +41 -3
  53. package/src/core/primitives/button/button.tsx +36 -12
  54. package/src/core/primitives/card/__workshop__/asComponent.tsx +1 -1
  55. package/src/core/primitives/card/card.tsx +24 -13
  56. package/src/core/primitives/code/code.tsx +16 -7
  57. package/src/core/primitives/container/container.tsx +23 -10
  58. package/src/core/primitives/flex/flex.tsx +21 -11
  59. package/src/core/primitives/grid/grid.test.tsx +80 -0
  60. package/src/core/primitives/grid/grid.tsx +39 -13
  61. package/src/core/primitives/heading/heading.tsx +19 -11
  62. package/src/core/primitives/inline/inline.test.tsx +51 -0
  63. package/src/core/primitives/inline/inline.tsx +28 -12
  64. package/src/core/primitives/kbd/kbd.tsx +19 -11
  65. package/src/core/primitives/label/label.tsx +19 -11
  66. package/src/core/primitives/popover/__workshop__/SidePanelStory.tsx +1 -1
  67. package/src/core/primitives/select/select.test.tsx +52 -0
  68. package/src/core/primitives/select/select.tsx +8 -2
  69. package/src/core/primitives/stack/stack.test.tsx +57 -0
  70. package/src/core/primitives/stack/stack.tsx +27 -13
  71. package/src/core/primitives/text/text.tsx +19 -11
  72. package/src/core/primitives/textInput/textInput.test.tsx +48 -0
  73. package/src/core/primitives/textInput/textInput.tsx +9 -4
  74. package/src/core/primitives/types.ts +33 -0
  75. package/src/core/types/component.ts +32 -0
  76. package/src/core/types/index.ts +1 -0
  77. package/src/core/utils/virtualList/virtualList.tsx +2 -2
  78. package/src/theme/system/css.ts +10 -4
@@ -8,31 +8,32 @@ import {
8
8
  responsiveFlexStyle,
9
9
  ResponsiveFlexStyleProps,
10
10
  } from '../../styles/internal'
11
- import {Box, BoxProps} from '../box'
11
+ import {ElementType, Props} from '../../types'
12
+ import {Box, BoxOwnProps} from '../box'
12
13
  import {ResponsiveFlexItemProps, ResponsiveFlexProps} from '../types'
13
14
 
14
15
  /**
15
16
  * @public
16
17
  */
17
- export interface FlexProps
18
- extends Omit<BoxProps, 'display'>,
18
+ export interface FlexOwnProps
19
+ extends Omit<BoxOwnProps, 'display'>,
19
20
  ResponsiveFlexProps,
20
21
  ResponsiveFlexItemProps {
21
22
  gap?: number | number[]
22
23
  }
23
24
 
25
+ /**
26
+ * @public
27
+ */
28
+ export type FlexProps<E extends ElementType = 'div'> = Props<FlexOwnProps, E>
29
+
24
30
  const StyledFlex = styled(Box)<FlexItemStyleProps & ResponsiveFlexStyleProps>(
25
31
  flexItemStyle,
26
32
  responsiveFlexStyle,
27
33
  )
28
34
 
29
- /**
30
- * The `Flex` component is a wrapper component for flexible elements (`Box`, `Card` and `Flex`).
31
- *
32
- * @public
33
- */
34
- export const Flex = forwardRef(function Flex(
35
- props: FlexProps & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'wrap'>,
35
+ const FlexComponent = forwardRef(function Flex(
36
+ props: FlexOwnProps & {as?: ElementType} & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'wrap'>,
36
37
  ref: React.ForwardedRef<HTMLDivElement>,
37
38
  ) {
38
39
  const {align, as, direction = 'row', gap, justify, wrap, ...restProps} = props
@@ -51,4 +52,13 @@ export const Flex = forwardRef(function Flex(
51
52
  />
52
53
  )
53
54
  })
54
- Flex.displayName = 'ForwardRef(Flex)'
55
+ FlexComponent.displayName = 'ForwardRef(Flex)'
56
+
57
+ /**
58
+ * The `Flex` component is a wrapper component for flexible elements (`Box`, `Card` and `Flex`).
59
+ *
60
+ * @public
61
+ */
62
+ export const Flex = FlexComponent as unknown as <E extends ElementType = 'div'>(
63
+ props: FlexProps<E>,
64
+ ) => React.JSX.Element
@@ -0,0 +1,80 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {responsiveGridStyle} from '../../styles/internal'
5
+ import {Grid} from './grid'
6
+
7
+ jest.mock('../../styles/internal', () => {
8
+ const actual = jest.requireActual('../../styles/internal')
9
+
10
+ return {
11
+ ...actual,
12
+ responsiveGridStyle: jest.fn(actual.responsiveGridStyle),
13
+ }
14
+ })
15
+
16
+ describe('primitives/grid', () => {
17
+ const mockedResponsiveGridStyle = jest.mocked(responsiveGridStyle)
18
+
19
+ beforeEach(() => {
20
+ mockedResponsiveGridStyle.mockClear()
21
+ })
22
+
23
+ it('should support `columns` and `gridTemplateColumns` with the same behavior', () => {
24
+ render(
25
+ <Grid columns={2}>
26
+ <div>A</div>
27
+ </Grid>,
28
+ )
29
+ expect(mockedResponsiveGridStyle).toHaveBeenCalledWith(expect.objectContaining({$columns: [2]}))
30
+
31
+ mockedResponsiveGridStyle.mockClear()
32
+ render(
33
+ <Grid gridTemplateColumns={2}>
34
+ <div>A</div>
35
+ </Grid>,
36
+ )
37
+ expect(mockedResponsiveGridStyle).toHaveBeenCalledWith(expect.objectContaining({$columns: [2]}))
38
+ })
39
+
40
+ it('should prefer `gridTemplateColumns` over `columns` when both are provided', () => {
41
+ render(
42
+ <Grid columns={1} gridTemplateColumns={3}>
43
+ <div>A</div>
44
+ </Grid>,
45
+ )
46
+ expect(mockedResponsiveGridStyle).toHaveBeenCalledWith(expect.objectContaining({$columns: [3]}))
47
+ expect(mockedResponsiveGridStyle).not.toHaveBeenCalledWith(
48
+ expect.objectContaining({$columns: [1]}),
49
+ )
50
+ })
51
+
52
+ it('should support `rows` and `gridTemplateRows` with the same behavior', () => {
53
+ render(
54
+ <Grid rows={2}>
55
+ <div>A</div>
56
+ </Grid>,
57
+ )
58
+ expect(mockedResponsiveGridStyle).toHaveBeenCalledWith(expect.objectContaining({$rows: [2]}))
59
+
60
+ mockedResponsiveGridStyle.mockClear()
61
+ render(
62
+ <Grid gridTemplateRows={2}>
63
+ <div>A</div>
64
+ </Grid>,
65
+ )
66
+ expect(mockedResponsiveGridStyle).toHaveBeenCalledWith(expect.objectContaining({$rows: [2]}))
67
+ })
68
+
69
+ it('should prefer `gridTemplateRows` over `rows` when both are provided', () => {
70
+ render(
71
+ <Grid gridTemplateRows={3} rows={1}>
72
+ <div>A</div>
73
+ </Grid>,
74
+ )
75
+ expect(mockedResponsiveGridStyle).toHaveBeenCalledWith(expect.objectContaining({$rows: [3]}))
76
+ expect(mockedResponsiveGridStyle).not.toHaveBeenCalledWith(
77
+ expect.objectContaining({$rows: [1]}),
78
+ )
79
+ })
80
+ })
@@ -3,27 +3,44 @@ import {styled} from 'styled-components'
3
3
 
4
4
  import {_getArrayProp} from '../../styles'
5
5
  import {responsiveGridStyle, ResponsiveGridStyleProps} from '../../styles/internal'
6
- import {Box, BoxProps} from '../box'
6
+ import {ElementType, Props} from '../../types'
7
+ import {Box, BoxOwnProps} from '../box'
7
8
  import {ResponsiveGridProps} from '../types'
8
9
 
9
10
  /**
10
11
  * @public
11
12
  */
12
- export interface GridProps extends Omit<BoxProps, 'display'>, ResponsiveGridProps {}
13
-
14
- const StyledGrid = styled(Box)<ResponsiveGridStyleProps>(responsiveGridStyle)
13
+ export interface GridOwnProps extends Omit<BoxOwnProps, 'display'>, ResponsiveGridProps {}
15
14
 
16
15
  /**
17
- * The `Grid` component is for building 2-dimensional layers (based on CSS grid).
18
- *
19
16
  * @public
20
17
  */
21
- export const Grid = forwardRef(function Grid(
22
- props: GridProps & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'height' | 'rows'>,
18
+ export type GridProps<E extends ElementType = 'div'> = Props<GridOwnProps, E>
19
+
20
+ const StyledGrid = styled(Box)<ResponsiveGridStyleProps>(responsiveGridStyle)
21
+
22
+ const GridComponent = forwardRef(function Grid(
23
+ props: GridOwnProps & {as?: ElementType} & Omit<
24
+ React.HTMLProps<HTMLDivElement>,
25
+ 'as' | 'height' | 'rows'
26
+ >,
23
27
  ref: React.ForwardedRef<HTMLDivElement>,
24
28
  ) {
25
- const {as, autoRows, autoCols, autoFlow, columns, gap, gapX, gapY, rows, children, ...restProps} =
26
- props
29
+ const {
30
+ as,
31
+ autoRows,
32
+ autoCols,
33
+ autoFlow,
34
+ columns,
35
+ gridTemplateColumns,
36
+ gap,
37
+ gapX,
38
+ gapY,
39
+ rows,
40
+ gridTemplateRows,
41
+ children,
42
+ ...restProps
43
+ } = props
27
44
 
28
45
  return (
29
46
  <StyledGrid
@@ -33,11 +50,11 @@ export const Grid = forwardRef(function Grid(
33
50
  $autoRows={_getArrayProp(autoRows)}
34
51
  $autoCols={_getArrayProp(autoCols)}
35
52
  $autoFlow={_getArrayProp(autoFlow)}
36
- $columns={_getArrayProp(columns)}
53
+ $columns={_getArrayProp(gridTemplateColumns === undefined ? columns : gridTemplateColumns)}
37
54
  $gap={_getArrayProp(gap)}
38
55
  $gapX={_getArrayProp(gapX)}
39
56
  $gapY={_getArrayProp(gapY)}
40
- $rows={_getArrayProp(rows)}
57
+ $rows={_getArrayProp(gridTemplateRows === undefined ? rows : gridTemplateRows)}
41
58
  forwardedAs={as}
42
59
  ref={ref}
43
60
  >
@@ -45,4 +62,13 @@ export const Grid = forwardRef(function Grid(
45
62
  </StyledGrid>
46
63
  )
47
64
  })
48
- Grid.displayName = 'ForwardRef(Grid)'
65
+ GridComponent.displayName = 'ForwardRef(Grid)'
66
+
67
+ /**
68
+ * The `Grid` component is for building 2-dimensional layers (based on CSS grid).
69
+ *
70
+ * @public
71
+ */
72
+ export const Grid = GridComponent as unknown as <E extends ElementType = 'div'>(
73
+ props: GridProps<E>,
74
+ ) => React.JSX.Element
@@ -9,7 +9,7 @@ import {
9
9
  responsiveTextAlignStyle,
10
10
  ResponsiveTextAlignStyleProps,
11
11
  } from '../../styles/internal'
12
- import {TextAlign} from '../../types'
12
+ import {ElementType, Props, TextAlign} from '../../types'
13
13
  import {SpanWithTextOverflow} from '../../utils/spanWithTextOverflow'
14
14
  import {headingBaseStyle} from './styles'
15
15
  import {HeadingStyleProps} from './types'
@@ -17,10 +17,9 @@ import {HeadingStyleProps} from './types'
17
17
  /**
18
18
  * @public
19
19
  */
20
- export interface HeadingProps {
20
+ export interface HeadingOwnProps {
21
21
  accent?: boolean
22
22
  align?: TextAlign | TextAlign[]
23
- as?: React.ElementType | keyof React.JSX.IntrinsicElements
24
23
  muted?: boolean
25
24
  size?: number | number[]
26
25
  /**
@@ -32,17 +31,17 @@ export interface HeadingProps {
32
31
  weight?: ThemeFontWeightKey
33
32
  }
34
33
 
34
+ /**
35
+ * @public
36
+ */
37
+ export type HeadingProps<E extends ElementType = 'div'> = Props<HeadingOwnProps, E>
38
+
35
39
  const StyledHeading = styled.div<
36
40
  HeadingStyleProps & ResponsiveTextAlignStyleProps & ResponsiveFontStyleProps
37
41
  >(headingBaseStyle, responsiveTextAlignStyle, responsiveHeadingFont)
38
42
 
39
- /**
40
- * Typographic headings.
41
- *
42
- * @public
43
- */
44
- export const Heading = forwardRef(function Heading(
45
- props: HeadingProps & Omit<React.HTMLProps<HTMLElement>, 'as' | 'size'>,
43
+ const HeadingComponent = forwardRef(function Heading(
44
+ props: HeadingOwnProps & {as?: ElementType} & Omit<React.HTMLProps<HTMLElement>, 'as' | 'size'>,
46
45
  ref: React.ForwardedRef<HTMLElement>,
47
46
  ) {
48
47
  const {
@@ -77,4 +76,13 @@ export const Heading = forwardRef(function Heading(
77
76
  </StyledHeading>
78
77
  )
79
78
  })
80
- Heading.displayName = 'ForwardRef(Heading)'
79
+ HeadingComponent.displayName = 'ForwardRef(Heading)'
80
+
81
+ /**
82
+ * Typographic headings.
83
+ *
84
+ * @public
85
+ */
86
+ export const Heading = HeadingComponent as unknown as <E extends ElementType = 'div'>(
87
+ props: HeadingProps<E>,
88
+ ) => React.JSX.Element
@@ -0,0 +1,51 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Inline} from './inline'
5
+ import {inlineSpaceStyle} from './styles'
6
+
7
+ jest.mock('./styles', () => {
8
+ const actual = jest.requireActual('./styles')
9
+
10
+ return {
11
+ ...actual,
12
+ inlineSpaceStyle: jest.fn(actual.inlineSpaceStyle),
13
+ }
14
+ })
15
+
16
+ describe('primitives/inline', () => {
17
+ const mockedInlineSpaceStyle = jest.mocked(inlineSpaceStyle)
18
+
19
+ beforeEach(() => {
20
+ mockedInlineSpaceStyle.mockClear()
21
+ })
22
+
23
+ it('should support `space` and `gap` with the same behavior', () => {
24
+ render(
25
+ <Inline space={2}>
26
+ <span>One</span>
27
+ <span>Two</span>
28
+ </Inline>,
29
+ )
30
+ expect(mockedInlineSpaceStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [2]}))
31
+
32
+ mockedInlineSpaceStyle.mockClear()
33
+ render(
34
+ <Inline gap={2}>
35
+ <span>One</span>
36
+ <span>Two</span>
37
+ </Inline>,
38
+ )
39
+ expect(mockedInlineSpaceStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [2]}))
40
+ })
41
+
42
+ it('should prefer `gap` over `space` when both are provided', () => {
43
+ render(
44
+ <Inline gap={3} space={1}>
45
+ <span>One</span>
46
+ <span>Two</span>
47
+ </Inline>,
48
+ )
49
+ expect(mockedInlineSpaceStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [3]}))
50
+ })
51
+ })
@@ -2,30 +2,37 @@ import {Children, forwardRef, useMemo} from 'react'
2
2
  import {styled} from 'styled-components'
3
3
 
4
4
  import {_getArrayProp} from '../../styles'
5
- import {Box, BoxProps} from '../box'
5
+ import {ElementType, Props} from '../../types'
6
+ import {Box, BoxOwnProps} from '../box'
6
7
  import {inlineBaseStyle, inlineSpaceStyle} from './styles'
7
8
  import {ResponsiveInlineSpaceStyleProps} from './types'
8
9
 
9
10
  /**
10
11
  * @public
11
12
  */
12
- export interface InlineProps extends Omit<BoxProps, 'display'> {
13
- /** The spacing between children. */
13
+ export interface InlineOwnProps extends Omit<BoxOwnProps, 'display'> {
14
+ /**
15
+ * The spacing between children.
16
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
17
+ */
14
18
  space?: number | number[]
19
+ /** The spacing between children. */
20
+ gap?: number | number[]
15
21
  }
16
22
 
17
- const StyledInline = styled(Box)<ResponsiveInlineSpaceStyleProps>(inlineBaseStyle, inlineSpaceStyle)
18
-
19
23
  /**
20
- * The `Inline` component is a layout utility for aligning and spacing items horizontally.
21
- *
22
24
  * @public
23
25
  */
24
- export const Inline = forwardRef(function Inline(
25
- props: InlineProps & React.HTMLProps<HTMLDivElement>,
26
+ export type InlineProps<E extends ElementType = 'div'> = Props<InlineOwnProps, E>
27
+
28
+ const StyledInline = styled(Box)<ResponsiveInlineSpaceStyleProps>(inlineBaseStyle, inlineSpaceStyle)
29
+
30
+ const InlineComponent = forwardRef(function Inline(
31
+ props: InlineOwnProps & {as?: ElementType} & React.HTMLProps<HTMLDivElement>,
26
32
  ref,
27
33
  ) {
28
- const {as, children: childrenProp, space, ...restProps} = props
34
+ const {as, children: childrenProp, gap, space: deprecated_space, ...restProps} = props
35
+ const spacing = gap === undefined ? deprecated_space : gap
29
36
 
30
37
  const children = useMemo(
31
38
  () => Children.map(childrenProp, (child) => child && <div>{child}</div>),
@@ -36,7 +43,7 @@ export const Inline = forwardRef(function Inline(
36
43
  <StyledInline
37
44
  data-ui="Inline"
38
45
  {...restProps}
39
- $space={_getArrayProp(space)}
46
+ $space={_getArrayProp(spacing)}
40
47
  forwardedAs={as}
41
48
  ref={ref as any}
42
49
  >
@@ -44,4 +51,13 @@ export const Inline = forwardRef(function Inline(
44
51
  </StyledInline>
45
52
  )
46
53
  })
47
- Inline.displayName = 'ForwardRef(Inline)'
54
+ InlineComponent.displayName = 'ForwardRef(Inline)'
55
+
56
+ /**
57
+ * The `Inline` component is a layout utility for aligning and spacing items horizontally.
58
+ *
59
+ * @public
60
+ */
61
+ export const Inline = InlineComponent as unknown as <E extends ElementType = 'div'>(
62
+ props: InlineProps<E>,
63
+ ) => React.JSX.Element
@@ -3,20 +3,24 @@ import {css, styled} from 'styled-components'
3
3
 
4
4
  import {_getArrayProp} from '../../styles'
5
5
  import {responsiveRadiusStyle, ResponsiveRadiusStyleProps} from '../../styles/internal'
6
- import {Radius} from '../../types'
6
+ import {ElementType, Props, Radius} from '../../types'
7
7
  import {Box} from '../box'
8
8
  import {Text} from '../text'
9
9
 
10
10
  /**
11
11
  * @public
12
12
  */
13
- export interface KBDProps {
14
- as?: React.ElementType | keyof React.JSX.IntrinsicElements
13
+ export interface KBDOwnProps {
15
14
  fontSize?: number | number[]
16
15
  padding?: number | number[]
17
16
  radius?: Radius | Radius[]
18
17
  }
19
18
 
19
+ /**
20
+ * @public
21
+ */
22
+ export type KBDProps<E extends ElementType = 'kbd'> = Props<KBDOwnProps, E>
23
+
20
24
  function kbdStyle() {
21
25
  return css`
22
26
  --card-bg-color: var(--card-kbd-bg-color);
@@ -37,13 +41,8 @@ function kbdStyle() {
37
41
 
38
42
  const StyledKBD = styled.kbd<ResponsiveRadiusStyleProps>(responsiveRadiusStyle, kbdStyle)
39
43
 
40
- /**
41
- * Used to define some text as keyboard input.
42
- *
43
- * @public
44
- */
45
- export const KBD = forwardRef(function KBD(
46
- props: KBDProps & Omit<React.HTMLProps<HTMLElement>, 'as' | 'ref' | 'size'>,
44
+ const KBDComponent = forwardRef(function KBD(
45
+ props: KBDOwnProps & {as?: ElementType} & Omit<React.HTMLProps<HTMLElement>, 'as' | 'ref' | 'size'>,
47
46
  ref: React.ForwardedRef<HTMLDivElement>,
48
47
  ) {
49
48
  const {children, fontSize = 0, padding = 1, radius = 2, ...restProps} = props
@@ -58,4 +57,13 @@ export const KBD = forwardRef(function KBD(
58
57
  </StyledKBD>
59
58
  )
60
59
  })
61
- KBD.displayName = 'ForwardRef(KBD)'
60
+ KBDComponent.displayName = 'ForwardRef(KBD)'
61
+
62
+ /**
63
+ * Used to define some text as keyboard input.
64
+ *
65
+ * @public
66
+ */
67
+ export const KBD = KBDComponent as unknown as <E extends ElementType = 'kbd'>(
68
+ props: KBDProps<E>,
69
+ ) => React.JSX.Element
@@ -4,17 +4,16 @@ import {styled} from 'styled-components'
4
4
 
5
5
  import {_getArrayProp} from '../../styles'
6
6
  import {responsiveLabelFont, responsiveTextAlignStyle} from '../../styles/internal'
7
- import {TextAlign} from '../../types'
7
+ import {ElementType, Props, TextAlign} from '../../types'
8
8
  import {SpanWithTextOverflow} from '../../utils/spanWithTextOverflow'
9
9
  import {labelBaseStyle} from './styles'
10
10
 
11
11
  /**
12
12
  * @public
13
13
  */
14
- export interface LabelProps {
14
+ export interface LabelOwnProps {
15
15
  accent?: boolean
16
16
  align?: TextAlign | TextAlign[]
17
- as?: React.ElementType | keyof React.JSX.IntrinsicElements
18
17
  muted?: boolean
19
18
  size?: number | number[]
20
19
  /**
@@ -26,6 +25,11 @@ export interface LabelProps {
26
25
  weight?: ThemeFontWeightKey
27
26
  }
28
27
 
28
+ /**
29
+ * @public
30
+ */
31
+ export type LabelProps<E extends ElementType = 'div'> = Props<LabelOwnProps, E>
32
+
29
33
  const StyledLabel = styled.div<{
30
34
  $accent?: boolean
31
35
  $align: TextAlign[]
@@ -33,13 +37,8 @@ const StyledLabel = styled.div<{
33
37
  $size: number[]
34
38
  }>(responsiveLabelFont, responsiveTextAlignStyle, labelBaseStyle)
35
39
 
36
- /**
37
- * Typographic labels.
38
- *
39
- * @public
40
- */
41
- export const Label = forwardRef(function Label(
42
- props: LabelProps & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'size'>,
40
+ const LabelComponent = forwardRef(function Label(
41
+ props: LabelOwnProps & {as?: ElementType} & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'size'>,
43
42
  ref: React.ForwardedRef<HTMLDivElement>,
44
43
  ) {
45
44
  const {
@@ -76,4 +75,13 @@ export const Label = forwardRef(function Label(
76
75
  </StyledLabel>
77
76
  )
78
77
  })
79
- Label.displayName = 'ForwardRef(Label)'
78
+ LabelComponent.displayName = 'ForwardRef(Label)'
79
+
80
+ /**
81
+ * Typographic labels.
82
+ *
83
+ * @public
84
+ */
85
+ export const Label = LabelComponent as unknown as <E extends ElementType = 'div'>(
86
+ props: LabelProps<E>,
87
+ ) => React.JSX.Element
@@ -34,7 +34,7 @@ export default function SidePanelStory() {
34
34
  </Box>
35
35
  </Card>
36
36
  <BoundaryElementProvider element={sidePanel}>
37
- <Card borderLeft flex="none" ref={setSidePanel} style={{width: sidePanelWidth}} width={0}>
37
+ <Card borderLeft flex="none" ref={setSidePanel} style={{width: sidePanelWidth}}>
38
38
  <Stack padding={4} space={5}>
39
39
  <Text muted size={1}>
40
40
  Click the <code>reference</code> text below to toggle the popover.
@@ -0,0 +1,52 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Select} from './select'
5
+ import {selectStyle} from './styles'
6
+
7
+ jest.mock('./styles', () => {
8
+ const actual = jest.requireActual('./styles')
9
+
10
+ return {
11
+ ...actual,
12
+ selectStyle: {
13
+ ...actual.selectStyle,
14
+ input: jest.fn(actual.selectStyle.input),
15
+ },
16
+ }
17
+ })
18
+
19
+ describe('primitives/select spacing', () => {
20
+ const mockedSelectInputStyle = jest.mocked(selectStyle.input)
21
+
22
+ beforeEach(() => {
23
+ mockedSelectInputStyle.mockClear()
24
+ })
25
+
26
+ it('should support `space` and `gap` with the same behavior', () => {
27
+ render(
28
+ <Select space={2}>
29
+ <option>Option A</option>
30
+ </Select>,
31
+ )
32
+ expect(mockedSelectInputStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [2]}))
33
+
34
+ mockedSelectInputStyle.mockClear()
35
+ render(
36
+ <Select gap={2}>
37
+ <option>Option A</option>
38
+ </Select>,
39
+ )
40
+ expect(mockedSelectInputStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [2]}))
41
+ })
42
+
43
+ it('should prefer `gap` over `space` when both are provided', () => {
44
+ render(
45
+ <Select gap={3} space={1}>
46
+ <option>Option A</option>
47
+ </Select>,
48
+ )
49
+ expect(mockedSelectInputStyle).toHaveBeenCalledWith(expect.objectContaining({$space: [3]}))
50
+ expect(mockedSelectInputStyle).not.toHaveBeenCalledWith(expect.objectContaining({$space: [1]}))
51
+ })
52
+ })
@@ -14,8 +14,12 @@ import {selectStyle} from './styles'
14
14
  */
15
15
  export interface SelectProps {
16
16
  fontSize?: number | number[]
17
+ gap?: number | number[]
17
18
  padding?: number | number[]
18
19
  radius?: Radius | Radius[]
20
+ /**
21
+ * @deprecated Use `gap` instead. `space` will be removed in v4.
22
+ */
19
23
  space?: number | number[]
20
24
  customValidity?: string
21
25
  }
@@ -45,12 +49,14 @@ export const Select = forwardRef(function Select(
45
49
  customValidity,
46
50
  disabled,
47
51
  fontSize = 2,
52
+ gap,
48
53
  padding = 3,
49
54
  radius = 2,
50
55
  readOnly,
51
- space = 3,
56
+ space: deprecated_space = 3,
52
57
  ...restProps
53
58
  } = props
59
+ const spacing = gap === undefined ? deprecated_space : gap
54
60
 
55
61
  const ref = useRef<HTMLSelectElement | null>(null)
56
62
 
@@ -70,7 +76,7 @@ export const Select = forwardRef(function Select(
70
76
  $fontSize={_getArrayProp(fontSize)}
71
77
  $padding={_getArrayProp(padding)}
72
78
  $radius={_getArrayProp(radius)}
73
- $space={_getArrayProp(space)}
79
+ $space={_getArrayProp(spacing)}
74
80
  disabled={disabled || readOnly}
75
81
  ref={ref}
76
82
  >
@@ -0,0 +1,57 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import {render} from '../../../../test'
4
+ import {Stack} from './stack'
5
+ import {responsiveStackSpaceStyle} from './styles'
6
+
7
+ jest.mock('./styles', () => {
8
+ const actual = jest.requireActual('./styles')
9
+
10
+ return {
11
+ ...actual,
12
+ responsiveStackSpaceStyle: jest.fn(actual.responsiveStackSpaceStyle),
13
+ }
14
+ })
15
+
16
+ describe('primitives/stack', () => {
17
+ const mockedResponsiveStackSpaceStyle = jest.mocked(responsiveStackSpaceStyle)
18
+ beforeEach(() => {
19
+ mockedResponsiveStackSpaceStyle.mockClear()
20
+ })
21
+
22
+ it('should support `space` and `gap` with the same behavior', () => {
23
+ render(
24
+ <Stack space={2}>
25
+ <span>One</span>
26
+ <span>Two</span>
27
+ </Stack>,
28
+ )
29
+
30
+ expect(mockedResponsiveStackSpaceStyle).toHaveBeenCalledWith(
31
+ expect.objectContaining({$space: [2]}),
32
+ )
33
+ mockedResponsiveStackSpaceStyle.mockClear()
34
+ render(
35
+ <Stack gap={2}>
36
+ <span>One</span>
37
+ <span>Two</span>
38
+ </Stack>,
39
+ )
40
+
41
+ expect(mockedResponsiveStackSpaceStyle).toHaveBeenCalledWith(
42
+ expect.objectContaining({$space: [2]}),
43
+ )
44
+ })
45
+
46
+ it('should prefer `gap` over `space` when both are provided', () => {
47
+ render(
48
+ <Stack gap={3} space={1}>
49
+ <span>One</span>
50
+ <span>Two</span>
51
+ </Stack>,
52
+ )
53
+ expect(mockedResponsiveStackSpaceStyle).toHaveBeenCalledWith(
54
+ expect.objectContaining({$space: [3]}),
55
+ )
56
+ })
57
+ })