@driftime/sanity-plugin-handbook 0.2.0 → 0.3.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.
- package/README.md +133 -80
- package/dist/index.d.ts +203 -110
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2790 -2064
- package/dist/index.js.map +1 -1
- package/package.json +36 -37
- package/dist/index.cjs +0 -2094
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -143
- package/src/blocks/callout/block.tsx +0 -56
- package/src/blocks/callout/content.tsx +0 -41
- package/src/blocks/callout/preview.tsx +0 -15
- package/src/blocks/code/block.tsx +0 -18
- package/src/blocks/code/content.tsx +0 -18
- package/src/blocks/code/preview.tsx +0 -17
- package/src/blocks/horizontal-rule/block.tsx +0 -5
- package/src/blocks/horizontal-rule/content.tsx +0 -8
- package/src/blocks/horizontal-rule/preview.tsx +0 -5
- package/src/blocks/image/block.tsx +0 -23
- package/src/blocks/image/content.tsx +0 -31
- package/src/blocks/image/preview.tsx +0 -23
- package/src/blocks/video/block.tsx +0 -21
- package/src/blocks/video/content.tsx +0 -37
- package/src/blocks/video/preview.tsx +0 -21
- package/src/components/content-panel.tsx +0 -58
- package/src/components/decorators.tsx +0 -53
- package/src/components/document-types.tsx +0 -49
- package/src/components/field.tsx +0 -530
- package/src/components/guide-content.tsx +0 -117
- package/src/components/guides.tsx +0 -36
- package/src/components/pane-header.tsx +0 -26
- package/src/components/sidebar-group.tsx +0 -30
- package/src/components/sidebar-tab.tsx +0 -78
- package/src/contexts/handbook-context.tsx +0 -84
- package/src/getting-started/document-types.tsx +0 -104
- package/src/getting-started/how-to-use.tsx +0 -95
- package/src/getting-started/overview.tsx +0 -65
- package/src/hooks/use-container-width.ts +0 -41
- package/src/hooks/use-handbook-configuration.ts +0 -74
- package/src/hooks/use-is-handbook-editor.ts +0 -18
- package/src/icons/book-open-text.tsx +0 -22
- package/src/icons/book-text.tsx +0 -19
- package/src/icons/code.tsx +0 -18
- package/src/icons/image.tsx +0 -19
- package/src/icons/info.tsx +0 -19
- package/src/icons/layout-grid.tsx +0 -20
- package/src/icons/lightbulb.tsx +0 -19
- package/src/icons/list-plus.tsx +0 -21
- package/src/icons/message-circle-question-mark.tsx +0 -19
- package/src/icons/minus.tsx +0 -17
- package/src/icons/triangle-alert.tsx +0 -19
- package/src/icons/video.tsx +0 -18
- package/src/index.ts +0 -145
- package/src/layouts/content.tsx +0 -13
- package/src/layouts/sidebar.tsx +0 -15
- package/src/layouts/tool.tsx +0 -205
- package/src/lib/handbook-structure.ts +0 -47
- package/src/lib/icons.ts +0 -16
- package/src/lib/utils.ts +0 -69
- package/src/schemas/annotations/link.ts +0 -19
- package/src/schemas/blocks/callout.ts +0 -69
- package/src/schemas/blocks/code.ts +0 -46
- package/src/schemas/blocks/horizontal-rule.ts +0 -31
- package/src/schemas/blocks/image.ts +0 -40
- package/src/schemas/blocks/video.ts +0 -34
- package/src/schemas/guide-content.ts +0 -58
- package/src/schemas/types/guide.ts +0 -48
- package/src/schemas/types/handbook.ts +0 -86
- package/src/types.ts +0 -101
package/src/icons/image.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function ImageIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
|
15
|
-
<circle cx="9" cy="9" r="2" />
|
|
16
|
-
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
package/src/icons/info.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function InfoIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<circle cx="12" cy="12" r="10" />
|
|
15
|
-
<path d="M12 16v-4" />
|
|
16
|
-
<path d="M12 8h.01" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function LayoutGridIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<rect width="7" height="7" x="3" y="3" rx="1" />
|
|
15
|
-
<rect width="7" height="7" x="14" y="3" rx="1" />
|
|
16
|
-
<rect width="7" height="7" x="14" y="14" rx="1" />
|
|
17
|
-
<rect width="7" height="7" x="3" y="14" rx="1" />
|
|
18
|
-
</svg>
|
|
19
|
-
);
|
|
20
|
-
}
|
package/src/icons/lightbulb.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function LightbulbIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5" />
|
|
15
|
-
<path d="M9 18h6" />
|
|
16
|
-
<path d="M10 22h4" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
package/src/icons/list-plus.tsx
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function ListPlusIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<path d="M16 5H3" />
|
|
15
|
-
<path d="M11 12H3" />
|
|
16
|
-
<path d="M16 19H3" />
|
|
17
|
-
<path d="M18 9v6" />
|
|
18
|
-
<path d="M21 12h-6" />
|
|
19
|
-
</svg>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function MessageCircleQuestionMarkIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719" />
|
|
15
|
-
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
|
16
|
-
<path d="M12 17h.01" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
package/src/icons/minus.tsx
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function MinusIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<path d="M5 12h14" />
|
|
15
|
-
</svg>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function TriangleAlertIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
15
|
-
<path d="M12 9v4" />
|
|
16
|
-
<path d="M12 17h.01" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
package/src/icons/video.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function VideoIcon(props: SVGProps<SVGSVGElement>) {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
fill="none"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
strokeLinecap="round"
|
|
11
|
-
strokeLinejoin="round"
|
|
12
|
-
{...props}
|
|
13
|
-
>
|
|
14
|
-
<path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5" />
|
|
15
|
-
<rect x="2" y="6" width="14" height="12" rx="2" />
|
|
16
|
-
</svg>
|
|
17
|
-
);
|
|
18
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { definePlugin } from "sanity";
|
|
2
|
-
|
|
3
|
-
import { BookTextIcon } from "./icons/book-text";
|
|
4
|
-
import { createHandbookTool } from "./layouts/tool";
|
|
5
|
-
import { setHandbookEditors } from "./lib/handbook-structure";
|
|
6
|
-
import { createSanityIcon } from "./lib/icons";
|
|
7
|
-
import { isDefined, isPermittedEditor } from "./lib/utils";
|
|
8
|
-
import { handbookCalloutType } from "./schemas/blocks/callout";
|
|
9
|
-
import { handbookCodeType } from "./schemas/blocks/code";
|
|
10
|
-
import { handbookHorizontalRuleType } from "./schemas/blocks/horizontal-rule";
|
|
11
|
-
import { handbookImageType } from "./schemas/blocks/image";
|
|
12
|
-
import { handbookVideoType } from "./schemas/blocks/video";
|
|
13
|
-
import { createHandbookGuideType } from "./schemas/types/guide";
|
|
14
|
-
import { handbookType } from "./schemas/types/handbook";
|
|
15
|
-
import type { HandbookConfig, HandbookStructureGroup } from "./types";
|
|
16
|
-
|
|
17
|
-
export type {
|
|
18
|
-
HandbookBlockDefinition,
|
|
19
|
-
HandbookConfig,
|
|
20
|
-
HandbookGuideGroup,
|
|
21
|
-
HandbookMetadata,
|
|
22
|
-
HandbookStructureGroup,
|
|
23
|
-
SanityHandbook,
|
|
24
|
-
SanityHandbookGuide,
|
|
25
|
-
} from "./types";
|
|
26
|
-
export { handbookStructure } from "./lib/handbook-structure";
|
|
27
|
-
export { useIsHandbookEditor } from "./hooks/use-is-handbook-editor";
|
|
28
|
-
|
|
29
|
-
/** Document types that should not appear in the "create new" menu. */
|
|
30
|
-
const handbookSingletonTypes = new Set(["handbook.handbook"]);
|
|
31
|
-
|
|
32
|
-
/** All handbook document types used to filter template items for non-editors. */
|
|
33
|
-
const handbookDocumentTypes = new Set(["handbook.handbook", "handbook.guide"]);
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks whether a template ID matches or is a variant of any type in the given set.
|
|
37
|
-
*
|
|
38
|
-
* @param templateId - The template identifier to check.
|
|
39
|
-
* @param typeSet - The set of document type names to match against.
|
|
40
|
-
* @returns True if the template ID matches a type or starts with a type prefix.
|
|
41
|
-
*/
|
|
42
|
-
function isHandbookTemplate(templateId: string, typeSet: Set<string>) {
|
|
43
|
-
return typeSet.has(templateId) || [...typeSet].some((type) => templateId.startsWith(`${type}-`));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Validates a single document type group entry in the plugin configuration.
|
|
48
|
-
*
|
|
49
|
-
* @param index - Position of the group in the configuration array.
|
|
50
|
-
* @param group - The document type group to validate.
|
|
51
|
-
*/
|
|
52
|
-
function validateGroup(index: number, group: HandbookStructureGroup) {
|
|
53
|
-
const prefix = `sanity-handbook: groups[${index}]`;
|
|
54
|
-
|
|
55
|
-
if (!isDefined(group.title)) throw new Error(`${prefix} is missing a required "title" property.`);
|
|
56
|
-
|
|
57
|
-
if (!Array.isArray(group.documents) || group.documents.length === 0) {
|
|
58
|
-
throw new Error(`${prefix} must have a non-empty "documents" array.`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
for (const document of group.documents) {
|
|
62
|
-
if (!isDefined(document.name)) {
|
|
63
|
-
throw new Error(`${prefix}.documents contains a document without a "name" property.`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!Array.isArray(document.fields)) {
|
|
67
|
-
throw new TypeError(`${prefix}.documents.${document.name} is missing a "fields" array.`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Validates the full handbook plugin configuration, checking all groups.
|
|
74
|
-
*
|
|
75
|
-
* @param config - The plugin configuration to validate.
|
|
76
|
-
*/
|
|
77
|
-
function validateConfig(config: HandbookConfig) {
|
|
78
|
-
if (config.groups) {
|
|
79
|
-
for (const [index, group] of config.groups.entries()) {
|
|
80
|
-
validateGroup(index, group);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Creates a Handbook tool for Sanity Studio, providing auto-generated documentation
|
|
87
|
-
* for document types and Portable Text guides within the Studio interface.
|
|
88
|
-
*
|
|
89
|
-
* @public
|
|
90
|
-
* @param config - Optional plugin configuration.
|
|
91
|
-
* @returns Sanity plugin definition.
|
|
92
|
-
*/
|
|
93
|
-
export function handbookPlugin(config?: HandbookConfig) {
|
|
94
|
-
if (config) validateConfig(config);
|
|
95
|
-
|
|
96
|
-
const customBlocks = config?.blocks ?? [];
|
|
97
|
-
const editors = config?.editors;
|
|
98
|
-
setHandbookEditors(editors);
|
|
99
|
-
|
|
100
|
-
const guideType = createHandbookGuideType(customBlocks);
|
|
101
|
-
|
|
102
|
-
return definePlugin(() => ({
|
|
103
|
-
name: "@driftime/sanity-plugin-handbook",
|
|
104
|
-
schema: {
|
|
105
|
-
types: [
|
|
106
|
-
handbookType,
|
|
107
|
-
guideType,
|
|
108
|
-
handbookImageType,
|
|
109
|
-
handbookVideoType,
|
|
110
|
-
handbookCodeType,
|
|
111
|
-
handbookCalloutType,
|
|
112
|
-
handbookHorizontalRuleType,
|
|
113
|
-
...customBlocks.map((block) => block.schema),
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
document: {
|
|
117
|
-
newDocumentOptions: (templateItems, { creationContext, currentUser }) => {
|
|
118
|
-
if (creationContext.type === "global" || creationContext.type === "structure") {
|
|
119
|
-
if (!isPermittedEditor(editors, currentUser?.email)) {
|
|
120
|
-
return templateItems.filter((item) => !isHandbookTemplate(item.templateId, handbookDocumentTypes));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return templateItems.filter((item) => !isHandbookTemplate(item.templateId, handbookSingletonTypes));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return templateItems;
|
|
127
|
-
},
|
|
128
|
-
actions: (actionComponents, { schemaType }) => {
|
|
129
|
-
if (handbookSingletonTypes.has(schemaType)) {
|
|
130
|
-
return actionComponents.filter(({ action }) => action !== "duplicate");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return actionComponents;
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
tools: [
|
|
137
|
-
{
|
|
138
|
-
name: "handbook",
|
|
139
|
-
title: config?.title ?? "Handbook",
|
|
140
|
-
icon: createSanityIcon(BookTextIcon),
|
|
141
|
-
component: createHandbookTool({ ...config, blocks: customBlocks }),
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
}))();
|
|
145
|
-
}
|
package/src/layouts/content.tsx
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { DocumentTypesPanels } from "../components/document-types";
|
|
2
|
-
import { GuidesPanels } from "../components/guides";
|
|
3
|
-
import { GettingStartedPanels } from "../getting-started/overview";
|
|
4
|
-
|
|
5
|
-
export function Content() {
|
|
6
|
-
return (
|
|
7
|
-
<>
|
|
8
|
-
<GettingStartedPanels />
|
|
9
|
-
<DocumentTypesPanels />
|
|
10
|
-
<GuidesPanels />
|
|
11
|
-
</>
|
|
12
|
-
);
|
|
13
|
-
}
|
package/src/layouts/sidebar.tsx
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Box } from "@sanity/ui";
|
|
2
|
-
|
|
3
|
-
import { DocumentTypesTabs } from "../components/document-types";
|
|
4
|
-
import { GuidesTabs } from "../components/guides";
|
|
5
|
-
import { GettingStartedTabs } from "../getting-started/overview";
|
|
6
|
-
|
|
7
|
-
export function Sidebar() {
|
|
8
|
-
return (
|
|
9
|
-
<Box paddingX={3} paddingBottom={1}>
|
|
10
|
-
<GettingStartedTabs />
|
|
11
|
-
<DocumentTypesTabs />
|
|
12
|
-
<GuidesTabs />
|
|
13
|
-
</Box>
|
|
14
|
-
);
|
|
15
|
-
}
|
package/src/layouts/tool.tsx
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { ArrowLeftIcon } from "@sanity/icons";
|
|
2
|
-
import { Button, Card, Flex, Text } from "@sanity/ui";
|
|
3
|
-
|
|
4
|
-
import { PaneHeader } from "../components/pane-header";
|
|
5
|
-
import { HandbookProvider, useHandbookContext } from "../contexts/handbook-context";
|
|
6
|
-
import { useContainerWidth } from "../hooks/use-container-width";
|
|
7
|
-
import { isDefined } from "../lib/utils";
|
|
8
|
-
import type { HandbookConfig } from "../types";
|
|
9
|
-
import { Content } from "./content";
|
|
10
|
-
import { Sidebar } from "./sidebar";
|
|
11
|
-
|
|
12
|
-
/** Default fallback message shown when a field has no description. */
|
|
13
|
-
const defaultUndocumentedFieldMessage =
|
|
14
|
-
"This field has not been documented yet. Contact your development team for guidance.";
|
|
15
|
-
|
|
16
|
-
/** Minimum container width for the wide (sidebar always visible) layout. */
|
|
17
|
-
const wideBreakpoint = 920;
|
|
18
|
-
|
|
19
|
-
/** Minimum container width for the medium (collapsible sidebar) layout. */
|
|
20
|
-
const narrowBreakpoint = 480;
|
|
21
|
-
|
|
22
|
-
/** Sidebar width in pixels when fully expanded. */
|
|
23
|
-
const sidebarWidth = 320;
|
|
24
|
-
|
|
25
|
-
/** Sidebar width in pixels when collapsed to a vertical label. */
|
|
26
|
-
const collapsedSidebarWidth = 51;
|
|
27
|
-
|
|
28
|
-
function CollapsedSidebar() {
|
|
29
|
-
const { sidebarTitle, setSidebarExpanded } = useHandbookContext();
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div
|
|
33
|
-
style={{
|
|
34
|
-
width: `${collapsedSidebarWidth}px`,
|
|
35
|
-
minWidth: `${collapsedSidebarWidth}px`,
|
|
36
|
-
overflow: "hidden",
|
|
37
|
-
borderRight: "1px solid var(--card-border-color)",
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
40
|
-
<Card tone="inherit" data-collapsed="">
|
|
41
|
-
<Flex
|
|
42
|
-
direction="column"
|
|
43
|
-
padding={3}
|
|
44
|
-
onClick={() => {
|
|
45
|
-
setSidebarExpanded(true);
|
|
46
|
-
}}
|
|
47
|
-
style={{
|
|
48
|
-
width: "100vh",
|
|
49
|
-
boxSizing: "border-box",
|
|
50
|
-
transform: "rotate(90deg)",
|
|
51
|
-
transformOrigin: `calc(${collapsedSidebarWidth}px / 2)`,
|
|
52
|
-
cursor: "pointer",
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
<Flex align="flex-start">
|
|
56
|
-
<Card flex={1} paddingLeft={2} padding={2}>
|
|
57
|
-
<Flex align="center" gap={1}>
|
|
58
|
-
<Text size={1} textOverflow="ellipsis" weight="semibold" style={{ cursor: "default", outline: "none" }}>
|
|
59
|
-
{sidebarTitle}
|
|
60
|
-
</Text>
|
|
61
|
-
</Flex>
|
|
62
|
-
</Card>
|
|
63
|
-
</Flex>
|
|
64
|
-
</Flex>
|
|
65
|
-
</Card>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function NarrowContentHeader() {
|
|
71
|
-
const { sidebarTitle, setSidebarExpanded } = useHandbookContext();
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<Flex align="center" gap={2} padding={3}>
|
|
75
|
-
<Button
|
|
76
|
-
icon={ArrowLeftIcon}
|
|
77
|
-
mode="bleed"
|
|
78
|
-
onClick={() => {
|
|
79
|
-
setSidebarExpanded(true);
|
|
80
|
-
}}
|
|
81
|
-
/>
|
|
82
|
-
<Text size={1} weight="semibold">
|
|
83
|
-
{sidebarTitle}
|
|
84
|
-
</Text>
|
|
85
|
-
</Flex>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function ExpandedSidebar() {
|
|
90
|
-
const { sidebarTitle } = useHandbookContext();
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<Card
|
|
94
|
-
borderRight
|
|
95
|
-
style={{ display: "flex", flexDirection: "column", width: `${sidebarWidth}px`, minWidth: `${sidebarWidth}px` }}
|
|
96
|
-
>
|
|
97
|
-
<PaneHeader title={sidebarTitle} />
|
|
98
|
-
<div style={{ flex: 1, paddingBottom: "4px", overflowY: "auto" }}>
|
|
99
|
-
<Sidebar />
|
|
100
|
-
</div>
|
|
101
|
-
</Card>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function NarrowLayout() {
|
|
106
|
-
const { sidebarTitle, sidebarExpanded } = useHandbookContext();
|
|
107
|
-
|
|
108
|
-
if (sidebarExpanded) {
|
|
109
|
-
return (
|
|
110
|
-
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
|
|
111
|
-
<PaneHeader title={sidebarTitle} />
|
|
112
|
-
<div style={{ flex: 1, paddingBottom: "4px", overflowY: "auto" }}>
|
|
113
|
-
<Sidebar />
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
|
|
121
|
-
<NarrowContentHeader />
|
|
122
|
-
<Content />
|
|
123
|
-
</div>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function MediumLayout() {
|
|
128
|
-
const { sidebarExpanded, setSidebarExpanded } = useHandbookContext();
|
|
129
|
-
|
|
130
|
-
if (sidebarExpanded) {
|
|
131
|
-
return (
|
|
132
|
-
<Flex style={{ flex: 1 }}>
|
|
133
|
-
<ExpandedSidebar />
|
|
134
|
-
<Card
|
|
135
|
-
flex={1}
|
|
136
|
-
padding={3}
|
|
137
|
-
onClick={() => {
|
|
138
|
-
setSidebarExpanded(false);
|
|
139
|
-
}}
|
|
140
|
-
style={{ opacity: 0.4, overflowY: "auto", cursor: "pointer" }}
|
|
141
|
-
/>
|
|
142
|
-
</Flex>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<Flex style={{ flex: 1 }}>
|
|
148
|
-
<CollapsedSidebar />
|
|
149
|
-
<Card flex={1} style={{ display: "flex", flexDirection: "column" }}>
|
|
150
|
-
<Content />
|
|
151
|
-
</Card>
|
|
152
|
-
</Flex>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function WideLayout() {
|
|
157
|
-
return (
|
|
158
|
-
<Flex style={{ flex: 1 }}>
|
|
159
|
-
<ExpandedSidebar />
|
|
160
|
-
<Card flex={1} style={{ display: "flex", flexDirection: "column" }}>
|
|
161
|
-
<Content />
|
|
162
|
-
</Card>
|
|
163
|
-
</Flex>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function HandbookLayout() {
|
|
168
|
-
const { reference: containerReference, width: containerWidth } = useContainerWidth<HTMLDivElement>();
|
|
169
|
-
|
|
170
|
-
const isNarrow = isDefined(containerWidth) && containerWidth < narrowBreakpoint;
|
|
171
|
-
const isMedium = isDefined(containerWidth) && containerWidth >= narrowBreakpoint && containerWidth < wideBreakpoint;
|
|
172
|
-
|
|
173
|
-
function renderLayout() {
|
|
174
|
-
if (isNarrow) return <NarrowLayout />;
|
|
175
|
-
if (isMedium) return <MediumLayout />;
|
|
176
|
-
return <WideLayout />;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<div ref={containerReference} style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
|
181
|
-
{renderLayout()}
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Creates the Handbook tool component bound to the given plugin configuration.
|
|
188
|
-
*
|
|
189
|
-
* @param config - Handbook plugin configuration.
|
|
190
|
-
* @returns Tool component for Sanity Studio.
|
|
191
|
-
*/
|
|
192
|
-
export function createHandbookTool(config: HandbookConfig) {
|
|
193
|
-
return function HandbookTool() {
|
|
194
|
-
return (
|
|
195
|
-
<HandbookProvider
|
|
196
|
-
sidebarTitle={config.sidebarTitle ?? "Handbook"}
|
|
197
|
-
groups={config.groups ?? []}
|
|
198
|
-
blocks={config.blocks ?? []}
|
|
199
|
-
undocumentedFieldMessage={config.undocumentedFieldMessage ?? defaultUndocumentedFieldMessage}
|
|
200
|
-
>
|
|
201
|
-
<HandbookLayout />
|
|
202
|
-
</HandbookProvider>
|
|
203
|
-
);
|
|
204
|
-
};
|
|
205
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { CurrentUser } from "sanity";
|
|
2
|
-
import type { StructureBuilder } from "sanity/structure";
|
|
3
|
-
|
|
4
|
-
import { BookOpenTextIcon } from "../icons/book-open-text";
|
|
5
|
-
import { BookTextIcon } from "../icons/book-text";
|
|
6
|
-
import { createSanityIcon } from "./icons";
|
|
7
|
-
import { isPermittedEditor } from "./utils";
|
|
8
|
-
|
|
9
|
-
/** Editor email addresses stored from plugin configuration for use by the structure helper. */
|
|
10
|
-
let registeredEditors: string[] | undefined = undefined;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Stores the editors list from the plugin configuration for use by the structure helper.
|
|
14
|
-
*
|
|
15
|
-
* @internal
|
|
16
|
-
* @param editors - The editors list from HandbookConfig.
|
|
17
|
-
*/
|
|
18
|
-
export function setHandbookEditors(editors?: string[]) {
|
|
19
|
-
registeredEditors = editors;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Creates Structure tool items for handbook documents.
|
|
24
|
-
* Returns an empty array if the current user is not in the editors list
|
|
25
|
-
* configured via handbookPlugin.
|
|
26
|
-
*
|
|
27
|
-
* @public
|
|
28
|
-
* @param structureBuilder - The Sanity Structure builder instance.
|
|
29
|
-
* @param context - The Structure context containing the current user.
|
|
30
|
-
* @returns An array of list items for the handbook singleton and guides list.
|
|
31
|
-
*/
|
|
32
|
-
export function handbookStructure(structureBuilder: StructureBuilder, context: { currentUser: CurrentUser | null }) {
|
|
33
|
-
if (!isPermittedEditor(registeredEditors, context.currentUser?.email)) return [];
|
|
34
|
-
|
|
35
|
-
const { listItem, document, documentTypeList } = structureBuilder;
|
|
36
|
-
|
|
37
|
-
return [
|
|
38
|
-
listItem()
|
|
39
|
-
.title("Handbook")
|
|
40
|
-
.icon(createSanityIcon(BookTextIcon))
|
|
41
|
-
.child(document().id("handbook.handbook").schemaType("handbook.handbook").title("Handbook")),
|
|
42
|
-
listItem()
|
|
43
|
-
.title("Handbook Guides")
|
|
44
|
-
.icon(createSanityIcon(BookOpenTextIcon))
|
|
45
|
-
.child(documentTypeList("handbook.guide").title("Handbook Guides")),
|
|
46
|
-
];
|
|
47
|
-
}
|
package/src/lib/icons.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, SVGProps } from "react";
|
|
2
|
-
import { createElement } from "react";
|
|
3
|
-
|
|
4
|
-
/** Default properties applied to all icons throughout the plugin. */
|
|
5
|
-
export const defaultIconProps: SVGProps<SVGSVGElement> = { width: "1em", height: "1em", strokeWidth: 1.5 };
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a Sanity Studio icon component from an SVG icon with default props applied.
|
|
9
|
-
*
|
|
10
|
-
* @param icon - The icon component to wrap.
|
|
11
|
-
* @param props - Optional props to merge with defaults.
|
|
12
|
-
* @returns A React component function for use in Sanity Studio.
|
|
13
|
-
*/
|
|
14
|
-
export function createSanityIcon(icon: ComponentType<SVGProps<SVGSVGElement>>, props?: SVGProps<SVGSVGElement>) {
|
|
15
|
-
return () => createElement(icon, { ...defaultIconProps, ...props });
|
|
16
|
-
}
|