@sanity/sdk-react 0.0.0-alpha.1
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/dist/_chunks-es/useLogOut.js +36 -0
- package/dist/_chunks-es/useLogOut.js.map +1 -0
- package/dist/components.d.ts +235 -0
- package/dist/components.js +250 -0
- package/dist/components.js.map +1 -0
- package/dist/hooks.d.ts +145 -0
- package/dist/hooks.js +27 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/package.json +113 -0
- package/src/_exports/components.ts +12 -0
- package/src/_exports/hooks.ts +7 -0
- package/src/_exports/index.ts +10 -0
- package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +95 -0
- package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +42 -0
- package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +23 -0
- package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +95 -0
- package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +42 -0
- package/src/components/DocumentListLayout/DocumentListLayout.tsx +15 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +49 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +34 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +30 -0
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +115 -0
- package/src/components/Login/LoginLinks.test.tsx +100 -0
- package/src/components/Login/LoginLinks.tsx +73 -0
- package/src/components/auth/AuthBoundary.test.tsx +103 -0
- package/src/components/auth/AuthBoundary.tsx +101 -0
- package/src/components/auth/AuthError.test.ts +36 -0
- package/src/components/auth/AuthError.ts +27 -0
- package/src/components/auth/Login.test.tsx +41 -0
- package/src/components/auth/Login.tsx +58 -0
- package/src/components/auth/LoginCallback.test.tsx +86 -0
- package/src/components/auth/LoginCallback.tsx +41 -0
- package/src/components/auth/LoginError.test.tsx +56 -0
- package/src/components/auth/LoginError.tsx +54 -0
- package/src/components/auth/LoginFooter.test.tsx +29 -0
- package/src/components/auth/LoginFooter.tsx +67 -0
- package/src/components/auth/LoginLayout.test.tsx +33 -0
- package/src/components/auth/LoginLayout.tsx +99 -0
- package/src/components/context/SanityProvider.test.tsx +25 -0
- package/src/components/context/SanityProvider.tsx +42 -0
- package/src/hooks/Documents/.keep +0 -0
- package/src/hooks/auth/useAuthState.test.tsx +106 -0
- package/src/hooks/auth/useAuthState.tsx +33 -0
- package/src/hooks/auth/useAuthToken.test.tsx +94 -0
- package/src/hooks/auth/useAuthToken.tsx +16 -0
- package/src/hooks/auth/useCurrentUser.test.tsx +50 -0
- package/src/hooks/auth/useCurrentUser.tsx +27 -0
- package/src/hooks/auth/useHandleCallback.test.tsx +25 -0
- package/src/hooks/auth/useHandleCallback.tsx +50 -0
- package/src/hooks/auth/useLogOut.test.tsx +67 -0
- package/src/hooks/auth/useLogOut.tsx +15 -0
- package/src/hooks/auth/useLoginUrls.test.tsx +61 -0
- package/src/hooks/auth/useLoginUrls.tsx +51 -0
- package/src/hooks/client/useClient.test.tsx +130 -0
- package/src/hooks/client/useClient.ts +56 -0
- package/src/hooks/context/useSanityInstance.test.tsx +31 -0
- package/src/hooks/context/useSanityInstance.ts +23 -0
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {AuthProvider} from '@sanity/sdk'
|
|
2
|
+
import {AuthState} from '@sanity/sdk'
|
|
3
|
+
import {AuthStore} from '@sanity/sdk'
|
|
4
|
+
import {CurrentUser} from '@sanity/sdk'
|
|
5
|
+
import type {SanityInstance} from '@sanity/sdk'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A React hook that subscribes to authentication state changes.
|
|
9
|
+
*
|
|
10
|
+
* This hook provides access to the current authentication state type from the Sanity auth store.
|
|
11
|
+
* It automatically re-renders the component when the authentication state changes.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* The hook uses `useSyncExternalStore` to safely subscribe to auth state changes
|
|
15
|
+
* and ensure consistency between server and client rendering.
|
|
16
|
+
*
|
|
17
|
+
* @returns The current authentication state type
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function AuthStatus() {
|
|
22
|
+
* const authState = useAuthState()
|
|
23
|
+
* return <div>Current auth state: {authState}</div>
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export declare function useAuthState(): AuthState
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hook to get the currently logged in user
|
|
33
|
+
* @public
|
|
34
|
+
* @returns The current user or null if not authenticated
|
|
35
|
+
*/
|
|
36
|
+
export declare const useAuthToken: () => string | null
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook to get the currently logged in user
|
|
40
|
+
* @public
|
|
41
|
+
* @returns The current user or null if not authenticated
|
|
42
|
+
*/
|
|
43
|
+
export declare const useCurrentUser: () => CurrentUser | null
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A React hook that returns a function for handling authentication callbacks.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* This hook provides access to the authentication store's callback handler,
|
|
50
|
+
* which processes auth redirects by extracting the session ID and fetching the
|
|
51
|
+
* authentication token. If fetching the long-lived token is successful,
|
|
52
|
+
* `handleCallback` will return a Promise that resolves a new location that
|
|
53
|
+
* removes the short-lived token from the URL. Use this in combination with
|
|
54
|
+
* `history.replaceState` or your own router's `replace` function to update the
|
|
55
|
+
* current location without triggering a reload.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* function AuthCallback() {
|
|
60
|
+
* const handleCallback = useHandleCallback()
|
|
61
|
+
* const router = useRouter() // Example router
|
|
62
|
+
*
|
|
63
|
+
* useEffect(() => {
|
|
64
|
+
* async function processCallback() {
|
|
65
|
+
* // Handle the callback and get the cleaned URL
|
|
66
|
+
* const newUrl = await handleCallback(window.location.href)
|
|
67
|
+
*
|
|
68
|
+
* if (newUrl) {
|
|
69
|
+
* // Replace URL without triggering navigation
|
|
70
|
+
* router.replace(newUrl, {shallow: true})
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* processCallback().catch(console.error)
|
|
75
|
+
* }, [handleCallback, router])
|
|
76
|
+
*
|
|
77
|
+
* return <div>Completing login...</div>
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @returns A callback handler function that processes OAuth redirects
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export declare function useHandleCallback(): AuthStore['handleCallback']
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A React hook that retrieves the available authentication provider URLs for login.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* This hook fetches the login URLs from the Sanity auth store when the component mounts.
|
|
91
|
+
* Each provider object contains information about an authentication method, including its URL.
|
|
92
|
+
* The hook will suspend if the login URLs have not yet loaded.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```tsx
|
|
96
|
+
* // LoginProviders component that uses the hook
|
|
97
|
+
* function LoginProviders() {
|
|
98
|
+
* const providers = useLoginUrls()
|
|
99
|
+
*
|
|
100
|
+
* return (
|
|
101
|
+
* <div>
|
|
102
|
+
* {providers.map((provider) => (
|
|
103
|
+
* <a key={provider.name} href={provider.url}>
|
|
104
|
+
* Login with {provider.title}
|
|
105
|
+
* </a>
|
|
106
|
+
* ))}
|
|
107
|
+
* </div>
|
|
108
|
+
* )
|
|
109
|
+
* }
|
|
110
|
+
*
|
|
111
|
+
* // Parent component with Suspense boundary
|
|
112
|
+
* function LoginPage() {
|
|
113
|
+
* return (
|
|
114
|
+
* <Suspense fallback={<div>Loading authentication providers...</div>}>
|
|
115
|
+
* <LoginProviders />
|
|
116
|
+
* </Suspense>
|
|
117
|
+
* )
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @returns An array of {@link AuthProvider} objects containing login URLs and provider information
|
|
122
|
+
* @public
|
|
123
|
+
*/
|
|
124
|
+
export declare function useLoginUrls(): AuthProvider[]
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Hook to log out of the current session
|
|
128
|
+
* @public
|
|
129
|
+
* @returns A function to log out of the current session
|
|
130
|
+
*/
|
|
131
|
+
export declare const useLogOut: () => AuthStore['logout']
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Hook that provides the current Sanity instance from the context.
|
|
135
|
+
* This must be called from within a `SanityProvider` component.
|
|
136
|
+
* @public
|
|
137
|
+
* @returns the current Sanity instance
|
|
138
|
+
* @example
|
|
139
|
+
* ```tsx
|
|
140
|
+
* const instance = useSanityInstance()
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare const useSanityInstance: () => SanityInstance
|
|
144
|
+
|
|
145
|
+
export {}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useSanityInstance } from "./_chunks-es/useLogOut.js";
|
|
2
|
+
import { useAuthState, useHandleCallback, useLogOut, useLoginUrls } from "./_chunks-es/useLogOut.js";
|
|
3
|
+
import { getAuthStore } from "@sanity/sdk";
|
|
4
|
+
import { useStore } from "zustand";
|
|
5
|
+
const useAuthToken = () => {
|
|
6
|
+
const instance = useSanityInstance(), { tokenState } = getAuthStore(instance);
|
|
7
|
+
return useStore(tokenState);
|
|
8
|
+
}, useCurrentUser = () => {
|
|
9
|
+
const instance = useSanityInstance(), { currentUserState } = getAuthStore(instance);
|
|
10
|
+
if (!currentUserState.getState())
|
|
11
|
+
throw new Promise((resolve) => {
|
|
12
|
+
const unsubscribe = currentUserState.subscribe((currentUser) => {
|
|
13
|
+
currentUser && (unsubscribe(), resolve());
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
return useStore(currentUserState);
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
useAuthState,
|
|
20
|
+
useAuthToken,
|
|
21
|
+
useCurrentUser,
|
|
22
|
+
useHandleCallback,
|
|
23
|
+
useLogOut,
|
|
24
|
+
useLoginUrls,
|
|
25
|
+
useSanityInstance
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sources":["../src/hooks/auth/useAuthToken.tsx","../src/hooks/auth/useCurrentUser.tsx"],"sourcesContent":["import {getAuthStore} from '@sanity/sdk'\nimport {useStore} from 'zustand'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\n\n/**\n * Hook to get the currently logged in user\n * @public\n * @returns The current user or null if not authenticated\n */\nexport const useAuthToken = (): string | null => {\n const instance = useSanityInstance()\n const {tokenState} = getAuthStore(instance)\n\n return useStore(tokenState)\n}\n","import {type CurrentUser, type CurrentUserSlice, getAuthStore} from '@sanity/sdk'\nimport {useStore} from 'zustand'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\n\n/**\n * Hook to get the currently logged in user\n * @public\n * @returns The current user or null if not authenticated\n */\nexport const useCurrentUser = (): CurrentUser | null => {\n const instance = useSanityInstance()\n const {currentUserState} = getAuthStore(instance)\n\n // TODO: update this hook so it can never return null\n if (!currentUserState.getState())\n throw new Promise<void>((resolve) => {\n const unsubscribe = currentUserState.subscribe((currentUser) => {\n if (currentUser) {\n unsubscribe()\n resolve()\n }\n })\n })\n\n return useStore<CurrentUserSlice>(currentUserState)\n}\n"],"names":[],"mappings":";;;;AAUO,MAAM,eAAe,MAAqB;AAC/C,QAAM,WAAW,kBAAkB,GAC7B,EAAC,WAAU,IAAI,aAAa,QAAQ;AAE1C,SAAO,SAAS,UAAU;AAC5B,GCLa,iBAAiB,MAA0B;AACtD,QAAM,WAAW,kBAAkB,GAC7B,EAAC,iBAAgB,IAAI,aAAa,QAAQ;AAG5C,MAAA,CAAC,iBAAiB,SAAS;AACvB,UAAA,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,cAAc,iBAAiB,UAAU,CAAC,gBAAgB;AAC1D,wBACF,eACA;MAAQ,CAEX;AAAA,IAAA,CACF;AAEH,SAAO,SAA2B,gBAAgB;AACpD;"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/_exports/index.ts"],"sourcesContent":["/**\n * An example function that will be removed.\n * @public\n */\nexport function main(): void {\n //\n}\n\n// export * from './components'\n// export * from './hooks'\n"],"names":[],"mappings":"AAIO,SAAS,OAAa;AAE7B;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sanity/sdk-react",
|
|
3
|
+
"version": "0.0.0-alpha.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Sanity SDK React toolkit for Content OS",
|
|
6
|
+
"keywords": [],
|
|
7
|
+
"homepage": "https://github.com/sanity-io/sdk#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/sanity-io/sdk/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+ssh://git@github.com/sanity-io/sdk.git"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Sanity <developers@sanity.io>",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"source": "./src/_exports/index.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./components": {
|
|
26
|
+
"source": "./src/_exports/components.ts",
|
|
27
|
+
"import": "./dist/components.js",
|
|
28
|
+
"default": "./dist/components.js"
|
|
29
|
+
},
|
|
30
|
+
"./hooks": {
|
|
31
|
+
"source": "./src/_exports/hooks.ts",
|
|
32
|
+
"import": "./dist/hooks.js",
|
|
33
|
+
"default": "./dist/hooks.js"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"main": "./dist/index.js",
|
|
38
|
+
"module": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"typesVersions": {
|
|
41
|
+
"*": {
|
|
42
|
+
"components": [
|
|
43
|
+
"./dist/components.d.ts"
|
|
44
|
+
],
|
|
45
|
+
"hooks": [
|
|
46
|
+
"./dist/hooks.d.ts"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"src"
|
|
53
|
+
],
|
|
54
|
+
"lint-staged": {
|
|
55
|
+
"*": [
|
|
56
|
+
"prettier --write --cache --ignore-unknown"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"browserslist": "extends @sanity/browserslist-config",
|
|
60
|
+
"prettier": "@sanity/prettier-config",
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@sanity/logos": "^2.1.13",
|
|
63
|
+
"@sanity/ui": "^2.8.19",
|
|
64
|
+
"react-error-boundary": "^4.1.2",
|
|
65
|
+
"rxjs": "^7.8.1",
|
|
66
|
+
"styled-components": "^6.1.13",
|
|
67
|
+
"zustand": "^5.0.1",
|
|
68
|
+
"@sanity/sdk": "0.0.0-alpha.1"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@sanity/client": "^6.24.1",
|
|
72
|
+
"@sanity/pkg-utils": "^6.11.14",
|
|
73
|
+
"@sanity/prettier-config": "^1.0.3",
|
|
74
|
+
"@storybook/react": "^8.4.6",
|
|
75
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
76
|
+
"@testing-library/react": "^16.0.1",
|
|
77
|
+
"@types/react": "^18.3.13",
|
|
78
|
+
"@types/react-dom": "^18.3.1",
|
|
79
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
80
|
+
"@vitest/coverage-v8": "2.1.8",
|
|
81
|
+
"eslint": "^9.16.0",
|
|
82
|
+
"jsdom": "^25.0.1",
|
|
83
|
+
"lint-staged": "^15.2.10",
|
|
84
|
+
"prettier": "^3.4.2",
|
|
85
|
+
"react": "^18.3.1",
|
|
86
|
+
"react-dom": "^18.3.1",
|
|
87
|
+
"typescript": "^5.7.2",
|
|
88
|
+
"vitest": "^2.1.8",
|
|
89
|
+
"@repo/config-eslint": "0.0.0",
|
|
90
|
+
"@repo/package.config": "0.0.1"
|
|
91
|
+
},
|
|
92
|
+
"peerDependencies": {
|
|
93
|
+
"react": "^18.0.0",
|
|
94
|
+
"react-dom": "^18.0.0"
|
|
95
|
+
},
|
|
96
|
+
"engines": {
|
|
97
|
+
"node": ">=20.0.0"
|
|
98
|
+
},
|
|
99
|
+
"publishConfig": {
|
|
100
|
+
"access": "restricted"
|
|
101
|
+
},
|
|
102
|
+
"scripts": {
|
|
103
|
+
"build": "pkg build --strict --clean --check",
|
|
104
|
+
"clean": "rimraf dist",
|
|
105
|
+
"dev": "pkg watch",
|
|
106
|
+
"format": "prettier --write --cache --ignore-unknown .",
|
|
107
|
+
"lint": "eslint .",
|
|
108
|
+
"test": "vitest run",
|
|
109
|
+
"test:coverage": "vitest run --coverage",
|
|
110
|
+
"test:watch": "vitest",
|
|
111
|
+
"ts:check": "tsc --noEmit"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {AuthBoundary, type AuthBoundaryProps} from '../components/auth/AuthBoundary'
|
|
2
|
+
export {Login} from '../components/auth/Login'
|
|
3
|
+
export {LoginCallback} from '../components/auth/LoginCallback'
|
|
4
|
+
export {LoginError, type LoginErrorProps} from '../components/auth/LoginError'
|
|
5
|
+
export {LoginLayout, type LoginLayoutProps} from '../components/auth/LoginLayout'
|
|
6
|
+
export type {SanityProviderProps} from '../components/context/SanityProvider'
|
|
7
|
+
export {SanityProvider} from '../components/context/SanityProvider'
|
|
8
|
+
export {DocumentGridLayout} from '../components/DocumentGridLayout/DocumentGridLayout'
|
|
9
|
+
export {DocumentListLayout} from '../components/DocumentListLayout/DocumentListLayout'
|
|
10
|
+
export {DocumentPreviewLayout} from '../components/DocumentPreviewLayout/DocumentPreviewLayout'
|
|
11
|
+
export {type DocumentPreviewLayoutProps} from '../components/DocumentPreviewLayout/DocumentPreviewLayout'
|
|
12
|
+
export {LoginLinks} from '../components/Login/LoginLinks'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export {useAuthState} from '../hooks/auth/useAuthState'
|
|
2
|
+
export {useAuthToken} from '../hooks/auth/useAuthToken'
|
|
3
|
+
export {useCurrentUser} from '../hooks/auth/useCurrentUser'
|
|
4
|
+
export {useHandleCallback} from '../hooks/auth/useHandleCallback'
|
|
5
|
+
export {useLoginUrls} from '../hooks/auth/useLoginUrls'
|
|
6
|
+
export {useLogOut} from '../hooks/auth/useLogOut'
|
|
7
|
+
export {useSanityInstance} from '../hooks/context/useSanityInstance'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {type Meta, type StoryObj} from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import {DocumentPreviewLayout} from '../DocumentPreviewLayout/DocumentPreviewLayout.tsx'
|
|
4
|
+
import {DocumentGridLayout} from './DocumentGridLayout.tsx'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof DocumentGridLayout> = {
|
|
7
|
+
title: 'DocumentGridLayout',
|
|
8
|
+
component: DocumentGridLayout,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof meta>
|
|
13
|
+
|
|
14
|
+
const mockDocs = [
|
|
15
|
+
{id: '1', title: 'Just a title', url: '#', docType: 'article', status: 'published'},
|
|
16
|
+
{
|
|
17
|
+
id: '2',
|
|
18
|
+
title: 'A title, but also',
|
|
19
|
+
subtitle: 'A subtitle',
|
|
20
|
+
url: '#',
|
|
21
|
+
docType: 'article',
|
|
22
|
+
status: 'draft',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: '3',
|
|
26
|
+
title: 'Hello World',
|
|
27
|
+
subtitle: 'What a nice list I get to live in',
|
|
28
|
+
url: '#',
|
|
29
|
+
docType: 'image',
|
|
30
|
+
status: 'published',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: '4',
|
|
34
|
+
title: 'I’ve been selected',
|
|
35
|
+
subtitle: 'I feel special',
|
|
36
|
+
selected: true,
|
|
37
|
+
url: '#',
|
|
38
|
+
docType: 'video',
|
|
39
|
+
status: 'draft',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: '5',
|
|
43
|
+
title: 'A very long title that at some point might get truncated if it goes for long enough',
|
|
44
|
+
subtitle:
|
|
45
|
+
'Along with a subtitle that is quite long as well, in order to demonstrate the truncation of its text',
|
|
46
|
+
url: '#',
|
|
47
|
+
docType: 'audio',
|
|
48
|
+
status: 'published',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: '6',
|
|
52
|
+
title: 'Hello World',
|
|
53
|
+
subtitle: 'What a nice list I get to live in',
|
|
54
|
+
url: '#',
|
|
55
|
+
docType: 'pdf',
|
|
56
|
+
status: 'published',
|
|
57
|
+
},
|
|
58
|
+
{id: '7', title: 'Just a title', url: '#', docType: 'note', status: 'published,'},
|
|
59
|
+
{
|
|
60
|
+
id: '8',
|
|
61
|
+
title: 'A title, but also',
|
|
62
|
+
subtitle: 'A subtitle',
|
|
63
|
+
url: '#',
|
|
64
|
+
docType: 'document',
|
|
65
|
+
status: 'draft',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: '9',
|
|
69
|
+
title: 'Hello World',
|
|
70
|
+
subtitle: 'What a nice list I get to live in',
|
|
71
|
+
url: '#',
|
|
72
|
+
docType: 'biography',
|
|
73
|
+
status: 'published',
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
export const Default: Story = {
|
|
78
|
+
render: () => {
|
|
79
|
+
return (
|
|
80
|
+
<DocumentGridLayout>
|
|
81
|
+
{mockDocs.map((doc) => (
|
|
82
|
+
<li key={doc.id}>
|
|
83
|
+
<DocumentPreviewLayout
|
|
84
|
+
title={doc.title}
|
|
85
|
+
subtitle={doc.subtitle}
|
|
86
|
+
docType={doc.docType}
|
|
87
|
+
status={doc.status}
|
|
88
|
+
url={doc.url}
|
|
89
|
+
/>
|
|
90
|
+
</li>
|
|
91
|
+
))}
|
|
92
|
+
</DocumentGridLayout>
|
|
93
|
+
)
|
|
94
|
+
},
|
|
95
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {render, screen} from '../../../test/test-utils.tsx'
|
|
4
|
+
import {DocumentGridLayout} from './DocumentGridLayout.tsx'
|
|
5
|
+
|
|
6
|
+
describe('DocumentGridLayout', () => {
|
|
7
|
+
const mockDocuments = [
|
|
8
|
+
{
|
|
9
|
+
id: '1',
|
|
10
|
+
title: 'Test Document 1',
|
|
11
|
+
subtitle: 'Subtitle 1',
|
|
12
|
+
docType: 'post',
|
|
13
|
+
status: 'published',
|
|
14
|
+
url: '/doc/1',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: '2',
|
|
18
|
+
title: 'Test Document 2',
|
|
19
|
+
subtitle: 'Subtitle 2',
|
|
20
|
+
docType: 'page',
|
|
21
|
+
status: 'draft',
|
|
22
|
+
url: '/doc/2',
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
it('renders the expected content', () => {
|
|
27
|
+
render(
|
|
28
|
+
<DocumentGridLayout>
|
|
29
|
+
{mockDocuments.map((doc) => (
|
|
30
|
+
<li key={doc.id}>{doc.title}</li>
|
|
31
|
+
))}
|
|
32
|
+
</DocumentGridLayout>,
|
|
33
|
+
)
|
|
34
|
+
const list = screen.getByRole('list')
|
|
35
|
+
expect(list.tagName).toBe('OL')
|
|
36
|
+
expect(list.dataset['ui']).toBe('DocumentGridLayout')
|
|
37
|
+
|
|
38
|
+
const items = screen.getAllByRole('listitem')
|
|
39
|
+
expect(items[0]).toContainHTML('Test Document 1')
|
|
40
|
+
expect(items[1]).toContainHTML('Test Document 2')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type {PropsWithChildren, ReactElement} from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
|
|
4
|
+
const DocumentGrid = styled.div`
|
|
5
|
+
display: grid;
|
|
6
|
+
list-style: none;
|
|
7
|
+
margin: unset;
|
|
8
|
+
padding: unset;
|
|
9
|
+
grid-template-columns: repeat(auto-fit, minmax(38ch, 1fr));
|
|
10
|
+
`
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export const DocumentGridLayout = (props: PropsWithChildren): ReactElement => {
|
|
16
|
+
return (
|
|
17
|
+
<DocumentGrid as="ol" data-ui="DocumentGridLayout">
|
|
18
|
+
{props.children}
|
|
19
|
+
</DocumentGrid>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
DocumentGridLayout.displayName = 'DocumentGridLayout'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {type Meta, type StoryObj} from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import {DocumentPreviewLayout} from '../DocumentPreviewLayout/DocumentPreviewLayout.tsx'
|
|
4
|
+
import {DocumentListLayout} from './DocumentListLayout.tsx'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof DocumentListLayout> = {
|
|
7
|
+
title: 'DocumentListLayout',
|
|
8
|
+
component: DocumentListLayout,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof meta>
|
|
13
|
+
|
|
14
|
+
const mockDocs = [
|
|
15
|
+
{id: '1', title: 'Just a title', url: '#', docType: 'article', status: 'published'},
|
|
16
|
+
{
|
|
17
|
+
id: '2',
|
|
18
|
+
title: 'A title, but also',
|
|
19
|
+
subtitle: 'A subtitle',
|
|
20
|
+
url: '#',
|
|
21
|
+
docType: 'article',
|
|
22
|
+
status: 'draft',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: '3',
|
|
26
|
+
title: 'Hello World',
|
|
27
|
+
subtitle: 'What a nice list I get to live in',
|
|
28
|
+
url: '#',
|
|
29
|
+
docType: 'image',
|
|
30
|
+
status: 'published',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: '4',
|
|
34
|
+
title: 'I’ve been selected',
|
|
35
|
+
subtitle: 'I feel special',
|
|
36
|
+
selected: true,
|
|
37
|
+
url: '#',
|
|
38
|
+
docType: 'video',
|
|
39
|
+
status: 'draft',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: '5',
|
|
43
|
+
title: 'A very long title that at some point might get truncated if it goes for long enough',
|
|
44
|
+
subtitle:
|
|
45
|
+
'Along with a subtitle that is quite long as well, in order to demonstrate the truncation of its text',
|
|
46
|
+
url: '#',
|
|
47
|
+
docType: 'audio',
|
|
48
|
+
status: 'published',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: '6',
|
|
52
|
+
title: 'Hello World',
|
|
53
|
+
subtitle: 'What a nice list I get to live in',
|
|
54
|
+
url: '#',
|
|
55
|
+
docType: 'pdf',
|
|
56
|
+
status: 'published',
|
|
57
|
+
},
|
|
58
|
+
{id: '7', title: 'Just a title', url: '#', docType: 'note', status: 'published,'},
|
|
59
|
+
{
|
|
60
|
+
id: '8',
|
|
61
|
+
title: 'A title, but also',
|
|
62
|
+
subtitle: 'A subtitle',
|
|
63
|
+
url: '#',
|
|
64
|
+
docType: 'document',
|
|
65
|
+
status: 'draft',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: '9',
|
|
69
|
+
title: 'Hello World',
|
|
70
|
+
subtitle: 'What a nice list I get to live in',
|
|
71
|
+
url: '#',
|
|
72
|
+
docType: 'biography',
|
|
73
|
+
status: 'published',
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
export const Default: Story = {
|
|
78
|
+
render: () => {
|
|
79
|
+
return (
|
|
80
|
+
<DocumentListLayout>
|
|
81
|
+
{mockDocs.map((doc) => (
|
|
82
|
+
<li key={doc.id}>
|
|
83
|
+
<DocumentPreviewLayout
|
|
84
|
+
title={doc.title}
|
|
85
|
+
subtitle={doc.subtitle}
|
|
86
|
+
docType={doc.docType}
|
|
87
|
+
status={doc.status}
|
|
88
|
+
url={doc.url}
|
|
89
|
+
/>
|
|
90
|
+
</li>
|
|
91
|
+
))}
|
|
92
|
+
</DocumentListLayout>
|
|
93
|
+
)
|
|
94
|
+
},
|
|
95
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {render, screen} from '../../../test/test-utils.tsx'
|
|
4
|
+
import {DocumentListLayout} from './DocumentListLayout.tsx'
|
|
5
|
+
|
|
6
|
+
describe('DocumentListLayout', () => {
|
|
7
|
+
const mockDocuments = [
|
|
8
|
+
{
|
|
9
|
+
id: '1',
|
|
10
|
+
title: 'Test Document 1',
|
|
11
|
+
subtitle: 'Subtitle 1',
|
|
12
|
+
docType: 'post',
|
|
13
|
+
status: 'published',
|
|
14
|
+
url: '/doc/1',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: '2',
|
|
18
|
+
title: 'Test Document 2',
|
|
19
|
+
subtitle: 'Subtitle 2',
|
|
20
|
+
docType: 'page',
|
|
21
|
+
status: 'draft',
|
|
22
|
+
url: '/doc/2',
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
it('renders the expected content', () => {
|
|
27
|
+
render(
|
|
28
|
+
<DocumentListLayout>
|
|
29
|
+
{mockDocuments.map((doc) => (
|
|
30
|
+
<li key={doc.id}>{doc.title}</li>
|
|
31
|
+
))}
|
|
32
|
+
</DocumentListLayout>,
|
|
33
|
+
)
|
|
34
|
+
const list = screen.getByRole('list')
|
|
35
|
+
expect(list.tagName).toBe('OL')
|
|
36
|
+
expect(list.dataset['ui']).toBe('DocumentListLayout')
|
|
37
|
+
|
|
38
|
+
const items = screen.getAllByRole('listitem')
|
|
39
|
+
expect(items[0]).toContainHTML('Test Document 1')
|
|
40
|
+
expect(items[1]).toContainHTML('Test Document 2')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {Stack} from '@sanity/ui'
|
|
2
|
+
import type {PropsWithChildren, ReactElement} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export const DocumentListLayout = (props: PropsWithChildren): ReactElement => {
|
|
8
|
+
return (
|
|
9
|
+
<Stack as="ol" data-ui="DocumentListLayout">
|
|
10
|
+
{props.children}
|
|
11
|
+
</Stack>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
DocumentListLayout.displayName = 'DocumentListLayout'
|