@sanity/ui 2.0.0-alpha.17 → 2.0.0-alpha.19
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 -0
- package/dist/index.esm.js +37 -21
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +37 -21
- package/dist/index.js.map +1 -1
- package/package.json +4 -6
- package/src/primitives/avatar/avatar.tsx +14 -9
- package/src/primitives/avatar/avatarCounter.tsx +3 -3
- package/src/primitives/avatar/styles.ts +1 -1
- package/src/primitives/button/__workshop__/sanityUploadButton.tsx +2 -1
- package/src/primitives/textInput/textInput.tsx +7 -0
- package/src/primitives/tooltip/tooltip.test.tsx +67 -0
- package/src/primitives/tooltip/tooltip.tsx +25 -5
- package/src/styles/input/textInputStyle.ts +11 -6
- package/src/theme/lib/theme/avatar.ts +2 -0
- package/src/theme/studioTheme/theme.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.19",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"test": "jest",
|
|
45
45
|
"test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
|
|
46
46
|
"watch": "pkg watch --strict",
|
|
47
|
-
"workshop:build": "
|
|
48
|
-
"workshop:dev": "
|
|
47
|
+
"workshop:build": "workshop build",
|
|
48
|
+
"workshop:dev": "workshop dev",
|
|
49
49
|
"workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist",
|
|
50
50
|
"storybook": "storybook dev -p 6006",
|
|
51
51
|
"build-storybook": "storybook build"
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@juggle/resize-observer": "^3.4.0",
|
|
69
69
|
"@sanity/pkg-utils": "^2.4.8",
|
|
70
70
|
"@sanity/semantic-release-preset": "^4.1.4",
|
|
71
|
-
"@sanity/ui-workshop": "^
|
|
71
|
+
"@sanity/ui-workshop": "^2.0.0",
|
|
72
72
|
"@storybook/addon-a11y": "^7.4.5",
|
|
73
73
|
"@storybook/addon-docs": "^7.4.5",
|
|
74
74
|
"@storybook/addon-essentials": "^7.4.5",
|
|
@@ -100,8 +100,6 @@
|
|
|
100
100
|
"cypress": "^13.1.0",
|
|
101
101
|
"cypress-real-events": "^1.10.1",
|
|
102
102
|
"cz-conventional-changelog": "^3.3.0",
|
|
103
|
-
"esbuild": "^0.19.2",
|
|
104
|
-
"esbuild-register": "^3.4.2",
|
|
105
103
|
"eslint": "^8.48.0",
|
|
106
104
|
"eslint-config-prettier": "^9.0.0",
|
|
107
105
|
"eslint-plugin-import": "^2.28.1",
|
|
@@ -15,6 +15,8 @@ export interface AvatarProps {
|
|
|
15
15
|
arrowPosition?: AvatarPosition
|
|
16
16
|
as?: React.ElementType | keyof JSX.IntrinsicElements
|
|
17
17
|
color?: ThemeColorSpotKey
|
|
18
|
+
/** @beta */
|
|
19
|
+
hideInnerStroke?: boolean
|
|
18
20
|
initials?: string
|
|
19
21
|
onImageLoadError?: (event: Error) => void
|
|
20
22
|
size?: AvatarSize | AvatarSize[]
|
|
@@ -58,6 +60,7 @@ export const Avatar = forwardRef(function Avatar(
|
|
|
58
60
|
onImageLoadError,
|
|
59
61
|
arrowPosition: arrowPositionProp,
|
|
60
62
|
animateArrowFrom,
|
|
63
|
+
hideInnerStroke,
|
|
61
64
|
status = 'online',
|
|
62
65
|
size: sizeProp = 0,
|
|
63
66
|
...restProps
|
|
@@ -142,13 +145,15 @@ export const Avatar = forwardRef(function Avatar(
|
|
|
142
145
|
</defs>
|
|
143
146
|
|
|
144
147
|
<circle cx={_radius} cy={_radius} r={_radius} fill={`url(#${imageId})`} />
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
{!hideInnerStroke && (
|
|
149
|
+
<BgStroke
|
|
150
|
+
cx={_radius}
|
|
151
|
+
cy={_radius}
|
|
152
|
+
rx={_radius}
|
|
153
|
+
ry={_radius}
|
|
154
|
+
vectorEffect="non-scaling-stroke"
|
|
155
|
+
/>
|
|
156
|
+
)}
|
|
152
157
|
<Stroke
|
|
153
158
|
cx={_radius}
|
|
154
159
|
cy={_radius}
|
|
@@ -163,8 +168,8 @@ export const Avatar = forwardRef(function Avatar(
|
|
|
163
168
|
{(imageFailed || !src) && initials && (
|
|
164
169
|
<>
|
|
165
170
|
<Initials>
|
|
166
|
-
<Text as="span" size={initialsSize}>
|
|
167
|
-
|
|
171
|
+
<Text as="span" size={initialsSize} weight="medium">
|
|
172
|
+
{initials}
|
|
168
173
|
</Text>
|
|
169
174
|
</Initials>
|
|
170
175
|
</>
|
|
@@ -36,7 +36,7 @@ function _avatarCounterBaseStyle(props: ThemeProps) {
|
|
|
36
36
|
background: var(--card-bg-color);
|
|
37
37
|
box-shadow:
|
|
38
38
|
0 0 0 1px var(--card-bg-color),
|
|
39
|
-
inset 0 0 0
|
|
39
|
+
inset 0 0 0 1px var(--card-hairline-hard-color);
|
|
40
40
|
padding: 0 ${rem(theme.sanity.space[2])};
|
|
41
41
|
|
|
42
42
|
&:not([hidden]) {
|
|
@@ -73,8 +73,8 @@ export const AvatarCounter = forwardRef(function AvatarCounter(
|
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
75
|
<Root $size={size} data-ui="AvatarCounter" ref={ref}>
|
|
76
|
-
<Text as="span" size={counterSize}>
|
|
77
|
-
|
|
76
|
+
<Text as="span" size={counterSize} weight="medium">
|
|
77
|
+
{count}
|
|
78
78
|
</Text>
|
|
79
79
|
</Root>
|
|
80
80
|
)
|
|
@@ -4,7 +4,7 @@ import styled from 'styled-components'
|
|
|
4
4
|
|
|
5
5
|
const SanityUploadButton = styled(Button).attrs({forwardedAs: 'label'})`
|
|
6
6
|
& input {
|
|
7
|
-
|
|
7
|
+
appearance: none;
|
|
8
8
|
overflow: hidden;
|
|
9
9
|
overflow: clip;
|
|
10
10
|
top: 0;
|
|
@@ -14,6 +14,7 @@ const SanityUploadButton = styled(Button).attrs({forwardedAs: 'label'})`
|
|
|
14
14
|
opacity: 0;
|
|
15
15
|
position: absolute;
|
|
16
16
|
max-width: 0;
|
|
17
|
+
width: -webkit-fill-available;
|
|
17
18
|
width: stretch;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -70,6 +70,10 @@ export interface TextInputProps {
|
|
|
70
70
|
suffix?: React.ReactNode
|
|
71
71
|
type?: TextInputType
|
|
72
72
|
weight?: ThemeFontWeightKey
|
|
73
|
+
/**
|
|
74
|
+
* @beta
|
|
75
|
+
*/
|
|
76
|
+
unstableDisableFocusRing?: boolean
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
const CLEAR_BUTTON_BOX_STYLE: React.CSSProperties = {zIndex: 2}
|
|
@@ -165,6 +169,7 @@ export const TextInput = forwardRef(function TextInput(
|
|
|
165
169
|
customValidity,
|
|
166
170
|
type = 'text',
|
|
167
171
|
weight,
|
|
172
|
+
unstableDisableFocusRing,
|
|
168
173
|
...restProps
|
|
169
174
|
} = props
|
|
170
175
|
|
|
@@ -221,6 +226,7 @@ export const TextInput = forwardRef(function TextInput(
|
|
|
221
226
|
() => (
|
|
222
227
|
<Presentation
|
|
223
228
|
$hasPrefix={$hasPrefix}
|
|
229
|
+
$unstableDisableFocusRing={unstableDisableFocusRing}
|
|
224
230
|
$hasSuffix={$hasSuffix}
|
|
225
231
|
$radius={radius}
|
|
226
232
|
$scheme={rootTheme.scheme}
|
|
@@ -259,6 +265,7 @@ export const TextInput = forwardRef(function TextInput(
|
|
|
259
265
|
$hasClearButton,
|
|
260
266
|
$hasPrefix,
|
|
261
267
|
$hasSuffix,
|
|
268
|
+
unstableDisableFocusRing,
|
|
262
269
|
],
|
|
263
270
|
)
|
|
264
271
|
|
|
@@ -275,4 +275,71 @@ describe('Tooltip', () => {
|
|
|
275
275
|
jest.clearAllMocks()
|
|
276
276
|
})
|
|
277
277
|
})
|
|
278
|
+
describe('Closing the <Tooltip /> with the Escape key', () => {
|
|
279
|
+
it('Standalone tooltip closes immediately with Escape key', () => {
|
|
280
|
+
const delay = 150
|
|
281
|
+
|
|
282
|
+
jest.useFakeTimers()
|
|
283
|
+
|
|
284
|
+
render(
|
|
285
|
+
<Tooltip
|
|
286
|
+
content={<Text size={1}>{'Tooltip content'}</Text>}
|
|
287
|
+
placement={'top'}
|
|
288
|
+
delay={delay}
|
|
289
|
+
>
|
|
290
|
+
<Button mode="bleed" text="Hover me" />
|
|
291
|
+
</Tooltip>,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const button = screen.getByText('Hover me')
|
|
295
|
+
|
|
296
|
+
// Validate tooltip content is not rendered
|
|
297
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
298
|
+
fireEvent.focus(button)
|
|
299
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
300
|
+
|
|
301
|
+
// Validate tooltip content is rendered
|
|
302
|
+
screen.getByText('Tooltip content')
|
|
303
|
+
|
|
304
|
+
act(() => {
|
|
305
|
+
fireEvent.keyDown(button, {key: 'Escape', code: 'Escape'})
|
|
306
|
+
})
|
|
307
|
+
// Validate tooltip content is not rendered anymore
|
|
308
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
309
|
+
})
|
|
310
|
+
it('With <TooltipDelayGroupProvider /> closes immediately with Escape key', () => {
|
|
311
|
+
const delay = 150
|
|
312
|
+
|
|
313
|
+
jest.useFakeTimers()
|
|
314
|
+
|
|
315
|
+
render(
|
|
316
|
+
<TooltipDelayGroupProvider delay={{close: delay}}>
|
|
317
|
+
<Tooltip
|
|
318
|
+
content={<Text size={1}>{'Tooltip content'}</Text>}
|
|
319
|
+
placement={'top'}
|
|
320
|
+
delay={{close: delay}}
|
|
321
|
+
>
|
|
322
|
+
<Button mode="bleed" text="Hover me" />
|
|
323
|
+
</Tooltip>
|
|
324
|
+
</TooltipDelayGroupProvider>,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
const button = screen.getByText('Hover me')
|
|
328
|
+
|
|
329
|
+
// Validate tooltip content is not rendered
|
|
330
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
331
|
+
fireEvent.focus(button)
|
|
332
|
+
|
|
333
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
334
|
+
|
|
335
|
+
// Validate tooltip content is rendered
|
|
336
|
+
screen.getByText('Tooltip content')
|
|
337
|
+
|
|
338
|
+
act(() => {
|
|
339
|
+
fireEvent.keyDown(button, {key: 'Escape', code: 'Escape'})
|
|
340
|
+
})
|
|
341
|
+
// Validate tooltip content is not rendered anymore
|
|
342
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
343
|
+
})
|
|
344
|
+
})
|
|
278
345
|
})
|
|
@@ -180,23 +180,27 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
180
180
|
const closeDelay = isInsideGroup ? delayGroupContext.closeDelay : closeDelayProp
|
|
181
181
|
|
|
182
182
|
const handleIsOpenChange = useCallback(
|
|
183
|
-
(open: boolean) => {
|
|
183
|
+
(open: boolean, immediate?: boolean) => {
|
|
184
184
|
if (isInsideGroup) {
|
|
185
185
|
// When it's inside a group, the open or close status will be handled by the group.
|
|
186
186
|
if (open) {
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const groupedOpenDelay = immediate ? 0 : openDelay
|
|
188
|
+
|
|
189
|
+
delayGroupContext.setIsGroupActive(open, groupedOpenDelay)
|
|
190
|
+
delayGroupContext.setOpenTooltipId(tooltipId, groupedOpenDelay)
|
|
189
191
|
} else {
|
|
190
192
|
const minimumGroupDeactivateDelay = 200 // We should provide some delay to allow the user to reach the next tooltip.
|
|
191
193
|
const groupDeactivateDelay =
|
|
192
194
|
closeDelay > minimumGroupDeactivateDelay ? closeDelay : minimumGroupDeactivateDelay
|
|
193
195
|
|
|
194
196
|
delayGroupContext.setIsGroupActive(open, groupDeactivateDelay)
|
|
195
|
-
delayGroupContext.setOpenTooltipId(null, closeDelay)
|
|
197
|
+
delayGroupContext.setOpenTooltipId(null, immediate ? 0 : closeDelay)
|
|
196
198
|
}
|
|
197
199
|
} else {
|
|
200
|
+
const standaloneDelay = immediate ? 0 : open ? openDelay : closeDelay
|
|
201
|
+
|
|
198
202
|
// When it's not inside a group, the open or close status will be handled by the tooltip itself.
|
|
199
|
-
setIsOpen(open,
|
|
203
|
+
setIsOpen(open, standaloneDelay)
|
|
200
204
|
}
|
|
201
205
|
},
|
|
202
206
|
[isInsideGroup, delayGroupContext, openDelay, tooltipId, closeDelay, setIsOpen],
|
|
@@ -246,6 +250,22 @@ export const Tooltip = forwardRef(function Tooltip(
|
|
|
246
250
|
// Update reference
|
|
247
251
|
useEffect(() => refs.setReference(referenceElement), [referenceElement, refs])
|
|
248
252
|
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
// If the user clicks on escape key, close the tooltip.
|
|
255
|
+
if (!showTooltip) return
|
|
256
|
+
|
|
257
|
+
function handleWindowKeyDown(event: KeyboardEvent) {
|
|
258
|
+
if (event.key === 'Escape') {
|
|
259
|
+
handleIsOpenChange(false, true)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
window.addEventListener('keydown', handleWindowKeyDown)
|
|
264
|
+
|
|
265
|
+
return () => {
|
|
266
|
+
window.removeEventListener('keydown', handleWindowKeyDown)
|
|
267
|
+
}
|
|
268
|
+
}, [handleIsOpenChange, showTooltip])
|
|
249
269
|
const setArrow = useCallback(
|
|
250
270
|
(arrowEl: HTMLDivElement | null) => {
|
|
251
271
|
arrowRef.current = arrowEl
|
|
@@ -24,6 +24,7 @@ export interface TextInputRepresentationStyleProps {
|
|
|
24
24
|
$hasSuffix?: boolean
|
|
25
25
|
$scheme: ThemeColorSchemeKey
|
|
26
26
|
$tone: CardTone
|
|
27
|
+
$unstableDisableFocusRing?: boolean
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const ROOT_STYLE = css`
|
|
@@ -129,7 +130,7 @@ export function textInputFontSizeStyle(props: TextInputInputStyleProps & ThemePr
|
|
|
129
130
|
export function textInputRepresentationStyle(
|
|
130
131
|
props: TextInputRepresentationStyleProps & ThemeProps,
|
|
131
132
|
): ReturnType<typeof css> {
|
|
132
|
-
const {$hasPrefix, $hasSuffix, $scheme, $tone, theme} = props
|
|
133
|
+
const {$hasPrefix, $hasSuffix, $scheme, $tone, $unstableDisableFocusRing, theme} = props
|
|
133
134
|
const {input} = theme.sanity
|
|
134
135
|
const {focusRing} = input.text
|
|
135
136
|
const color = theme.sanity.color.input
|
|
@@ -182,14 +183,18 @@ export function textInputRepresentationStyle(
|
|
|
182
183
|
/* focused */
|
|
183
184
|
*:not(:disabled):focus + & {
|
|
184
185
|
&[data-border] {
|
|
185
|
-
--input-box-shadow: ${
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
--input-box-shadow: ${$unstableDisableFocusRing
|
|
187
|
+
? undefined
|
|
188
|
+
: focusRingStyle({
|
|
189
|
+
border: {color: color.default.enabled.border, width: input.border.width},
|
|
190
|
+
focusRing,
|
|
191
|
+
})};
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
&:not([data-border]) {
|
|
192
|
-
--input-box-shadow: ${
|
|
195
|
+
--input-box-shadow: ${$unstableDisableFocusRing
|
|
196
|
+
? undefined
|
|
197
|
+
: focusRingStyle({focusRing})};
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
|
|
@@ -8,11 +8,11 @@ import {fonts} from './fonts'
|
|
|
8
8
|
export const studioTheme: RootTheme = {
|
|
9
9
|
avatar: {
|
|
10
10
|
sizes: [
|
|
11
|
-
{distance: -3, size:
|
|
12
|
-
{distance: -6, size:
|
|
13
|
-
{distance: -9, size:
|
|
11
|
+
{distance: -3, size: 23},
|
|
12
|
+
{distance: -6, size: 33},
|
|
13
|
+
{distance: -9, size: 53},
|
|
14
14
|
],
|
|
15
|
-
focusRing: {offset:
|
|
15
|
+
focusRing: {offset: 0, width: 1},
|
|
16
16
|
},
|
|
17
17
|
button: {
|
|
18
18
|
textWeight: 'medium',
|