@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.2",
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": "^2.2.5",
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",
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Left/right border offsets for header + footer components (in px).
3
+ */
4
+ export const BORDER_OFFSET_X: number = 12
@@ -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: 0;
130
- right: 0;
139
+ left: ${BORDER_OFFSET_X}px;
140
+ right: ${BORDER_OFFSET_X}px;
131
141
  bottom: -1px;
132
- border-bottom: 1px solid var(--card-hairline-soft-color);
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
- border-top: 1px solid var(--card-hairline-soft-color);
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="semibold">
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 && <DialogFooter>{footer}</DialogFooter>}
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, ColorTints, ColorValue, black, hues, white} from '@sanity/color'
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, ColorTintKey | ColorValue>
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
- ): ColorValue => {
135
+ ): ColorTint => {
136
136
  const value = colorTints[dark ? 'dark' : 'light'][tone][key]
137
137
 
138
138
  if (typeof value === 'string') {