@sanity/ui 2.0.0-alpha.24 → 2.0.0-alpha.26

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.24",
3
+ "version": "2.0.0-alpha.26",
4
4
  "sideEffects": false,
5
5
  "types": "./dist/index.d.ts",
6
6
  "source": "./src/index.ts",
@@ -16,6 +16,8 @@ import {DialogPosition, Radius} from '../../types'
16
16
  import {Layer, LayerProps, Portal, useBoundaryElement, useLayer, usePortal} from '../../utils'
17
17
  import {BORDER_OFFSET_X} from './constants'
18
18
  import {
19
+ animationDialogStyle,
20
+ AnimationDialogStyleProps,
19
21
  dialogStyle,
20
22
  responsiveDialogPositionStyle,
21
23
  ResponsiveDialogPositionStyleProps,
@@ -48,6 +50,13 @@ export interface DialogProps extends ResponsivePaddingProps, ResponsiveWidthProp
48
50
  position?: DialogPosition | DialogPosition[]
49
51
  scheme?: ThemeColorSchemeKey
50
52
  zOffset?: number | number[]
53
+ /**
54
+ * Whether the dialog should animate in on mount.
55
+ *
56
+ * @beta
57
+ * @defaultValue false
58
+ */
59
+ animate?: boolean
51
60
  }
52
61
 
53
62
  interface DialogCardProps extends ResponsiveWidthProps {
@@ -94,11 +103,9 @@ function isTargetWithinScope(
94
103
  )
95
104
  }
96
105
 
97
- const Root = styled(Layer)<ResponsiveDialogPositionStyleProps & ResponsivePaddingStyleProps>(
98
- responsivePaddingStyle,
99
- dialogStyle,
100
- responsiveDialogPositionStyle,
101
- )
106
+ const Root = styled(Layer)<
107
+ ResponsiveDialogPositionStyleProps & ResponsivePaddingStyleProps & AnimationDialogStyleProps
108
+ >(responsivePaddingStyle, dialogStyle, responsiveDialogPositionStyle, animationDialogStyle)
102
109
 
103
110
  const DialogContainer = styled(Container)`
104
111
  &:not([hidden]) {
@@ -382,6 +389,7 @@ export const Dialog = forwardRef(function Dialog(
382
389
  scheme,
383
390
  width: widthProp = 0,
384
391
  zOffset: zOffsetProp = dialog.zOffset || theme.sanity.layer?.dialog.zOffset,
392
+ animate = false,
385
393
  ...restProps
386
394
  } = props
387
395
  const portal = usePortal()
@@ -470,6 +478,7 @@ export const Dialog = forwardRef(function Dialog(
470
478
  ref={ref}
471
479
  role="dialog"
472
480
  zOffset={zOffset}
481
+ animate={animate}
473
482
  >
474
483
  <div ref={preDivRef} tabIndex={0} />
475
484
  <DialogCard
@@ -1,3 +1,4 @@
1
+ import {css} from 'styled-components'
1
2
  import {_responsive, ThemeProps} from '../../styles'
2
3
  import {DialogPosition} from '../../types'
3
4
  import {CSSObject} from '../../types/styled'
@@ -36,3 +37,41 @@ export function responsiveDialogPositionStyle(
36
37
 
37
38
  return _responsive(media, props.$position, (position) => ({'&&': {position}}))
38
39
  }
40
+
41
+ /**
42
+ * @internal
43
+ */
44
+ export interface AnimationDialogStyleProps {
45
+ animate: boolean
46
+ }
47
+
48
+ export function animationDialogStyle(props: AnimationDialogStyleProps): ReturnType<typeof css> {
49
+ if (!props.animate) return css``
50
+
51
+ return css`
52
+ @keyframes zoomIn {
53
+ from {
54
+ opacity: 0;
55
+ transform: scale(0.95);
56
+ }
57
+ to {
58
+ opacity: 1;
59
+ transform: scale(1);
60
+ }
61
+ }
62
+ @keyframes fadeIn {
63
+ from {
64
+ opacity: 0;
65
+ }
66
+ to {
67
+ opacity: 1;
68
+ }
69
+ }
70
+
71
+ animation: fadeIn 200ms ease-out;
72
+ // Animates the dialog card.
73
+ & > [data-ui='DialogCard'] {
74
+ animation: zoomIn 200ms ease-out;
75
+ }
76
+ `
77
+ }
@@ -0,0 +1,15 @@
1
+ export function ConditionalWrapper({
2
+ children,
3
+ condition,
4
+ wrapper,
5
+ }: {
6
+ children: React.ReactNode
7
+ condition: boolean
8
+ wrapper: (children: React.ReactNode) => React.ReactNode
9
+ }): React.ReactNode {
10
+ if (!condition) {
11
+ return children
12
+ }
13
+
14
+ return wrapper(children)
15
+ }
@@ -3,16 +3,16 @@ import {Placement} from '@floating-ui/react-dom'
3
3
  export const DEFAULT_TOOLTIP_DISTANCE = 4
4
4
  export const DEFAULT_TOOLTIP_PADDING = 4
5
5
  export const DEFAULT_FALLBACK_PLACEMENTS: Record<Placement, Placement[]> = {
6
- top: ['bottom', 'left', 'right'],
7
- 'top-start': ['bottom-start', 'left-start', 'right-start'],
8
- 'top-end': ['bottom-end', 'left-end', 'right-end'],
9
- bottom: ['top', 'left', 'right'],
10
- 'bottom-start': ['top-start', 'left-start', 'right-start'],
11
- 'bottom-end': ['top-end', 'left-end', 'right-end'],
12
- left: ['right', 'top', 'bottom'],
13
- 'left-start': ['right-start', 'top-start', 'bottom-start'],
14
- 'left-end': ['right-end', 'top-end', 'bottom-end'],
15
- right: ['left', 'top', 'bottom'],
16
- 'right-start': ['left-start', 'top-start', 'bottom-start'],
17
- 'right-end': ['left-end', 'top-end', 'bottom-end'],
6
+ top: ['top-end', 'top-start', 'bottom', 'left', 'right'],
7
+ 'top-start': ['top', 'top-end', 'bottom-start', 'left-start', 'right-start'],
8
+ 'top-end': ['top', 'top-start', 'bottom-end', 'left-end', 'right-end'],
9
+ bottom: ['bottom-end', 'bottom-start', 'top', 'left', 'right'],
10
+ 'bottom-start': ['bottom', 'bottom-end', 'top-start', 'left-start', 'right-start'],
11
+ 'bottom-end': ['bottom', 'bottom-start', 'top-end', 'left-end', 'right-end'],
12
+ left: ['left-end', 'left-start', 'right', 'top', 'bottom'],
13
+ 'left-start': ['left', 'left-end', 'right-start', 'top-start', 'bottom-start'],
14
+ 'left-end': ['left', 'left-start', 'right-end', 'top-end', 'bottom-end'],
15
+ right: ['right-end', 'right-start', 'left', 'top', 'bottom'],
16
+ 'right-start': ['right', 'right-end', 'left-start', 'top-start', 'bottom-start'],
17
+ 'right-end': ['right', 'right-start', 'left-end', 'top-end', 'bottom-end'],
18
18
  }
@@ -9,6 +9,7 @@ import {
9
9
  Middleware,
10
10
  RootBoundary,
11
11
  } from '@floating-ui/react-dom'
12
+ import {AnimatePresence, motion} from 'framer-motion'
12
13
  import {
13
14
  cloneElement,
14
15
  forwardRef,
@@ -30,6 +31,7 @@ import {Placement} from '../../types'
30
31
  import {Layer, LayerProps, Portal, useBoundaryElement, usePortal} from '../../utils'
31
32
  import {Card} from '../card'
32
33
  import {Delay} from '../types'
34
+ import {ConditionalWrapper} from './conditionalWrapper'
33
35
  import {
34
36
  DEFAULT_FALLBACK_PLACEMENTS,
35
37
  DEFAULT_TOOLTIP_DISTANCE,
@@ -57,13 +59,23 @@ export interface TooltipProps extends Omit<LayerProps, 'as'> {
57
59
  scheme?: ThemeColorSchemeKey
58
60
  shadow?: number | number[]
59
61
  /**
60
- * @public Adds a delay to open or close the tooltip. Defaults to 0.
62
+ * Adds a delay to open or close the tooltip.
61
63
  *
62
64
  * If only a `number` is passed, it will be used for both opening and closing.
63
65
  *
64
66
  * If an object `{open: number; close:number}` is passed, it can be used to set different delays for each action.
67
+ *
68
+ * @public
69
+ * @defaultValue 0
65
70
  */
66
71
  delay?: Delay
72
+ /**
73
+ * Whether the tooltip should animate in and out.
74
+ *
75
+ * @beta
76
+ * @defaultValue false
77
+ */
78
+ animate?: boolean
67
79
  }
68
80
 
69
81
  const Root = styled(Layer)<{$maxWidth: number}>`
@@ -97,6 +109,7 @@ export const Tooltip = forwardRef(function Tooltip(
97
109
  shadow = 2,
98
110
  zOffset = theme.sanity.layer?.tooltip.zOffset,
99
111
  delay,
112
+ animate = false,
100
113
  ...restProps
101
114
  } = props
102
115
  const fallbackPlacements = useArrayProp(fallbackPlacementsProp)
@@ -383,35 +396,53 @@ export const Tooltip = forwardRef(function Tooltip(
383
396
  zOffset={zOffset}
384
397
  $maxWidth={tooltipWidth}
385
398
  >
386
- <Card
387
- data-ui="Tooltip__card"
388
- data-placement={placement}
389
- padding={padding}
390
- radius={3}
391
- scheme={scheme}
392
- shadow={shadow}
399
+ <ConditionalWrapper
400
+ condition={animate} // Add animation wrapper if it should animate
401
+ wrapper={(children) => (
402
+ <motion.div
403
+ initial={{opacity: 0.5, scale: 0.95}}
404
+ animate={{opacity: 1, scale: 1}}
405
+ exit={{opacity: 0, scale: 0.95, transition: {duration: 0.4, type: 'spring'}}}
406
+ >
407
+ {children}
408
+ </motion.div>
409
+ )}
393
410
  >
394
- {content}
395
- {arrowProp && <TooltipArrow ref={setArrow} style={arrowStyle} />}
396
- </Card>
411
+ <Card
412
+ data-ui="Tooltip__card"
413
+ data-placement={placement}
414
+ padding={padding}
415
+ radius={3}
416
+ scheme={scheme}
417
+ shadow={shadow}
418
+ >
419
+ {content}
420
+ {arrowProp && <TooltipArrow ref={setArrow} style={arrowStyle} />}
421
+ </Card>
422
+ </ConditionalWrapper>
397
423
  </Root>
398
424
  )
399
425
 
400
426
  return (
401
427
  <>
402
428
  {child}
403
-
404
- {showTooltip && (
405
- <>
406
- {portalProp ? (
407
- <Portal __unstable_name={typeof portalProp === 'string' ? portalProp : undefined}>
408
- {root}
409
- </Portal>
410
- ) : (
411
- root
412
- )}
413
- </>
414
- )}
429
+ <ConditionalWrapper
430
+ condition={animate}
431
+ wrapper={(children) => <AnimatePresence>{children}</AnimatePresence>} // Add AnimatePresence if it should animate
432
+ >
433
+ {showTooltip && (
434
+ <ConditionalWrapper
435
+ condition={!!portalProp} // Add portal if portalProp is set
436
+ wrapper={(children) => (
437
+ <Portal __unstable_name={typeof portalProp === 'string' ? portalProp : undefined}>
438
+ {children}
439
+ </Portal>
440
+ )}
441
+ >
442
+ {root}
443
+ </ConditionalWrapper>
444
+ )}
445
+ </ConditionalWrapper>
415
446
  </>
416
447
  )
417
448
  })
@@ -10,11 +10,13 @@ import {TooltipDelayGroupContextValue} from './types'
10
10
  export interface TooltipDelayGroupProviderProps {
11
11
  children?: React.ReactNode
12
12
  /**
13
- * @public Handles the delays to open or close a tooltip inside a group
13
+ * Handles the delays to open or close a tooltip inside a group
14
14
  *
15
15
  * If only a `number` is passed, it will be used for both opening and closing.
16
16
  *
17
17
  * If an object `{open: number; close:number}` is passed, it can be used to set different delays for each action.
18
+ *
19
+ * @public
18
20
  */
19
21
  delay: Delay
20
22
  }