@sanity/dashboard 5.0.1 → 6.0.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 (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +4 -50
  3. package/dist/index.d.ts +54 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +605 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +37 -79
  8. package/lib/index.d.mts +0 -65
  9. package/lib/index.d.ts +0 -65
  10. package/lib/index.esm.js +0 -568
  11. package/lib/index.esm.js.map +0 -1
  12. package/lib/index.js +0 -564
  13. package/lib/index.js.map +0 -1
  14. package/lib/index.mjs +0 -568
  15. package/lib/index.mjs.map +0 -1
  16. package/sanity.json +0 -8
  17. package/src/components/DashboardLayout.tsx +0 -10
  18. package/src/components/DashboardWidgetContainer.tsx +0 -69
  19. package/src/components/NotFoundWidget.tsx +0 -30
  20. package/src/components/WidgetGroup.tsx +0 -106
  21. package/src/containers/Dashboard.tsx +0 -19
  22. package/src/containers/DashboardContext.tsx +0 -8
  23. package/src/containers/WidgetContainer.tsx +0 -21
  24. package/src/index.ts +0 -7
  25. package/src/plugin.tsx +0 -72
  26. package/src/types.ts +0 -21
  27. package/src/versionedClient.ts +0 -5
  28. package/src/widgets/projectInfo/ProjectInfo.tsx +0 -221
  29. package/src/widgets/projectInfo/index.ts +0 -10
  30. package/src/widgets/projectInfo/types.ts +0 -28
  31. package/src/widgets/projectUsers/ProjectUser.tsx +0 -45
  32. package/src/widgets/projectUsers/ProjectUsers.tsx +0 -148
  33. package/src/widgets/projectUsers/index.ts +0 -10
  34. package/src/widgets/sanityTutorials/SanityTutorials.tsx +0 -77
  35. package/src/widgets/sanityTutorials/Tutorial.tsx +0 -111
  36. package/src/widgets/sanityTutorials/dataAdapter.ts +0 -49
  37. package/src/widgets/sanityTutorials/index.ts +0 -10
  38. package/v2-incompatible.js +0 -11
package/sanity.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "parts": [
3
- {
4
- "implements": "part:@sanity/base/sanity-root",
5
- "path": "./v2-incompatible.js"
6
- }
7
- ]
8
- }
@@ -1,10 +0,0 @@
1
- import React, {PropsWithChildren} from 'react'
2
- import {Container} from '@sanity/ui'
3
-
4
- export function DashboardLayout(props: PropsWithChildren<{}>) {
5
- return (
6
- <Container width={4} padding={4} sizing="border" style={{height: '100%', overflowY: 'auto'}}>
7
- {props.children}
8
- </Container>
9
- )
10
- }
@@ -1,69 +0,0 @@
1
- import React, {forwardRef} from 'react'
2
- import {Card, Box, Heading} from '@sanity/ui'
3
- import {styled} from 'styled-components'
4
-
5
- const Root = styled(Card)`
6
- display: flex;
7
- flex-direction: column;
8
- justify-content: stretch;
9
- height: 100%;
10
- box-sizing: border-box;
11
- position: relative;
12
- `
13
-
14
- const Header = styled(Card)`
15
- position: sticky;
16
- top: 0;
17
- z-index: 2;
18
- border-top-left-radius: inherit;
19
- border-top-right-radius: inherit;
20
- `
21
-
22
- const Footer = styled(Card)`
23
- position: sticky;
24
- overflow: hidden;
25
- bottom: 0;
26
- z-index: 2;
27
- border-bottom-right-radius: inherit;
28
- border-bottom-left-radius: inherit;
29
- margin-top: auto;
30
- `
31
-
32
- const Content = styled(Box)`
33
- position: relative;
34
- z-index: 1;
35
- height: stretch;
36
- min-height: 21.5em;
37
-
38
- @media (min-width: ${({theme}) => theme.sanity.media[0]}px) {
39
- overflow-y: auto;
40
- outline: none;
41
- }
42
- `
43
-
44
- interface DashboardWidgetProps {
45
- header?: string
46
- children: React.ReactNode
47
- footer?: React.ReactNode
48
- }
49
-
50
- export const DashboardWidgetContainer = forwardRef(function DashboardWidgetContainer(
51
- props: DashboardWidgetProps,
52
- ref: React.Ref<HTMLDivElement>,
53
- ) {
54
- const {header, children, footer} = props
55
-
56
- return (
57
- <Root radius={3} display="flex" ref={ref}>
58
- {header && (
59
- <Header borderBottom paddingX={3} paddingY={4}>
60
- <Heading size={1} textOverflow="ellipsis">
61
- {header}
62
- </Heading>
63
- </Header>
64
- )}
65
- {children && <Content>{children}</Content>}
66
- {footer && <Footer borderTop>{footer}</Footer>}
67
- </Root>
68
- )
69
- })
@@ -1,30 +0,0 @@
1
- import React, {PropsWithChildren, ReactNode} from 'react'
2
- import {Card, Stack, Heading, Box} from '@sanity/ui'
3
- import {styled} from 'styled-components'
4
-
5
- const Root = styled(Card)`
6
- display: flex;
7
- flex-direction: column;
8
- justify-content: stretch;
9
- height: 100%;
10
- `
11
-
12
- export type NotFoundWidgetProps = PropsWithChildren<{
13
- title?: ReactNode
14
- }>
15
-
16
- export function NotFoundWidget(props: NotFoundWidgetProps) {
17
- const {title, children} = props
18
- return (
19
- <Root radius={3} paddingX={3} paddingY={4} tone="critical">
20
- <Stack space={2}>
21
- {title && (
22
- <Heading size={1} as="h2">
23
- {title}
24
- </Heading>
25
- )}
26
- {children && <Box>{children}</Box>}
27
- </Stack>
28
- </Root>
29
- )
30
- }
@@ -1,106 +0,0 @@
1
- import React from 'react'
2
- import {styled, css} from 'styled-components'
3
- import {Box, Card, Grid, Text} from '@sanity/ui'
4
- import {WidgetContainer} from '../containers/WidgetContainer'
5
- import {DashboardConfig, LayoutConfig, DashboardWidget} from '../types'
6
-
7
- const media = {
8
- small: (...args: Parameters<typeof css>) => css`
9
- @media (min-width: ${({theme}) => theme.sanity.media[0]}px) {
10
- ${css(...args)}
11
- }
12
- `,
13
- medium: (...args: Parameters<typeof css>) => css`
14
- @media (min-width: ${({theme}) => theme.sanity.media[2]}px) {
15
- ${css(...args)}
16
- }
17
- `,
18
- }
19
-
20
- const Root = styled(Grid)`
21
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
22
-
23
- & > div {
24
- overflow: hidden;
25
- }
26
-
27
- & > div[data-width='medium'] {
28
- ${media.small`
29
- grid-column: span 2;
30
- `}
31
- }
32
-
33
- & > div[data-width='large'] {
34
- ${media.small`
35
- grid-column: span 2;
36
- `}
37
-
38
- ${media.medium`
39
- grid-column: span 3;
40
- `}
41
- }
42
-
43
- & > div[data-width='full'] {
44
- ${media.small`
45
- grid-column: 1 / -1;
46
- `}
47
- }
48
-
49
- & > div[data-height='medium'] {
50
- ${media.small`
51
- grid-row: span 2;
52
- `}
53
- }
54
-
55
- & > div[data-height='large'] {
56
- ${media.small`
57
- grid-row: span 2;
58
- `}
59
-
60
- ${media.medium`
61
- grid-row: span 3;
62
- `}
63
- }
64
-
65
- & > div[data-height='full'] {
66
- ${media.medium`
67
- grid-row: 1 / -1;
68
- `}
69
- }
70
- `
71
-
72
- export interface WidgetGroupProps {
73
- config: Partial<DashboardConfig>
74
- }
75
-
76
- const NO_WIDGETS: DashboardWidget[] = []
77
- const NO_LAYOUT: LayoutConfig = {}
78
-
79
- export function WidgetGroup(props: WidgetGroupProps) {
80
- const {
81
- config: {layout = NO_LAYOUT, widgets = NO_WIDGETS},
82
- } = props
83
- return (
84
- <Root
85
- autoFlow="row dense"
86
- data-width={layout.width || 'auto'}
87
- data-height={layout.height || 'auto'}
88
- gap={4}
89
- >
90
- {widgets.length ? null : (
91
- <Card padding={4} shadow={1} tone="primary">
92
- <Text align="center">Add some widgets to populate this space.</Text>
93
- </Card>
94
- )}
95
- {widgets.map((widgetConfig, index) => {
96
- if (widgetConfig.type === '__experimental_group') {
97
- return <WidgetGroup key={index} config={widgetConfig} />
98
- }
99
- if (widgetConfig.component) {
100
- return <WidgetContainer key={index} {...widgetConfig} />
101
- }
102
- return <Box key={index}>{widgetConfig.name} is missing widget component</Box>
103
- })}
104
- </Root>
105
- )
106
- }
@@ -1,19 +0,0 @@
1
- import React from 'react'
2
- import {DashboardLayout} from '../components/DashboardLayout'
3
- import {WidgetGroup} from '../components/WidgetGroup'
4
- import {DashboardContext} from './DashboardContext'
5
- import {DashboardConfig} from '../types'
6
-
7
- export function Dashboard({config}: {config: DashboardConfig}) {
8
- if (!config) {
9
- return null
10
- }
11
-
12
- return (
13
- <DashboardContext.Provider value={config}>
14
- <DashboardLayout>
15
- <WidgetGroup config={config} />
16
- </DashboardLayout>
17
- </DashboardContext.Provider>
18
- )
19
- }
@@ -1,8 +0,0 @@
1
- import {createContext, useContext} from 'react'
2
- import {DashboardConfig} from '../types'
3
-
4
- export const DashboardContext = createContext<DashboardConfig>({widgets: []})
5
-
6
- export function useDashboardConfig(): DashboardConfig {
7
- return useContext(DashboardContext)
8
- }
@@ -1,21 +0,0 @@
1
- import React, {createElement, useMemo} from 'react'
2
- import {useDashboardConfig} from './DashboardContext'
3
- import {Card} from '@sanity/ui'
4
- import {DashboardWidget} from '../types'
5
-
6
- export function WidgetContainer(props: DashboardWidget) {
7
- const config = useDashboardConfig()
8
- const layout = useMemo(
9
- () => ({
10
- ...(props.layout || {}),
11
- ...(config.layout || {}),
12
- }),
13
- [props.layout, config.layout],
14
- )
15
-
16
- return (
17
- <Card shadow={1} data-width={layout.width} data-height={layout.height}>
18
- {createElement(props.component, {})}
19
- </Card>
20
- )
21
- }
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- export * from './types'
2
- export * from './components/DashboardWidgetContainer'
3
- export * from './widgets/projectInfo'
4
- export * from './widgets/projectUsers'
5
- export * from './widgets/sanityTutorials'
6
-
7
- export {type DashboardPluginConfig, dashboardTool} from './plugin'
package/src/plugin.tsx DELETED
@@ -1,72 +0,0 @@
1
- import React, {ComponentType, CSSProperties} from 'react'
2
- import {Dashboard} from './containers/Dashboard'
3
- import {definePlugin} from 'sanity'
4
- import {DashboardConfig, DashboardWidget, LayoutConfig} from './types'
5
-
6
- const strokeStyle: CSSProperties = {
7
- stroke: 'currentColor',
8
- strokeWidth: 1.2,
9
- }
10
-
11
- const DashboardIcon = () => (
12
- <svg
13
- data-sanity-icon
14
- viewBox="0 0 25 25"
15
- fill="none"
16
- xmlns="http://www.w3.org/2000/svg"
17
- preserveAspectRatio="xMidYMid"
18
- width="1em"
19
- height="1em"
20
- >
21
- <path d="M19.5 19.5H5.5V5.5H19.5V19.5Z" style={strokeStyle} />
22
- <path d="M5.5 12.5H19.5" style={strokeStyle} />
23
- <path d="M14.5 19.5V12.5M10.5 12.5V5.5" style={strokeStyle} />
24
- </svg>
25
- )
26
-
27
- export interface DashboardPluginConfig {
28
- /**
29
- * Dashboard tool title
30
- */
31
- title?: string
32
- /**
33
- * Dashboard tool name (used in url path)
34
- */
35
- name?: string
36
- /**
37
- * Dashboard tool icon
38
- */
39
- icon?: ComponentType
40
- widgets?: DashboardWidget[]
41
-
42
- /**
43
- * Will be used for widgets that do not define a layout directly.
44
- */
45
- defaultLayout?: LayoutConfig
46
- }
47
-
48
- export const dashboardTool = definePlugin<DashboardPluginConfig>((config = {}) => {
49
- const pluginConfig: DashboardConfig = {
50
- layout: config.defaultLayout ?? {},
51
- widgets: config.widgets ?? [],
52
- }
53
-
54
- const title = config.title ?? 'Dashboard'
55
- const name = config.name ?? 'dashboard'
56
- const icon = config.icon ?? DashboardIcon
57
-
58
- return {
59
- name: 'dashboard',
60
- tools: (prev, context) => {
61
- return [
62
- ...prev,
63
- {
64
- title,
65
- name,
66
- icon,
67
- component: () => <Dashboard config={pluginConfig} />,
68
- },
69
- ]
70
- },
71
- }
72
- })
package/src/types.ts DELETED
@@ -1,21 +0,0 @@
1
- import {ComponentClass, FunctionComponent} from 'react'
2
-
3
- export interface DashboardWidget {
4
- name: string
5
- type?: '__experimental_group'
6
- component: FunctionComponent<any> | ComponentClass<any>
7
- layout?: LayoutConfig
8
- widgets?: DashboardWidget[]
9
- }
10
-
11
- export type LayoutSize = 'auto' | 'small' | 'medium' | 'large' | 'full'
12
-
13
- export interface LayoutConfig {
14
- width?: LayoutSize
15
- height?: LayoutSize
16
- }
17
-
18
- export interface DashboardConfig {
19
- widgets: DashboardWidget[]
20
- layout?: LayoutConfig
21
- }
@@ -1,5 +0,0 @@
1
- import {useClient} from 'sanity'
2
-
3
- export function useVersionedClient() {
4
- return useClient({apiVersion: '2024-08-01'})
5
- }
@@ -1,221 +0,0 @@
1
- import React, {useEffect, useMemo, useState} from 'react'
2
- import {Box, Card, Stack, Heading, Grid, Label, Text, Code, Button} from '@sanity/ui'
3
- import {useVersionedClient} from '../../versionedClient'
4
- import {Subscription} from 'rxjs'
5
- import {WidgetContainer} from '../../containers/WidgetContainer'
6
- import {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'
7
- import {type DashboardWidget} from '../../types'
8
- import {type App, type ProjectInfoProps, type ProjectData, UserApplication} from './types'
9
-
10
- function isUrl(url?: string) {
11
- return url && /^https?:\/\//.test(`${url}`)
12
- }
13
-
14
- function getGraphQLUrl(projectId: string, dataset: string) {
15
- return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`
16
- }
17
-
18
- function getGroqUrl(projectId: string, dataset: string) {
19
- return `https://${projectId}.api.sanity.io/v1/groq/${dataset}`
20
- }
21
-
22
- function getManageUrl(projectId: string) {
23
- return `https://manage.sanity.io/projects/${projectId}`
24
- }
25
-
26
- const NO_EXPERIMENTAL: DashboardWidget[] = []
27
- const NO_DATA: ProjectData[] = []
28
-
29
- export function ProjectInfo(props: ProjectInfoProps) {
30
- const {__experimental_before = NO_EXPERIMENTAL, data = NO_DATA} = props
31
- const [studioApps, setStudioApps] = useState<UserApplication[] | {error: string} | undefined>()
32
- const [graphQLApi, setGraphQLApi] = useState<string | {error: string} | undefined>()
33
- const versionedClient = useVersionedClient()
34
- const {projectId = 'unknown', dataset = 'unknown'} = versionedClient.config()
35
-
36
- useEffect(() => {
37
- const subscriptions: Subscription[] = []
38
-
39
- subscriptions.push(
40
- versionedClient.observable
41
- .request<UserApplication[]>({uri: '/user-applications', tag: 'dashboard.project-info'})
42
- .subscribe({
43
- next: (result) => setStudioApps(result.filter((app) => app.type === 'studio')),
44
- error: (error) => {
45
- console.error('Error while resolving user applications', error)
46
- setStudioApps({
47
- error: 'Something went wrong while resolving user applications. See console.',
48
- })
49
- },
50
- }),
51
- )
52
-
53
- // ping assumed graphql endpoint
54
- subscriptions.push(
55
- versionedClient.observable
56
- .request({
57
- method: 'HEAD',
58
- uri: `/graphql/${dataset}/default`,
59
- tag: 'dashboard.project-info.graphql-api',
60
- })
61
- .subscribe({
62
- next: () => setGraphQLApi(getGraphQLUrl(projectId, dataset)),
63
- error: (error) => {
64
- if (error.statusCode === 404) {
65
- setGraphQLApi(undefined)
66
- } else {
67
- console.error('Error while looking for graphQLApi', error)
68
- setGraphQLApi({
69
- error: 'Something went wrong while looking up graphQLApi. See console.',
70
- })
71
- }
72
- },
73
- }),
74
- )
75
-
76
- return () => {
77
- subscriptions.forEach((s) => s.unsubscribe())
78
- }
79
- }, [dataset, projectId, versionedClient, setGraphQLApi])
80
-
81
- const assembleTableRows = useMemo(() => {
82
- let result: App[] = [
83
- {
84
- title: 'Sanity project',
85
- rows: [
86
- {title: 'Project ID', value: projectId},
87
- {title: 'Dataset', value: dataset},
88
- ],
89
- },
90
- ]
91
-
92
- const apps: App[] = data.filter((item) => item.category === 'apps')
93
-
94
- // Handle studios
95
- ;(Array.isArray(studioApps) ? studioApps : []).forEach((app) => {
96
- apps.push({
97
- title: app.title || 'Studio',
98
- value: app.urlType === 'internal' ? `https://${app.appHost}.sanity.studio` : app.appHost,
99
- })
100
- })
101
-
102
- if (apps.length > 0) {
103
- result = result.concat([{title: 'Apps', rows: apps}])
104
- }
105
-
106
- // Handle APIs
107
- result = result.concat(
108
- [
109
- {
110
- title: 'APIs',
111
- rows: [
112
- {title: 'GROQ', value: getGroqUrl(projectId, dataset)},
113
- {
114
- title: 'GraphQL',
115
- value: (typeof graphQLApi === 'object' ? 'Error' : graphQLApi) ?? 'Not deployed',
116
- },
117
- ],
118
- },
119
- ],
120
- data.filter((item) => item.category === 'apis'),
121
- )
122
-
123
- // Handle whatever else there might be
124
- const otherStuff: Record<string, ProjectData[]> = {}
125
- data.forEach((item) => {
126
- if (item.category && item.category !== 'apps' && item.category !== 'apis') {
127
- if (!otherStuff[item.category]) {
128
- otherStuff[item.category] = []
129
- }
130
- otherStuff[item.category].push(item)
131
- }
132
- })
133
- Object.keys(otherStuff).forEach((category) => {
134
- result.push({title: category, rows: otherStuff[category]})
135
- })
136
-
137
- return result
138
- }, [graphQLApi, studioApps, projectId, dataset, data])
139
-
140
- return (
141
- <>
142
- {__experimental_before.map((widgetConfig, idx) => (
143
- <WidgetContainer key={idx} {...widgetConfig} />
144
- ))}
145
- <Box height="fill" marginTop={__experimental_before?.length > 0 ? 4 : 0}>
146
- <DashboardWidgetContainer
147
- footer={
148
- <Button
149
- style={{width: '100%'}}
150
- paddingX={2}
151
- paddingY={4}
152
- mode="bleed"
153
- tone="primary"
154
- text="Manage project"
155
- as="a"
156
- href={getManageUrl(projectId)}
157
- />
158
- }
159
- >
160
- <Card
161
- paddingY={4}
162
- radius={2}
163
- role="table"
164
- aria-label="Project info"
165
- aria-describedby="project_info_table"
166
- >
167
- <Stack space={4}>
168
- <Box paddingX={3} as="header">
169
- <Heading size={1} as="h2" id="project_info_table">
170
- Project info
171
- </Heading>
172
- </Box>
173
- {assembleTableRows.map((item) => {
174
- if (!item || !item.rows) {
175
- return null
176
- }
177
-
178
- return (
179
- <Stack key={item.title} space={3}>
180
- <Card borderBottom padding={3}>
181
- <Label size={0} muted role="columnheader">
182
- {item.title}
183
- </Label>
184
- </Card>
185
- <Stack space={4} paddingX={3} role="rowgroup">
186
- {item.rows.map((row) => {
187
- return (
188
- <Grid key={`${row.value}-${row.title}`} columns={2} role="row">
189
- <Text weight="medium" role="rowheader">
190
- {row.title}
191
- </Text>
192
- {typeof row.value === 'object' && (
193
- <Text size={1}>{row.value?.error}</Text>
194
- )}
195
- {typeof row.value === 'string' && (
196
- <>
197
- {isUrl(row.value) ? (
198
- <Text size={1} role="cell" style={{wordBreak: 'break-word'}}>
199
- <a href={row.value}>{row.value}</a>
200
- </Text>
201
- ) : (
202
- <Code size={1} role="cell" style={{wordBreak: 'break-word'}}>
203
- {row.value}
204
- </Code>
205
- )}
206
- </>
207
- )}
208
- </Grid>
209
- )
210
- })}
211
- </Stack>
212
- </Stack>
213
- )
214
- })}
215
- </Stack>
216
- </Card>
217
- </DashboardWidgetContainer>
218
- </Box>
219
- </>
220
- )
221
- }
@@ -1,10 +0,0 @@
1
- import {ProjectInfo} from './ProjectInfo'
2
- import {type LayoutConfig, type DashboardWidget} from '../../types'
3
-
4
- export function projectInfoWidget(config?: {layout?: LayoutConfig}): DashboardWidget {
5
- return {
6
- name: 'project-info',
7
- component: ProjectInfo,
8
- layout: config?.layout ?? {width: 'medium'},
9
- }
10
- }
@@ -1,28 +0,0 @@
1
- import {type DashboardWidget} from '../../types'
2
-
3
- export interface ProjectInfoProps {
4
- __experimental_before?: DashboardWidget[]
5
- data: ProjectData[]
6
- }
7
-
8
- export interface App {
9
- title: string
10
- rows?: App[]
11
- value?: string | {error: string}
12
- }
13
-
14
- export interface ProjectData {
15
- title: string
16
- category?: string
17
- }
18
-
19
- export interface UserApplication {
20
- id: string
21
- projectId: string
22
- title: string | null
23
- type: string
24
- urlType: 'internal' | 'external'
25
- appHost: string
26
-
27
- // … there are other props here, but we don't really care about them for our use case
28
- }