@sanity/ui 2.0.0-alpha.2 → 2.0.0-alpha.4
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.
|
|
3
|
+
"version": "2.0.0-alpha.4",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@floating-ui/react-dom": "2.0.0",
|
|
55
|
-
"@sanity/color": "
|
|
55
|
+
"@sanity/color": "3.0.0-beta.3",
|
|
56
56
|
"@sanity/icons": "^2.4.1",
|
|
57
57
|
"csstype": "^3.1.2",
|
|
58
58
|
"framer-motion": "^10.16.2",
|
|
@@ -8,12 +8,13 @@ import {
|
|
|
8
8
|
isHTMLElement,
|
|
9
9
|
} from '../../helpers'
|
|
10
10
|
import {useArrayProp, useClickOutside, useForwardedRef, useGlobalKeyDown} from '../../hooks'
|
|
11
|
-
import {Box, Button, Card, Container, Flex, Text} from '../../primitives'
|
|
11
|
+
import {Box, Button, Card, CardProps, Container, Flex, Text} from '../../primitives'
|
|
12
12
|
import {ResponsivePaddingProps, ResponsiveWidthProps} from '../../primitives/types'
|
|
13
13
|
import {responsivePaddingStyle, ResponsivePaddingStyleProps} from '../../styles/internal'
|
|
14
14
|
import {ThemeColorSchemeKey, useTheme} from '../../theme'
|
|
15
15
|
import {DialogPosition, Radius} from '../../types'
|
|
16
16
|
import {Layer, LayerProps, Portal, useBoundaryElement, useLayer, usePortal} from '../../utils'
|
|
17
|
+
import {BORDER_OFFSET_X} from './constants'
|
|
17
18
|
import {
|
|
18
19
|
dialogStyle,
|
|
19
20
|
responsiveDialogPositionStyle,
|
|
@@ -71,6 +72,15 @@ interface DialogCardProps extends ResponsiveWidthProps {
|
|
|
71
72
|
shadow: number | number[]
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
interface DialogHeaderProps extends CardProps {
|
|
76
|
+
$hasScrolledFromTop: boolean
|
|
77
|
+
$isContentScrollable: boolean
|
|
78
|
+
}
|
|
79
|
+
interface DialogFooterProps extends CardProps {
|
|
80
|
+
$hasScrolledToBottom: boolean
|
|
81
|
+
$isContentScrollable: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
function isTargetWithinScope(
|
|
75
85
|
boundaryElement: HTMLElement | null,
|
|
76
86
|
portalElement: HTMLElement | null,
|
|
@@ -118,7 +128,7 @@ const DialogLayout = styled(Flex)`
|
|
|
118
128
|
width: 100%;
|
|
119
129
|
`
|
|
120
130
|
|
|
121
|
-
const DialogHeader = styled(Card)
|
|
131
|
+
const DialogHeader = styled(Card)<DialogHeaderProps>`
|
|
122
132
|
position: relative;
|
|
123
133
|
z-index: 2;
|
|
124
134
|
|
|
@@ -126,10 +136,13 @@ const DialogHeader = styled(Card)`
|
|
|
126
136
|
content: '';
|
|
127
137
|
display: block;
|
|
128
138
|
position: absolute;
|
|
129
|
-
left:
|
|
130
|
-
right:
|
|
139
|
+
left: ${BORDER_OFFSET_X}px;
|
|
140
|
+
right: ${BORDER_OFFSET_X}px;
|
|
131
141
|
bottom: -1px;
|
|
132
|
-
border-bottom: 1px solid var(--card-
|
|
142
|
+
border-bottom: 1px solid var(--card-border-color);
|
|
143
|
+
width: auto;
|
|
144
|
+
opacity: ${(props) => (props.$isContentScrollable && props.$hasScrolledFromTop ? 1 : 0)};
|
|
145
|
+
transition: opacity 200ms ease-in;
|
|
133
146
|
}
|
|
134
147
|
`
|
|
135
148
|
|
|
@@ -140,10 +153,22 @@ const DialogContent = styled(Box)`
|
|
|
140
153
|
outline: none;
|
|
141
154
|
`
|
|
142
155
|
|
|
143
|
-
const DialogFooter = styled(Box)
|
|
156
|
+
const DialogFooter = styled(Box)<DialogFooterProps>`
|
|
144
157
|
position: relative;
|
|
145
158
|
z-index: 3;
|
|
146
|
-
|
|
159
|
+
|
|
160
|
+
&:before {
|
|
161
|
+
content: '';
|
|
162
|
+
display: block;
|
|
163
|
+
position: absolute;
|
|
164
|
+
left: ${BORDER_OFFSET_X}px;
|
|
165
|
+
right: ${BORDER_OFFSET_X}px;
|
|
166
|
+
top: -1px;
|
|
167
|
+
border-top: 1px solid var(--card-border-color);
|
|
168
|
+
width: auto;
|
|
169
|
+
opacity: ${(props) => (props.$isContentScrollable && !props.$hasScrolledToBottom ? 1 : 0)};
|
|
170
|
+
transition: opacity 200ms ease-in;
|
|
171
|
+
}
|
|
147
172
|
`
|
|
148
173
|
|
|
149
174
|
const DialogCard = forwardRef(function DialogCard(
|
|
@@ -180,6 +205,9 @@ const DialogCard = forwardRef(function DialogCard(
|
|
|
180
205
|
const labelId = `${id}_label`
|
|
181
206
|
const showCloseButton = Boolean(onClose) && hideCloseButton === false
|
|
182
207
|
const showHeader = Boolean(header) || showCloseButton
|
|
208
|
+
const [isContentScrollable, setIsContentScrollable] = useState(false)
|
|
209
|
+
const [hasScrolledFromTop, setHasScrolledFromTop] = useState(false)
|
|
210
|
+
const [hasScrolledToBottom, setHasScrolledToBottom] = useState(false)
|
|
183
211
|
|
|
184
212
|
useEffect(() => {
|
|
185
213
|
if (!autoFocus) return
|
|
@@ -248,16 +276,44 @@ const DialogCard = forwardRef(function DialogCard(
|
|
|
248
276
|
[contentRef],
|
|
249
277
|
)
|
|
250
278
|
|
|
279
|
+
const handleContentUpdate = useCallback(() => {
|
|
280
|
+
if (localContentRef.current) {
|
|
281
|
+
setIsContentScrollable(
|
|
282
|
+
localContentRef.current.clientHeight < localContentRef.current.scrollHeight,
|
|
283
|
+
)
|
|
284
|
+
setHasScrolledFromTop(localContentRef.current.scrollTop > 0)
|
|
285
|
+
setHasScrolledToBottom(
|
|
286
|
+
localContentRef.current.scrollTop >=
|
|
287
|
+
localContentRef.current.scrollHeight - localContentRef.current.clientHeight,
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
}, [])
|
|
291
|
+
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
const resizeObserver = new ResizeObserver(handleContentUpdate)
|
|
294
|
+
|
|
295
|
+
if (localContentRef.current) {
|
|
296
|
+
resizeObserver.observe(localContentRef.current)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return () => {
|
|
300
|
+
resizeObserver.disconnect()
|
|
301
|
+
}
|
|
302
|
+
}, [contentRef, handleContentUpdate])
|
|
303
|
+
|
|
251
304
|
return (
|
|
252
305
|
<DialogContainer data-ui="DialogCard" width={width}>
|
|
253
306
|
<DialogCardRoot radius={radius} ref={setRef} scheme={scheme} shadow={shadow}>
|
|
254
307
|
<DialogLayout direction="column">
|
|
255
308
|
{showHeader && (
|
|
256
|
-
<DialogHeader
|
|
309
|
+
<DialogHeader
|
|
310
|
+
$isContentScrollable={isContentScrollable}
|
|
311
|
+
$hasScrolledFromTop={hasScrolledFromTop}
|
|
312
|
+
>
|
|
257
313
|
<Flex>
|
|
258
314
|
<Box flex={1} padding={4}>
|
|
259
315
|
{header && (
|
|
260
|
-
<Text id={labelId} weight="
|
|
316
|
+
<Text id={labelId} size={1} weight="medium">
|
|
261
317
|
{header}
|
|
262
318
|
</Text>
|
|
263
319
|
)}
|
|
@@ -278,11 +334,18 @@ const DialogCard = forwardRef(function DialogCard(
|
|
|
278
334
|
</DialogHeader>
|
|
279
335
|
)}
|
|
280
336
|
|
|
281
|
-
<DialogContent flex={1} ref={setContentRef} tabIndex={-1}>
|
|
337
|
+
<DialogContent flex={1} ref={setContentRef} tabIndex={-1} onScroll={handleContentUpdate}>
|
|
282
338
|
{children}
|
|
283
339
|
</DialogContent>
|
|
284
340
|
|
|
285
|
-
{footer &&
|
|
341
|
+
{footer && (
|
|
342
|
+
<DialogFooter
|
|
343
|
+
$isContentScrollable={isContentScrollable}
|
|
344
|
+
$hasScrolledToBottom={hasScrolledToBottom}
|
|
345
|
+
>
|
|
346
|
+
{footer}
|
|
347
|
+
</DialogFooter>
|
|
348
|
+
)}
|
|
286
349
|
</DialogLayout>
|
|
287
350
|
</DialogCardRoot>
|
|
288
351
|
</DialogContainer>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* e.g. for positive theme: `{"bg_base": "white", "bg_base_hover": "gray/50", "bg_base_active": "cyan/50"}
|
|
5
5
|
* This is not possible in the previous configuration, as each ThemeColor uses only one tint.
|
|
6
6
|
*/
|
|
7
|
-
import {ColorTintKey,
|
|
7
|
+
import {ColorTintKey, ColorTint, ColorTints, black, hues, white} from '@sanity/color'
|
|
8
8
|
import {ThemeColorName, ThemeColorSchemeKey} from '../lib/theme'
|
|
9
9
|
|
|
10
10
|
export const colorKeys = [
|
|
@@ -27,7 +27,7 @@ export const colorKeys = [
|
|
|
27
27
|
|
|
28
28
|
export type ColorKey = (typeof colorKeys)[number]
|
|
29
29
|
|
|
30
|
-
type ColorTintsDictionary = Record<ColorKey,
|
|
30
|
+
type ColorTintsDictionary = Record<ColorKey, ColorTint | ColorTintKey>
|
|
31
31
|
const defaultTints: Record<ThemeColorSchemeKey, ColorTintsDictionary> = {
|
|
32
32
|
light: {
|
|
33
33
|
text_primary: '900',
|
|
@@ -132,7 +132,7 @@ export const getColorValue = (
|
|
|
132
132
|
dark: boolean,
|
|
133
133
|
tone: ThemeColorName,
|
|
134
134
|
key: ColorKey,
|
|
135
|
-
):
|
|
135
|
+
): ColorTint => {
|
|
136
136
|
const value = colorTints[dark ? 'dark' : 'light'][tone][key]
|
|
137
137
|
|
|
138
138
|
if (typeof value === 'string') {
|