@sanity/ui 3.0.11 → 3.0.12-canary.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "3.0.11",
3
+ "version": "3.0.12-canary.0",
4
4
  "keywords": [
5
5
  "sanity",
6
6
  "ui",
@@ -60,32 +60,6 @@
60
60
  "exports",
61
61
  "src"
62
62
  ],
63
- "scripts": {
64
- "build": "run-s clean pkg:build pkg:check figma:pkg:build",
65
- "clean": "rimraf .workshop dist",
66
- "commit": "cz",
67
- "cypress:dev": "run-p dev cypress:open",
68
- "cypress:open": "cypress open",
69
- "cypress:run": "cypress run",
70
- "dev": "run-p storybook:dev workshop:dev",
71
- "figma:pkg:build": "cd figma && pnpm build",
72
- "format": "prettier --write --cache --ignore-unknown .",
73
- "lint": "eslint --cache .",
74
- "prepack": "pnpm build",
75
- "pkg:build": "pkg build --strict",
76
- "pkg:check": "pkg --strict",
77
- "prepare": "husky install",
78
- "release": "semantic-release",
79
- "storybook:build": "storybook build",
80
- "storybook:dev": "storybook dev -p 6006",
81
- "test": "jest",
82
- "test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
83
- "ts:check": "tsc",
84
- "watch": "pkg watch --strict",
85
- "workshop:build": "workshop build",
86
- "workshop:dev": "workshop dev",
87
- "workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
88
- },
89
63
  "commitlint": {
90
64
  "extends": [
91
65
  "@commitlint/config-conventional"
@@ -210,25 +184,37 @@
210
184
  "react-is": "^18 || >=19.0.0-0",
211
185
  "styled-components": "^5.2 || ^6"
212
186
  },
213
- "packageManager": "pnpm@9.15.9",
214
187
  "engines": {
215
188
  "node": ">=20.19 <22 || >=22.12"
216
189
  },
217
190
  "publishConfig": {
218
191
  "access": "public"
219
192
  },
220
- "pnpm": {
221
- "overrides": {
222
- "@types/react": "$@types/react",
223
- "@types/react-dom": "$@types/react-dom",
224
- "@types/react-is": "$@types/react-is",
225
- "conventional-changelog-conventionalcommits": ">= 8.0.0",
226
- "react": "$react",
227
- "react-dom": "$react-dom",
228
- "react-is": "$react-is"
229
- }
230
- },
231
193
  "esm.sh": {
232
194
  "bundle": false
195
+ },
196
+ "scripts": {
197
+ "build": "run-s clean pkg:build pkg:check figma:pkg:build",
198
+ "clean": "rimraf .workshop dist",
199
+ "commit": "cz",
200
+ "cypress:dev": "run-p dev cypress:open",
201
+ "cypress:open": "cypress open",
202
+ "cypress:run": "cypress run",
203
+ "dev": "run-p storybook:dev workshop:dev",
204
+ "figma:pkg:build": "cd figma && pnpm build",
205
+ "format": "prettier --write --cache --ignore-unknown .",
206
+ "lint": "eslint .",
207
+ "pkg:build": "pkg build --strict",
208
+ "pkg:check": "pkg --strict",
209
+ "release": "semantic-release",
210
+ "storybook:build": "storybook build",
211
+ "storybook:dev": "storybook dev -p 6006",
212
+ "test": "jest",
213
+ "test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
214
+ "ts:check": "tsc",
215
+ "watch": "pkg watch --strict",
216
+ "workshop:build": "workshop build",
217
+ "workshop:dev": "workshop dev",
218
+ "workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
233
219
  }
234
- }
220
+ }
@@ -42,44 +42,16 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
42
42
 
43
43
  const rawItems = useMemo(() => Children.toArray(children).filter(isValidElement), [children])
44
44
 
45
- const items = useMemo(() => {
46
- const len = rawItems.length
47
-
48
- if (maxLength && len > maxLength) {
49
- const beforeLength = Math.ceil(maxLength / 2)
50
- const afterLength = Math.floor(maxLength / 2)
51
-
52
- return [
53
- ...rawItems.slice(0, beforeLength - 1),
54
- <Popover
55
- constrainSize
56
- content={
57
- <Stack as="ol" overflow="auto" padding={space} space={space}>
58
- {rawItems.slice(beforeLength - 1, len - afterLength)}
59
- </Stack>
60
- }
61
- key="button"
62
- open={open}
63
- placement="top"
64
- portal
65
- ref={popoverElementRef}
66
- >
67
- <ExpandButton
68
- fontSize={1}
69
- mode="bleed"
70
- onClick={open ? collapse : expand}
71
- padding={1}
72
- ref={expandElementRef}
73
- selected={open}
74
- text="…"
75
- />
76
- </Popover>,
77
- ...rawItems.slice(len - afterLength),
78
- ]
79
- }
80
-
81
- return rawItems
82
- }, [collapse, expand, maxLength, open, rawItems, space])
45
+ const items = useItems({
46
+ collapse,
47
+ expand,
48
+ expandElementRef,
49
+ maxLength,
50
+ open,
51
+ popoverElementRef,
52
+ rawItems,
53
+ space,
54
+ })
83
55
 
84
56
  return (
85
57
  <StyledBreadcrumbs data-ui="Breadcrumbs" {...restProps} ref={ref}>
@@ -97,3 +69,60 @@ export const Breadcrumbs = forwardRef(function Breadcrumbs(
97
69
  )
98
70
  })
99
71
  Breadcrumbs.displayName = 'ForwardRef(Breadcrumbs)'
72
+
73
+ function useItems({
74
+ collapse,
75
+ expand,
76
+ expandElementRef,
77
+ maxLength,
78
+ open,
79
+ popoverElementRef,
80
+ rawItems,
81
+ space,
82
+ }: {
83
+ collapse: () => void
84
+ expand: () => void
85
+ expandElementRef: React.RefObject<HTMLButtonElement | null>
86
+ maxLength: number | undefined
87
+ open: boolean
88
+ popoverElementRef: React.RefObject<HTMLDivElement | null>
89
+ rawItems: React.ReactElement<unknown, string | React.JSXElementConstructor<any>>[]
90
+ space: number[]
91
+ }) {
92
+ const len = rawItems.length
93
+
94
+ if (maxLength && len > maxLength) {
95
+ const beforeLength = Math.ceil(maxLength / 2)
96
+ const afterLength = Math.floor(maxLength / 2)
97
+
98
+ return [
99
+ ...rawItems.slice(0, beforeLength - 1),
100
+ <Popover
101
+ constrainSize
102
+ content={
103
+ <Stack as="ol" overflow="auto" padding={space} space={space}>
104
+ {rawItems.slice(beforeLength - 1, len - afterLength)}
105
+ </Stack>
106
+ }
107
+ key="button"
108
+ open={open}
109
+ placement="top"
110
+ portal
111
+ ref={popoverElementRef}
112
+ >
113
+ <ExpandButton
114
+ fontSize={1}
115
+ mode="bleed"
116
+ onClick={open ? collapse : expand}
117
+ padding={1}
118
+ ref={expandElementRef}
119
+ selected={open}
120
+ text="…"
121
+ />
122
+ </Popover>,
123
+ ...rawItems.slice(len - afterLength),
124
+ ]
125
+ }
126
+
127
+ return rawItems
128
+ }
@@ -1,23 +1,28 @@
1
- import {detectOverflow, Elements, Middleware} from '@floating-ui/react-dom'
1
+ import {detectOverflow, Middleware} from '@floating-ui/react-dom'
2
2
 
3
3
  import {PopoverMargins} from '../../../types'
4
4
 
5
- export interface SizeMiddlewareApplyOptions {
6
- availableWidth: number
7
- availableHeight: number
8
- elements: Elements
9
- referenceWidth: number
10
- }
11
-
12
5
  export function size(options: {
13
- apply: (args: SizeMiddlewareApplyOptions) => void
14
6
  boundaryElement?: HTMLElement | null
15
7
  constrainSize: boolean
16
8
  margins: PopoverMargins
17
9
  matchReferenceWidth?: boolean
10
+ maxWidthRef: React.RefObject<number | undefined>
18
11
  padding?: number
12
+ referenceWidthRef: React.RefObject<number | undefined>
13
+ setReferenceWidth: (referenceWidth: number) => void
14
+ widthRef: React.RefObject<number | undefined>
19
15
  }): Middleware {
20
- const {apply, margins, padding = 0} = options
16
+ const {
17
+ constrainSize,
18
+ margins,
19
+ matchReferenceWidth,
20
+ maxWidthRef,
21
+ padding = 0,
22
+ referenceWidthRef,
23
+ setReferenceWidth,
24
+ widthRef,
25
+ } = options
21
26
 
22
27
  return {
23
28
  name: '@sanity/ui/size',
@@ -61,12 +66,23 @@ export function size(options: {
61
66
 
62
67
  // IMPORTANT – APPLY ELEMENT STYLES HERE
63
68
  // Elements need to be resized BEFORE the `platform.getDimensions` call below
64
- apply({
65
- availableWidth: maxWidth - margins[1] - margins[3],
66
- availableHeight: maxHeight - margins[0] - margins[2],
67
- elements,
68
- referenceWidth: reference.width - margins[1] - margins[3],
69
- })
69
+ const availableWidth = maxWidth - margins[1] - margins[3]
70
+ const availableHeight = maxHeight - margins[0] - margins[2]
71
+ const referenceWidth = reference.width - margins[1] - margins[3]
72
+ referenceWidthRef.current = referenceWidth
73
+ setReferenceWidth(referenceWidth)
74
+
75
+ if (matchReferenceWidth) {
76
+ elements.floating.style.width = `${referenceWidth}px`
77
+ } else if (widthRef.current !== undefined) {
78
+ elements.floating.style.width = `${widthRef.current}px`
79
+ }
80
+
81
+ if (constrainSize) {
82
+ elements.floating.style.maxWidth = `${Math.min(availableWidth, maxWidthRef.current ?? Infinity)}px`
83
+
84
+ elements.floating.style.maxHeight = `${availableHeight}px`
85
+ }
70
86
 
71
87
  const nextDimensions = await platform.getDimensions(elements.floating)
72
88
 
@@ -16,13 +16,13 @@ import {
16
16
  cloneElement,
17
17
  forwardRef,
18
18
  memo,
19
- MutableRefObject,
20
- RefCallback,
19
+ type Ref,
21
20
  useCallback,
22
21
  useEffect,
23
22
  useImperativeHandle,
24
23
  useMemo,
25
24
  useRef,
25
+ useState,
26
26
  } from 'react'
27
27
 
28
28
  import {useArrayProp, useElementSize, useMediaIndex, usePrefersReducedMotion} from '../../hooks'
@@ -119,9 +119,7 @@ export interface PopoverProps
119
119
  scheme?: ThemeColorSchemeKey
120
120
  tone?: CardTone
121
121
  /** @beta */
122
- updateRef?:
123
- | MutableRefObject<PopoverUpdateCallback | undefined>
124
- | RefCallback<PopoverUpdateCallback | undefined>
122
+ updateRef?: Ref<PopoverUpdateCallback | undefined>
125
123
  width?: PopoverWidth | PopoverWidth[]
126
124
  }
127
125
 
@@ -149,15 +147,14 @@ export const Popover = memo(
149
147
  __unstable_margins: margins = DEFAULT_POPOVER_MARGINS,
150
148
  animate: _animate = false,
151
149
  arrow: arrowProp = false,
152
- boundaryElement = boundaryElementContext.element,
150
+ boundaryElement: _boundaryElement,
153
151
  children: childProp,
154
152
  constrainSize = false,
155
153
  content,
156
154
  disabled,
157
- fallbackPlacements = props.fallbackPlacements ??
158
- DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom'],
155
+ fallbackPlacements: _fallbackPlacements,
159
156
  matchReferenceWidth,
160
- floatingBoundary = props.boundaryElement ?? boundaryElementContext.element,
157
+ floatingBoundary: _floatingBoundary,
161
158
  modal,
162
159
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
163
160
  onActivate,
@@ -169,16 +166,24 @@ export const Popover = memo(
169
166
  portal,
170
167
  preventOverflow = true,
171
168
  radius: radiusProp = 3,
172
- referenceBoundary = props.boundaryElement ?? boundaryElementContext.element,
169
+ referenceBoundary: _referenceBoundary,
173
170
  referenceElement,
174
171
  scheme,
175
172
  shadow: shadowProp = 3,
176
173
  tone = 'inherit',
177
174
  width: widthProp = 'auto',
178
- zOffset: zOffsetProp = layer.popover.zOffset,
175
+ zOffset: _zOffsetProp,
179
176
  updateRef,
180
177
  ...restProps
181
178
  } = props
179
+ const boundaryElement = _boundaryElement ?? boundaryElementContext?.element
180
+ const fallbackPlacements =
181
+ _fallbackPlacements ?? DEFAULT_FALLBACK_PLACEMENTS[props.placement ?? 'bottom']
182
+ const floatingBoundary =
183
+ _floatingBoundary ?? props.boundaryElement ?? boundaryElementContext.element
184
+ const referenceBoundary =
185
+ _referenceBoundary ?? props.boundaryElement ?? boundaryElementContext.element
186
+ const zOffsetProp = _zOffsetProp ?? layer.popover.zOffset
182
187
  const prefersReducedMotion = usePrefersReducedMotion()
183
188
  const animate = prefersReducedMotion ? false : _animate
184
189
  const boundarySize = useElementSize(boundaryElement)?.border
@@ -247,117 +252,26 @@ export const Popover = memo(
247
252
  }
248
253
  }, [width, matchReferenceWidth, maxWidth, open])
249
254
 
250
- const middleware = useMemo(() => {
251
- const ret: Middleware[] = []
252
-
253
- // Flip the floating element when leaving the boundary box
254
- if (constrainSize || preventOverflow) {
255
- if (placementStrategy === 'autoPlacement') {
256
- ret.push(
257
- autoPlacement({
258
- allowedPlacements: [placementProp].concat(fallbackPlacements),
259
- }),
260
- )
261
- } else {
262
- ret.push(
263
- flip({
264
- boundary: floatingBoundary || undefined,
265
- fallbackPlacements,
266
- padding: DEFAULT_POPOVER_PADDING,
267
- rootBoundary,
268
- }),
269
- )
270
- }
271
- }
272
-
273
- // Define distance between reference and floating element
274
- ret.push(offset({mainAxis: DEFAULT_POPOVER_DISTANCE}))
275
-
276
- // Track sizes
277
- if (constrainSize || matchReferenceWidth) {
278
- ret.push(
279
- size({
280
- apply({availableWidth, availableHeight, elements, referenceWidth}) {
281
- // not fresh, so use refs
282
-
283
- referenceWidthRef.current = referenceWidth
284
-
285
- const _currentWidth = widthRef.current
286
- const _maxWidth = maxWidthRef.current
287
-
288
- if (matchReferenceWidth) {
289
- elements.floating.style.width = `${referenceWidth}px`
290
- } else if (_currentWidth !== undefined) {
291
- elements.floating.style.width = `${_currentWidth}px`
292
- }
293
-
294
- if (constrainSize) {
295
- elements.floating.style.maxWidth = `${Math.min(
296
- availableWidth,
297
- _maxWidth ?? Infinity,
298
- )}px`
299
-
300
- elements.floating.style.maxHeight = `${availableHeight}px`
301
- }
302
- },
303
- boundaryElement: floatingBoundary || undefined,
304
- constrainSize,
305
- margins,
306
- matchReferenceWidth,
307
- padding: DEFAULT_POPOVER_PADDING,
308
- }),
309
- )
310
- }
311
-
312
- // Shift the popover so its sits within the boundary element
313
- if (preventOverflow) {
314
- ret.push(
315
- shift({
316
- boundary: floatingBoundary || undefined,
317
- rootBoundary,
318
- padding: DEFAULT_POPOVER_PADDING,
319
- }),
320
- )
321
- }
322
-
323
- // Place arrow
324
- if (arrowProp) {
325
- ret.push(
326
- arrow({
327
- element: arrowRef,
328
- padding: DEFAULT_POPOVER_PADDING,
329
- }),
330
- )
331
- }
332
-
333
- // Determine the origin to scale from.
334
- // Must be placed after `@sanity/ui/size` and `shift` middleware.
335
- if (animate) {
336
- ret.push(origin)
337
- }
338
-
339
- ret.push(
340
- hide({
341
- boundary: referenceBoundary || undefined,
342
- padding: DEFAULT_POPOVER_PADDING,
343
- strategy: 'referenceHidden',
344
- }),
345
- )
346
-
347
- return ret
348
- }, [
255
+ const [referenceWidth, setReferenceWidth] = useState<number | undefined>(undefined)
256
+ const middleware = useMiddleware({
349
257
  animate,
350
258
  arrowProp,
259
+ arrowRef,
351
260
  constrainSize,
352
261
  fallbackPlacements,
353
- placementProp,
354
- placementStrategy,
355
262
  floatingBoundary,
356
263
  margins,
357
264
  matchReferenceWidth,
265
+ maxWidthRef,
266
+ placementProp,
267
+ placementStrategy,
358
268
  preventOverflow,
359
269
  referenceBoundary,
360
- ])
270
+ referenceWidthRef,
271
+ rootBoundary,
272
+ setReferenceWidth,
273
+ widthRef,
274
+ })
361
275
 
362
276
  const {x, y, middlewareData, placement, refs, strategy, update} = useFloating({
363
277
  middleware,
@@ -390,20 +304,9 @@ export const Popover = memo(
390
304
  [refs],
391
305
  )
392
306
 
393
- const setReference = useCallback(
394
- (node: HTMLElement | null) => {
395
- refs.setReference(node)
396
-
397
- const childRef = getElementRef(childProp as any)
398
-
399
- if (typeof childRef === 'function') {
400
- childRef(node)
401
- } else if (childRef) {
402
- childRef.current = node
403
- }
404
- },
405
- [childProp, refs],
406
- )
307
+ // If there's a child then we need to set the reference element to the cloned child ref
308
+ // and if child changes we make sure to update or remove the reference element.
309
+ useImperativeHandle(childProp ? getElementRef(childProp) : null, () => refs.reference.current)
407
310
 
408
311
  const child = useMemo(() => {
409
312
  // If a reference element is defined, we don't need to clone the child
@@ -411,18 +314,10 @@ export const Popover = memo(
411
314
 
412
315
  if (!childProp) return null
413
316
 
414
- return cloneElement(childProp, {ref: setReference})
415
- }, [childProp, referenceElement, setReference])
317
+ return cloneElement(childProp, {ref: refs.setReference})
318
+ }, [childProp, referenceElement, refs.setReference])
416
319
 
417
- useEffect(() => {
418
- if (updateRef) {
419
- if (typeof updateRef === 'function') {
420
- updateRef(update)
421
- } else if (updateRef) {
422
- updateRef.current = update
423
- }
424
- }
425
- }, [update, updateRef])
320
+ useImperativeHandle(updateRef, () => update, [update])
426
321
 
427
322
  if (disabled) {
428
323
  return childProp || <></>
@@ -453,7 +348,7 @@ export const Popover = memo(
453
348
  originY={originY}
454
349
  strategy={strategy}
455
350
  tone={tone}
456
- width={matchReferenceWidth ? referenceWidthRef.current : width}
351
+ width={matchReferenceWidth ? referenceWidth : width}
457
352
  x={x}
458
353
  y={y}
459
354
  >
@@ -482,3 +377,140 @@ export const Popover = memo(
482
377
  }),
483
378
  )
484
379
  Popover.displayName = 'Memo(ForwardRef(Popover))'
380
+
381
+ function useMiddleware({
382
+ animate,
383
+ arrowProp,
384
+ arrowRef,
385
+ constrainSize,
386
+ fallbackPlacements,
387
+ floatingBoundary,
388
+ margins,
389
+ matchReferenceWidth,
390
+ maxWidthRef,
391
+ placementProp,
392
+ placementStrategy,
393
+ preventOverflow,
394
+ referenceBoundary,
395
+ referenceWidthRef,
396
+ rootBoundary,
397
+ setReferenceWidth,
398
+ widthRef,
399
+ }: {
400
+ animate: boolean
401
+ arrowProp: boolean
402
+ arrowRef: React.RefObject<HTMLDivElement | null>
403
+ constrainSize: boolean
404
+ fallbackPlacements: Placement[]
405
+ floatingBoundary: HTMLElement | null
406
+ margins: PopoverMargins
407
+ matchReferenceWidth: boolean | undefined
408
+ maxWidthRef: React.RefObject<number | undefined>
409
+ placementProp: Placement
410
+ placementStrategy: 'flip' | 'autoPlacement'
411
+ preventOverflow: boolean
412
+ referenceBoundary: HTMLElement | null
413
+ referenceWidthRef: React.RefObject<number | undefined>
414
+ rootBoundary: RootBoundary
415
+ setReferenceWidth: (referenceWidth: number) => void
416
+ widthRef: React.RefObject<number | undefined>
417
+ }) {
418
+ return useMemo(() => {
419
+ const ret: Middleware[] = []
420
+
421
+ // Flip the floating element when leaving the boundary box
422
+ if (constrainSize || preventOverflow) {
423
+ if (placementStrategy === 'autoPlacement') {
424
+ ret.push(
425
+ autoPlacement({
426
+ allowedPlacements: [placementProp].concat(fallbackPlacements),
427
+ }),
428
+ )
429
+ } else {
430
+ ret.push(
431
+ flip({
432
+ boundary: floatingBoundary || undefined,
433
+ fallbackPlacements,
434
+ padding: DEFAULT_POPOVER_PADDING,
435
+ rootBoundary,
436
+ }),
437
+ )
438
+ }
439
+ }
440
+
441
+ // Define distance between reference and floating element
442
+ ret.push(offset({mainAxis: DEFAULT_POPOVER_DISTANCE}))
443
+
444
+ // Track sizes
445
+ if (constrainSize || matchReferenceWidth) {
446
+ ret.push(
447
+ size({
448
+ boundaryElement: floatingBoundary || undefined,
449
+ constrainSize,
450
+ margins,
451
+ matchReferenceWidth,
452
+ maxWidthRef,
453
+ padding: DEFAULT_POPOVER_PADDING,
454
+ referenceWidthRef,
455
+ setReferenceWidth,
456
+ widthRef,
457
+ }),
458
+ )
459
+ }
460
+
461
+ // Shift the popover so its sits within the boundary element
462
+ if (preventOverflow) {
463
+ ret.push(
464
+ shift({
465
+ boundary: floatingBoundary || undefined,
466
+ rootBoundary,
467
+ padding: DEFAULT_POPOVER_PADDING,
468
+ }),
469
+ )
470
+ }
471
+
472
+ // Place arrow
473
+ if (arrowProp) {
474
+ ret.push(
475
+ arrow({
476
+ element: arrowRef,
477
+ padding: DEFAULT_POPOVER_PADDING,
478
+ }),
479
+ )
480
+ }
481
+
482
+ // Determine the origin to scale from.
483
+ // Must be placed after `@sanity/ui/size` and `shift` middleware.
484
+ if (animate) {
485
+ ret.push(origin)
486
+ }
487
+
488
+ ret.push(
489
+ hide({
490
+ boundary: referenceBoundary || undefined,
491
+ padding: DEFAULT_POPOVER_PADDING,
492
+ strategy: 'referenceHidden',
493
+ }),
494
+ )
495
+
496
+ return ret
497
+ }, [
498
+ animate,
499
+ arrowProp,
500
+ arrowRef,
501
+ constrainSize,
502
+ fallbackPlacements,
503
+ floatingBoundary,
504
+ margins,
505
+ matchReferenceWidth,
506
+ maxWidthRef,
507
+ placementProp,
508
+ placementStrategy,
509
+ preventOverflow,
510
+ referenceBoundary,
511
+ referenceWidthRef,
512
+ rootBoundary,
513
+ setReferenceWidth,
514
+ widthRef,
515
+ ])
516
+ }