@sanity/ui 4.0.0-static.31 → 4.0.0-static.32
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/index.d.ts +8 -1
- package/dist/index.js +1076 -510
- package/dist/index.js.map +1 -1
- package/package.json +9 -10
- package/src/core/components/autocomplete/Autocomplete.tsx +80 -48
- package/src/core/components/breadcrumbs/Breadcrumbs.tsx +74 -41
- package/src/core/components/dialog/DialogContext.ts +1 -1
- package/src/core/components/dialog/__workshop__/Panes.tsx +5 -21
- package/src/core/components/dialog/__workshop__/styles.css.ts +18 -0
- package/src/core/components/menu/Menu.test.tsx +2 -6
- package/src/core/components/menu/Menu.tsx +13 -14
- package/src/core/components/menu/MenuContext.ts +2 -3
- package/src/core/components/menu/MenuGroup.tsx +2 -0
- package/src/core/components/menu/useMenu.ts +2 -2
- package/src/core/components/menu/useMenuController.ts +5 -5
- package/src/core/components/toast/ToastContext.ts +1 -1
- package/src/core/components/tree/Tree.tsx +1 -0
- package/src/core/components/tree/TreeContext.ts +1 -1
- package/src/core/components/tree/TreeItem.tsx +17 -14
- package/src/core/components/virtualList/VirtualList.tsx +86 -44
- package/src/core/hooks/useGlobalKeyDown.ts +11 -4
- package/src/core/lib/createGlobalScopedContext.ts +2 -2
- package/src/core/primitives/avatar/Avatar.tsx +1 -0
- package/src/core/primitives/button/Button.tsx +2 -1
- package/src/core/primitives/button/__workshop__/SanityUploadButton.tsx +10 -25
- package/src/core/primitives/button/__workshop__/Styled1.tsx +2 -8
- package/src/core/primitives/button/__workshop__/index.ts +0 -5
- package/src/core/primitives/button/__workshop__/styles.css.ts +30 -0
- package/src/core/primitives/card/CardContext.ts +1 -1
- package/src/core/primitives/card/__workshop__/Styled.tsx +2 -5
- package/src/core/primitives/flex/__workshop__/Example.tsx +7 -15
- package/src/core/primitives/flex/__workshop__/styles.css.ts +12 -0
- package/src/core/primitives/inline/Inline.tsx +1 -0
- package/src/core/primitives/layer/LayerContext.ts +1 -1
- package/src/core/primitives/popover/Popover.tsx +146 -130
- package/src/core/primitives/popover/floating-ui/size.ts +17 -16
- package/src/core/primitives/skeleton/Skeleton.tsx +2 -2
- package/src/core/primitives/text/__workshop__/Colored.tsx +5 -10
- package/src/core/primitives/tooltip/Tooltip.tsx +69 -41
- package/src/core/primitives/tooltip/tooltipDelayGroup/TooltipDelayGroupContext.tsx +1 -1
- package/src/core/utils/boundaryElement/BoundaryElementContext.ts +1 -1
- package/src/core/utils/elementQuery/__workshop__/CustomMedia.tsx +3 -25
- package/src/core/utils/elementQuery/__workshop__/styles.css.ts +31 -0
- package/src/core/utils/portal/Portal.test.tsx +6 -6
- package/src/core/utils/portal/PortalContext.ts +1 -1
- package/src/core/utils/portal/PortalProvider.tsx +5 -37
- package/src/core/primitives/button/__workshop__/Styled2.tsx +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "4.0.0-static.
|
|
3
|
+
"version": "4.0.0-static.32",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"react",
|
|
6
6
|
"ui",
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
"@babel/preset-typescript": "^7.27.1",
|
|
131
131
|
"@commitlint/cli": "^19.8.1",
|
|
132
132
|
"@commitlint/config-conventional": "^19.8.1",
|
|
133
|
-
"@eslint/js": "^9.
|
|
133
|
+
"@eslint/js": "^9.36.0",
|
|
134
134
|
"@sanity/browserslist-config": "^1.0.5",
|
|
135
|
-
"@sanity/pkg-utils": "^8.1.
|
|
135
|
+
"@sanity/pkg-utils": "^8.1.16",
|
|
136
136
|
"@sanity/prettier-config": "^2.0.1",
|
|
137
137
|
"@sanity/semantic-release-preset": "^5.0.0",
|
|
138
138
|
"@sanity/ui-workshop": "4.0.0-static.11",
|
|
@@ -156,16 +156,16 @@
|
|
|
156
156
|
"cypress-real-events": "^1.14.0",
|
|
157
157
|
"cz-conventional-changelog": "^3.3.0",
|
|
158
158
|
"dotenv-flow": "^4.1.0",
|
|
159
|
-
"eslint": "^9.
|
|
159
|
+
"eslint": "^9.36.0",
|
|
160
160
|
"eslint-config-prettier": "^10.1.8",
|
|
161
|
-
"eslint-formatter-
|
|
161
|
+
"eslint-formatter-gha": "^1.6.0",
|
|
162
162
|
"eslint-plugin-boundaries": "^5.0.1",
|
|
163
163
|
"eslint-plugin-import": "^2.32.0",
|
|
164
164
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
165
165
|
"eslint-plugin-react": "^7.37.5",
|
|
166
|
-
"eslint-plugin-react-
|
|
167
|
-
"eslint-plugin-react-hooks": "0.0.0-experimental-
|
|
168
|
-
"eslint-plugin-react-refresh": "^0.4.
|
|
166
|
+
"eslint-plugin-react-hooks": "6.0.0-rc.2",
|
|
167
|
+
"eslint-plugin-react-hooks-with-use-effect-event": "npm:eslint-plugin-react-hooks@0.0.0-experimental-83c88ad4-20250923",
|
|
168
|
+
"eslint-plugin-react-refresh": "^0.4.21",
|
|
169
169
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
170
170
|
"eslint-plugin-storybook": "^9.1.7",
|
|
171
171
|
"globals": "^16.3.0",
|
|
@@ -183,10 +183,9 @@
|
|
|
183
183
|
"rimraf": "^5.0.10",
|
|
184
184
|
"semantic-release": "24.2.7",
|
|
185
185
|
"start-server-and-test": "^2.1.1",
|
|
186
|
-
"styled-components": "^6.1.19",
|
|
187
186
|
"tsconfig-paths": "^4.2.0",
|
|
188
187
|
"typescript": "5.9.2",
|
|
189
|
-
"typescript-eslint": "^8.44.
|
|
188
|
+
"typescript-eslint": "^8.44.1",
|
|
190
189
|
"vite": "^7.1.6",
|
|
191
190
|
"vite-tsconfig-paths": "^5.1.4",
|
|
192
191
|
"vitest": "^3.2.4",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
useMemo,
|
|
17
17
|
useReducer,
|
|
18
18
|
useRef,
|
|
19
|
+
useState,
|
|
19
20
|
} from 'react'
|
|
20
21
|
|
|
21
22
|
import {EMPTY_ARRAY, EMPTY_RECORD} from '../../constants'
|
|
@@ -177,6 +178,8 @@ export function Autocomplete<
|
|
|
177
178
|
const resultsPopoverElementRef = useRef<HTMLDivElement | null>(null)
|
|
178
179
|
const inputElementRef = useRef<HTMLInputElement | null>(null)
|
|
179
180
|
const listBoxElementRef = useRef<HTMLUListElement | null>(null)
|
|
181
|
+
// Element refs that need to be accessed during render
|
|
182
|
+
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null)
|
|
180
183
|
|
|
181
184
|
// Value refs
|
|
182
185
|
const listFocusedRef = useRef(false)
|
|
@@ -184,10 +187,17 @@ export function Autocomplete<
|
|
|
184
187
|
const valuePropRef = useRef(valueProp)
|
|
185
188
|
const popoverMouseWithinRef = useRef(false)
|
|
186
189
|
|
|
187
|
-
// Forward
|
|
190
|
+
// Forward inputElement state to inputElementRef
|
|
191
|
+
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
|
|
192
|
+
inputElementRef,
|
|
193
|
+
() => inputElement,
|
|
194
|
+
[inputElement],
|
|
195
|
+
)
|
|
196
|
+
// Forward inputElement to parent
|
|
188
197
|
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
|
|
189
198
|
forwardedRef,
|
|
190
|
-
() =>
|
|
199
|
+
() => inputElement,
|
|
200
|
+
[inputElement],
|
|
191
201
|
)
|
|
192
202
|
|
|
193
203
|
const listBoxId = `${id}-listbox`
|
|
@@ -525,41 +535,6 @@ export function Autocomplete<
|
|
|
525
535
|
return query
|
|
526
536
|
}, [currentOption, query, renderValue, value])
|
|
527
537
|
|
|
528
|
-
const input = (
|
|
529
|
-
<TextInput
|
|
530
|
-
{...rest}
|
|
531
|
-
aria-activedescendant={activeItemId}
|
|
532
|
-
aria-autocomplete="list"
|
|
533
|
-
aria-expanded={expanded}
|
|
534
|
-
aria-owns={listBoxId}
|
|
535
|
-
autoCapitalize="off"
|
|
536
|
-
autoComplete="off"
|
|
537
|
-
autoCorrect="off"
|
|
538
|
-
as={as}
|
|
539
|
-
border={border}
|
|
540
|
-
clearButton={clearButton}
|
|
541
|
-
customValidity={customValidity}
|
|
542
|
-
disabled={disabled}
|
|
543
|
-
fontSize={fontSize}
|
|
544
|
-
icon={icon}
|
|
545
|
-
iconRight={loading && AnimatedSpinnerIcon}
|
|
546
|
-
id={id}
|
|
547
|
-
inputMode="search"
|
|
548
|
-
onChange={handleInputChange}
|
|
549
|
-
onClear={handleClearButtonClick}
|
|
550
|
-
onFocus={handleInputFocus}
|
|
551
|
-
padding={padding}
|
|
552
|
-
prefix={prefix}
|
|
553
|
-
radius={radius}
|
|
554
|
-
readOnly={readOnly}
|
|
555
|
-
ref={inputElementRef}
|
|
556
|
-
role="combobox"
|
|
557
|
-
spellCheck={false}
|
|
558
|
-
suffix={suffix || openButtonNode}
|
|
559
|
-
value={inputValue}
|
|
560
|
-
/>
|
|
561
|
-
)
|
|
562
|
-
|
|
563
538
|
const handleListBoxKeyDown = useCallback(
|
|
564
539
|
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
565
540
|
// If the focus is currently in the list, move focus to the input element
|
|
@@ -629,15 +604,16 @@ export function Autocomplete<
|
|
|
629
604
|
|
|
630
605
|
const results = useMemo(() => {
|
|
631
606
|
if (renderPopover) {
|
|
632
|
-
return
|
|
633
|
-
|
|
634
|
-
content
|
|
635
|
-
hidden
|
|
636
|
-
inputElement
|
|
637
|
-
onMouseEnter
|
|
638
|
-
onMouseLeave
|
|
639
|
-
|
|
640
|
-
|
|
607
|
+
return (
|
|
608
|
+
<RenderPopover
|
|
609
|
+
content={content}
|
|
610
|
+
hidden={!expanded}
|
|
611
|
+
inputElement={inputElement}
|
|
612
|
+
onMouseEnter={handlePopoverMouseEnter}
|
|
613
|
+
onMouseLeave={handlePopoverMouseLeave}
|
|
614
|
+
resultsPopoverElementRef={resultsPopoverElementRef}
|
|
615
|
+
renderPopover={renderPopover}
|
|
616
|
+
/>
|
|
641
617
|
)
|
|
642
618
|
}
|
|
643
619
|
|
|
@@ -660,7 +636,7 @@ export function Autocomplete<
|
|
|
660
636
|
portal
|
|
661
637
|
radius={3}
|
|
662
638
|
ref={resultsPopoverElementRef}
|
|
663
|
-
referenceElement={
|
|
639
|
+
referenceElement={inputElement}
|
|
664
640
|
{...popover}
|
|
665
641
|
/>
|
|
666
642
|
)
|
|
@@ -670,6 +646,7 @@ export function Autocomplete<
|
|
|
670
646
|
filteredOptionsLen,
|
|
671
647
|
handlePopoverMouseEnter,
|
|
672
648
|
handlePopoverMouseLeave,
|
|
649
|
+
inputElement,
|
|
673
650
|
popover,
|
|
674
651
|
renderPopover,
|
|
675
652
|
])
|
|
@@ -683,8 +660,63 @@ export function Autocomplete<
|
|
|
683
660
|
onKeyDown={handleRootKeyDown}
|
|
684
661
|
ref={rootElementRef}
|
|
685
662
|
>
|
|
686
|
-
|
|
663
|
+
<TextInput
|
|
664
|
+
{...rest}
|
|
665
|
+
aria-activedescendant={activeItemId}
|
|
666
|
+
aria-autocomplete="list"
|
|
667
|
+
aria-expanded={expanded}
|
|
668
|
+
aria-owns={listBoxId}
|
|
669
|
+
autoCapitalize="off"
|
|
670
|
+
autoComplete="off"
|
|
671
|
+
autoCorrect="off"
|
|
672
|
+
as={as}
|
|
673
|
+
border={border}
|
|
674
|
+
clearButton={clearButton}
|
|
675
|
+
customValidity={customValidity}
|
|
676
|
+
disabled={disabled}
|
|
677
|
+
fontSize={fontSize}
|
|
678
|
+
icon={icon}
|
|
679
|
+
iconRight={loading && AnimatedSpinnerIcon}
|
|
680
|
+
id={id}
|
|
681
|
+
inputMode="search"
|
|
682
|
+
onChange={handleInputChange}
|
|
683
|
+
onClear={handleClearButtonClick}
|
|
684
|
+
onFocus={handleInputFocus}
|
|
685
|
+
padding={padding}
|
|
686
|
+
prefix={prefix}
|
|
687
|
+
radius={radius}
|
|
688
|
+
readOnly={readOnly}
|
|
689
|
+
ref={setInputElement}
|
|
690
|
+
role="combobox"
|
|
691
|
+
spellCheck={false}
|
|
692
|
+
suffix={suffix || openButtonNode}
|
|
693
|
+
value={inputValue}
|
|
694
|
+
/>
|
|
687
695
|
{results}
|
|
688
696
|
</div>
|
|
689
697
|
)
|
|
690
698
|
}
|
|
699
|
+
|
|
700
|
+
function RenderPopover({
|
|
701
|
+
renderPopover,
|
|
702
|
+
content,
|
|
703
|
+
hidden,
|
|
704
|
+
inputElement,
|
|
705
|
+
onMouseEnter,
|
|
706
|
+
onMouseLeave,
|
|
707
|
+
resultsPopoverElementRef,
|
|
708
|
+
}: {
|
|
709
|
+
renderPopover: Exclude<AutocompleteProps['renderPopover'], undefined>
|
|
710
|
+
resultsPopoverElementRef: Parameters<Exclude<AutocompleteProps['renderPopover'], undefined>>[1]
|
|
711
|
+
} & Parameters<Exclude<AutocompleteProps['renderPopover'], undefined>>[0]) {
|
|
712
|
+
return renderPopover(
|
|
713
|
+
{
|
|
714
|
+
content,
|
|
715
|
+
hidden,
|
|
716
|
+
inputElement,
|
|
717
|
+
onMouseEnter,
|
|
718
|
+
onMouseLeave,
|
|
719
|
+
},
|
|
720
|
+
resultsPopoverElementRef,
|
|
721
|
+
)
|
|
722
|
+
}
|
|
@@ -48,8 +48,8 @@ export function Breadcrumbs<E extends BreadcrumbsElementType = typeof DEFAULT_BR
|
|
|
48
48
|
className,
|
|
49
49
|
expandButton: expandButtonProps,
|
|
50
50
|
gap = 2,
|
|
51
|
-
gapX,
|
|
52
|
-
gapY,
|
|
51
|
+
gapX = gap,
|
|
52
|
+
gapY = gap,
|
|
53
53
|
maxLength,
|
|
54
54
|
separator,
|
|
55
55
|
...rest
|
|
@@ -66,45 +66,17 @@ export function Breadcrumbs<E extends BreadcrumbsElementType = typeof DEFAULT_BR
|
|
|
66
66
|
|
|
67
67
|
const rawItems = useMemo(() => Children.toArray(children).filter(isValidElement), [children])
|
|
68
68
|
|
|
69
|
-
const items =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
content={
|
|
81
|
-
<Stack as="ol" gap={gapY} overflow="auto">
|
|
82
|
-
{rawItems.slice(beforeLength - 1, len - afterLength)}
|
|
83
|
-
</Stack>
|
|
84
|
-
}
|
|
85
|
-
key="button"
|
|
86
|
-
open={open}
|
|
87
|
-
padding={2}
|
|
88
|
-
placement="top"
|
|
89
|
-
portal
|
|
90
|
-
ref={popoverElementRef}
|
|
91
|
-
>
|
|
92
|
-
<Button
|
|
93
|
-
mode="bleed"
|
|
94
|
-
padding={0}
|
|
95
|
-
text="…"
|
|
96
|
-
{...expandButtonProps}
|
|
97
|
-
onClick={open ? collapse : expand}
|
|
98
|
-
ref={expandElementRef}
|
|
99
|
-
selected={open}
|
|
100
|
-
/>
|
|
101
|
-
</Popover>,
|
|
102
|
-
...rawItems.slice(len - afterLength),
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return rawItems
|
|
107
|
-
}, [collapse, expand, expandButtonProps, gapY, maxLength, open, rawItems])
|
|
69
|
+
const items = useItems({
|
|
70
|
+
collapse,
|
|
71
|
+
expand,
|
|
72
|
+
expandElementRef,
|
|
73
|
+
maxLength,
|
|
74
|
+
open,
|
|
75
|
+
popoverElementRef,
|
|
76
|
+
rawItems,
|
|
77
|
+
gapY,
|
|
78
|
+
expandButtonProps,
|
|
79
|
+
})
|
|
108
80
|
|
|
109
81
|
return (
|
|
110
82
|
<Flex
|
|
@@ -129,3 +101,64 @@ export function Breadcrumbs<E extends BreadcrumbsElementType = typeof DEFAULT_BR
|
|
|
129
101
|
</Flex>
|
|
130
102
|
)
|
|
131
103
|
}
|
|
104
|
+
|
|
105
|
+
function useItems({
|
|
106
|
+
collapse,
|
|
107
|
+
expand,
|
|
108
|
+
expandElementRef,
|
|
109
|
+
maxLength,
|
|
110
|
+
open,
|
|
111
|
+
popoverElementRef,
|
|
112
|
+
rawItems,
|
|
113
|
+
gapY,
|
|
114
|
+
expandButtonProps,
|
|
115
|
+
}: {
|
|
116
|
+
collapse: () => void
|
|
117
|
+
expand: () => void
|
|
118
|
+
expandElementRef: React.RefObject<HTMLButtonElement | null>
|
|
119
|
+
maxLength: number | undefined
|
|
120
|
+
open: boolean
|
|
121
|
+
popoverElementRef: React.RefObject<HTMLDivElement | null>
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
rawItems: React.ReactElement<unknown, string | React.JSXElementConstructor<any>>[]
|
|
124
|
+
gapY: NonNullable<GapStyleProps['gapY']>
|
|
125
|
+
expandButtonProps: BreadcrumbsOwnProps['expandButton']
|
|
126
|
+
}): React.JSX.Element[] {
|
|
127
|
+
const len = rawItems.length
|
|
128
|
+
|
|
129
|
+
if (maxLength && len > maxLength) {
|
|
130
|
+
const beforeLength = Math.ceil(maxLength / 2)
|
|
131
|
+
const afterLength = Math.floor(maxLength / 2)
|
|
132
|
+
|
|
133
|
+
return [
|
|
134
|
+
...rawItems.slice(0, beforeLength - 1),
|
|
135
|
+
<Popover
|
|
136
|
+
constrainSize
|
|
137
|
+
content={
|
|
138
|
+
<Stack as="ol" gap={gapY} overflow="auto">
|
|
139
|
+
{rawItems.slice(beforeLength - 1, len - afterLength)}
|
|
140
|
+
</Stack>
|
|
141
|
+
}
|
|
142
|
+
key="button"
|
|
143
|
+
open={open}
|
|
144
|
+
padding={2}
|
|
145
|
+
placement="top"
|
|
146
|
+
portal
|
|
147
|
+
ref={popoverElementRef}
|
|
148
|
+
>
|
|
149
|
+
<Button
|
|
150
|
+
mode="bleed"
|
|
151
|
+
padding={0}
|
|
152
|
+
text="…"
|
|
153
|
+
{...expandButtonProps}
|
|
154
|
+
onClick={open ? collapse : expand}
|
|
155
|
+
ref={expandElementRef}
|
|
156
|
+
selected={open}
|
|
157
|
+
/>
|
|
158
|
+
</Popover>,
|
|
159
|
+
...rawItems.slice(len - afterLength),
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return rawItems
|
|
164
|
+
}
|
|
@@ -18,4 +18,4 @@ export interface DialogContextValue {
|
|
|
18
18
|
* @internal
|
|
19
19
|
*/
|
|
20
20
|
export const DialogContext: Context<DialogContextValue> =
|
|
21
|
-
createGlobalScopedContext<DialogContextValue>('@sanity/ui/
|
|
21
|
+
createGlobalScopedContext<DialogContextValue>('@sanity/ui/v4/dialog', {version: 0.0})
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
PortalProvider,
|
|
12
12
|
} from '@sanity/ui'
|
|
13
13
|
import {useCallback, useState} from 'react'
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
import {panePortal, paneRoot} from './styles.css'
|
|
15
16
|
|
|
16
17
|
export default function PanesStory(): React.JSX.Element {
|
|
17
18
|
return (
|
|
@@ -22,23 +23,6 @@ export default function PanesStory(): React.JSX.Element {
|
|
|
22
23
|
)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const PaneRoot = styled(Card)`
|
|
26
|
-
position: relative;
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
const PanePortal = styled.div`
|
|
30
|
-
position: absolute;
|
|
31
|
-
left: 0;
|
|
32
|
-
right: 0;
|
|
33
|
-
top: 0;
|
|
34
|
-
bottom: 0;
|
|
35
|
-
pointer-events: none;
|
|
36
|
-
|
|
37
|
-
& > * {
|
|
38
|
-
pointer-events: auto;
|
|
39
|
-
}
|
|
40
|
-
`
|
|
41
|
-
|
|
42
26
|
function Pane(props: {borderLeft?: boolean; id: string}) {
|
|
43
27
|
const {borderLeft, id} = props
|
|
44
28
|
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
|
@@ -52,13 +36,13 @@ function Pane(props: {borderLeft?: boolean; id: string}) {
|
|
|
52
36
|
return (
|
|
53
37
|
<BoundaryElementProvider element={element}>
|
|
54
38
|
<PortalProvider element={portalElement}>
|
|
55
|
-
<
|
|
39
|
+
<Card className={paneRoot} borderLeft={borderLeft} flex={1} ref={setElement}>
|
|
56
40
|
<Box padding={4}>
|
|
57
41
|
<Button onClick={() => setDialogOpen(true)} selected={dialogOpen} text="Open dialog" />
|
|
58
42
|
</Box>
|
|
59
43
|
|
|
60
|
-
<
|
|
61
|
-
</
|
|
44
|
+
<div className={panePortal} ref={setPortalElement} />
|
|
45
|
+
</Card>
|
|
62
46
|
|
|
63
47
|
{dialogOpen && (
|
|
64
48
|
<Dialog
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {globalStyle, style} from '@vanilla-extract/css'
|
|
2
|
+
|
|
3
|
+
export const paneRoot: string = style({
|
|
4
|
+
position: 'relative',
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
export const panePortal: string = style({
|
|
8
|
+
position: 'absolute',
|
|
9
|
+
left: 0,
|
|
10
|
+
right: 0,
|
|
11
|
+
top: 0,
|
|
12
|
+
bottom: 0,
|
|
13
|
+
pointerEvents: 'none',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
globalStyle(`${panePortal} > *`, {
|
|
17
|
+
pointerEvents: 'auto',
|
|
18
|
+
})
|
|
@@ -34,15 +34,12 @@ describe('components/menu', () => {
|
|
|
34
34
|
|
|
35
35
|
const value: MenuContextValue = useMemo(
|
|
36
36
|
() => ({
|
|
37
|
-
version:
|
|
37
|
+
version: 2,
|
|
38
38
|
activeElement: null,
|
|
39
|
-
activeIndex: 0,
|
|
40
39
|
mount: (element: HTMLElement | null) => () => console.log(element),
|
|
41
40
|
onItemClick: () => undefined,
|
|
42
41
|
onItemMouseEnter: handleItemMouseEnter,
|
|
43
42
|
onItemMouseLeave: handleItemMouseLeave,
|
|
44
|
-
onMouseEnter: handleItemMouseEnter,
|
|
45
|
-
onMouseLeave: handleItemMouseLeave,
|
|
46
43
|
}),
|
|
47
44
|
[handleItemMouseEnter, handleItemMouseLeave],
|
|
48
45
|
)
|
|
@@ -58,8 +55,7 @@ describe('components/menu', () => {
|
|
|
58
55
|
|
|
59
56
|
const contextValue = log.mock.calls[0][0]
|
|
60
57
|
|
|
61
|
-
expect(contextValue.version).toBe(
|
|
62
|
-
expect(contextValue.activeIndex).toBe(0)
|
|
58
|
+
expect(contextValue.version).toBe(2)
|
|
63
59
|
expect(typeof contextValue.mount).toBe('function')
|
|
64
60
|
expect(typeof contextValue.onItemClick).toBe('function')
|
|
65
61
|
expect(typeof contextValue.onItemMouseEnter).toBe('function')
|
|
@@ -127,22 +127,21 @@ export function Menu<E extends MenuElementType = typeof DEFAULT_MENU_ELEMENT>(
|
|
|
127
127
|
),
|
|
128
128
|
)
|
|
129
129
|
|
|
130
|
-
const value
|
|
131
|
-
() =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
130
|
+
const value = useMemo(
|
|
131
|
+
() =>
|
|
132
|
+
({
|
|
133
|
+
version: 2,
|
|
134
|
+
activeElement,
|
|
135
|
+
mount,
|
|
136
|
+
onClickOutside,
|
|
137
|
+
onEscape,
|
|
138
|
+
onItemClick,
|
|
139
|
+
onItemMouseEnter: handleItemMouseEnter,
|
|
140
|
+
onItemMouseLeave: handleItemMouseLeave,
|
|
141
|
+
registerElement,
|
|
142
|
+
}) satisfies MenuContextValue,
|
|
143
143
|
[
|
|
144
144
|
activeElement,
|
|
145
|
-
activeIndex,
|
|
146
145
|
mount,
|
|
147
146
|
handleItemMouseEnter,
|
|
148
147
|
handleItemMouseLeave,
|
|
@@ -3,9 +3,8 @@ import {type Context, type MouseEvent as ReactMouseEvent} from 'react'
|
|
|
3
3
|
import {createGlobalScopedContext} from '../../lib/createGlobalScopedContext'
|
|
4
4
|
|
|
5
5
|
export interface MenuContextValue {
|
|
6
|
-
version:
|
|
6
|
+
version: 2
|
|
7
7
|
activeElement: HTMLElement | null
|
|
8
|
-
activeIndex: number
|
|
9
8
|
mount: (element: HTMLElement | null, selected?: boolean) => () => void
|
|
10
9
|
onClickOutside?: (event: MouseEvent) => void
|
|
11
10
|
onEscape?: () => void
|
|
@@ -16,4 +15,4 @@ export interface MenuContextValue {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export const MenuContext: Context<MenuContextValue | null> =
|
|
19
|
-
createGlobalScopedContext<MenuContextValue | null>('@sanity/ui/
|
|
18
|
+
createGlobalScopedContext<MenuContextValue | null>('@sanity/ui/v4/menu', null)
|
|
@@ -142,11 +142,13 @@ export function MenuGroup<E extends MenuGroupElementType = typeof DEFAULT_MENU_G
|
|
|
142
142
|
|
|
143
143
|
// Close child menu when a sibling item becomes active
|
|
144
144
|
useEffect(() => {
|
|
145
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
145
146
|
if (!active) setOpen(false)
|
|
146
147
|
}, [active])
|
|
147
148
|
|
|
148
149
|
// Update state when child menu is no longer open
|
|
149
150
|
useEffect(() => {
|
|
151
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
150
152
|
if (!open) setWithinMenu(false)
|
|
151
153
|
}, [open])
|
|
152
154
|
|
|
@@ -12,8 +12,8 @@ export function useMenu(): MenuContextValue {
|
|
|
12
12
|
|
|
13
13
|
// NOTE: This check is for future-compatiblity
|
|
14
14
|
// - If the value is not an object, it’s not compatible with the current version
|
|
15
|
-
// - If the value is an object, but doesn’t have `version:
|
|
16
|
-
if (!isRecord(value) || value.version !==
|
|
15
|
+
// - If the value is an object, but doesn’t have `version: 2`, it’s not compatible with the current version
|
|
16
|
+
if (!isRecord(value) || value.version !== 2) {
|
|
17
17
|
throw new Error('useMenu(): the context value is not compatible')
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
type MutableRefObject,
|
|
6
6
|
useCallback,
|
|
7
7
|
useEffect,
|
|
8
|
-
useMemo,
|
|
9
8
|
useRef,
|
|
10
9
|
useState,
|
|
11
10
|
} from 'react'
|
|
@@ -39,12 +38,13 @@ export function useMenuController(props: {
|
|
|
39
38
|
const elementsRef = useRef<HTMLElement[]>([])
|
|
40
39
|
const [activeIndex, _setActiveIndex] = useState(-1)
|
|
41
40
|
const activeIndexRef = useRef(activeIndex)
|
|
42
|
-
const activeElement =
|
|
43
|
-
const mounted = Boolean(rootElementRef.current)
|
|
41
|
+
const [activeElement, setActiveElement] = useState<HTMLElement | null>(null)
|
|
44
42
|
|
|
45
43
|
const setActiveIndex = useCallback((nextActiveIndex: number) => {
|
|
46
44
|
_setActiveIndex(nextActiveIndex)
|
|
47
45
|
activeIndexRef.current = nextActiveIndex
|
|
46
|
+
|
|
47
|
+
setActiveElement(elementsRef.current[nextActiveIndex] || null)
|
|
48
48
|
}, [])
|
|
49
49
|
|
|
50
50
|
const mount = useCallback(
|
|
@@ -193,7 +193,7 @@ export function useMenuController(props: {
|
|
|
193
193
|
|
|
194
194
|
// Set focus on the currently active element
|
|
195
195
|
useEffect(() => {
|
|
196
|
-
if (!
|
|
196
|
+
if (!rootElementRef.current) return
|
|
197
197
|
|
|
198
198
|
const rafId = requestAnimationFrame(() => {
|
|
199
199
|
if (activeIndex === -1) {
|
|
@@ -228,7 +228,7 @@ export function useMenuController(props: {
|
|
|
228
228
|
})
|
|
229
229
|
|
|
230
230
|
return () => cancelAnimationFrame(rafId)
|
|
231
|
-
}, [activeIndex,
|
|
231
|
+
}, [activeIndex, rootElementRef, setActiveIndex, shouldFocus])
|
|
232
232
|
|
|
233
233
|
return {
|
|
234
234
|
activeElement,
|
|
@@ -4,4 +4,4 @@ import {createGlobalScopedContext} from '../../lib/createGlobalScopedContext'
|
|
|
4
4
|
import type {ToastContextValue} from './types'
|
|
5
5
|
|
|
6
6
|
export const ToastContext: Context<ToastContextValue | null> =
|
|
7
|
-
createGlobalScopedContext<ToastContextValue | null>('@sanity/ui/
|
|
7
|
+
createGlobalScopedContext<ToastContextValue | null>('@sanity/ui/v4/toast', null)
|
|
@@ -226,6 +226,7 @@ export function Tree<E extends TreeElementType = typeof DEFAULT_TREE_ELEMENT>(
|
|
|
226
226
|
ref.current.querySelectorAll('[data-ui="TreeItem"]'),
|
|
227
227
|
) as HTMLElement[]
|
|
228
228
|
|
|
229
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
229
230
|
setItemElements(_itemElements)
|
|
230
231
|
}, [children])
|
|
231
232
|
|
|
@@ -7,4 +7,4 @@ import type {TreeContextValue} from './types'
|
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
9
|
export const TreeContext: Context<TreeContextValue | null> =
|
|
10
|
-
createGlobalScopedContext<TreeContextValue | null>('@sanity/ui/
|
|
10
|
+
createGlobalScopedContext<TreeContextValue | null>('@sanity/ui/v4/tree', null)
|