@sanity/ui 5.0.0-alpha.3 → 5.0.0-alpha.5

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": "5.0.0-alpha.3",
3
+ "version": "5.0.0-alpha.5",
4
4
  "description": "Sanity UI Library",
5
5
  "keywords": [
6
6
  "components",
@@ -15,6 +15,7 @@
15
15
  @import './label/label.css';
16
16
  @import './link/link.css';
17
17
  @import './list/list.css';
18
+ @import './modal/modal.css';
18
19
  @import './press-area/press-area.css';
19
20
  @import './radio/radio.css';
20
21
  @import './spinner/spinner.css';
@@ -0,0 +1,144 @@
1
+ import {CloseIcon} from '@sanity/icons'
2
+ import clsx from 'clsx'
3
+ import {type ComponentProps, useEffect, useId, useRef} from 'react'
4
+
5
+ import {getProps} from '../../utils/getProps'
6
+ import {suffixClassName} from '../../utils/suffixClassName'
7
+ import {Box} from '../box/Box'
8
+ import {Flex} from '../flex/Flex'
9
+ import {Heading} from '../heading/Heading'
10
+ import {IconButton} from '../icon-button/IconButton'
11
+ import {type ModalProps, modalProps} from './modal.props'
12
+
13
+ const modalClassName = suffixClassName('sui-Modal')
14
+ const modalContentClassName = suffixClassName('sui-ModalContent')
15
+ const modalFooterClassName = suffixClassName('sui-ModalFooter')
16
+
17
+ function ModalRoot(props: ModalProps) {
18
+ const {
19
+ children,
20
+ className,
21
+ style,
22
+ header,
23
+ open = false,
24
+ onClose,
25
+ ...rest
26
+ } = getProps(props, modalProps)
27
+
28
+ const modalRef = useRef<HTMLDialogElement>(null)
29
+ const headerId = useId()
30
+
31
+ const modalClasses = clsx(
32
+ modalClassName,
33
+ 'sui-inset0 sui-m-auto sui-radius5 sui-shadow3 sui-p4 sui-flex-direction-column sui-gap4 sui-overflow-y-auto',
34
+ )
35
+
36
+ useEffect(() => {
37
+ const dialogElement = modalRef.current
38
+ if (!dialogElement) return
39
+
40
+ // `open` = `open` prop
41
+ // dialogElement.open = the `open` attribute on the HTML dialog element
42
+ // The prop and the attribute are manipulated independently, thus they
43
+ // can get out of sync if not handled within this effect.
44
+ if (open) {
45
+ if (!dialogElement.open) {
46
+ dialogElement.showModal()
47
+ }
48
+ } else {
49
+ if (dialogElement.open) {
50
+ dialogElement.close()
51
+ }
52
+ }
53
+
54
+ // Ensure the modal is closed (and thus its `open` attribute updated)
55
+ // when the component is unmounted.
56
+ return () => {
57
+ dialogElement.close()
58
+ }
59
+ }, [open])
60
+
61
+ return (
62
+ <dialog
63
+ {...rest}
64
+ ref={modalRef}
65
+ closedby="any"
66
+ aria-labelledby={header ? headerId : undefined}
67
+ onClose={onClose}
68
+ className={clsx(modalClasses, className)}
69
+ style={style}
70
+ data-ui="Modal"
71
+ >
72
+ <Flex flexShrink={0} justifyContent="space-between" alignItems="center">
73
+ {header ? (
74
+ <Heading trim size={1} id={headerId}>
75
+ {header}
76
+ </Heading>
77
+ ) : (
78
+ <div />
79
+ )}
80
+ <IconButton
81
+ aria-label="Close"
82
+ level="tertiary"
83
+ icon={CloseIcon}
84
+ onClick={() => modalRef.current?.close()}
85
+ />
86
+ </Flex>
87
+ {children}
88
+ </dialog>
89
+ )
90
+ }
91
+
92
+ function ModalContent(props: ComponentProps<'div'>) {
93
+ const {children, className, style, ...rest} = getProps(props, {})
94
+
95
+ return (
96
+ <Box
97
+ {...rest}
98
+ className={clsx(modalContentClassName, className)}
99
+ style={style}
100
+ data-ui="ModalContent"
101
+ flexGrow={1}
102
+ overflowY="auto"
103
+ >
104
+ {children}
105
+ </Box>
106
+ )
107
+ }
108
+
109
+ function ModalFooter(props: ComponentProps<'div'>) {
110
+ const {children, className, style, ...rest} = getProps(props, {})
111
+
112
+ return (
113
+ <Box
114
+ {...rest}
115
+ className={clsx(modalFooterClassName, className)}
116
+ style={style}
117
+ data-ui="ModalFooter"
118
+ flexShrink={0}
119
+ >
120
+ {children}
121
+ </Box>
122
+ )
123
+ }
124
+
125
+ ModalRoot.displayName = 'Modal'
126
+
127
+ /**
128
+ * @beta
129
+ *
130
+ * Renders a modal dialog element — should not be used for nonmodal dialogs.
131
+ * Modal dialogs make their containing documents inert, and render on the topmost
132
+ * layer within that containing document; they must be dimissed before the containing
133
+ * document and its contents become unblocked. They also trap focus within the bounds
134
+ * of the modal element and its descendants.
135
+ *
136
+ * For more on dialogs and modality, refer to the HTML specification for the dialog element:
137
+ * https://html.spec.whatwg.org/dev/interactive-elements.html#the-dialog-element
138
+ */
139
+ export const Modal = ModalRoot
140
+
141
+ ModalRoot.Content = ModalContent
142
+ ModalRoot.Footer = ModalFooter
143
+
144
+ export type {ModalProps}
@@ -0,0 +1,49 @@
1
+ .sui-Modal {
2
+ min-width: 320px;
3
+ max-width: 80dvw;
4
+ max-height: 80dvh;
5
+ transition:
6
+ display 150ms allow-discrete,
7
+ overlay 150ms allow-discrete,
8
+ opacity 150ms var(--timing-linear),
9
+ scale 150ms var(--timing-curve);
10
+ }
11
+
12
+ .sui-Modal:not([open]) {
13
+ opacity: 0;
14
+ scale: 0.9;
15
+ }
16
+
17
+ .sui-Modal[open] {
18
+ display: flex;
19
+ opacity: 1;
20
+ scale: 1;
21
+ }
22
+
23
+ .sui-Modal::backdrop {
24
+ --modal-backdrop: hsl(from var(--separator-high) h s l / var(--overlay-opacity));
25
+ background-color: var(--modal-backdrop);
26
+ transition:
27
+ display 150ms allow-discrete,
28
+ overlay 150ms allow-discrete,
29
+ opacity 150ms var(--timing-linear);
30
+ }
31
+
32
+ .sui-Modal:not([open])::backdrop {
33
+ opacity: 0;
34
+ }
35
+
36
+ .sui-Modal[open]::backdrop {
37
+ opacity: 1;
38
+ }
39
+
40
+ @starting-style {
41
+ .sui-Modal[open] {
42
+ opacity: 0;
43
+ scale: 0.9;
44
+ }
45
+
46
+ .sui-Modal[open]::backdrop {
47
+ opacity: 0;
48
+ }
49
+ }
@@ -0,0 +1,20 @@
1
+ import {type PropDef} from '../../types/PropDef'
2
+
3
+ /** @beta */
4
+ export interface ModalProps extends Omit<React.ComponentProps<'dialog'>, 'open'> {
5
+ /** Text to be displayed as the modal's heading; optional but recommended for optimal accessibility and presentation */
6
+ header?: string
7
+ /** Used to set the value of open to false. Fires whenever the modal is closed, either programmatically or by the user (via Esc key, clicking background, clicking the modal's close button, or any other means.) */
8
+ onClose: React.ReactEventHandler<HTMLDialogElement>
9
+ /** Whether the modal is open; defaults to false. */
10
+ open?: boolean
11
+ }
12
+
13
+ export const modalProps: Record<string, PropDef> = {
14
+ header: {
15
+ type: 'string',
16
+ },
17
+ open: {
18
+ type: 'boolean',
19
+ },
20
+ }
@@ -1,10 +1,10 @@
1
1
  @breakpoints {
2
- .sui-display-block { display: block; }
3
- .sui-display-inline-block { display: inline-block; }
4
- .sui-display-inline { display: inline; }
5
- .sui-display-flex { display: flex; }
6
- .sui-display-inline-flex { display: inline-flex; }
7
- .sui-display-grid { display: grid; }
8
- .sui-display-inline-grid { display: inline-grid; }
9
- .sui-display-none { display: none; }
2
+ .sui-display-block:not([hidden]) { display: block; }
3
+ .sui-display-inline-block:not([hidden]) { display: inline-block; }
4
+ .sui-display-inline:not([hidden]) { display: inline; }
5
+ .sui-display-flex:not([hidden]) { display: flex; }
6
+ .sui-display-inline-flex:not([hidden]) { display: inline-flex; }
7
+ .sui-display-grid:not([hidden]) { display: grid; }
8
+ .sui-display-inline-grid:not([hidden]) { display: inline-grid; }
9
+ .sui-display-none:not([hidden]) { display: none; }
10
10
  }
@@ -0,0 +1,4 @@
1
+ :root {
2
+ --timing-linear: linear;
3
+ --timing-curve: cubic-bezier(0.34, 1.2, 0.64, 1);
4
+ }
@@ -1,3 +1,4 @@
1
+ @import './animation.css';
1
2
  @import './border.css';
2
3
  @import './text-overflow.css';
3
4
  @import './text-trim.css';
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from './components/inline/Inline'
18
18
  export * from './components/label/Label'
19
19
  export * from './components/link/Link'
20
20
  export * from './components/list/List'
21
+ export * from './components/modal/Modal'
21
22
  export * from './components/press-area/PressArea'
22
23
  export * from './components/radio/Radio'
23
24
  export * from './components/skip-to-content/SkipToContent'
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is generated by scripts/write-version.js. Do not edit.
2
- export const VERSION = '500alpha3'
3
- export const HASH = 'cfa475'
2
+ export const VERSION = '500alpha5'
3
+ export const HASH = '024922'