@sanity/ui 1.1.0-beta.2 → 1.2.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.
Files changed (51) hide show
  1. package/LICENSE +1 -1
  2. package/dist/index.cjs.mjs +120 -0
  3. package/dist/index.d.ts +26 -4
  4. package/dist/{index.cjs → index.esm.js} +1987 -1946
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +2114 -1819
  7. package/dist/index.js.map +1 -1
  8. package/package.json +47 -44
  9. package/src/components/dialog/__workshop__/activate.tsx +134 -0
  10. package/src/components/dialog/__workshop__/index.ts +3 -0
  11. package/src/components/dialog/__workshop__/nested.tsx +14 -3
  12. package/src/components/dialog/__workshop__/panes.tsx +86 -0
  13. package/src/components/dialog/__workshop__/wrapped.tsx +78 -0
  14. package/src/components/dialog/dialog.tsx +110 -25
  15. package/src/components/toast/toastProvider.tsx +55 -52
  16. package/src/constants.ts +2 -2
  17. package/src/helpers/element.ts +7 -0
  18. package/src/hooks/index.ts +1 -0
  19. package/src/hooks/useArrayProp.ts +2 -1
  20. package/src/hooks/useMediaIndex/useMediaIndex.test.tsx +31 -0
  21. package/src/hooks/useMediaIndex/useMediaIndex.ts +98 -6
  22. package/src/hooks/useMounted.ts +14 -0
  23. package/src/hooks/usePrefersDark.hydration.test.tsx +56 -0
  24. package/src/hooks/usePrefersDark.test.tsx +18 -0
  25. package/src/hooks/usePrefersDark.ts +51 -18
  26. package/src/hooks/usePrefersReducedMotion.hydration.test.tsx +56 -0
  27. package/src/hooks/usePrefersReducedMotion.test.tsx +18 -0
  28. package/src/hooks/usePrefersReducedMotion.ts +62 -0
  29. package/src/observers/resizeObserver.ts +1 -1
  30. package/src/primitives/code/styles.ts +6 -0
  31. package/src/primitives/heading/styles.ts +6 -0
  32. package/src/primitives/label/styles.ts +6 -0
  33. package/src/primitives/popover/__workshop__/OpenOnMountStory.tsx +11 -0
  34. package/src/primitives/popover/__workshop__/index.ts +5 -0
  35. package/src/primitives/text/styles.ts +6 -0
  36. package/src/utils/layer/__workshop__/_debug.tsx +9 -3
  37. package/src/utils/layer/__workshop__/index.ts +3 -3
  38. package/src/utils/layer/__workshop__/multipleRoots.tsx +2 -1
  39. package/src/utils/layer/__workshop__/{plain.tsx → nested.tsx} +14 -4
  40. package/src/utils/layer/getLayerContext.test.ts +30 -0
  41. package/src/utils/layer/getLayerContext.ts +21 -0
  42. package/src/utils/layer/layer.test.tsx +1 -0
  43. package/src/utils/layer/layer.tsx +49 -7
  44. package/src/utils/layer/layerContext.ts +2 -2
  45. package/src/utils/layer/layerProvider.tsx +73 -12
  46. package/src/utils/layer/types.ts +3 -4
  47. package/src/utils/layer/useLayer.ts +9 -8
  48. package/src/utils/portal/portalContext.ts +1 -1
  49. package/src/utils/portal/portalProvider.tsx +1 -1
  50. package/dist/index.cjs.map +0 -1
  51. package/src/hooks/useMediaIndex/lib/media.ts +0 -88
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
- "type": "module",
3
2
  "name": "@sanity/ui",
4
- "version": "1.1.0-beta.2",
3
+ "version": "1.2.0",
5
4
  "sideEffects": false,
6
5
  "types": "./dist/index.d.ts",
7
6
  "source": "./src/index.ts",
8
- "module": "./dist/index.js",
9
- "main": "./dist/index.cjs",
7
+ "module": "./dist/index.esm.js",
8
+ "main": "./dist/index.js",
10
9
  "exports": {
11
10
  ".": {
12
11
  "types": "./dist/index.d.ts",
13
12
  "source": "./src/index.ts",
14
- "import": "./dist/index.js",
15
- "require": "./dist/index.cjs",
16
- "default": "./dist/index.js"
13
+ "require": "./dist/index.js",
14
+ "node": {
15
+ "import": "./dist/index.cjs.mjs",
16
+ "require": "./dist/index.js"
17
+ },
18
+ "import": "./dist/index.esm.js",
19
+ "default": "./dist/index.esm.js"
17
20
  },
18
21
  "./package.json": "./package.json"
19
22
  },
@@ -41,73 +44,73 @@
41
44
  "test": "jest",
42
45
  "test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
43
46
  "watch": "pkg watch --strict",
44
- "workshop:build": "node --loader esbuild-register/loader -r esbuild-register -r tsconfig-paths/register scripts/build",
45
- "workshop:dev": "node --loader esbuild-register/loader -r esbuild-register -r tsconfig-paths/register scripts/dev",
47
+ "workshop:build": "node -r esbuild-register -r tsconfig-paths/register scripts/build",
48
+ "workshop:dev": "node -r esbuild-register -r tsconfig-paths/register scripts/dev",
46
49
  "workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
47
50
  },
48
51
  "dependencies": {
49
- "@floating-ui/react-dom": "^1.0.1",
50
- "@sanity/color": "2.2.0-alpha.1",
51
- "@sanity/icons": "2.2.0-beta.1",
52
+ "@floating-ui/react-dom": "1.1.1",
53
+ "@sanity/color": "^2.2.2",
54
+ "@sanity/icons": "^2.2.2",
52
55
  "csstype": "^3.1.1",
53
- "framer-motion": "^7.10.2",
56
+ "framer-motion": "^8.5.2",
54
57
  "react-refractor": "^2.1.7"
55
58
  },
56
59
  "devDependencies": {
57
- "@babel/core": "^7.20.5",
60
+ "@babel/core": "^7.20.12",
58
61
  "@babel/preset-env": "^7.20.2",
59
62
  "@babel/preset-react": "^7.18.6",
60
63
  "@babel/preset-typescript": "^7.18.6",
61
- "@commitlint/cli": "^17.3.0",
62
- "@commitlint/config-conventional": "^17.3.0",
64
+ "@commitlint/cli": "^17.4.2",
65
+ "@commitlint/config-conventional": "^17.4.2",
63
66
  "@juggle/resize-observer": "^3.4.0",
64
- "@sanity/pkg-utils": "2.0.0-beta.1",
65
- "@sanity/semantic-release-preset": "^2.0.4",
66
- "@sanity/ui-workshop": "^1.0.1",
67
- "@testing-library/dom": "^8.19.0",
67
+ "@sanity/pkg-utils": "^2.2.3",
68
+ "@sanity/semantic-release-preset": "^4.0.0",
69
+ "@sanity/ui-workshop": "^1.2.0",
70
+ "@testing-library/dom": "^8.20.0",
68
71
  "@testing-library/jest-dom": "^5.16.5",
69
72
  "@testing-library/react": "^13.4.0",
70
- "@types/jest": "^29.2.4",
73
+ "@types/jest": "^29.2.6",
71
74
  "@types/jest-axe": "^3.5.5",
72
- "@types/node": "^18.11.16",
73
- "@types/react": "^18.0.26",
74
- "@types/react-dom": "^18.0.9",
75
+ "@types/node": "^18.11.18",
76
+ "@types/react": "^18.0.27",
77
+ "@types/react-dom": "^18.0.10",
75
78
  "@types/react-is": "^17.0.3",
76
79
  "@types/refractor": "^3.0.2",
77
80
  "@types/styled-components": "^5.1.26",
78
81
  "@types/testing-library__jest-dom": "^5.14.5",
79
- "@typescript-eslint/eslint-plugin": "^5.46.1",
80
- "@typescript-eslint/parser": "^5.46.1",
81
- "commitizen": "^4.2.6",
82
- "cypress": "^11.2.0",
83
- "cypress-real-events": "^1.7.4",
82
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
83
+ "@typescript-eslint/parser": "^5.49.0",
84
+ "commitizen": "^4.3.0",
85
+ "cypress": "^12.4.0",
86
+ "cypress-real-events": "^1.7.6",
84
87
  "cz-conventional-changelog": "^3.3.0",
85
- "esbuild": "^0.16.7",
88
+ "esbuild": "^0.17.4",
86
89
  "esbuild-register": "^3.4.2",
87
- "eslint": "^8.29.0",
88
- "eslint-config-prettier": "^8.5.0",
89
- "eslint-plugin-import": "^2.26.0",
90
- "eslint-plugin-jsx-a11y": "^6.6.1",
90
+ "eslint": "^8.32.0",
91
+ "eslint-config-prettier": "^8.6.0",
92
+ "eslint-plugin-import": "^2.27.5",
93
+ "eslint-plugin-jsx-a11y": "^6.7.1",
91
94
  "eslint-plugin-prettier": "^4.2.1",
92
- "eslint-plugin-react": "^7.31.11",
95
+ "eslint-plugin-react": "^7.32.1",
93
96
  "eslint-plugin-react-hooks": "^4.6.0",
94
97
  "http-server": "^14.1.1",
95
- "husky": "^8.0.2",
96
- "jest": "^29.3.1",
98
+ "husky": "^8.0.3",
99
+ "jest": "^29.4.0",
97
100
  "jest-axe": "^7.0.0",
98
- "jest-environment-jsdom": "^29.3.1",
101
+ "jest-environment-jsdom": "^29.4.0",
99
102
  "lint-staged": "^13.1.0",
100
103
  "module-alias": "^2.2.2",
101
104
  "npm-run-all": "^4.1.5",
102
- "prettier": "^2.8.1",
105
+ "prettier": "^2.8.3",
103
106
  "react": "^18.2.0",
104
107
  "react-dom": "^18.2.0",
105
108
  "react-is": "^18.2.0",
106
- "rimraf": "^3.0.2",
107
- "semantic-release": "^19.0.5",
108
- "start-server-and-test": "^1.15.2",
109
+ "rimraf": "^4.1.2",
110
+ "semantic-release": "^20.0.4",
111
+ "start-server-and-test": "^1.15.3",
109
112
  "styled-components": "^5.3.6",
110
- "tsconfig-paths": "^4.1.1",
113
+ "tsconfig-paths": "^4.1.2",
111
114
  "typescript": "^4.9.4"
112
115
  },
113
116
  "peerDependencies": {
@@ -143,7 +146,7 @@
143
146
  "prettier --write --cache --ignore-unknown"
144
147
  ]
145
148
  },
146
- "packageManager": "pnpm@7.12.2",
149
+ "packageManager": "pnpm@7.26.0",
147
150
  "publishConfig": {
148
151
  "access": "public"
149
152
  },
@@ -0,0 +1,134 @@
1
+ import {useState} from 'react'
2
+ import {Stack, Flex, Button, Text} from '../../../primitives'
3
+ import {Layer, LayerProvider} from '../../../utils'
4
+ import {Menu, MenuButton, MenuItem} from '../../menu'
5
+ import {Dialog} from '../dialog'
6
+
7
+ export default function LayeringFocusStory() {
8
+ const [firstDialogOpen, setFirstDialogOpen] = useState<boolean>(false)
9
+ const [secondDialogOpen, setSecondDialogOpen] = useState<boolean>(false)
10
+ const [thirdDialogOpen, setThirdDialogOpen] = useState<boolean>(false)
11
+ const [fourthDialogOpen, setFourthDialogOpen] = useState<boolean>(false)
12
+
13
+ return (
14
+ <LayerProvider>
15
+ <Flex align="center" height="fill" justify="center">
16
+ <Layer onActivate={({activeElement}) => activeElement?.focus()}>
17
+ <Button
18
+ text="Open dialog 1"
19
+ onClick={() => setFirstDialogOpen(true)}
20
+ tone="primary"
21
+ id="open-dialog-1-button"
22
+ />
23
+
24
+ {firstDialogOpen && (
25
+ <Dialog
26
+ header="Dialog 1"
27
+ id="1"
28
+ onActivate={({activeElement}) => activeElement?.focus()}
29
+ onClose={() => setFirstDialogOpen(false)}
30
+ >
31
+ <Stack space={2} padding={3}>
32
+ <Button
33
+ mode="ghost"
34
+ text="Open dialog 2"
35
+ onClick={() => setSecondDialogOpen(true)}
36
+ id="open-dialog-2-button-1"
37
+ />
38
+ <Button
39
+ mode="ghost"
40
+ text="Open dialog 2"
41
+ onClick={() => setSecondDialogOpen(true)}
42
+ id="open-dialog-2-button-2"
43
+ />
44
+ <Button
45
+ mode="ghost"
46
+ text="Open dialog 2"
47
+ onClick={() => setSecondDialogOpen(true)}
48
+ id="open-dialog-2-button-3"
49
+ />
50
+ </Stack>
51
+
52
+ {secondDialogOpen && (
53
+ <Dialog
54
+ header="Dialog 2"
55
+ id="2"
56
+ onActivate={({activeElement}) => activeElement?.focus()}
57
+ onClose={() => setSecondDialogOpen(false)}
58
+ >
59
+ <Stack space={2} padding={3}>
60
+ <Button
61
+ mode="ghost"
62
+ text="Open dialog 3"
63
+ onClick={() => setThirdDialogOpen(true)}
64
+ id="open-dialog-3-button-1"
65
+ />
66
+ <Button
67
+ mode="ghost"
68
+ text="Open dialog 3"
69
+ onClick={() => setThirdDialogOpen(true)}
70
+ id="open-dialog-3-button-2"
71
+ />
72
+ <Button
73
+ mode="ghost"
74
+ text="Open dialog 3"
75
+ onClick={() => setThirdDialogOpen(true)}
76
+ id="open-dialog-3-button-3"
77
+ />
78
+ </Stack>
79
+
80
+ {thirdDialogOpen && (
81
+ <Dialog
82
+ header="Dialog 3"
83
+ id="3"
84
+ onActivate={({activeElement}) => activeElement?.focus()}
85
+ onClose={() => setThirdDialogOpen(false)}
86
+ >
87
+ <Stack space={2} padding={3}>
88
+ <MenuButton
89
+ id="menu"
90
+ button={
91
+ <Button mode="ghost" text="Open menu" id="open-dialog-4-menu-button" />
92
+ }
93
+ menu={
94
+ <Menu>
95
+ <MenuItem
96
+ text="Open dialog 4"
97
+ onClick={() => setFourthDialogOpen(true)}
98
+ />
99
+ <MenuItem
100
+ text="Open dialog 4"
101
+ onClick={() => setFourthDialogOpen(true)}
102
+ />
103
+ <MenuItem
104
+ text="Open dialog 4"
105
+ onClick={() => setFourthDialogOpen(true)}
106
+ />
107
+ </Menu>
108
+ }
109
+ />
110
+ </Stack>
111
+
112
+ {fourthDialogOpen && (
113
+ <Dialog
114
+ header="Dialog 4"
115
+ id="4"
116
+ onActivate={({activeElement}) => activeElement?.focus()}
117
+ onClose={() => setFourthDialogOpen(false)}
118
+ >
119
+ <Stack space={2} padding={3}>
120
+ <Text>👋</Text>
121
+ </Stack>
122
+ </Dialog>
123
+ )}
124
+ </Dialog>
125
+ )}
126
+ </Dialog>
127
+ )}
128
+ </Dialog>
129
+ )}
130
+ </Layer>
131
+ </Flex>
132
+ </LayerProvider>
133
+ )
134
+ }
@@ -12,5 +12,8 @@ export default defineScope({
12
12
  {name: 'position', title: 'Position', component: lazy(() => import('./position'))},
13
13
  {name: 'provider', title: 'Provider', component: lazy(() => import('./provider'))},
14
14
  {name: 'auto-focus', title: 'AutoFocus', component: lazy(() => import('./autoFocus'))},
15
+ {name: 'panes', title: 'Panes', component: lazy(() => import('./panes'))},
16
+ {name: 'activate', title: 'Activate', component: lazy(() => import('./activate'))},
17
+ {name: 'wrapped', title: 'Wrapped', component: lazy(() => import('./wrapped'))},
15
18
  ],
16
19
  })
@@ -1,23 +1,34 @@
1
1
  import {Box, Button, Dialog, LayerProvider, Menu, MenuButton, MenuItem} from '@sanity/ui'
2
- import {useState} from 'react'
2
+ import {useEffect, useRef, useState} from 'react'
3
3
 
4
4
  export default function NestedStory() {
5
5
  const [open1] = useState(true)
6
6
  const [open2, setOpen2] = useState(false)
7
7
  const [open3, setOpen3] = useState(false)
8
8
 
9
+ const dialog2Button = useRef<HTMLButtonElement>(null)
10
+ const dialog3Button = useRef<HTMLButtonElement>(null)
11
+
12
+ useEffect(() => {
13
+ if (!open2) dialog2Button.current?.focus()
14
+ }, [open2])
15
+
16
+ useEffect(() => {
17
+ if (!open3) dialog3Button.current?.focus()
18
+ }, [open3])
19
+
9
20
  return (
10
21
  <LayerProvider>
11
22
  {open1 && (
12
23
  <Dialog cardShadow={1} header="Dialog 1" id="dialog1">
13
24
  <Box padding={4}>
14
- <Button onClick={() => setOpen2(true)} text="Open Dialog 2" />
25
+ <Button onClick={() => setOpen2(true)} ref={dialog2Button} text="Open Dialog 2" />
15
26
  </Box>
16
27
 
17
28
  {open2 && (
18
29
  <Dialog cardShadow={2} header="Dialog 2" id="dialog2" onClose={() => setOpen2(false)}>
19
30
  <Box padding={4}>
20
- <Button onClick={() => setOpen3(true)} text="Open Dialog 3" />
31
+ <Button onClick={() => setOpen3(true)} ref={dialog3Button} text="Open Dialog 3" />
21
32
  </Box>
22
33
 
23
34
  {open3 && (
@@ -0,0 +1,86 @@
1
+ import {useCallback, useState} from 'react'
2
+ import styled from 'styled-components'
3
+ import {Box, Button, Card, Flex} from '../../../primitives'
4
+ import {BoundaryElementProvider, PortalProvider} from '../../../utils'
5
+ import {Menu, MenuButton, MenuItem} from '../../menu'
6
+ import {Dialog} from '../dialog'
7
+
8
+ export default function PanesStory() {
9
+ return (
10
+ <Flex height="fill">
11
+ <Pane id="A" />
12
+ <Pane id="B" borderLeft />
13
+ </Flex>
14
+ )
15
+ }
16
+
17
+ const PaneRoot = styled(Card)`
18
+ position: relative;
19
+ `
20
+
21
+ const PanePortal = styled.div`
22
+ position: absolute;
23
+ left: 0;
24
+ right: 0;
25
+ top: 0;
26
+ bottom: 0;
27
+ pointer-events: none;
28
+
29
+ & > * {
30
+ pointer-events: auto;
31
+ }
32
+ `
33
+
34
+ function Pane(props: {borderLeft?: boolean; id: string}) {
35
+ const {borderLeft, id} = props
36
+ const [element, setElement] = useState<HTMLDivElement | null>(null)
37
+ const [portalElement, setPortalElement] = useState<HTMLDivElement | null>(null)
38
+ const [dialogOpen, setDialogOpen] = useState(false)
39
+
40
+ const handleClose = useCallback(() => {
41
+ setDialogOpen(false)
42
+ }, [])
43
+
44
+ return (
45
+ <BoundaryElementProvider element={element}>
46
+ <PortalProvider element={portalElement}>
47
+ <PaneRoot borderLeft={borderLeft} flex={1} ref={setElement}>
48
+ <Box padding={4}>
49
+ <Button
50
+ onClick={() => setDialogOpen(true)}
51
+ selected={dialogOpen}
52
+ text="Open dialog"
53
+ tone="primary"
54
+ />
55
+ </Box>
56
+
57
+ <PanePortal ref={setPortalElement} />
58
+ </PaneRoot>
59
+
60
+ {dialogOpen && (
61
+ <Dialog
62
+ header={`Dialog ${id}`}
63
+ id={id}
64
+ onClickOutside={handleClose}
65
+ onClose={handleClose}
66
+ position="absolute"
67
+ >
68
+ <Box padding={4}>
69
+ <MenuButton
70
+ button={<Button text="Open menu" tone="primary" />}
71
+ id={`${id}-menu`}
72
+ menu={
73
+ <Menu>
74
+ <MenuItem text="Item 1" />
75
+ <MenuItem text="Item 2" />
76
+ <MenuItem text="Item 3" />
77
+ </Menu>
78
+ }
79
+ />
80
+ </Box>
81
+ </Dialog>
82
+ )}
83
+ </PortalProvider>
84
+ </BoundaryElementProvider>
85
+ )
86
+ }
@@ -0,0 +1,78 @@
1
+ import {ReactNode, useCallback, useEffect, useRef, useState} from 'react'
2
+ import {Box, Button} from '../../../primitives'
3
+ import {LayerProvider, useLayer} from '../../../utils'
4
+ import {Dialog, DialogProps} from '../dialog'
5
+
6
+ export default function WrappedStory() {
7
+ return (
8
+ <LayerProvider zOffset={100}>
9
+ <Box padding={4}>
10
+ <DialogButton level={0} />
11
+ </Box>
12
+ </LayerProvider>
13
+ )
14
+ }
15
+
16
+ function DialogButton(props: {level: number}) {
17
+ const {level} = props
18
+ const [open, setOpen] = useState(false)
19
+
20
+ const openRef = useRef(open)
21
+ const buttonRef = useRef<HTMLButtonElement>(null)
22
+
23
+ useEffect(() => {
24
+ if (openRef.current !== open) {
25
+ openRef.current = open
26
+
27
+ if (!open) {
28
+ // focus the button that opened the dialog
29
+ // buttonRef.current?.focus()
30
+ }
31
+ }
32
+ }, [open])
33
+
34
+ return (
35
+ <>
36
+ <Button onClick={() => setOpen(true)} ref={buttonRef} text={`Open dialog ${level + 1}`} />
37
+
38
+ {open && (
39
+ <WrappedDialog
40
+ header={`WrappedDialog ${level + 1}`}
41
+ id={`wrapped-${level + 1}`}
42
+ onClickOutside={() => setOpen(false)}
43
+ onClose={() => setOpen(false)}
44
+ >
45
+ <Box padding={4}>
46
+ <DialogButton level={level + 1} />
47
+ </Box>
48
+ </WrappedDialog>
49
+ )}
50
+ </>
51
+ )
52
+ }
53
+
54
+ function WrappedDialog(props: DialogProps & {children?: ReactNode}) {
55
+ const layer = useLayer()
56
+ const isTopLayer = layer.size === 1
57
+
58
+ const dialogRef = useRef<HTMLDivElement>(null)
59
+
60
+ const [lastFocusedElement, setLastFocusedElement] = useState<HTMLElement | null>(null)
61
+
62
+ const handleContentFocus = useCallback(() => {
63
+ const containsActiveElement = dialogRef.current?.contains(document.activeElement)
64
+
65
+ if (containsActiveElement) {
66
+ setLastFocusedElement(document.activeElement as HTMLElement)
67
+ }
68
+ }, [])
69
+
70
+ // Set focus on the last focused element when the dialog becomes the top layer.
71
+ useEffect(() => {
72
+ if (isTopLayer && lastFocusedElement) {
73
+ lastFocusedElement.focus()
74
+ }
75
+ }, [isTopLayer, lastFocusedElement])
76
+
77
+ return <Dialog {...props} data-size={layer.size} onFocus={handleContentFocus} ref={dialogRef} />
78
+ }