@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "2.0.0-alpha.17",
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": "node -r esbuild-register -r tsconfig-paths/register scripts/build",
48
- "workshop:dev": "node -r esbuild-register -r tsconfig-paths/register scripts/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": "^1.2.11",
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
- <BgStroke
146
- cx={_radius}
147
- cy={_radius}
148
- rx={_radius}
149
- ry={_radius}
150
- vectorEffect="non-scaling-stroke"
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
- <strong>{initials}</strong>
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 1.5px var(--card-hairline-hard-color);
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
- <strong>{count}</strong>
76
+ <Text as="span" size={counterSize} weight="medium">
77
+ {count}
78
78
  </Text>
79
79
  </Root>
80
80
  )
@@ -152,7 +152,7 @@ function avatarBgStrokeStyle(): CSSObject {
152
152
 
153
153
  function avatarStrokeStyle(): CSSObject {
154
154
  return {
155
- strokeWidth: '3px',
155
+ strokeWidth: '2px',
156
156
 
157
157
  '[data-status="editing"] &': {
158
158
  strokeDasharray: '2 4',
@@ -4,7 +4,7 @@ import styled from 'styled-components'
4
4
 
5
5
  const SanityUploadButton = styled(Button).attrs({forwardedAs: 'label'})`
6
6
  & input {
7
- -webkit-appearance: none;
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
- delayGroupContext.setIsGroupActive(open, openDelay)
188
- delayGroupContext.setOpenTooltipId(tooltipId, openDelay)
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, open ? openDelay : closeDelay)
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: ${focusRingStyle({
186
- border: {color: color.default.enabled.border, width: input.border.width},
187
- focusRing,
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: ${focusRingStyle({focusRing})};
195
+ --input-box-shadow: ${$unstableDisableFocusRing
196
+ ? undefined
197
+ : focusRingStyle({focusRing})};
193
198
  }
194
199
  }
195
200
 
@@ -5,7 +5,9 @@ import {FocusRing} from '../../types'
5
5
  */
6
6
  export interface ThemeAvatar {
7
7
  sizes: {
8
+ /** Distance between avatars in an <AvatarStack> component (px) */
8
9
  distance: number
10
+ /** Diameter (px) */
9
11
  size: number
10
12
  }[]
11
13
  focusRing: FocusRing
@@ -8,11 +8,11 @@ import {fonts} from './fonts'
8
8
  export const studioTheme: RootTheme = {
9
9
  avatar: {
10
10
  sizes: [
11
- {distance: -3, size: 25},
12
- {distance: -6, size: 35},
13
- {distance: -9, size: 55},
11
+ {distance: -3, size: 23},
12
+ {distance: -6, size: 33},
13
+ {distance: -9, size: 53},
14
14
  ],
15
- focusRing: {offset: 1, width: 1},
15
+ focusRing: {offset: 0, width: 1},
16
16
  },
17
17
  button: {
18
18
  textWeight: 'medium',