@sanity/ui 2.14.4 → 2.15.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/_visual-editing.js +60 -42
- package/dist/_chunks-cjs/_visual-editing.js.map +1 -1
- package/dist/_chunks-es/_visual-editing.mjs +60 -42
- package/dist/_chunks-es/_visual-editing.mjs.map +1 -1
- package/dist/_legacy/_visual-editing.esm.js +60 -42
- package/dist/_legacy/_visual-editing.esm.js.map +1 -1
- package/dist/index.d.mts +32 -6
- package/dist/index.d.ts +32 -6
- package/dist/index.esm.js +194 -180
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +192 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +194 -180
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -21
- package/src/core/components/toast/styles.ts +64 -52
- package/src/core/components/toast/toast.tsx +123 -34
- package/src/core/components/toast/toastLayer.tsx +50 -0
- package/src/core/components/toast/toastProvider.tsx +56 -142
- package/src/core/components/toast/types.ts +6 -0
- package/src/core/constants.ts +42 -29
- package/src/core/primitives/popover/popoverCard.tsx +20 -8
- package/src/core/primitives/tooltip/tooltipCard.tsx +7 -6
|
@@ -1,195 +1,109 @@
|
|
|
1
|
-
import {AnimatePresence
|
|
2
|
-
import {
|
|
3
|
-
import {styled} from 'styled-components'
|
|
4
|
-
import {POPOVER_MOTION_CONTENT_OPACITY_PROPERTY} from '../../constants'
|
|
1
|
+
import {AnimatePresence} from 'framer-motion'
|
|
2
|
+
import {startTransition, useMemo, useState} from 'react'
|
|
5
3
|
import {useMounted} from '../../hooks/useMounted'
|
|
6
|
-
import {
|
|
7
|
-
import {Box} from '../../primitives'
|
|
8
|
-
import {Layer} from '../../utils'
|
|
4
|
+
import {LayerProvider} from '../../utils'
|
|
9
5
|
import {Toast} from './toast'
|
|
10
6
|
import {ToastContext} from './toastContext'
|
|
7
|
+
import {ToastLayer, type ToastLayerProps} from './toastLayer'
|
|
11
8
|
import {generateToastId} from './toastState'
|
|
12
9
|
import {ToastContextValue, ToastParams} from './types'
|
|
13
10
|
|
|
14
11
|
type ToastState = {
|
|
15
12
|
dismiss: () => void
|
|
16
13
|
id: string
|
|
14
|
+
updatedAt: number
|
|
17
15
|
params: ToastParams
|
|
18
16
|
}[]
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* @public
|
|
22
20
|
*/
|
|
23
|
-
export interface ToastProviderProps {
|
|
21
|
+
export interface ToastProviderProps extends Omit<ToastLayerProps, 'children'> {
|
|
24
22
|
children?: React.ReactNode
|
|
25
|
-
padding?: number | number[]
|
|
26
|
-
paddingX?: number | number[]
|
|
27
|
-
paddingY?: number | number[]
|
|
28
23
|
zOffset?: number | number[]
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
const StyledToastProvider = styled(Layer)`
|
|
32
|
-
position: fixed;
|
|
33
|
-
top: 0;
|
|
34
|
-
left: 0;
|
|
35
|
-
right: 0;
|
|
36
|
-
bottom: 0;
|
|
37
|
-
pointer-events: none;
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
const ToastContainer = styled.div`
|
|
41
|
-
box-sizing: border-box;
|
|
42
|
-
position: absolute;
|
|
43
|
-
right: 0;
|
|
44
|
-
bottom: 0;
|
|
45
|
-
max-width: 420px;
|
|
46
|
-
width: 100%;
|
|
47
|
-
`
|
|
48
|
-
|
|
49
26
|
/**
|
|
50
27
|
* @public
|
|
51
28
|
*/
|
|
52
29
|
export function ToastProvider(props: ToastProviderProps): React.JSX.Element {
|
|
53
|
-
const {children, padding
|
|
54
|
-
const [state,
|
|
55
|
-
const toastsRef = useRef<{[key: string]: {timeoutId: NodeJS.Timeout}}>({})
|
|
30
|
+
const {children, padding, paddingX, paddingY, gap, zOffset = 1} = props
|
|
31
|
+
const [state, setState] = useState<ToastState>([])
|
|
56
32
|
const mounted = useMounted()
|
|
57
|
-
const prefersReducedMotion = usePrefersReducedMotion()
|
|
58
|
-
const variants = useMemo<Variants>(
|
|
59
|
-
() => ({
|
|
60
|
-
/**
|
|
61
|
-
* These variants makes use of special timing, by using a negative opacity as a starting position,
|
|
62
|
-
* as well as double opacity as the end position.
|
|
63
|
-
* The purpose of this is to make the tooltip/popover container appear before the content, and when exiting
|
|
64
|
-
* we want the content to disappear faster than the container.
|
|
65
|
-
*/
|
|
66
|
-
initial: {
|
|
67
|
-
opacity: 0,
|
|
68
|
-
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: -1,
|
|
69
|
-
y: 32,
|
|
70
|
-
scale: 0.25,
|
|
71
|
-
willChange: 'transform',
|
|
72
|
-
},
|
|
73
|
-
animate: {
|
|
74
|
-
opacity: 2,
|
|
75
|
-
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: 1,
|
|
76
|
-
y: 0,
|
|
77
|
-
scale: 1,
|
|
78
|
-
},
|
|
79
|
-
exit: {
|
|
80
|
-
opacity: 0,
|
|
81
|
-
[POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: -1,
|
|
82
|
-
scale: 0.5,
|
|
83
|
-
},
|
|
84
|
-
transition: {duration: prefersReducedMotion ? 0 : 0.2},
|
|
85
|
-
}),
|
|
86
|
-
[prefersReducedMotion],
|
|
87
|
-
)
|
|
88
33
|
|
|
89
34
|
const value: ToastContextValue = useMemo(() => {
|
|
90
35
|
const push = (params: ToastParams) => {
|
|
91
|
-
// Wrap setState in startTransition to allow React to give input state updates higher priority
|
|
92
|
-
const setState: typeof _setState = (state) => startTransition(() => _setState(state))
|
|
93
|
-
|
|
94
36
|
const id = params.id || generateToastId()
|
|
95
37
|
const duration = params.duration || 5000
|
|
96
38
|
|
|
97
|
-
|
|
98
|
-
const timeoutId = toastsRef.current[id]?.timeoutId
|
|
99
|
-
|
|
39
|
+
startTransition(() => {
|
|
100
40
|
setState((prevState): ToastState => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return
|
|
41
|
+
/**
|
|
42
|
+
* Backwards compatibility for `sanity` patterns workaround a lack of programatically dismissible toasts.
|
|
43
|
+
* It uses a super short duration that closes the toast immediately in previous versions of `@sanity/ui`.
|
|
44
|
+
* We interpret this as a request to dismiss the toast immediately, and remove it from the state right away.
|
|
45
|
+
* Even once we support programatic dismissal we'll need to keep this for backwards compatibility with v2 and v1.
|
|
46
|
+
*/
|
|
47
|
+
if (duration === 0.01) {
|
|
48
|
+
return prevState.filter((toast) => toast.id !== id)
|
|
109
49
|
}
|
|
110
50
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Creates a function to dismiss this specific toast.
|
|
53
|
+
* This function will be passed to the Toast component
|
|
54
|
+
* and called either on close button click or after duration.
|
|
55
|
+
*/
|
|
56
|
+
const dismiss = () =>
|
|
57
|
+
startTransition(() =>
|
|
58
|
+
setState((currentState) => currentState.filter((toast) => toast.id !== id)),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create updated state by:
|
|
63
|
+
* 1. Removing any existing toast with the same ID (prevents duplicates)
|
|
64
|
+
* 2. Adding the new toast with its dismiss handler
|
|
65
|
+
* 3. Updates the `updatedAt` timestamp, which resets progress bar count downs.
|
|
66
|
+
*/
|
|
67
|
+
return [
|
|
68
|
+
...prevState.filter((toast) => toast.id !== id),
|
|
124
69
|
{
|
|
125
70
|
dismiss,
|
|
126
71
|
id,
|
|
72
|
+
updatedAt: Date.now(),
|
|
127
73
|
params: {...params, duration},
|
|
128
74
|
},
|
|
129
|
-
]
|
|
75
|
+
]
|
|
76
|
+
})
|
|
130
77
|
})
|
|
131
78
|
|
|
132
|
-
if (toastsRef.current[id]) {
|
|
133
|
-
clearTimeout(toastsRef.current[id].timeoutId)
|
|
134
|
-
delete toastsRef.current[id]
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
toastsRef.current[id] = {timeoutId: setTimeout(dismiss, duration)}
|
|
138
|
-
|
|
139
79
|
return id
|
|
140
80
|
}
|
|
141
81
|
|
|
142
82
|
return {version: 0.0, push}
|
|
143
83
|
}, [])
|
|
144
84
|
|
|
145
|
-
// clear timeouts on unmount
|
|
146
|
-
useEffect(
|
|
147
|
-
() => () => {
|
|
148
|
-
for (const {timeoutId} of Object.values(toastsRef.current)) {
|
|
149
|
-
clearTimeout(timeoutId)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
toastsRef.current = {}
|
|
153
|
-
},
|
|
154
|
-
[],
|
|
155
|
-
)
|
|
156
|
-
|
|
157
85
|
return (
|
|
158
86
|
<ToastContext.Provider value={value}>
|
|
159
87
|
{children}
|
|
160
88
|
{mounted && (
|
|
161
|
-
<
|
|
162
|
-
<
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<Toast
|
|
180
|
-
closable={params.closable}
|
|
181
|
-
description={params.description}
|
|
182
|
-
onClose={dismiss}
|
|
183
|
-
status={params.status}
|
|
184
|
-
title={params.title}
|
|
185
|
-
duration={params.duration}
|
|
186
|
-
/>
|
|
187
|
-
</motion.div>
|
|
188
|
-
))}
|
|
189
|
-
</AnimatePresence>
|
|
190
|
-
</Box>
|
|
191
|
-
</ToastContainer>
|
|
192
|
-
</StyledToastProvider>
|
|
89
|
+
<LayerProvider zOffset={zOffset}>
|
|
90
|
+
<ToastLayer padding={padding} paddingX={paddingX} paddingY={paddingY} gap={gap}>
|
|
91
|
+
<AnimatePresence initial={false} mode="popLayout">
|
|
92
|
+
{state.map(({dismiss, id, params, updatedAt}) => (
|
|
93
|
+
<Toast
|
|
94
|
+
key={id}
|
|
95
|
+
closable={params.closable}
|
|
96
|
+
description={params.description}
|
|
97
|
+
onClose={dismiss}
|
|
98
|
+
status={params.status}
|
|
99
|
+
title={params.title}
|
|
100
|
+
duration={params.duration}
|
|
101
|
+
updatedAt={updatedAt}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</AnimatePresence>
|
|
105
|
+
</ToastLayer>
|
|
106
|
+
</LayerProvider>
|
|
193
107
|
)}
|
|
194
108
|
</ToastContext.Provider>
|
|
195
109
|
)
|
|
@@ -16,5 +16,11 @@ export interface ToastParams {
|
|
|
16
16
|
*/
|
|
17
17
|
export interface ToastContextValue {
|
|
18
18
|
version: 0.0
|
|
19
|
+
/**
|
|
20
|
+
* Creates or updates a toast notification.
|
|
21
|
+
* If a toast with the same ID already exists, it will be updated.
|
|
22
|
+
* If an ID is not provided, a random one will be generated.
|
|
23
|
+
* The returned ID can be used to programatically update a toast.
|
|
24
|
+
*/
|
|
19
25
|
push: (params: ToastParams) => string
|
|
20
26
|
}
|
package/src/core/constants.ts
CHANGED
|
@@ -10,47 +10,60 @@ export const EMPTY_ARRAY: never[] = []
|
|
|
10
10
|
*/
|
|
11
11
|
export const EMPTY_RECORD: Record<string, never> = {}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity' as string
|
|
13
|
+
const POPOVER_MOTION_DURATION = 0.2
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
16
|
* Shared `framer-motion` variants used by `Popover` and `Tooltip` components.
|
|
20
17
|
* @internal
|
|
21
18
|
*/
|
|
22
19
|
export const POPOVER_MOTION_PROPS: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
card: {
|
|
21
|
+
initial: Variant
|
|
22
|
+
hidden: Variant
|
|
23
|
+
visible: Variant
|
|
24
|
+
scaleIn: Variant
|
|
25
|
+
scaleOut: Variant
|
|
26
|
+
}
|
|
27
|
+
children: {
|
|
28
|
+
hidden: Variant
|
|
29
|
+
visible: Variant
|
|
30
|
+
}
|
|
26
31
|
transition: Transition
|
|
27
32
|
} = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
card: {
|
|
34
|
+
initial: {
|
|
35
|
+
scale: 0.97,
|
|
36
|
+
willChange: 'transform',
|
|
37
|
+
},
|
|
38
|
+
hidden: {
|
|
39
|
+
opacity: 0,
|
|
40
|
+
},
|
|
41
|
+
visible: {
|
|
42
|
+
opacity: 1,
|
|
43
|
+
transition: {
|
|
44
|
+
when: 'beforeChildren',
|
|
45
|
+
duration: POPOVER_MOTION_DURATION / 2,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
scaleIn: {
|
|
49
|
+
scale: 1,
|
|
50
|
+
},
|
|
51
|
+
scaleOut: {
|
|
52
|
+
scale: 0.97,
|
|
53
|
+
},
|
|
45
54
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
children: {
|
|
56
|
+
hidden: {
|
|
57
|
+
opacity: 0,
|
|
58
|
+
},
|
|
59
|
+
visible: {
|
|
60
|
+
opacity: 1,
|
|
61
|
+
},
|
|
50
62
|
},
|
|
51
63
|
transition: {
|
|
52
|
-
duration: 0.4,
|
|
53
64
|
type: 'spring',
|
|
65
|
+
visualDuration: POPOVER_MOTION_DURATION,
|
|
66
|
+
bounce: 0.25,
|
|
54
67
|
},
|
|
55
68
|
}
|
|
56
69
|
|
|
@@ -3,7 +3,7 @@ import {ThemeColorSchemeKey} from '@sanity/ui/theme'
|
|
|
3
3
|
import {type MotionProps, motion} from 'framer-motion'
|
|
4
4
|
import React, {CSSProperties, forwardRef, memo, useMemo} from 'react'
|
|
5
5
|
import {styled} from 'styled-components'
|
|
6
|
-
import {
|
|
6
|
+
import {POPOVER_MOTION_PROPS} from '../../constants'
|
|
7
7
|
import {BoxOverflow, CardTone, Placement, PopoverMargins, Radius} from '../../types'
|
|
8
8
|
import {Arrow, useLayer} from '../../utils'
|
|
9
9
|
import {Card, CardProps} from '../card'
|
|
@@ -22,10 +22,11 @@ const MotionCard = styled(motion.create(Card))`
|
|
|
22
22
|
flex-direction: column;
|
|
23
23
|
width: max-content;
|
|
24
24
|
min-width: min-content;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
will-change: transform;
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
const MotionFlex = styled(motion.create(Flex))`
|
|
29
|
+
will-change: opacity;
|
|
29
30
|
`
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -131,13 +132,24 @@ export const PopoverCard = memo(
|
|
|
131
132
|
sizing="border"
|
|
132
133
|
style={rootStyle}
|
|
133
134
|
tone={tone}
|
|
134
|
-
{
|
|
135
|
+
variants={POPOVER_MOTION_PROPS.card}
|
|
136
|
+
transition={POPOVER_MOTION_PROPS.transition}
|
|
137
|
+
initial={animate ? ['hidden', 'initial'] : undefined}
|
|
138
|
+
animate={animate ? ['visible', 'scaleIn'] : undefined}
|
|
139
|
+
exit={animate ? ['hidden', 'scaleOut'] : undefined}
|
|
135
140
|
>
|
|
136
|
-
<
|
|
141
|
+
<MotionFlex
|
|
142
|
+
data-ui="Popover__wrapper"
|
|
143
|
+
direction="column"
|
|
144
|
+
flex={1}
|
|
145
|
+
overflow={overflow}
|
|
146
|
+
variants={POPOVER_MOTION_PROPS.children}
|
|
147
|
+
transition={POPOVER_MOTION_PROPS.transition}
|
|
148
|
+
>
|
|
137
149
|
<Flex direction="column" flex={1} padding={padding}>
|
|
138
150
|
{children}
|
|
139
151
|
</Flex>
|
|
140
|
-
</
|
|
152
|
+
</MotionFlex>
|
|
141
153
|
|
|
142
154
|
{arrow && (
|
|
143
155
|
<Arrow
|
|
@@ -2,7 +2,7 @@ import {ThemeColorSchemeKey} from '@sanity/ui/theme'
|
|
|
2
2
|
import {type MotionProps, motion} from 'framer-motion'
|
|
3
3
|
import React, {CSSProperties, forwardRef, memo, useMemo} from 'react'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
|
-
import {
|
|
5
|
+
import {POPOVER_MOTION_PROPS} from '../../constants'
|
|
6
6
|
import {Placement, Radius} from '../../types'
|
|
7
7
|
import {Arrow} from '../../utils'
|
|
8
8
|
import {Card, CardProps} from '../card'
|
|
@@ -13,10 +13,7 @@ import {
|
|
|
13
13
|
} from './constants'
|
|
14
14
|
|
|
15
15
|
const MotionCard = styled(motion.create(Card))`
|
|
16
|
-
|
|
17
|
-
opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1);
|
|
18
|
-
will-change: opacity;
|
|
19
|
-
}
|
|
16
|
+
will-change: transform;
|
|
20
17
|
`
|
|
21
18
|
|
|
22
19
|
/**
|
|
@@ -89,7 +86,11 @@ export const TooltipCard = memo(
|
|
|
89
86
|
scheme={scheme}
|
|
90
87
|
shadow={shadow}
|
|
91
88
|
style={rootStyle}
|
|
92
|
-
{
|
|
89
|
+
variants={POPOVER_MOTION_PROPS.card}
|
|
90
|
+
transition={POPOVER_MOTION_PROPS.transition}
|
|
91
|
+
initial={animate ? ['hidden', 'initial'] : undefined}
|
|
92
|
+
animate={animate ? ['visible', 'scaleIn'] : undefined}
|
|
93
|
+
exit={animate ? ['hidden', 'scaleOut'] : undefined}
|
|
93
94
|
>
|
|
94
95
|
{children}
|
|
95
96
|
|