@sanity/ui 2.9.0-canary.6 → 2.10.0-corel.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/getTheme_v2.js +54 -84
- package/dist/_chunks-cjs/getTheme_v2.js.map +1 -1
- package/dist/_chunks-es/getTheme_v2.mjs +54 -84
- package/dist/_chunks-es/getTheme_v2.mjs.map +1 -1
- package/dist/_legacy/getTheme_v2.esm.js +54 -84
- package/dist/_legacy/getTheme_v2.esm.js.map +1 -1
- package/dist/index.d.mts +21 -14
- package/dist/index.d.ts +21 -14
- package/dist/index.esm.js +3150 -3805
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3148 -3802
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3150 -3805
- package/dist/index.mjs.map +1 -1
- package/dist/theme.d.mts +22 -28
- package/dist/theme.d.ts +22 -28
- package/dist/theme.esm.js +630 -1318
- package/dist/theme.esm.js.map +1 -1
- package/dist/theme.js +630 -1318
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +630 -1318
- package/dist/theme.mjs.map +1 -1
- package/package.json +1 -2
- package/src/core/__workshop__/constants.ts +13 -13
- package/src/core/components/autocomplete/autocomplete.tsx +31 -42
- package/src/core/components/breadcrumbs/__workshop__/buttons.tsx +58 -0
- package/src/core/components/breadcrumbs/__workshop__/example.tsx +3 -1
- package/src/core/components/breadcrumbs/__workshop__/index.ts +5 -0
- package/src/core/components/breadcrumbs/breadcrumbs.styles.ts +1 -13
- package/src/core/components/breadcrumbs/breadcrumbs.tsx +70 -50
- package/src/core/components/dialog/dialog.tsx +3 -5
- package/src/core/components/menu/menu.tsx +11 -15
- package/src/core/components/menu/menuButton.tsx +36 -14
- package/src/core/components/menu/menuGroup.tsx +1 -2
- package/src/core/components/menu/menuItem.tsx +2 -4
- package/src/core/components/menu/useMenuController.ts +21 -18
- package/src/core/components/tab/tabList.tsx +11 -3
- package/src/core/components/toast/toastProvider.tsx +6 -5
- package/src/core/components/tree/treeItem.tsx +15 -18
- package/src/core/constants.ts +1 -1
- package/src/core/hooks/useArrayProp.ts +9 -14
- package/src/core/hooks/useMatchMedia.ts +22 -12
- package/src/core/primitives/avatar/avatarStack.tsx +5 -2
- package/src/core/primitives/button/__workshop__/props.tsx +1 -1
- package/src/core/primitives/button/button.tsx +11 -6
- package/src/core/primitives/button/styles.ts +1 -1
- package/src/core/primitives/flex/flex.tsx +5 -1
- package/src/core/primitives/heading/heading.tsx +1 -1
- package/src/core/primitives/popover/popover.tsx +139 -74
- package/src/core/primitives/popover/popoverCard.tsx +4 -1
- package/src/core/primitives/text/styles.ts +1 -0
- package/src/core/primitives/text/text.tsx +4 -2
- package/src/core/primitives/tooltip/tooltip.tsx +17 -47
- package/src/core/styles/card/_cardColorStyle.ts +8 -0
- package/src/core/styles/flex/flexStyle.ts +18 -0
- package/src/core/styles/flex/types.ts +2 -0
- package/src/core/theme/themeProvider.tsx +6 -4
- package/src/core/utils/elementQuery/elementQuery.tsx +17 -16
- package/src/core/utils/layer/layerProvider.tsx +1 -1
- package/src/core/utils/portal/portalProvider.tsx +5 -5
- package/src/theme/build/_deprecated/color/factory.ts +1 -1
- package/src/theme/build/_deprecated/color/muted/createMuted.ts +2 -2
- package/src/theme/build/buildColorTheme.ts +45 -39
- package/src/theme/build/renderColorTheme.ts +239 -65
- package/src/theme/build/resolveColorTokens.ts +51 -115
- package/src/theme/config/tokens/color/types.ts +4 -18
- package/src/theme/defaults/colorTokens.ts +19 -11
- package/src/theme/getScopedTheme.ts +9 -2
- package/src/theme/system/color/_constants.ts +8 -0
- package/src/theme/system/color/badge.ts +3 -7
- package/src/theme/system/v0/color/_system.ts +8 -4
- package/src/theme/system/v0/color/color.ts +3 -2
- package/src/theme/versioning/themeColor_v0_v2.ts +26 -0
- package/src/theme/versioning/v0_v2.ts +4 -0
- package/src/core/components/toast/toastState.ts +0 -6
|
@@ -169,7 +169,7 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
169
169
|
}, [buttonElement, disableRestoreFocusOnClose])
|
|
170
170
|
|
|
171
171
|
const handleBlur = useCallback(
|
|
172
|
-
(event: FocusEvent) => {
|
|
172
|
+
(event: React.FocusEvent<HTMLButtonElement>) => {
|
|
173
173
|
const target = event.relatedTarget
|
|
174
174
|
|
|
175
175
|
if (!(target instanceof Node)) {
|
|
@@ -199,19 +199,32 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
199
199
|
return () => setChildMenuElements((els) => els.filter((_el) => _el !== el))
|
|
200
200
|
}, [])
|
|
201
201
|
|
|
202
|
-
const menuProps: MenuProps =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
202
|
+
const menuProps: MenuProps = useMemo(
|
|
203
|
+
() => ({
|
|
204
|
+
'aria-labelledby': id,
|
|
205
|
+
'onBlurCapture': handleBlur,
|
|
206
|
+
'onClickOutside': handleMenuClickOutside,
|
|
207
|
+
'onEscape': handleMenuEscape,
|
|
208
|
+
'onItemClick': handleItemClick,
|
|
209
|
+
'originElement': buttonElement,
|
|
210
|
+
registerElement,
|
|
211
|
+
shouldFocus,
|
|
212
|
+
}),
|
|
213
|
+
[
|
|
214
|
+
buttonElement,
|
|
215
|
+
handleMenuClickOutside,
|
|
216
|
+
handleMenuEscape,
|
|
217
|
+
handleItemClick,
|
|
218
|
+
id,
|
|
219
|
+
handleBlur,
|
|
220
|
+
registerElement,
|
|
221
|
+
shouldFocus,
|
|
222
|
+
],
|
|
223
|
+
)
|
|
212
224
|
|
|
213
225
|
const menu = menuProp && cloneElement(menuProp, menuProps)
|
|
214
226
|
|
|
227
|
+
const ref = useRef<HTMLButtonElement | null>(null)
|
|
215
228
|
const button = useMemo(
|
|
216
229
|
() =>
|
|
217
230
|
buttonProp &&
|
|
@@ -223,7 +236,7 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
223
236
|
'onMouseDown': handleMouseDown,
|
|
224
237
|
'aria-haspopup': true,
|
|
225
238
|
'aria-expanded': open,
|
|
226
|
-
'ref':
|
|
239
|
+
'ref': ref,
|
|
227
240
|
'selected': buttonProp.props.selected ?? open,
|
|
228
241
|
}),
|
|
229
242
|
[buttonProp, handleButtonClick, handleButtonKeyDown, handleMouseDown, id, open],
|
|
@@ -232,10 +245,19 @@ export const MenuButton = forwardRef(function MenuButton(
|
|
|
232
245
|
// Forward button ref to parent
|
|
233
246
|
useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
|
|
234
247
|
forwardedRef,
|
|
235
|
-
() =>
|
|
236
|
-
[buttonElement],
|
|
248
|
+
() => ref.current,
|
|
237
249
|
)
|
|
238
250
|
|
|
251
|
+
// If there's a button then we need to set the reference element to the cloned button ref
|
|
252
|
+
// and if button changes we make sure to update or remove the reference element.
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (!button) return undefined
|
|
255
|
+
|
|
256
|
+
setButtonElement(ref.current)
|
|
257
|
+
|
|
258
|
+
return () => setButtonElement(null)
|
|
259
|
+
}, [button])
|
|
260
|
+
|
|
239
261
|
const popoverProps: MenuButtonProps['popover'] = useMemo(
|
|
240
262
|
() => ({
|
|
241
263
|
boundaryElement: deprecated_boundaryElement,
|
|
@@ -53,10 +53,9 @@ export function MenuGroup(
|
|
|
53
53
|
onClickOutside,
|
|
54
54
|
onEscape,
|
|
55
55
|
onItemClick,
|
|
56
|
-
onItemMouseEnter
|
|
56
|
+
onItemMouseEnter = menu.onMouseEnter,
|
|
57
57
|
registerElement,
|
|
58
58
|
} = menu
|
|
59
|
-
const onItemMouseEnter = _onItemMouseEnter ?? menu.onMouseEnter
|
|
60
59
|
const [rootElement, setRootElement] = useState<HTMLButtonElement | HTMLDivElement | null>(null)
|
|
61
60
|
const [open, setOpen] = useState(false)
|
|
62
61
|
const [shouldFocus, setShouldFocus] = useState<'first' | 'last' | null>(null)
|
|
@@ -72,11 +72,9 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
72
72
|
activeElement,
|
|
73
73
|
mount,
|
|
74
74
|
onItemClick,
|
|
75
|
-
onItemMouseEnter
|
|
76
|
-
onItemMouseLeave
|
|
75
|
+
onItemMouseEnter = menu.onMouseEnter,
|
|
76
|
+
onItemMouseLeave = menu.onMouseLeave,
|
|
77
77
|
} = menu
|
|
78
|
-
const onItemMouseEnter = _onItemMouseEnter ?? menu.onMouseEnter
|
|
79
|
-
const onItemMouseLeave = _onItemMouseLeave ?? menu.onMouseLeave
|
|
80
78
|
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
81
79
|
const active = Boolean(activeElement) && activeElement === rootElement
|
|
82
80
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
@@ -36,28 +36,31 @@ export function useMenuController(props: {
|
|
|
36
36
|
activeIndexRef.current = nextActiveIndex
|
|
37
37
|
}, [])
|
|
38
38
|
|
|
39
|
-
const mount = (
|
|
40
|
-
|
|
39
|
+
const mount = useCallback(
|
|
40
|
+
(element: HTMLElement | null, selected?: boolean): (() => void) => {
|
|
41
|
+
if (!element) return () => undefined
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (elementsRef.current.indexOf(element) === -1) {
|
|
44
|
+
elementsRef.current.push(element)
|
|
45
|
+
_sortElements(rootElementRef.current, elementsRef.current)
|
|
46
|
+
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (selected) {
|
|
49
|
+
const selectedIndex = elementsRef.current.indexOf(element)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
setActiveIndex(selectedIndex)
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
return () => {
|
|
55
|
+
const idx = elementsRef.current.indexOf(element)
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if (idx > -1) {
|
|
58
|
+
elementsRef.current.splice(idx, 1)
|
|
59
|
+
}
|
|
58
60
|
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
+
},
|
|
62
|
+
[rootElementRef, setActiveIndex],
|
|
63
|
+
)
|
|
61
64
|
|
|
62
65
|
const handleKeyDown = useCallback(
|
|
63
66
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -167,7 +170,7 @@ export function useMenuController(props: {
|
|
|
167
170
|
[setActiveIndex],
|
|
168
171
|
)
|
|
169
172
|
|
|
170
|
-
const handleItemMouseLeave = () => {
|
|
173
|
+
const handleItemMouseLeave = useCallback(() => {
|
|
171
174
|
// Set the active index to -2 to deactivate all menu items
|
|
172
175
|
// when the user moves the mouse away from the menu item.
|
|
173
176
|
// We avoid using -1 because it would focus the first menu item,
|
|
@@ -175,7 +178,7 @@ export function useMenuController(props: {
|
|
|
175
178
|
// between two menu items or a menu divider.
|
|
176
179
|
setActiveIndex(-2)
|
|
177
180
|
rootElementRef.current?.focus()
|
|
178
|
-
}
|
|
181
|
+
}, [rootElementRef, setActiveIndex])
|
|
179
182
|
|
|
180
183
|
// Set focus on the currently active element
|
|
181
184
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {cloneElement, forwardRef, useCallback,
|
|
1
|
+
import {cloneElement, forwardRef, useCallback, useMemo, useState} from 'react'
|
|
2
2
|
import {styled} from 'styled-components'
|
|
3
3
|
import {Inline, InlineProps} from '../../primitives'
|
|
4
4
|
|
|
@@ -9,6 +9,10 @@ export interface TabListProps extends Omit<InlineProps, 'as' | 'height'> {
|
|
|
9
9
|
children: Array<React.ReactElement | null | undefined | false>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function _isReactElement(node: unknown): node is React.ReactElement {
|
|
13
|
+
return Boolean(node)
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
//Limits the width of tabs in tablist
|
|
13
17
|
const CustomInline = styled(Inline)`
|
|
14
18
|
& > div {
|
|
@@ -29,18 +33,22 @@ export const TabList = forwardRef(function TabList(
|
|
|
29
33
|
const {children: childrenProp, ...restProps} = props
|
|
30
34
|
const [focusedIndex, setFocusedIndex] = useState(-1)
|
|
31
35
|
|
|
32
|
-
const children
|
|
36
|
+
const children = useMemo(() => childrenProp.filter(_isReactElement), [childrenProp])
|
|
33
37
|
|
|
34
38
|
const tabs = children.map((child, childIndex) =>
|
|
35
39
|
cloneElement(child, {
|
|
36
40
|
focused: focusedIndex === childIndex,
|
|
37
41
|
key: childIndex,
|
|
38
|
-
onFocus: () =>
|
|
42
|
+
onFocus: () => handleTabFocus(childIndex),
|
|
39
43
|
}),
|
|
40
44
|
)
|
|
41
45
|
|
|
42
46
|
const numTabs = tabs.length
|
|
43
47
|
|
|
48
|
+
const handleTabFocus = useCallback((tabIdx: number) => {
|
|
49
|
+
setFocusedIndex(tabIdx)
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
44
52
|
const handleKeyDown = useCallback(
|
|
45
53
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
46
54
|
if (event.key === 'ArrowLeft') {
|
|
@@ -8,7 +8,6 @@ import {Box} from '../../primitives'
|
|
|
8
8
|
import {Layer} from '../../utils'
|
|
9
9
|
import {Toast} from './toast'
|
|
10
10
|
import {ToastContext} from './toastContext'
|
|
11
|
-
import {generateToastId} from './toastState'
|
|
12
11
|
import {ToastContextValue, ToastParams} from './types'
|
|
13
12
|
|
|
14
13
|
type ToastState = {
|
|
@@ -46,6 +45,8 @@ const ToastContainer = styled.div`
|
|
|
46
45
|
width: 100%;
|
|
47
46
|
`
|
|
48
47
|
|
|
48
|
+
let toastId = 0
|
|
49
|
+
|
|
49
50
|
/**
|
|
50
51
|
* @public
|
|
51
52
|
*/
|
|
@@ -59,20 +60,20 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
|
|
|
59
60
|
() => ({
|
|
60
61
|
initial: {
|
|
61
62
|
opacity: 0,
|
|
62
|
-
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: 0,
|
|
63
|
+
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: 0,
|
|
63
64
|
y: 32,
|
|
64
65
|
scale: 0.25,
|
|
65
66
|
willChange: 'transform',
|
|
66
67
|
},
|
|
67
68
|
animate: {
|
|
68
69
|
opacity: [0, 1, 1],
|
|
69
|
-
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: [0, 0, 1],
|
|
70
|
+
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [0, 0, 1],
|
|
70
71
|
y: 0,
|
|
71
72
|
scale: 1,
|
|
72
73
|
},
|
|
73
74
|
exit: {
|
|
74
75
|
opacity: [1, 1, 0],
|
|
75
|
-
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: [1, 0, 0],
|
|
76
|
+
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [1, 0, 0],
|
|
76
77
|
scale: 0.5,
|
|
77
78
|
transition: prefersReducedMotion ? {duration: 0} : {duration: 0.2},
|
|
78
79
|
},
|
|
@@ -85,7 +86,7 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
|
|
|
85
86
|
// Wrap setState in startTransition to allow React to give input state updates higher priority
|
|
86
87
|
const setState: typeof _setState = (state) => startTransition(() => _setState(state))
|
|
87
88
|
|
|
88
|
-
const id = params.id ||
|
|
89
|
+
const id = params.id || String(toastId++)
|
|
89
90
|
const duration = params.duration || 5000
|
|
90
91
|
|
|
91
92
|
const dismiss = () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ToggleArrowRightIcon} from '@sanity/icons'
|
|
2
2
|
import {ThemeFontWeightKey} from '@sanity/ui/theme'
|
|
3
|
-
import {memo, useCallback, useEffect, useId, useMemo, useRef
|
|
3
|
+
import {memo, useCallback, useEffect, useId, useMemo, useRef} from 'react'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
5
|
import {Box, BoxProps, Flex, Text} from '../../primitives'
|
|
6
6
|
import {
|
|
@@ -64,21 +64,18 @@ export const TreeItem = memo(function TreeItem(
|
|
|
64
64
|
weight,
|
|
65
65
|
...restProps
|
|
66
66
|
} = props
|
|
67
|
-
const
|
|
67
|
+
const rootRef = useRef<HTMLLIElement | null>(null)
|
|
68
68
|
const treeitemRef = useRef<HTMLDivElement | null>(null)
|
|
69
69
|
const tree = useTree()
|
|
70
70
|
const {path, registerItem, setExpanded, setFocusedElement} = tree
|
|
71
71
|
const _id = useId()
|
|
72
72
|
const id = idProp || _id
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return [result, result.join('/')]
|
|
77
|
-
}, [id, path])
|
|
73
|
+
const itemPath = useMemo(() => path.concat([id || '']), [id, path])
|
|
74
|
+
const itemKey = itemPath.join('/')
|
|
78
75
|
const itemState = tree.state[itemKey]
|
|
79
|
-
const focused = tree.focusedElement ===
|
|
76
|
+
const focused = tree.focusedElement === rootRef.current
|
|
80
77
|
const expanded = itemState?.expanded === undefined ? expandedProp : itemState?.expanded || false
|
|
81
|
-
const tabIndex = tree.focusedElement && tree.focusedElement ===
|
|
78
|
+
const tabIndex = tree.focusedElement && tree.focusedElement === rootRef.current ? 0 : -1
|
|
82
79
|
const contextValue = useMemo(
|
|
83
80
|
() => ({...tree, level: tree.level + 1, path: itemPath}),
|
|
84
81
|
[itemPath, tree],
|
|
@@ -97,28 +94,28 @@ export const TreeItem = memo(function TreeItem(
|
|
|
97
94
|
) {
|
|
98
95
|
event.stopPropagation()
|
|
99
96
|
setExpanded(itemKey, !expanded)
|
|
100
|
-
setFocusedElement(
|
|
97
|
+
setFocusedElement(rootRef.current)
|
|
101
98
|
}
|
|
102
99
|
},
|
|
103
|
-
[expanded, itemKey, onClick,
|
|
100
|
+
[expanded, itemKey, onClick, setExpanded, setFocusedElement],
|
|
104
101
|
)
|
|
105
102
|
|
|
106
103
|
const handleKeyDown = useCallback(
|
|
107
104
|
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
108
105
|
if (focused && event.key === 'Enter') {
|
|
109
|
-
const el = treeitemRef.current ||
|
|
106
|
+
const el = treeitemRef.current || rootRef.current
|
|
110
107
|
|
|
111
108
|
el?.click()
|
|
112
109
|
}
|
|
113
110
|
},
|
|
114
|
-
[focused
|
|
111
|
+
[focused],
|
|
115
112
|
)
|
|
116
113
|
|
|
117
114
|
useEffect(() => {
|
|
118
|
-
if (!
|
|
115
|
+
if (!rootRef.current) return
|
|
119
116
|
|
|
120
|
-
return registerItem(
|
|
121
|
-
}, [expanded, itemPath, registerItem,
|
|
117
|
+
return registerItem(rootRef.current, itemPath.join('/'), expanded, selected)
|
|
118
|
+
}, [expanded, itemPath, registerItem, selected])
|
|
122
119
|
|
|
123
120
|
const content = (
|
|
124
121
|
<Flex padding={padding}>
|
|
@@ -157,7 +154,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
157
154
|
data-ui="TreeItem"
|
|
158
155
|
{...restProps}
|
|
159
156
|
onClick={handleClick}
|
|
160
|
-
ref={
|
|
157
|
+
ref={rootRef}
|
|
161
158
|
role="none"
|
|
162
159
|
>
|
|
163
160
|
<TreeItemBox
|
|
@@ -190,7 +187,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
190
187
|
aria-expanded={expanded}
|
|
191
188
|
onClick={handleClick}
|
|
192
189
|
onKeyDown={handleKeyDown}
|
|
193
|
-
ref={
|
|
190
|
+
ref={rootRef}
|
|
194
191
|
role="treeitem"
|
|
195
192
|
tabIndex={tabIndex}
|
|
196
193
|
>
|
package/src/core/constants.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const EMPTY_RECORD: Record<string, never> = {}
|
|
|
13
13
|
/**
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
16
|
-
export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity'
|
|
16
|
+
export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity'
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Shared `framer-motion` variants used by `Popover` and `Tooltip` components.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {useMemo} from 'react'
|
|
2
2
|
import {_getArrayProp} from '../styles'
|
|
3
3
|
|
|
4
4
|
/** @beta */
|
|
@@ -12,19 +12,14 @@ export function useArrayProp<T extends ArrayPropPrimitive = ArrayPropPrimitive>(
|
|
|
12
12
|
val: T | T[] | undefined,
|
|
13
13
|
defaultVal?: T[],
|
|
14
14
|
): T[] {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
JSON.stringify(val ?? defaultVal),
|
|
18
|
-
])
|
|
15
|
+
// JSON.stringify is fast, but it's not faster than useMemo's referencial equality check
|
|
16
|
+
const __perf_hash__ = useMemo(() => JSON.stringify(val ?? defaultVal), [defaultVal, val])
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
return useMemo(
|
|
19
|
+
() => _getArrayProp(val, defaultVal),
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
setCache([_getArrayProp(val, defaultVal), hash])
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return cachedVal
|
|
21
|
+
// Improve performance: Keep object identify for a given hash of the value
|
|
22
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
|
+
[__perf_hash__],
|
|
24
|
+
)
|
|
30
25
|
}
|
|
@@ -11,24 +11,34 @@ export function useMatchMedia(
|
|
|
11
11
|
mediaQueryString: `(${string})`,
|
|
12
12
|
getServerSnapshot?: () => boolean,
|
|
13
13
|
): boolean {
|
|
14
|
-
/**
|
|
15
|
-
* `subscribe` and `getSnapshot` are only called on the client and both need access to the same `matchMedia` instance
|
|
16
|
-
* we don't want to eagerly instantiate it to ensure it's only created when actually used
|
|
17
|
-
*/
|
|
18
|
-
const cachedMatchMedia = useMemo(
|
|
19
|
-
() => (typeof window === 'undefined' ? null : window.matchMedia(mediaQueryString)),
|
|
20
|
-
[mediaQueryString],
|
|
21
|
-
)
|
|
22
14
|
const {subscribe, getSnapshot} = useMemo(() => {
|
|
15
|
+
/**
|
|
16
|
+
* `subscribe` and `getSnapshot` are only called on the client and both need access to the same `matchMedia` instance
|
|
17
|
+
* we don't want to eagerly instantiate it to ensure it's only created when actually used
|
|
18
|
+
*/
|
|
19
|
+
let MEDIA_QUERY_CACHE: MediaQueryList | undefined
|
|
20
|
+
|
|
21
|
+
const getMatchMedia = (): MediaQueryList => {
|
|
22
|
+
if (!MEDIA_QUERY_CACHE) {
|
|
23
|
+
// As this function is only called during `subscribe` and `getSnapshot`, we can assume that the
|
|
24
|
+
// the `window` global is available and we're in a browser environment
|
|
25
|
+
MEDIA_QUERY_CACHE = window.matchMedia(mediaQueryString)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return MEDIA_QUERY_CACHE
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
return {
|
|
24
32
|
subscribe: (onStoreChange: () => void): (() => void) => {
|
|
25
|
-
|
|
33
|
+
const matchMedia = getMatchMedia()
|
|
34
|
+
|
|
35
|
+
matchMedia.addEventListener('change', onStoreChange)
|
|
26
36
|
|
|
27
|
-
return () =>
|
|
37
|
+
return () => matchMedia.removeEventListener('change', onStoreChange)
|
|
28
38
|
},
|
|
29
|
-
getSnapshot: () =>
|
|
39
|
+
getSnapshot: () => getMatchMedia().matches,
|
|
30
40
|
}
|
|
31
|
-
}, [
|
|
41
|
+
}, [mediaQueryString])
|
|
32
42
|
|
|
33
43
|
useDebugValue(mediaQueryString)
|
|
34
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {getTheme_v2} from '@sanity/ui/theme'
|
|
2
|
-
import {Children, cloneElement, forwardRef, isValidElement} from 'react'
|
|
2
|
+
import {Children, cloneElement, forwardRef, isValidElement, useMemo} from 'react'
|
|
3
3
|
import {styled, css} from 'styled-components'
|
|
4
4
|
import {EMPTY_RECORD} from '../../constants'
|
|
5
5
|
import {useArrayProp} from '../../hooks'
|
|
@@ -65,7 +65,10 @@ export const AvatarStack = forwardRef(function AvatarStack(
|
|
|
65
65
|
size: sizeProp = 1,
|
|
66
66
|
...restProps
|
|
67
67
|
} = props
|
|
68
|
-
const children
|
|
68
|
+
const children = useMemo<React.ReactElement[]>(
|
|
69
|
+
() => Children.toArray(childrenProp).filter(isValidElement),
|
|
70
|
+
[childrenProp],
|
|
71
|
+
)
|
|
69
72
|
const maxLength = Math.max(maxLengthProp, 0)
|
|
70
73
|
const size = useArrayProp(sizeProp)
|
|
71
74
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
export default function ButtonStory() {
|
|
15
15
|
const disabled = useBoolean('Disabled', false, 'Props')
|
|
16
|
-
const fontSize = useSelect('Font size', WORKSHOP_TEXT_SIZE_OPTIONS,
|
|
16
|
+
const fontSize = useSelect('Font size', WORKSHOP_TEXT_SIZE_OPTIONS, 1, 'Props')
|
|
17
17
|
const icon = useSelect('Icon', WORKSHOP_ICON_SYMBOL_OPTIONS, 'add-circle', 'Props')
|
|
18
18
|
const iconRight = useSelect('Icon (right)', WORKSHOP_ICON_SYMBOL_OPTIONS, '', 'Props')
|
|
19
19
|
const justify = useSelect('Justify', WORKSHOP_FLEX_JUSTIFY_OPTIONS, 'center', 'Props')
|
|
@@ -9,7 +9,7 @@ import {ButtonMode, ButtonTextAlign, ButtonTone, ButtonWidth, FlexJustify} from
|
|
|
9
9
|
import {Box} from '../box'
|
|
10
10
|
import {Flex} from '../flex'
|
|
11
11
|
import {Spinner} from '../spinner'
|
|
12
|
-
import {Text} from '../text'
|
|
12
|
+
import {Text, TextProps} from '../text'
|
|
13
13
|
import {ResponsivePaddingProps, ResponsiveRadiusProps} from '../types'
|
|
14
14
|
import {buttonBaseStyles, buttonColorStyles} from './styles'
|
|
15
15
|
|
|
@@ -32,6 +32,8 @@ export interface ButtonProps extends ResponsivePaddingProps, ResponsiveRadiusPro
|
|
|
32
32
|
textAlign?: ButtonTextAlign
|
|
33
33
|
muted?: boolean
|
|
34
34
|
text?: React.ReactNode
|
|
35
|
+
textOverflow?: TextProps['textOverflow']
|
|
36
|
+
textWeight?: TextProps['weight']
|
|
35
37
|
tone?: ButtonTone
|
|
36
38
|
type?: 'button' | 'reset' | 'submit'
|
|
37
39
|
width?: ButtonWidth
|
|
@@ -85,6 +87,8 @@ export const Button = forwardRef(function Button(
|
|
|
85
87
|
space: spaceProp = 3,
|
|
86
88
|
text,
|
|
87
89
|
textAlign,
|
|
90
|
+
textOverflow = 'ellipsis',
|
|
91
|
+
textWeight,
|
|
88
92
|
tone = 'default',
|
|
89
93
|
type = 'button',
|
|
90
94
|
muted = false,
|
|
@@ -142,20 +146,21 @@ export const Button = forwardRef(function Button(
|
|
|
142
146
|
<Box as="span" {...boxProps}>
|
|
143
147
|
<Flex as="span" justify={justify} gap={space}>
|
|
144
148
|
{IconComponent && (
|
|
145
|
-
<Text size={fontSize}>
|
|
149
|
+
<Text as="span" size={fontSize} style={{flex: 'none'}}>
|
|
146
150
|
{isValidElement(IconComponent) && IconComponent}
|
|
147
151
|
{isValidElementType(IconComponent) && <IconComponent />}
|
|
148
152
|
</Text>
|
|
149
153
|
)}
|
|
150
154
|
|
|
151
155
|
{text && (
|
|
152
|
-
<Box>
|
|
156
|
+
<Box as="span">
|
|
153
157
|
<Text
|
|
158
|
+
as="span"
|
|
154
159
|
muted={muted}
|
|
155
160
|
align={textAlign}
|
|
156
161
|
size={fontSize}
|
|
157
|
-
textOverflow=
|
|
158
|
-
weight={button.textWeight}
|
|
162
|
+
textOverflow={textOverflow}
|
|
163
|
+
weight={textWeight ?? button.textWeight}
|
|
159
164
|
>
|
|
160
165
|
{text}
|
|
161
166
|
</Text>
|
|
@@ -163,7 +168,7 @@ export const Button = forwardRef(function Button(
|
|
|
163
168
|
)}
|
|
164
169
|
|
|
165
170
|
{IconRightComponent && (
|
|
166
|
-
<Text size={fontSize}>
|
|
171
|
+
<Text as="span" size={fontSize} style={{flex: 'none'}}>
|
|
167
172
|
{isValidElement(IconRightComponent) && IconRightComponent}
|
|
168
173
|
{isValidElementType(IconRightComponent) && <IconRightComponent />}
|
|
169
174
|
</Text>
|
|
@@ -18,6 +18,8 @@ export interface FlexProps
|
|
|
18
18
|
ResponsiveFlexProps,
|
|
19
19
|
ResponsiveFlexItemProps {
|
|
20
20
|
gap?: number | number[]
|
|
21
|
+
gapX?: number | number[]
|
|
22
|
+
gapY?: number | number[]
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const Root = styled(Box)<FlexItemStyleProps & ResponsiveFlexStyleProps>(
|
|
@@ -34,7 +36,7 @@ export const Flex = forwardRef(function Flex(
|
|
|
34
36
|
props: FlexProps & Omit<React.HTMLProps<HTMLDivElement>, 'as' | 'wrap'>,
|
|
35
37
|
ref: React.ForwardedRef<HTMLDivElement>,
|
|
36
38
|
) {
|
|
37
|
-
const {align, as, direction = 'row', gap, justify, wrap, ...restProps} = props
|
|
39
|
+
const {align, as, direction = 'row', gap, gapX, gapY, justify, wrap, ...restProps} = props
|
|
38
40
|
|
|
39
41
|
return (
|
|
40
42
|
<Root
|
|
@@ -43,6 +45,8 @@ export const Flex = forwardRef(function Flex(
|
|
|
43
45
|
$align={useArrayProp(align)}
|
|
44
46
|
$direction={useArrayProp(direction)}
|
|
45
47
|
$gap={useArrayProp(gap)}
|
|
48
|
+
$gapX={useArrayProp(gapX)}
|
|
49
|
+
$gapY={useArrayProp(gapY)}
|
|
46
50
|
$justify={useArrayProp(justify)}
|
|
47
51
|
$wrap={useArrayProp(wrap)}
|
|
48
52
|
forwardedAs={as}
|
|
@@ -26,7 +26,7 @@ export interface HeadingProps {
|
|
|
26
26
|
* Use `textOverflow="ellipsis"` to render text as a single line which is concatenated with a `…` symbol.
|
|
27
27
|
* @beta
|
|
28
28
|
*/
|
|
29
|
-
textOverflow?: 'ellipsis'
|
|
29
|
+
textOverflow?: 'ellipsis' | 'none'
|
|
30
30
|
weight?: ThemeFontWeightKey
|
|
31
31
|
}
|
|
32
32
|
|