@sanity/ui 2.6.7-canary.0 → 2.6.7
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.mts +15 -51
- package/dist/index.d.ts +15 -51
- package/dist/index.esm.js +186 -120
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +186 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +186 -120
- package/dist/index.mjs.map +1 -1
- package/dist/theme.d.mts +14 -14
- package/dist/theme.d.ts +14 -14
- package/dist/theme.esm.js.map +1 -1
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs.map +1 -1
- package/package.json +53 -46
- package/src/core/__workshop__/constants.ts +36 -36
- package/src/core/components/autocomplete/autocomplete.tsx +1 -1
- package/src/core/components/breadcrumbs/breadcrumbs.tsx +6 -15
- package/src/core/components/dialog/dialog.tsx +21 -12
- package/src/core/components/dialog/styles.ts +8 -8
- package/src/core/components/menu/menu.tsx +18 -11
- package/src/core/components/menu/menuButton.tsx +11 -11
- package/src/core/components/menu/menuContext.ts +1 -1
- package/src/core/components/menu/useMenuController.ts +17 -11
- package/src/core/components/tab/tab.tsx +8 -8
- package/src/core/components/tab/tabPanel.tsx +1 -1
- package/src/core/components/toast/styles.ts +1 -2
- package/src/core/components/toast/useToast.ts +0 -1
- package/src/core/components/tree/tree.tsx +0 -1
- package/src/core/components/tree/treeItem.tsx +0 -1
- package/src/core/helpers/focus.ts +0 -1
- package/src/core/helpers/scroll.ts +0 -1
- package/src/core/hooks/_internal/index.ts +1 -0
- package/src/core/hooks/_internal/useUnique.ts +34 -0
- package/src/core/hooks/index.ts +2 -3
- package/src/core/hooks/useClickOutside.ts +72 -38
- package/src/core/hooks/useElementSize.ts +0 -1
- package/src/core/hooks/useMediaIndex/useMediaIndex.ts +10 -2
- package/src/core/hooks/usePrefersDark.ts +55 -10
- package/src/core/hooks/usePrefersReducedMotion.ts +56 -10
- package/src/core/primitives/avatar/styles.ts +42 -42
- package/src/core/primitives/badge/styles.ts +2 -2
- package/src/core/primitives/button/styles.ts +4 -4
- package/src/core/primitives/inline/styles.ts +2 -2
- package/src/core/primitives/popover/__workshop__/AlignedStory.tsx +7 -9
- package/src/core/primitives/popover/constants.ts +4 -4
- package/src/core/primitives/stack/styles.ts +2 -2
- package/src/core/primitives/tooltip/__workshop__/customPortal.tsx +7 -9
- package/src/core/primitives/tooltip/constants.ts +4 -4
- package/src/core/primitives/tooltip/tooltip.tsx +49 -68
- package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupProvider.tsx +0 -1
- package/src/core/styles/font/responsiveFont.ts +8 -8
- package/src/core/theme/__workshop__/debug/story.tsx +11 -11
- package/src/core/utils/layer/layerProvider.tsx +0 -1
- package/src/core/utils/portal/__workshop__/named.tsx +8 -10
- package/src/core/utils/portal/portalProvider.tsx +13 -7
- package/src/theme/build/buildColorTheme.test.ts +13 -13
- package/src/theme/config/tokens/color/types.ts +14 -14
- package/src/theme/defaults/colorTokens.ts +36 -36
- package/src/core/hooks/useMatchMedia.ts +0 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "2.6.7
|
|
3
|
+
"version": "2.6.7",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"sanity",
|
|
6
6
|
"ui",
|
|
@@ -51,6 +51,32 @@
|
|
|
51
51
|
"src",
|
|
52
52
|
"theme.js"
|
|
53
53
|
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "run-s clean pkg:build pkg:check figma:pkg:build",
|
|
56
|
+
"clean": "rimraf .workshop dist",
|
|
57
|
+
"commit": "cz",
|
|
58
|
+
"cypress:dev": "run-p dev cypress:open",
|
|
59
|
+
"cypress:open": "cypress open",
|
|
60
|
+
"cypress:run": "cypress run",
|
|
61
|
+
"dev": "run-p storybook:dev workshop:dev",
|
|
62
|
+
"figma:pkg:build": "cd figma && pnpm build",
|
|
63
|
+
"format": "prettier --write --cache --ignore-unknown .",
|
|
64
|
+
"lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
|
|
65
|
+
"pkg:build": "pkg build --strict",
|
|
66
|
+
"pkg:check": "pkg --strict",
|
|
67
|
+
"prepare": "husky install",
|
|
68
|
+
"prepublishOnly": "pnpm build",
|
|
69
|
+
"release": "semantic-release",
|
|
70
|
+
"storybook:build": "storybook build",
|
|
71
|
+
"storybook:dev": "storybook dev -p 6006",
|
|
72
|
+
"test": "jest",
|
|
73
|
+
"test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
|
|
74
|
+
"ts:check": "tsc",
|
|
75
|
+
"watch": "pkg watch --strict",
|
|
76
|
+
"workshop:build": "workshop build",
|
|
77
|
+
"workshop:dev": "workshop dev",
|
|
78
|
+
"workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
|
|
79
|
+
},
|
|
54
80
|
"commitlint": {
|
|
55
81
|
"extends": [
|
|
56
82
|
"@commitlint/config-conventional"
|
|
@@ -61,7 +87,6 @@
|
|
|
61
87
|
"prettier --write --cache --ignore-unknown"
|
|
62
88
|
]
|
|
63
89
|
},
|
|
64
|
-
"prettier": "@sanity/prettier-config",
|
|
65
90
|
"release": {
|
|
66
91
|
"branches": [
|
|
67
92
|
"+([0-9])?(.{+([0-9]),x}).x",
|
|
@@ -84,8 +109,7 @@
|
|
|
84
109
|
"@sanity/icons": "^3.3.0",
|
|
85
110
|
"csstype": "^3.1.3",
|
|
86
111
|
"framer-motion": "11.0.8",
|
|
87
|
-
"react-refractor": "^2.2.0"
|
|
88
|
-
"use-effect-event": "^1.0.2"
|
|
112
|
+
"react-refractor": "^2.2.0"
|
|
89
113
|
},
|
|
90
114
|
"devDependencies": {
|
|
91
115
|
"@babel/core": "^7.24.7",
|
|
@@ -96,25 +120,24 @@
|
|
|
96
120
|
"@commitlint/config-conventional": "^19.2.2",
|
|
97
121
|
"@juggle/resize-observer": "^3.4.0",
|
|
98
122
|
"@sanity/pkg-utils": "^6.10.3",
|
|
99
|
-
"@sanity/
|
|
100
|
-
"@sanity/semantic-release-preset": "^5.0.0",
|
|
123
|
+
"@sanity/semantic-release-preset": "^4.1.8",
|
|
101
124
|
"@sanity/ui-workshop": "^2.0.15",
|
|
102
|
-
"@storybook/addon-a11y": "^8.
|
|
103
|
-
"@storybook/addon-docs": "^8.
|
|
104
|
-
"@storybook/addon-essentials": "^8.
|
|
105
|
-
"@storybook/addon-interactions": "^8.
|
|
106
|
-
"@storybook/addon-links": "^8.
|
|
107
|
-
"@storybook/addon-
|
|
108
|
-
"@storybook/addon-
|
|
109
|
-
"@storybook/
|
|
110
|
-
"@storybook/
|
|
111
|
-
"@storybook/manager-api": "^8.
|
|
112
|
-
"@storybook/react": "^8.
|
|
113
|
-
"@storybook/react-vite": "^8.
|
|
114
|
-
"@storybook/
|
|
115
|
-
"@storybook/theming": "^8.
|
|
125
|
+
"@storybook/addon-a11y": "^8.0.8",
|
|
126
|
+
"@storybook/addon-docs": "^8.0.8",
|
|
127
|
+
"@storybook/addon-essentials": "^8.0.8",
|
|
128
|
+
"@storybook/addon-interactions": "^8.0.8",
|
|
129
|
+
"@storybook/addon-links": "^8.0.8",
|
|
130
|
+
"@storybook/addon-storysource": "^8.0.8",
|
|
131
|
+
"@storybook/addon-themes": "^8.0.8",
|
|
132
|
+
"@storybook/blocks": "^8.0.8",
|
|
133
|
+
"@storybook/jest": "^0.2.3",
|
|
134
|
+
"@storybook/manager-api": "^8.0.8",
|
|
135
|
+
"@storybook/react": "^8.0.8",
|
|
136
|
+
"@storybook/react-vite": "^8.0.8",
|
|
137
|
+
"@storybook/testing-library": "^0.2.2",
|
|
138
|
+
"@storybook/theming": "^8.0.8",
|
|
116
139
|
"@testing-library/dom": "^10.3.1",
|
|
117
|
-
"@testing-library/jest-dom": "^
|
|
140
|
+
"@testing-library/jest-dom": "^5.17.0",
|
|
118
141
|
"@testing-library/react": "^16.0.0",
|
|
119
142
|
"@types/jest": "^29.5.12",
|
|
120
143
|
"@types/jest-axe": "^3.5.9",
|
|
@@ -123,6 +146,7 @@
|
|
|
123
146
|
"@types/react-dom": "^18.3.0",
|
|
124
147
|
"@types/react-is": "^18.3.0",
|
|
125
148
|
"@types/refractor": "^3.4.1",
|
|
149
|
+
"@types/testing-library__jest-dom": "^5.14.9",
|
|
126
150
|
"@typescript-eslint/eslint-plugin": "^7.16.0",
|
|
127
151
|
"@typescript-eslint/parser": "^7.16.0",
|
|
128
152
|
"commitizen": "^4.3.0",
|
|
@@ -148,6 +172,7 @@
|
|
|
148
172
|
"module-alias": "^2.2.3",
|
|
149
173
|
"npm-run-all2": "^5.0.2",
|
|
150
174
|
"prettier": "^3.3.2",
|
|
175
|
+
"prettier-plugin-packagejson": "^2.5.0",
|
|
151
176
|
"react": "^18.3.1",
|
|
152
177
|
"react-dom": "^18.3.1",
|
|
153
178
|
"react-is": "^18.3.1",
|
|
@@ -155,7 +180,7 @@
|
|
|
155
180
|
"rimraf": "^5.0.5",
|
|
156
181
|
"semantic-release": "^24.0.0",
|
|
157
182
|
"start-server-and-test": "^2.0.4",
|
|
158
|
-
"storybook": "^8.
|
|
183
|
+
"storybook": "^8.0.8",
|
|
159
184
|
"styled-components": "^6.1.11",
|
|
160
185
|
"tsconfig-paths": "^4.2.0",
|
|
161
186
|
"typescript": "5.5.3",
|
|
@@ -168,6 +193,7 @@
|
|
|
168
193
|
"react-is": "^18",
|
|
169
194
|
"styled-components": "^5.2 || ^6"
|
|
170
195
|
},
|
|
196
|
+
"packageManager": "pnpm@9.5.0",
|
|
171
197
|
"engines": {
|
|
172
198
|
"node": ">=14.0.0"
|
|
173
199
|
},
|
|
@@ -177,28 +203,9 @@
|
|
|
177
203
|
"esm.sh": {
|
|
178
204
|
"bundle": false
|
|
179
205
|
},
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"cypress:dev": "run-p dev cypress:open",
|
|
185
|
-
"cypress:open": "cypress open",
|
|
186
|
-
"cypress:run": "cypress run",
|
|
187
|
-
"dev": "run-p storybook:dev workshop:dev",
|
|
188
|
-
"figma:pkg:build": "cd figma && pnpm build",
|
|
189
|
-
"format": "prettier --write --cache --ignore-unknown .",
|
|
190
|
-
"lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
|
|
191
|
-
"pkg:build": "pkg build --strict",
|
|
192
|
-
"pkg:check": "pkg --strict",
|
|
193
|
-
"release": "semantic-release",
|
|
194
|
-
"storybook:build": "storybook build",
|
|
195
|
-
"storybook:dev": "storybook dev -p 6006",
|
|
196
|
-
"test": "jest",
|
|
197
|
-
"test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
|
|
198
|
-
"ts:check": "tsc",
|
|
199
|
-
"watch": "pkg watch --strict",
|
|
200
|
-
"workshop:build": "workshop build",
|
|
201
|
-
"workshop:dev": "workshop dev",
|
|
202
|
-
"workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
|
|
206
|
+
"pnpm": {
|
|
207
|
+
"overrides": {
|
|
208
|
+
"conventional-changelog-conventionalcommits": ">= 8.0.0"
|
|
209
|
+
}
|
|
203
210
|
}
|
|
204
|
-
}
|
|
211
|
+
}
|
|
@@ -44,9 +44,9 @@ export const WORKSHOP_BUTTON_MODE_OPTIONS: {[key: string]: ButtonMode} = {
|
|
|
44
44
|
|
|
45
45
|
export const WORKSHOP_BUTTON_TEXT_ALIGN_OPTIONS: {[key: string]: ButtonTextAlign | ''} = {
|
|
46
46
|
'(none)': '',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
Left: 'left',
|
|
48
|
+
Right: 'right',
|
|
49
|
+
Center: 'center',
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export const WORKSHOP_BUTTON_TONE_OPTIONS: {[key: string]: ButtonTone} = {
|
|
@@ -61,11 +61,11 @@ export const WORKSHOP_CARD_AS_OPTIONS: {
|
|
|
61
61
|
[key: string]: 'div' | 'button' | 'span' | 'ol' | 'pre' | 'ul'
|
|
62
62
|
} = {
|
|
63
63
|
'DIV (default)': 'div',
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
BUTTON: 'button',
|
|
65
|
+
SPAN: 'span',
|
|
66
|
+
OL: 'ol',
|
|
67
|
+
PRE: 'pre',
|
|
68
|
+
UL: 'ul',
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export const WORKSHOP_CARD_TONE_OPTIONS: {[key: string]: CardTone} = {
|
|
@@ -92,7 +92,7 @@ export const WORKSHOP_CONTAINER_WIDTH_OPTIONS: {[key: string]: number | 'auto'}
|
|
|
92
92
|
'3': 3,
|
|
93
93
|
'4': 4,
|
|
94
94
|
'5': 5,
|
|
95
|
-
|
|
95
|
+
auto: 'auto',
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export const WORKSHOP_DIALOG_POSITION_OPTIONS: {[key: string]: DialogPosition} = {
|
|
@@ -106,15 +106,15 @@ export const WORKSHOP_FLEX_DIRECTION_OPTIONS: {[key: string]: 'row' | 'column'}
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
export const WORKSHOP_FLEX_ALIGN_OPTIONS: Record<FlexAlign, FlexAlign> = {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
baseline: 'baseline',
|
|
110
|
+
center: 'center',
|
|
111
111
|
'flex-end': 'flex-end',
|
|
112
112
|
'flex-start': 'flex-start',
|
|
113
|
-
|
|
113
|
+
stretch: 'stretch',
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export const WORKSHOP_FLEX_JUSTIFY_OPTIONS: Record<FlexJustify, FlexJustify> = {
|
|
117
|
-
|
|
117
|
+
center: 'center',
|
|
118
118
|
'flex-end': 'flex-end',
|
|
119
119
|
'flex-start': 'flex-start',
|
|
120
120
|
'space-around': 'space-around',
|
|
@@ -157,16 +157,16 @@ export const WORKSHOP_LABEL_FONT_SIZE_OPTIONS = {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
export const WORKSHOP_PLACEMENT_OPTIONS: Record<Placement, Placement> = {
|
|
160
|
-
|
|
160
|
+
top: 'top',
|
|
161
161
|
'top-start': 'top-start',
|
|
162
162
|
'top-end': 'top-end',
|
|
163
|
-
|
|
163
|
+
right: 'right',
|
|
164
164
|
'right-start': 'right-start',
|
|
165
165
|
'right-end': 'right-end',
|
|
166
|
-
|
|
166
|
+
left: 'left',
|
|
167
167
|
'left-start': 'left-start',
|
|
168
168
|
'left-end': 'left-end',
|
|
169
|
-
|
|
169
|
+
bottom: 'bottom',
|
|
170
170
|
'bottom-start': 'bottom-start',
|
|
171
171
|
'bottom-end': 'bottom-end',
|
|
172
172
|
}
|
|
@@ -217,11 +217,11 @@ export const WORKSHOP_SPOT_COLOR_OPTIONS: {[key: string]: ThemeColorSpotKey} = {
|
|
|
217
217
|
|
|
218
218
|
export const WORKSHOP_TEXT_ALIGN_OPTIONS: {[key: string]: TextAlign | ''} = {
|
|
219
219
|
'(none)': '',
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
Initial: 'initial',
|
|
221
|
+
Left: 'left',
|
|
222
|
+
Right: 'right',
|
|
223
|
+
Center: 'center',
|
|
224
|
+
Justify: 'justify',
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
export const WORKSHOP_TEXT_FONT_SIZE_OPTIONS = {
|
|
@@ -233,17 +233,17 @@ export const WORKSHOP_TEXT_FONT_SIZE_OPTIONS = {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
export const WORKSHOP_TEXT_INPUT_TYPE_OPTIONS: {[key: string]: TextInputType} = {
|
|
236
|
-
|
|
236
|
+
date: 'date',
|
|
237
237
|
'datetime-local': 'datetime-local',
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
238
|
+
email: 'email',
|
|
239
|
+
month: 'month',
|
|
240
|
+
number: 'number',
|
|
241
|
+
password: 'password',
|
|
242
|
+
tel: 'tel',
|
|
243
|
+
time: 'time',
|
|
244
|
+
text: 'text',
|
|
245
|
+
week: 'week',
|
|
246
|
+
color: 'color',
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
export const WORKSHOP_TEXT_OVERFLOW_OPTIONS: {[key: string]: 'ellipsis' | ''} = {
|
|
@@ -261,9 +261,9 @@ export const WORKSHOP_TEXT_SIZE_OPTIONS = {
|
|
|
261
261
|
|
|
262
262
|
export const WORKSHOP_TEXT_WEIGHT_OPTIONS: {[key: string]: ThemeFontWeightKey | ''} = {
|
|
263
263
|
'Regular (default)': '',
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
Medium: 'medium',
|
|
265
|
+
Semibold: 'semibold',
|
|
266
|
+
Bold: 'bold',
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
export const WORKSHOP_THEME_COLOR_TONE_OPTIONS: {[key: string]: ThemeColorToneKey} = {
|
|
@@ -286,7 +286,7 @@ export const WORKSHOP_TOAST_STATUS_OPTIONS: {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
export const WORKSHOP_WIDTH_OPTIONS: {[key: string]: number | 'auto'} = {
|
|
289
|
-
|
|
289
|
+
Auto: 'auto',
|
|
290
290
|
'0': 0,
|
|
291
291
|
'1': 1,
|
|
292
292
|
'2': 2,
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Children,
|
|
3
|
-
forwardRef,
|
|
4
|
-
Fragment,
|
|
5
|
-
isValidElement,
|
|
6
|
-
useCallback,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
} from 'react'
|
|
1
|
+
import {Children, forwardRef, Fragment, isValidElement, useCallback, useMemo, useState} from 'react'
|
|
11
2
|
import {useArrayProp, useClickOutside} from '../../hooks'
|
|
12
3
|
import {Box, Popover, Stack, Text} from '../../primitives'
|
|
13
4
|
import {ExpandButton, Root} from './breadcrumbs.styles'
|
|
@@ -31,13 +22,13 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
|
|
|
31
22
|
const {children, maxLength, separator, space: spaceRaw = 2, ...restProps} = props
|
|
32
23
|
const space = useArrayProp(spaceRaw)
|
|
33
24
|
const [open, setOpen] = useState(false)
|
|
34
|
-
const
|
|
35
|
-
const
|
|
25
|
+
const [expandElement, setExpandElement] = useState<HTMLButtonElement | null>(null)
|
|
26
|
+
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
|
|
36
27
|
|
|
37
28
|
const collapse = useCallback(() => setOpen(false), [])
|
|
38
29
|
const expand = useCallback(() => setOpen(true), [])
|
|
39
30
|
|
|
40
|
-
useClickOutside(collapse,
|
|
31
|
+
useClickOutside(collapse, [expandElement, popoverElement])
|
|
41
32
|
|
|
42
33
|
const rawItems = useMemo(() => Children.toArray(children).filter(isValidElement), [children])
|
|
43
34
|
|
|
@@ -61,14 +52,14 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
|
|
|
61
52
|
open={open}
|
|
62
53
|
placement="top"
|
|
63
54
|
portal
|
|
64
|
-
ref={
|
|
55
|
+
ref={setPopoverElement}
|
|
65
56
|
>
|
|
66
57
|
<ExpandButton
|
|
67
58
|
fontSize={1}
|
|
68
59
|
mode="bleed"
|
|
69
60
|
onClick={open ? collapse : expand}
|
|
70
61
|
padding={1}
|
|
71
|
-
ref={
|
|
62
|
+
ref={setExpandElement}
|
|
72
63
|
selected={open}
|
|
73
64
|
text="…"
|
|
74
65
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {CloseIcon} from '@sanity/icons'
|
|
2
2
|
import {ThemeColorSchemeKey} from '@sanity/ui/theme'
|
|
3
|
-
import {forwardRef, useCallback, useEffect, useImperativeHandle, useRef} from 'react'
|
|
3
|
+
import {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
5
|
import {
|
|
6
6
|
containsOrEqualsElement,
|
|
@@ -170,6 +170,7 @@ const DialogCard = forwardRef(function DialogCard(
|
|
|
170
170
|
const shadow = useArrayProp(shadowProp)
|
|
171
171
|
const width = useArrayProp(widthProp)
|
|
172
172
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
173
|
+
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
173
174
|
const contentRef = useRef<HTMLDivElement | null>(null)
|
|
174
175
|
const layer = useLayer()
|
|
175
176
|
const {isTopLayer} = layer
|
|
@@ -215,24 +216,32 @@ const DialogCard = forwardRef(function DialogCard(
|
|
|
215
216
|
)
|
|
216
217
|
|
|
217
218
|
useClickOutside(
|
|
218
|
-
(
|
|
219
|
-
|
|
219
|
+
useCallback(
|
|
220
|
+
(event: MouseEvent) => {
|
|
221
|
+
if (!isTopLayer || !onClickOutside) return
|
|
220
222
|
|
|
221
|
-
|
|
223
|
+
const target = event.target as Node | null
|
|
222
224
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
if (target && !isTargetWithinScope(boundaryElement, portalElement, target)) {
|
|
226
|
+
// Ignore clicks outside of the scope
|
|
227
|
+
return
|
|
228
|
+
}
|
|
227
229
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
onClickOutside()
|
|
231
|
+
},
|
|
232
|
+
[boundaryElement, isTopLayer, onClickOutside, portalElement],
|
|
233
|
+
),
|
|
234
|
+
[rootElement],
|
|
231
235
|
)
|
|
232
236
|
|
|
237
|
+
const setRef = useCallback((el: HTMLDivElement | null) => {
|
|
238
|
+
setRootElement(el)
|
|
239
|
+
ref.current = el
|
|
240
|
+
}, [])
|
|
241
|
+
|
|
233
242
|
return (
|
|
234
243
|
<DialogContainer data-ui="DialogCard" width={width}>
|
|
235
|
-
<DialogCardRoot radius={radius} ref={
|
|
244
|
+
<DialogCardRoot radius={radius} ref={setRef} scheme={scheme} shadow={shadow}>
|
|
236
245
|
<DialogLayout direction="column">
|
|
237
246
|
{showHeader && (
|
|
238
247
|
<DialogHeader>
|
|
@@ -18,14 +18,14 @@ export function dialogStyle({theme}: ThemeProps): CSSObject {
|
|
|
18
18
|
display: 'flex',
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
outline: 'none',
|
|
28
|
+
background: color.backdrop,
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -19,7 +19,7 @@ export interface MenuProps extends ResponsivePaddingProps {
|
|
|
19
19
|
* @deprecated Use `shouldFocus="last"` instead.
|
|
20
20
|
*/
|
|
21
21
|
focusLast?: boolean
|
|
22
|
-
onClickOutside?: (event: MouseEvent
|
|
22
|
+
onClickOutside?: (event: MouseEvent) => void
|
|
23
23
|
onEscape?: () => void
|
|
24
24
|
onItemClick?: () => void
|
|
25
25
|
onItemSelect?: (index: number) => void
|
|
@@ -75,18 +75,16 @@ export const Menu = forwardRef(function Menu(
|
|
|
75
75
|
handleItemMouseLeave,
|
|
76
76
|
handleKeyDown,
|
|
77
77
|
mount,
|
|
78
|
-
|
|
78
|
+
rootElement,
|
|
79
|
+
setRootElement,
|
|
80
|
+
} = useMenuController({onKeyDown, originElement, shouldFocus})
|
|
79
81
|
|
|
80
82
|
const handleRefChange = useCallback(
|
|
81
83
|
(el: HTMLDivElement | null) => {
|
|
84
|
+
setRootElement(el)
|
|
82
85
|
ref.current = el
|
|
83
|
-
|
|
84
|
-
// Register root element (for nested menus)
|
|
85
|
-
if (ref.current && registerElement) {
|
|
86
|
-
registerElement(ref.current)
|
|
87
|
-
}
|
|
88
86
|
},
|
|
89
|
-
[
|
|
87
|
+
[setRootElement],
|
|
90
88
|
)
|
|
91
89
|
|
|
92
90
|
// Trigger `onItemSelect` when active index changes
|
|
@@ -96,8 +94,11 @@ export const Menu = forwardRef(function Menu(
|
|
|
96
94
|
|
|
97
95
|
// Close menu when clicking outside
|
|
98
96
|
useClickOutside(
|
|
99
|
-
(
|
|
100
|
-
|
|
97
|
+
useCallback(
|
|
98
|
+
(event) => isTopLayer && onClickOutside && onClickOutside(event),
|
|
99
|
+
[isTopLayer, onClickOutside],
|
|
100
|
+
),
|
|
101
|
+
[rootElement],
|
|
101
102
|
)
|
|
102
103
|
|
|
103
104
|
// Close menu when pressing Escape
|
|
@@ -115,7 +116,13 @@ export const Menu = forwardRef(function Menu(
|
|
|
115
116
|
),
|
|
116
117
|
)
|
|
117
118
|
|
|
118
|
-
//
|
|
119
|
+
// Register root element (for nested menus)
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!rootElement || !registerElement) return
|
|
122
|
+
|
|
123
|
+
return registerElement(rootElement)
|
|
124
|
+
}, [registerElement, rootElement])
|
|
125
|
+
|
|
119
126
|
const value: MenuContextValue = useMemo(
|
|
120
127
|
() => ({
|
|
121
128
|
version: 0.0,
|
|
@@ -140,7 +140,7 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
140
140
|
}, [])
|
|
141
141
|
|
|
142
142
|
const handleMenuClickOutside = useCallback(
|
|
143
|
-
(event: MouseEvent
|
|
143
|
+
(event: MouseEvent) => {
|
|
144
144
|
const target = event.target
|
|
145
145
|
|
|
146
146
|
if (!(target instanceof Node)) {
|
|
@@ -206,11 +206,11 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
206
206
|
const menuProps: MenuProps = useMemo(
|
|
207
207
|
() => ({
|
|
208
208
|
'aria-labelledby': id,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
onBlurCapture: handleBlur,
|
|
210
|
+
onClickOutside: handleMenuClickOutside,
|
|
211
|
+
onEscape: handleMenuEscape,
|
|
212
|
+
onItemClick: handleItemClick,
|
|
213
|
+
originElement: buttonElement,
|
|
214
214
|
registerElement,
|
|
215
215
|
shouldFocus,
|
|
216
216
|
}),
|
|
@@ -235,13 +235,13 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
235
235
|
cloneElement(buttonProp, {
|
|
236
236
|
'data-ui': 'MenuButton',
|
|
237
237
|
id,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
onClick: handleButtonClick,
|
|
239
|
+
onKeyDown: handleButtonKeyDown,
|
|
240
|
+
onMouseDown: handleMouseDown,
|
|
241
241
|
'aria-haspopup': true,
|
|
242
242
|
'aria-expanded': open,
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
ref: ref,
|
|
244
|
+
selected: buttonProp.props.selected ?? open,
|
|
245
245
|
}),
|
|
246
246
|
[buttonProp, handleButtonClick, handleButtonKeyDown, handleMouseDown, id, open],
|
|
247
247
|
)
|
|
@@ -5,7 +5,7 @@ export interface MenuContextValue {
|
|
|
5
5
|
activeElement: HTMLElement | null
|
|
6
6
|
activeIndex: number
|
|
7
7
|
mount: (element: HTMLElement | null, selected?: boolean) => () => void
|
|
8
|
-
onClickOutside?: (event: MouseEvent
|
|
8
|
+
onClickOutside?: (event: MouseEvent) => void
|
|
9
9
|
onEscape?: () => void
|
|
10
10
|
onItemClick?: () => void
|
|
11
11
|
onItemMouseEnter?: (event: React.MouseEvent<HTMLElement>) => void
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useCallback, useEffect,
|
|
1
|
+
import {useCallback, useEffect, useRef, useState} from 'react'
|
|
2
2
|
import {_getFocusableElements, _sortElements} from './helpers'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -11,6 +11,8 @@ export interface MenuController {
|
|
|
11
11
|
handleItemMouseLeave: () => void
|
|
12
12
|
handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void
|
|
13
13
|
mount: (element: HTMLElement | null, selected?: boolean) => () => void
|
|
14
|
+
rootElement: HTMLDivElement | null
|
|
15
|
+
setRootElement: (el: HTMLDivElement | null) => void
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -22,14 +24,14 @@ export function useMenuController(props: {
|
|
|
22
24
|
onKeyDown?: React.KeyboardEventHandler
|
|
23
25
|
originElement?: HTMLElement | null
|
|
24
26
|
shouldFocus: 'first' | 'last' | null
|
|
25
|
-
rootElementRef: React.MutableRefObject<HTMLDivElement | null>
|
|
26
27
|
}): MenuController {
|
|
27
|
-
const {onKeyDown, originElement, shouldFocus
|
|
28
|
+
const {onKeyDown, originElement, shouldFocus} = props
|
|
28
29
|
const elementsRef = useRef<HTMLElement[]>([])
|
|
30
|
+
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
29
31
|
const [activeIndex, _setActiveIndex] = useState(-1)
|
|
30
32
|
const activeIndexRef = useRef(activeIndex)
|
|
31
|
-
const activeElement =
|
|
32
|
-
const mounted = Boolean(
|
|
33
|
+
const activeElement = elementsRef.current[activeIndex] || null
|
|
34
|
+
const mounted = Boolean(rootElement)
|
|
33
35
|
|
|
34
36
|
const setActiveIndex = useCallback((nextActiveIndex: number) => {
|
|
35
37
|
_setActiveIndex(nextActiveIndex)
|
|
@@ -42,7 +44,7 @@ export function useMenuController(props: {
|
|
|
42
44
|
|
|
43
45
|
if (elementsRef.current.indexOf(element) === -1) {
|
|
44
46
|
elementsRef.current.push(element)
|
|
45
|
-
_sortElements(
|
|
47
|
+
_sortElements(rootElement, elementsRef.current)
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
if (selected) {
|
|
@@ -59,7 +61,7 @@ export function useMenuController(props: {
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
},
|
|
62
|
-
[
|
|
64
|
+
[rootElement, setActiveIndex],
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
const handleKeyDown = useCallback(
|
|
@@ -177,15 +179,17 @@ export function useMenuController(props: {
|
|
|
177
179
|
// which would be incorrect when the user hovers over a gap
|
|
178
180
|
// between two menu items or a menu divider.
|
|
179
181
|
setActiveIndex(-2)
|
|
180
|
-
|
|
181
|
-
}, [
|
|
182
|
+
rootElement?.focus()
|
|
183
|
+
}, [setActiveIndex, rootElement])
|
|
182
184
|
|
|
183
185
|
// Set focus on the currently active element
|
|
184
186
|
useEffect(() => {
|
|
185
187
|
if (!mounted) return
|
|
186
188
|
|
|
187
189
|
const rafId = window.requestAnimationFrame(() => {
|
|
188
|
-
|
|
190
|
+
const _activeIndex = activeIndexRef.current
|
|
191
|
+
|
|
192
|
+
if (_activeIndex === -1) {
|
|
189
193
|
if (shouldFocus === 'first') {
|
|
190
194
|
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
191
195
|
const el = focusableElements[0]
|
|
@@ -213,7 +217,7 @@ export function useMenuController(props: {
|
|
|
213
217
|
return
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
const element = elementsRef.current[
|
|
220
|
+
const element = elementsRef.current[_activeIndex] || null
|
|
217
221
|
|
|
218
222
|
element?.focus()
|
|
219
223
|
})
|
|
@@ -230,5 +234,7 @@ export function useMenuController(props: {
|
|
|
230
234
|
handleItemMouseLeave,
|
|
231
235
|
handleKeyDown,
|
|
232
236
|
mount,
|
|
237
|
+
rootElement,
|
|
238
|
+
setRootElement,
|
|
233
239
|
}
|
|
234
240
|
}
|