next-sanity 0.0.0-dev.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +910 -0
  3. package/dist/_chunks/NextStudioLoading-8cf56cdf.js +127 -0
  4. package/dist/_chunks/NextStudioLoading-8cf56cdf.js.map +1 -0
  5. package/dist/_chunks/NextStudioLoading-bf57e61a.cjs +131 -0
  6. package/dist/_chunks/NextStudioLoading-bf57e61a.cjs.map +1 -0
  7. package/dist/index.cjs +26 -0
  8. package/dist/index.cjs.js +6 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.ts +14 -0
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/preview.cjs +19 -0
  14. package/dist/preview.cjs.js +6 -0
  15. package/dist/preview.cjs.map +1 -0
  16. package/dist/preview.d.ts +20 -0
  17. package/dist/preview.js +2 -0
  18. package/dist/preview.js.map +1 -0
  19. package/dist/studio/head.cjs +51 -0
  20. package/dist/studio/head.cjs.js +5 -0
  21. package/dist/studio/head.cjs.map +1 -0
  22. package/dist/studio/head.d.ts +79 -0
  23. package/dist/studio/head.js +46 -0
  24. package/dist/studio/head.js.map +1 -0
  25. package/dist/studio/index.cjs +111 -0
  26. package/dist/studio/index.cjs.js +10 -0
  27. package/dist/studio/index.cjs.map +1 -0
  28. package/dist/studio/index.d.ts +88 -0
  29. package/dist/studio/index.js +96 -0
  30. package/dist/studio/index.js.map +1 -0
  31. package/dist/studio/loading.cjs +8 -0
  32. package/dist/studio/loading.cjs.js +5 -0
  33. package/dist/studio/loading.cjs.map +1 -0
  34. package/dist/studio/loading.d.ts +23 -0
  35. package/dist/studio/loading.js +2 -0
  36. package/dist/studio/loading.js.map +1 -0
  37. package/dist/webhook.cjs +54 -0
  38. package/dist/webhook.cjs.js +6 -0
  39. package/dist/webhook.cjs.map +1 -0
  40. package/dist/webhook.d.ts +31 -0
  41. package/dist/webhook.js +42 -0
  42. package/dist/webhook.js.map +1 -0
  43. package/package.json +207 -0
  44. package/src/client.ts +2 -0
  45. package/src/index.ts +3 -0
  46. package/src/preview/definePreview.tsx +37 -0
  47. package/src/preview/index.ts +2 -0
  48. package/src/studio/NextStudio.tsx +61 -0
  49. package/src/studio/NextStudioClientOnly.tsx +15 -0
  50. package/src/studio/NextStudioLayout.tsx +46 -0
  51. package/src/studio/NextStudioLoading.tsx +120 -0
  52. package/src/studio/NextStudioNoScript.tsx +40 -0
  53. package/src/studio/head/NextStudioHead.tsx +112 -0
  54. package/src/studio/head/apple-touch-icon.png +0 -0
  55. package/src/studio/head/favicon.ico +0 -0
  56. package/src/studio/head/favicon.svg +7 -0
  57. package/src/studio/head/index.ts +1 -0
  58. package/src/studio/index.ts +6 -0
  59. package/src/studio/loading.ts +1 -0
  60. package/src/studio/usePrefersColorScheme.ts +30 -0
  61. package/src/studio/useTheme.ts +13 -0
  62. package/src/webhook/config.ts +18 -0
  63. package/src/webhook/index.ts +2 -0
  64. package/src/webhook/parseBody.ts +44 -0
  65. package/src/webhook/readBody.ts +10 -0
@@ -0,0 +1,40 @@
1
+ /* eslint-disable react/no-danger */
2
+
3
+ const style = {
4
+ __html: `
5
+ .sanity-app-no-js__root {
6
+ position: absolute;
7
+ top: 0;
8
+ right: 0;
9
+ left: 0;
10
+ bottom: 0;
11
+ background: #fff;
12
+ z-index: 1;
13
+ }
14
+
15
+ .sanity-app-no-js__content {
16
+ position: absolute;
17
+ top: 50%;
18
+ left: 50%;
19
+ transform: translate(-50%, -50%);
20
+ text-align: center;
21
+ font-family: helvetica, arial, sans-serif;
22
+ }
23
+ `,
24
+ } as const
25
+
26
+ /** @alpha */
27
+ export const NextStudioNoScript = () => (
28
+ <noscript>
29
+ <div className="sanity-app-no-js__root">
30
+ <div className="sanity-app-no-js__content">
31
+ <style type="text/css" dangerouslySetInnerHTML={style} />
32
+ <h1>JavaScript disabled</h1>
33
+ <p>
34
+ Please <a href="https://www.enable-javascript.com/">enable JavaScript</a> in your
35
+ browser and reload the page to proceed.
36
+ </p>
37
+ </div>
38
+ </div>
39
+ </noscript>
40
+ )
@@ -0,0 +1,112 @@
1
+ import _faviconPng from './apple-touch-icon.png'
2
+ import _faviconIco from './favicon.ico'
3
+ import _faviconSvg from './favicon.svg'
4
+
5
+ const faviconPng = typeof _faviconPng === 'string' ? _faviconPng : _faviconPng.default
6
+ const faviconIco = typeof _faviconIco === 'string' ? _faviconIco : _faviconIco.default
7
+ const faviconSvg = typeof _faviconSvg === 'string' ? _faviconSvg : _faviconSvg.default
8
+
9
+ /** @public */
10
+ export interface NextStudioHeadProps {
11
+ /**
12
+ * @defaultValue 'utf-8'
13
+ */
14
+ charSet?: false | string
15
+ /**
16
+ * Sets the viewport to `viewport-fit=cover` to integrate with iOS devices with display cutouts (The Notch, Dynamic Island).
17
+ * Also sets `width=device-width, initial-scale=1` to make the studio page responsive.
18
+ * @defaultValue true
19
+ */
20
+ viewport?: boolean
21
+ /**
22
+ * It's common practice to hide the address to your Sanity Studio from search engines by setting `robots` to `noindex`
23
+ * @defaultValue 'noindex'
24
+ */
25
+ robots?: false | string
26
+ /**
27
+ * @defaultValue 'same-origin'
28
+ */
29
+ referrer?: false | string
30
+ /**
31
+ * Adds the same favicons as the `npx sanity dev` pipeline.
32
+ * @defaultValue true
33
+ */
34
+ favicons?: boolean
35
+ /**
36
+ * @defaultValue 'Sanity'
37
+ */
38
+ title?: false | string
39
+ }
40
+
41
+ /**
42
+ * In Next 13 appDir mode (`/app/studio/[[...index]]/head.tsx`):
43
+ * ```tsx
44
+ * // If you don't want to change any defaults you can just re-export the head component directly:
45
+ * export {NextStudioHead as default} from 'next-sanity/studio/head'
46
+ *
47
+ * // To customize it, use it as a children component:
48
+ * import {NextStudioHead} from 'next-sanity/studio/head'
49
+ *
50
+ * export default function CustomStudioHead() {
51
+ * return (
52
+ * <>
53
+ * <NextStudioHead favicons={false} />
54
+ * <link
55
+ * rel="icon"
56
+ * type="image/png"
57
+ * sizes="32x32"
58
+ * href="https://www.sanity.io/static/images/favicons/favicon-32x32.png"
59
+ * />
60
+ * </>
61
+ * )
62
+ * }
63
+ * ```
64
+ * If you're using Next 12 or the `pages` folder (`/pages/studio/[[...index]].tsx`):
65
+ * ```tsx
66
+ * import Head from 'next/head'
67
+ * import {NextStudio} from 'next-sanity/studio'
68
+ * import {NextStudioHead} from 'next-sanity/studio/head'
69
+ *
70
+ * export default function StudioPage() {
71
+ * return (
72
+ * <>
73
+ * <Head>
74
+ * <NextStudioHead />
75
+ * </Head>
76
+ * <NextStudio config={config} />
77
+ * </>
78
+ * )
79
+ * }
80
+ * ```
81
+ * @public
82
+ */
83
+ export function NextStudioHead(props: NextStudioHeadProps) {
84
+ const {
85
+ charSet = 'utf-8',
86
+ viewport = true,
87
+ robots = 'noindex',
88
+ referrer = 'same-origin',
89
+ favicons = true,
90
+ title = 'Sanity',
91
+ } = props
92
+
93
+ return (
94
+ <>
95
+ {charSet && <meta key="charset" charSet={charSet} />}
96
+ {viewport && (
97
+ <meta
98
+ key="viewport"
99
+ name="viewport"
100
+ // Studio implements display cutouts CSS (The iPhone Notch ™ ) and needs `viewport-fit=covered` for it to work correctly
101
+ content="width=device-width,initial-scale=1,viewport-fit=cover"
102
+ />
103
+ )}
104
+ {robots && <meta key="robots" name="robots" content={robots} />}
105
+ {referrer && <meta key="referrer" name="referrer" content={referrer} />}
106
+ {title && <title>{title}</title>}
107
+ {favicons && <link key="favicon.ico" rel="icon" href={faviconIco} sizes="any" />}
108
+ {favicons && <link key="apple-touch-icon.png" rel="apple-touch-icon" href={faviconPng} />}
109
+ {favicons && <link key="favicon.svg" rel="icon" href={faviconSvg} type="image/svg+xml" />}
110
+ </>
111
+ )
112
+ }
Binary file
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <rect width="512" height="512" fill="#F03E2F" rx="30" />
4
+ <path d="M161.527 136.723C161.527 179.76 187.738 205.443 240.388 219.095L296 232.283C345.687 243.852 376 272.775 376 319.514C376 341.727 369.162 360.931 357.538 375.971C357.538 329.232 333.607 303.78 276.171 288.74L221.47 276.246C177.709 266.065 143.977 242.464 143.977 191.56C143.977 170.505 150.359 151.994 161.527 136.723Z" fill="white" />
5
+ <path opacity="0.5" d="M323.35 308.176C347.054 323.679 357.538 345.197 357.538 376.202C337.709 401.654 303.293 416 262.724 416C194.575 416 146.484 381.756 136 322.753H201.641C210.074 350.056 232.41 362.551 262.268 362.551C298.735 362.32 322.895 342.652 323.35 308.176Z" fill="white" />
6
+ <path opacity="0.5" d="M195.715 200.816C172.923 186.007 161.527 165.183 161.527 136.954C180.672 111.503 213.493 96 253.835 96C323.35 96 363.692 133.252 373.721 185.776H310.359C303.293 165.183 285.971 148.986 254.291 148.986C220.33 148.986 197.311 169.116 195.715 200.816Z" fill="white" />
7
+ </svg>
@@ -0,0 +1 @@
1
+ export * from './NextStudioHead'
@@ -0,0 +1,6 @@
1
+ export * from './NextStudio'
2
+ export * from './NextStudioClientOnly'
3
+ export * from './NextStudioLayout'
4
+ export * from './NextStudioNoScript'
5
+ export * from './usePrefersColorScheme'
6
+ export * from './useTheme'
@@ -0,0 +1 @@
1
+ export {NextStudioLoading, type NextStudioLoadingProps} from './NextStudioLoading'
@@ -0,0 +1,30 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import type {ThemeColorSchemeKey} from '@sanity/ui'
3
+ import {useSyncExternalStore} from 'react'
4
+
5
+ function createStore() {
6
+ if (typeof document === 'undefined') {
7
+ return {
8
+ subscribe: () => () => {},
9
+ getSnapshot: () => 'light' as const,
10
+ getServerSnapshot: () => 'light' as const,
11
+ }
12
+ }
13
+
14
+ const matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
15
+
16
+ return {
17
+ subscribe: (onStoreChange: () => void) => {
18
+ matchMedia.addEventListener('change', onStoreChange)
19
+ return () => matchMedia.removeEventListener('change', onStoreChange)
20
+ },
21
+ getSnapshot: () => (matchMedia.matches ? 'dark' : 'light'),
22
+ getServerSnapshot: () => 'light' as const,
23
+ }
24
+ }
25
+ const store = createStore()
26
+
27
+ /** @alpha */
28
+ export function usePrefersColorScheme(): ThemeColorSchemeKey {
29
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)
30
+ }
@@ -0,0 +1,13 @@
1
+ import {studioTheme} from '@sanity/ui'
2
+ import {useMemo} from 'react'
3
+ import type {Config, SingleWorkspace, StudioTheme} from 'sanity'
4
+
5
+ /** @alpha */
6
+ export function useTheme(
7
+ config?: Config | Required<Pick<SingleWorkspace, 'theme'>>
8
+ ): StudioTheme {
9
+ const workspace = useMemo<
10
+ SingleWorkspace | Required<Pick<SingleWorkspace, 'theme'>> | undefined
11
+ >(() => (Array.isArray(config) ? config[0] : config), [config])
12
+ return useMemo<StudioTheme>(() => workspace?.theme || studioTheme, [workspace])
13
+ }
@@ -0,0 +1,18 @@
1
+ import type {PageConfig} from 'next/types'
2
+
3
+ /**
4
+ * Configurates the API function with the right runtime and body parsing to handle Sanity Webhook events.
5
+ * @public
6
+ */
7
+ export const config: PageConfig = {
8
+ api: {
9
+ /**
10
+ * Next.js will by default parse the body, which can lead to invalid signatures.
11
+ */
12
+ bodyParser: false,
13
+ },
14
+ /**
15
+ * `@sanity/webhook` isn't updated to support the edge runtime yet, and currently requires Node.js APIs such as Buffer.
16
+ */
17
+ runtime: 'nodejs',
18
+ }
@@ -0,0 +1,2 @@
1
+ export * from './config'
2
+ export * from './parseBody'
@@ -0,0 +1,44 @@
1
+ import type {SanityDocument} from '@sanity/types'
2
+ import sanityWebhook from '@sanity/webhook'
3
+ import type {NextApiRequest} from 'next'
4
+
5
+ // As `@sanity/webhook` isn't shipping ESM, extracting from the default export have the best ecosystem support
6
+ const {isValidSignature, SIGNATURE_HEADER_NAME} = sanityWebhook
7
+
8
+ import {_readBody as readBody} from './readBody'
9
+
10
+ /** @public */
11
+ export type ParseBody = {
12
+ /**
13
+ * If a secret is given then it returns a boolean. If no secret is provided then no validation is done on the signature, and it'll return `null`
14
+ */
15
+ isValidSignature: boolean | null
16
+ body: SanityDocument
17
+ }
18
+ /**
19
+ * Handles parsing the body JSON, and validating its signature. Also waits for Content Lake eventual consistency so you can run your queries
20
+ * without worrying about getting stale data.
21
+ * @public
22
+ */
23
+ export async function parseBody(
24
+ req: NextApiRequest,
25
+ secret?: string,
26
+ waitForContentLakeEventualConsistency: boolean = true
27
+ ): Promise<ParseBody> {
28
+ let signature = req.headers[SIGNATURE_HEADER_NAME]!
29
+ if (Array.isArray(signature)) {
30
+ signature = signature[0]
31
+ }
32
+
33
+ const body = await readBody(req)
34
+ const validSignature = secret ? isValidSignature(body, signature, secret.trim()) : null
35
+
36
+ if (validSignature !== false && waitForContentLakeEventualConsistency) {
37
+ await new Promise((resolve) => setTimeout(resolve, 1000))
38
+ }
39
+
40
+ return {
41
+ body: body.trim() && JSON.parse(body),
42
+ isValidSignature: validSignature,
43
+ }
44
+ }
@@ -0,0 +1,10 @@
1
+ import type {NextApiRequest} from 'next'
2
+
3
+ /** @internal */
4
+ export async function _readBody(readable: NextApiRequest): Promise<string> {
5
+ const chunks = []
6
+ for await (const chunk of readable) {
7
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)
8
+ }
9
+ return Buffer.concat(chunks).toString('utf8')
10
+ }