@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.
- package/dist/_chunks-cjs/_visual-editing.js +323 -292
- package/dist/_chunks-cjs/_visual-editing.js.map +1 -1
- package/dist/_chunks-es/_visual-editing.mjs +323 -292
- package/dist/_chunks-es/_visual-editing.mjs.map +1 -1
- package/dist/_visual-editing.d.mts +195 -76
- package/dist/_visual-editing.d.ts +195 -76
- package/dist/index.d.mts +308 -140
- package/dist/index.d.ts +308 -140
- package/dist/index.js +97 -93
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -93
- package/dist/index.mjs.map +1 -1
- package/dist/theme.d.mts +10 -4
- package/dist/theme.d.ts +10 -4
- package/package.json +1 -1
- package/src/core/components/autocomplete/autocomplete.tsx +1 -1
- package/src/core/components/autocomplete/types.ts +2 -2
- package/src/core/components/breadcrumbs/breadcrumbs.test.tsx +51 -0
- package/src/core/components/breadcrumbs/breadcrumbs.tsx +7 -3
- package/src/core/components/hotkeys/hotkeys.test.tsx +42 -0
- package/src/core/components/hotkeys/hotkeys.tsx +7 -3
- package/src/core/components/menu/__workshop__/asComponent.tsx +1 -1
- package/src/core/components/menu/menu.spacing.test.tsx +63 -0
- package/src/core/components/menu/menu.tsx +8 -2
- package/src/core/components/menu/menuDivider.ts +16 -5
- package/src/core/components/menu/menuGroup.spacing.test.tsx +63 -0
- package/src/core/components/menu/menuGroup.tsx +22 -8
- package/src/core/components/menu/menuItem.spacing.test.tsx +59 -0
- package/src/core/components/menu/menuItem.tsx +25 -8
- package/src/core/components/skeleton/skeleton.tsx +2 -2
- package/src/core/components/tab/tabList.test.tsx +48 -0
- package/src/core/components/tab/tabList.tsx +2 -2
- package/src/core/components/tab/tabPanel.tsx +2 -2
- package/src/core/components/toast/styles.ts +2 -2
- package/src/core/components/tree/__workshop__/basic.perf.ts +1 -1
- package/src/core/components/tree/__workshop__/tabFromElement.tsx +1 -1
- package/src/core/components/tree/tree.test.tsx +55 -0
- package/src/core/components/tree/tree.tsx +19 -10
- package/src/core/components/tree/treeGroup.tsx +3 -3
- package/src/core/components/tree/treeItem.test.tsx +65 -0
- package/src/core/components/tree/treeItem.tsx +11 -4
- package/src/core/components/tree/types.ts +4 -0
- package/src/core/primitives/avatar/avatar.tsx +19 -11
- package/src/core/primitives/badge/badge.tsx +20 -12
- package/src/core/primitives/box/__workshop__/asComponent.tsx +31 -0
- package/src/core/primitives/box/__workshop__/index.ts +5 -0
- package/src/core/primitives/box/box.test.tsx +142 -0
- package/src/core/primitives/box/box.tsx +41 -18
- package/src/core/primitives/button/__workshop__/disabled.tsx +2 -2
- package/src/core/primitives/button/__workshop__/sanityUploadButton.tsx +1 -1
- package/src/core/primitives/button/__workshop__/uploadButton.tsx +1 -1
- package/src/core/primitives/button/button.test.tsx +41 -3
- package/src/core/primitives/button/button.tsx +36 -12
- package/src/core/primitives/card/__workshop__/asComponent.tsx +1 -1
- package/src/core/primitives/card/card.tsx +24 -13
- package/src/core/primitives/code/code.tsx +16 -7
- package/src/core/primitives/container/container.tsx +23 -10
- package/src/core/primitives/flex/flex.tsx +21 -11
- package/src/core/primitives/grid/grid.test.tsx +80 -0
- package/src/core/primitives/grid/grid.tsx +39 -13
- package/src/core/primitives/heading/heading.tsx +19 -11
- package/src/core/primitives/inline/inline.test.tsx +51 -0
- package/src/core/primitives/inline/inline.tsx +28 -12
- package/src/core/primitives/kbd/kbd.tsx +19 -11
- package/src/core/primitives/label/label.tsx +19 -11
- package/src/core/primitives/popover/__workshop__/SidePanelStory.tsx +1 -1
- package/src/core/primitives/select/select.test.tsx +52 -0
- package/src/core/primitives/select/select.tsx +8 -2
- package/src/core/primitives/stack/stack.test.tsx +57 -0
- package/src/core/primitives/stack/stack.tsx +27 -13
- package/src/core/primitives/text/text.tsx +19 -11
- package/src/core/primitives/textInput/textInput.test.tsx +48 -0
- package/src/core/primitives/textInput/textInput.tsx +9 -4
- package/src/core/primitives/types.ts +33 -0
- package/src/core/types/component.ts +32 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/utils/virtualList/virtualList.tsx +2 -2
- package/src/theme/system/css.ts +10 -4
package/dist/theme.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {ColorHueKey} from '@sanity/color'
|
|
2
2
|
import {ColorTint} from '@sanity/color'
|
|
3
3
|
import {ColorTintKey} from '@sanity/color'
|
|
4
|
-
import type
|
|
4
|
+
import type * as CSS_2 from 'csstype'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @public
|
|
@@ -229,11 +229,17 @@ export declare function createColorTheme(
|
|
|
229
229
|
): ThemeColorSchemes
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
232
|
+
* A CSS-in-JS style object: standard CSS properties plus an index signature that allows nested
|
|
233
|
+
* selectors, at-rules and custom properties.
|
|
234
|
+
*
|
|
235
|
+
* This is a self-owned definition (based on `csstype`) so the published `.d.ts` types stay fully
|
|
236
|
+
* controlled by us and do not reference any external CSS-in-JS library types.
|
|
237
|
+
*
|
|
234
238
|
* @internal
|
|
235
239
|
*/
|
|
236
|
-
export declare
|
|
240
|
+
export declare interface CSSObject extends CSS_2.Properties<number | (string & {})> {
|
|
241
|
+
[key: string]: CSSObject | string | number | undefined
|
|
242
|
+
}
|
|
237
243
|
|
|
238
244
|
/** @internal */
|
|
239
245
|
export declare function getContrastRatio(bg: string, fg: string): number
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {ColorHueKey} from '@sanity/color'
|
|
2
2
|
import {ColorTint} from '@sanity/color'
|
|
3
3
|
import {ColorTintKey} from '@sanity/color'
|
|
4
|
-
import type
|
|
4
|
+
import type * as CSS_2 from 'csstype'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @public
|
|
@@ -229,11 +229,17 @@ export declare function createColorTheme(
|
|
|
229
229
|
): ThemeColorSchemes
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
232
|
+
* A CSS-in-JS style object: standard CSS properties plus an index signature that allows nested
|
|
233
|
+
* selectors, at-rules and custom properties.
|
|
234
|
+
*
|
|
235
|
+
* This is a self-owned definition (based on `csstype`) so the published `.d.ts` types stay fully
|
|
236
|
+
* controlled by us and do not reference any external CSS-in-JS library types.
|
|
237
|
+
*
|
|
234
238
|
* @internal
|
|
235
239
|
*/
|
|
236
|
-
export declare
|
|
240
|
+
export declare interface CSSObject extends CSS_2.Properties<number | (string & {})> {
|
|
241
|
+
[key: string]: CSSObject | string | number | undefined
|
|
242
|
+
}
|
|
237
243
|
|
|
238
244
|
/** @internal */
|
|
239
245
|
export declare function getContrastRatio(bg: string, fg: string): number
|
package/package.json
CHANGED
|
@@ -190,7 +190,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
190
190
|
const rootElementRef = useRef<HTMLDivElement | null>(null)
|
|
191
191
|
const resultsPopoverElementRef = useRef<HTMLDivElement | null>(null)
|
|
192
192
|
const inputElementRef = useRef<HTMLInputElement | null>(null)
|
|
193
|
-
const listBoxElementRef = useRef<
|
|
193
|
+
const listBoxElementRef = useRef<HTMLUListElement | null>(null)
|
|
194
194
|
// Element refs that need to be accessed during render
|
|
195
195
|
const [inputElement, _setInputElement] = useState<HTMLInputElement | null>(null)
|
|
196
196
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ButtonOwnProps} from '../../primitives'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @public
|
|
5
5
|
*/
|
|
6
|
-
export type AutocompleteOpenButtonProps =
|
|
6
|
+
export type AutocompleteOpenButtonProps = ButtonOwnProps &
|
|
7
7
|
Omit<React.HTMLProps<HTMLButtonElement>, 'as' | 'ref'>
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -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:
|
|
34
|
-
const space = _getArrayProp(
|
|
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}
|
|
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:
|
|
44
|
-
const
|
|
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"
|
|
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
|
-
|
|
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
|
|
173
|
+
<Stack gap={spacing}>{children}</Stack>
|
|
168
174
|
</StyledMenu>
|
|
169
175
|
</MenuContext.Provider>
|
|
170
176
|
)
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import {styled} from 'styled-components'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const MenuDivider = styled.hr`
|
|
3
|
+
import {ElementType, EmptyProps, Props} from '../../types'
|
|
4
|
+
|
|
5
|
+
const StyledMenuDivider = styled.hr`
|
|
7
6
|
height: 1px;
|
|
8
7
|
border: 0;
|
|
9
8
|
background: var(--card-hairline-soft-color);
|
|
10
9
|
margin: 0;
|
|
11
10
|
`
|
|
12
|
-
|
|
11
|
+
StyledMenuDivider.displayName = 'MenuDivider'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export type MenuDividerProps<E extends ElementType = 'hr'> = Props<EmptyProps, E>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export const MenuDivider = StyledMenuDivider as unknown as <E extends ElementType = 'hr'>(
|
|
22
|
+
props: MenuDividerProps<E>,
|
|
23
|
+
) => React.JSX.Element
|
|
@@ -0,0 +1,63 @@
|
|
|
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(({children}: {children?: React.ReactNode}) => children),
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const menuContextValue: MenuContextValue = {
|
|
20
|
+
version: 2,
|
|
21
|
+
activeElement: null,
|
|
22
|
+
mount: () => () => undefined,
|
|
23
|
+
onItemMouseEnter: () => undefined,
|
|
24
|
+
onItemMouseLeave: () => undefined,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function renderMenuGroup(props: Partial<React.ComponentProps<typeof MenuGroup>> = {}) {
|
|
28
|
+
return render(
|
|
29
|
+
<MenuContext.Provider value={menuContextValue}>
|
|
30
|
+
<MenuGroup id="menu-group" text="Menu group" {...props}>
|
|
31
|
+
<MenuItem id="submenu-item" text="Submenu item" />
|
|
32
|
+
</MenuGroup>
|
|
33
|
+
</MenuContext.Provider>,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('components/menuGroup spacing', () => {
|
|
38
|
+
const mockedFlex = jest.mocked(Flex)
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
mockedFlex.mockClear()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should support `space` and `gap` with the same behavior', () => {
|
|
45
|
+
renderMenuGroup({space: 2})
|
|
46
|
+
expect(mockedFlex.mock.calls.map(([props]) => props)).toContainEqual(
|
|
47
|
+
expect.objectContaining({gap: 2}),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
mockedFlex.mockClear()
|
|
51
|
+
renderMenuGroup({gap: 2})
|
|
52
|
+
expect(mockedFlex.mock.calls.map(([props]) => props)).toContainEqual(
|
|
53
|
+
expect.objectContaining({gap: 2}),
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should prefer `gap` over `space` when both are provided', () => {
|
|
58
|
+
renderMenuGroup({gap: 3, space: 1})
|
|
59
|
+
const propsList = mockedFlex.mock.calls.map(([props]) => props)
|
|
60
|
+
expect(propsList).toContainEqual(expect.objectContaining({gap: 3}))
|
|
61
|
+
expect(propsList).not.toContainEqual(expect.objectContaining({gap: 1}))
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -6,15 +6,14 @@ import {Box, Flex, Popover, PopoverProps, Text} from '../../primitives'
|
|
|
6
6
|
import {Selectable} from '../../primitives/_selectable'
|
|
7
7
|
import {_getArrayProp} from '../../styles'
|
|
8
8
|
import {useRootTheme} from '../../theme'
|
|
9
|
-
import {Radius, SelectableTone} from '../../types'
|
|
9
|
+
import {ElementType, Props, Radius, SelectableTone} from '../../types'
|
|
10
10
|
import {Menu, MenuProps} from './menu'
|
|
11
11
|
import {useMenu} from './useMenu'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @public
|
|
15
15
|
*/
|
|
16
|
-
export interface
|
|
17
|
-
as?: React.ElementType | keyof React.JSX.IntrinsicElements
|
|
16
|
+
export interface MenuGroupOwnProps {
|
|
18
17
|
fontSize?: number | number[]
|
|
19
18
|
icon?: React.ElementType | React.ReactNode
|
|
20
19
|
menu?: Omit<
|
|
@@ -31,6 +30,10 @@ export interface MenuGroupProps {
|
|
|
31
30
|
padding?: number | number[]
|
|
32
31
|
popover?: Omit<PopoverProps, 'content' | 'open'>
|
|
33
32
|
radius?: Radius | Radius[]
|
|
33
|
+
gap?: number | number[]
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Use `gap` instead. `space` will be removed in v4.
|
|
36
|
+
*/
|
|
34
37
|
space?: number | number[]
|
|
35
38
|
text: React.ReactNode
|
|
36
39
|
tone?: SelectableTone
|
|
@@ -39,9 +42,11 @@ export interface MenuGroupProps {
|
|
|
39
42
|
/**
|
|
40
43
|
* @public
|
|
41
44
|
*/
|
|
42
|
-
export
|
|
45
|
+
export type MenuGroupProps<E extends ElementType = 'button'> = Props<MenuGroupOwnProps, E>
|
|
46
|
+
|
|
47
|
+
function MenuGroupComponent(
|
|
43
48
|
props: Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'height' | 'popover' | 'ref' | 'tabIndex'> &
|
|
44
|
-
|
|
49
|
+
MenuGroupOwnProps & {as?: ElementType},
|
|
45
50
|
): React.JSX.Element {
|
|
46
51
|
const {
|
|
47
52
|
as = 'button',
|
|
@@ -53,11 +58,13 @@ export function MenuGroup(
|
|
|
53
58
|
padding = 3,
|
|
54
59
|
popover,
|
|
55
60
|
radius = 2,
|
|
56
|
-
|
|
61
|
+
gap,
|
|
62
|
+
space: deprecated_space = 3,
|
|
57
63
|
text,
|
|
58
64
|
tone = 'default',
|
|
59
65
|
...restProps
|
|
60
66
|
} = props
|
|
67
|
+
const spacing = gap === undefined ? deprecated_space : gap
|
|
61
68
|
const menu = useMenu()
|
|
62
69
|
const {scheme} = useRootTheme()
|
|
63
70
|
const {
|
|
@@ -193,7 +200,7 @@ export function MenuGroup(
|
|
|
193
200
|
tabIndex={-1}
|
|
194
201
|
type={as === 'button' ? 'button' : undefined}
|
|
195
202
|
>
|
|
196
|
-
<Flex gap={
|
|
203
|
+
<Flex gap={spacing} padding={padding}>
|
|
197
204
|
{IconComponent && (
|
|
198
205
|
<Text size={fontSize}>
|
|
199
206
|
{isValidElement(IconComponent) && IconComponent}
|
|
@@ -216,4 +223,11 @@ export function MenuGroup(
|
|
|
216
223
|
)
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
|
|
226
|
+
MenuGroupComponent.displayName = 'MenuGroup'
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @public
|
|
230
|
+
*/
|
|
231
|
+
export const MenuGroup = MenuGroupComponent as unknown as <E extends ElementType = 'button'>(
|
|
232
|
+
props: MenuGroupProps<E>,
|
|
233
|
+
) => React.JSX.Element
|
|
@@ -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
|
+
})
|
|
@@ -15,6 +15,7 @@ import {Selectable} from '../../primitives/_selectable'
|
|
|
15
15
|
import {ResponsivePaddingProps, ResponsiveRadiusProps} from '../../primitives/types'
|
|
16
16
|
import {_getArrayProp} from '../../styles'
|
|
17
17
|
import {useRootTheme} from '../../theme'
|
|
18
|
+
import {ElementType, Props} from '../../types'
|
|
18
19
|
import {SelectableTone} from '../../types/selectable'
|
|
19
20
|
import {Hotkeys} from '../hotkeys'
|
|
20
21
|
import {useMenu} from './useMenu'
|
|
@@ -22,14 +23,17 @@ import {useMenu} from './useMenu'
|
|
|
22
23
|
/**
|
|
23
24
|
* @public
|
|
24
25
|
*/
|
|
25
|
-
export interface
|
|
26
|
-
as?: React.ElementType | keyof React.JSX.IntrinsicElements
|
|
26
|
+
export interface MenuItemOwnProps extends ResponsivePaddingProps, ResponsiveRadiusProps {
|
|
27
27
|
fontSize?: number | number[]
|
|
28
28
|
hotkeys?: string[]
|
|
29
29
|
icon?: React.ElementType | React.ReactNode
|
|
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
|
|
@@ -38,9 +42,13 @@ export interface MenuItemProps extends ResponsivePaddingProps, ResponsiveRadiusP
|
|
|
38
42
|
/**
|
|
39
43
|
* @public
|
|
40
44
|
*/
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
export type MenuItemProps<E extends ElementType = 'button'> = Props<MenuItemOwnProps, E>
|
|
46
|
+
|
|
47
|
+
const MenuItemComponent = forwardRef(function MenuItem(
|
|
48
|
+
props: MenuItemOwnProps & {as?: ElementType} & Omit<
|
|
49
|
+
React.HTMLProps<HTMLDivElement>,
|
|
50
|
+
'as' | 'height' | 'ref' | 'selected' | 'tabIndex'
|
|
51
|
+
>,
|
|
44
52
|
forwardedRef: React.ForwardedRef<HTMLDivElement>,
|
|
45
53
|
) {
|
|
46
54
|
const {
|
|
@@ -62,11 +70,13 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
62
70
|
pressed,
|
|
63
71
|
radius = 2,
|
|
64
72
|
selected: selectedProp,
|
|
65
|
-
|
|
73
|
+
gap,
|
|
74
|
+
space: deprecated_space = 3,
|
|
66
75
|
text,
|
|
67
76
|
tone = 'default',
|
|
68
77
|
...restProps
|
|
69
78
|
} = props
|
|
79
|
+
const spacing = gap === undefined ? deprecated_space : gap
|
|
70
80
|
const {scheme} = useRootTheme()
|
|
71
81
|
const menu = useMenu()
|
|
72
82
|
const {
|
|
@@ -137,7 +147,7 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
137
147
|
type={as === 'button' ? 'button' : undefined}
|
|
138
148
|
>
|
|
139
149
|
{(IconComponent || text || IconRightComponent) && (
|
|
140
|
-
<Flex as="span" gap={
|
|
150
|
+
<Flex as="span" gap={spacing} align="center" {...paddingProps}>
|
|
141
151
|
{IconComponent && (
|
|
142
152
|
<Text size={fontSize}>
|
|
143
153
|
{isValidElement(IconComponent) && IconComponent}
|
|
@@ -177,4 +187,11 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
177
187
|
</Selectable>
|
|
178
188
|
)
|
|
179
189
|
})
|
|
180
|
-
|
|
190
|
+
MenuItemComponent.displayName = 'ForwardRef(MenuItem)'
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @public
|
|
194
|
+
*/
|
|
195
|
+
export const MenuItem = MenuItemComponent as unknown as <E extends ElementType = 'button'>(
|
|
196
|
+
props: MenuItemProps<E>,
|
|
197
|
+
) => React.JSX.Element
|