@driftime/sanity-plugin-handbook 0.2.1 → 0.3.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/README.md +130 -77
- 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 -34
- 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
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Card, Flex, Text } from "@sanity/ui";
|
|
2
|
-
|
|
3
|
-
interface PaneHeaderProps {
|
|
4
|
-
/** Heading text displayed in the pane header. */
|
|
5
|
-
title: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function PaneHeader({ title }: PaneHeaderProps) {
|
|
9
|
-
return (
|
|
10
|
-
<div style={{ position: "sticky", top: 0, zIndex: 1, lineHeight: 0 }}>
|
|
11
|
-
<Card tone="inherit">
|
|
12
|
-
<Flex direction="column" padding={3} style={{ boxSizing: "border-box" }}>
|
|
13
|
-
<Flex align="flex-start">
|
|
14
|
-
<Card flex={1} paddingLeft={2} padding={2}>
|
|
15
|
-
<Flex align="center" gap={1}>
|
|
16
|
-
<Text size={1} textOverflow="ellipsis" weight="semibold" style={{ cursor: "default", outline: "none" }}>
|
|
17
|
-
{title}
|
|
18
|
-
</Text>
|
|
19
|
-
</Flex>
|
|
20
|
-
</Card>
|
|
21
|
-
</Flex>
|
|
22
|
-
</Flex>
|
|
23
|
-
</Card>
|
|
24
|
-
</div>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Box, Card, Flex, Text } from "@sanity/ui";
|
|
2
|
-
import type { PropsWithChildren } from "react";
|
|
3
|
-
|
|
4
|
-
type SidebarGroupProps = PropsWithChildren<{
|
|
5
|
-
/** Text label displayed above the group. */
|
|
6
|
-
heading: string;
|
|
7
|
-
}>;
|
|
8
|
-
|
|
9
|
-
export function SidebarGroup({ heading, children }: SidebarGroupProps) {
|
|
10
|
-
return (
|
|
11
|
-
<div>
|
|
12
|
-
<Box paddingY={2}>
|
|
13
|
-
<Card paddingLeft={2} padding={2}>
|
|
14
|
-
<Flex align="center" gap={1}>
|
|
15
|
-
<Text
|
|
16
|
-
size={1}
|
|
17
|
-
muted
|
|
18
|
-
textOverflow="ellipsis"
|
|
19
|
-
weight="semibold"
|
|
20
|
-
style={{ cursor: "default", outline: "none" }}
|
|
21
|
-
>
|
|
22
|
-
{heading}
|
|
23
|
-
</Text>
|
|
24
|
-
</Flex>
|
|
25
|
-
</Card>
|
|
26
|
-
</Box>
|
|
27
|
-
{children}
|
|
28
|
-
</div>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { ChevronRightIcon } from "@sanity/icons";
|
|
2
|
-
import { Box, Card, Flex, Stack, Text } from "@sanity/ui";
|
|
3
|
-
import type { ComponentType } from "react";
|
|
4
|
-
|
|
5
|
-
import { useHandbookContext } from "../contexts/handbook-context";
|
|
6
|
-
import { isDefined } from "../lib/utils";
|
|
7
|
-
|
|
8
|
-
interface SidebarTabProps {
|
|
9
|
-
/** Unique identifier linking this tab to its content panel. */
|
|
10
|
-
id: string;
|
|
11
|
-
/** Text label displayed on the tab. */
|
|
12
|
-
label: string;
|
|
13
|
-
/** Optional icon component rendered beside the label. */
|
|
14
|
-
icon?: ComponentType;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function SidebarTab({ id, label, icon: Icon }: SidebarTabProps) {
|
|
18
|
-
const { activeTab, setActiveTab, setSidebarExpanded } = useHandbookContext();
|
|
19
|
-
const selected = activeTab === id;
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Card
|
|
23
|
-
id={id}
|
|
24
|
-
aria-controls={`panel-${id}`}
|
|
25
|
-
aria-selected={selected}
|
|
26
|
-
role="option"
|
|
27
|
-
as="button"
|
|
28
|
-
selected={selected}
|
|
29
|
-
marginBottom={1}
|
|
30
|
-
radius={2}
|
|
31
|
-
sizing="border"
|
|
32
|
-
tabIndex={-1}
|
|
33
|
-
tone="inherit"
|
|
34
|
-
onClick={() => {
|
|
35
|
-
setActiveTab(id);
|
|
36
|
-
setSidebarExpanded(false);
|
|
37
|
-
}}
|
|
38
|
-
style={{ width: "100%", textAlign: "left", border: "none", cursor: "pointer" }}
|
|
39
|
-
>
|
|
40
|
-
<Flex
|
|
41
|
-
align="center"
|
|
42
|
-
paddingLeft={isDefined(Icon) ? 1 : 2}
|
|
43
|
-
paddingRight={2}
|
|
44
|
-
paddingY={1}
|
|
45
|
-
style={{ height: "1.5625rem", boxSizing: "content-box" }}
|
|
46
|
-
>
|
|
47
|
-
<Flex align="center" flex={1} gap={2}>
|
|
48
|
-
{isDefined(Icon) && (
|
|
49
|
-
<div
|
|
50
|
-
style={{
|
|
51
|
-
display: "flex",
|
|
52
|
-
alignItems: "center",
|
|
53
|
-
justifyContent: "center",
|
|
54
|
-
width: "25px",
|
|
55
|
-
height: "25px",
|
|
56
|
-
color: "var(--card-icon-color)",
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
<Icon />
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
|
-
<Stack flex={1} space={2}>
|
|
63
|
-
<Text size={1} textOverflow="ellipsis" weight="medium" style={{ color: "inherit" }}>
|
|
64
|
-
{label}
|
|
65
|
-
</Text>
|
|
66
|
-
</Stack>
|
|
67
|
-
<Box paddingLeft={4} paddingRight={1}>
|
|
68
|
-
<Box style={{ opacity: 0.5 }}>
|
|
69
|
-
<Text muted size={1}>
|
|
70
|
-
<ChevronRightIcon />
|
|
71
|
-
</Text>
|
|
72
|
-
</Box>
|
|
73
|
-
</Box>
|
|
74
|
-
</Flex>
|
|
75
|
-
</Flex>
|
|
76
|
-
</Card>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useState } from "react";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
import { useHandbookConfiguration } from "../hooks/use-handbook-configuration";
|
|
5
|
-
import { isDefined } from "../lib/utils";
|
|
6
|
-
import type { HandbookBlockDefinition, HandbookStructureGroup, SanityHandbook } from "../types";
|
|
7
|
-
|
|
8
|
-
interface HandbookProviderConfig {
|
|
9
|
-
/** Heading displayed at the top of the sidebar. */
|
|
10
|
-
sidebarTitle: string;
|
|
11
|
-
/** Configured document type groups containing document definitions. */
|
|
12
|
-
groups: HandbookStructureGroup[];
|
|
13
|
-
/** Custom block definitions registered by the consumer. */
|
|
14
|
-
blocks: HandbookBlockDefinition[];
|
|
15
|
-
/** Fallback message shown when a field has no description. */
|
|
16
|
-
undocumentedFieldMessage: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface HandbookContextValues extends HandbookProviderConfig {
|
|
20
|
-
/** Identifier of the currently selected tab, or null if none. */
|
|
21
|
-
activeTab: string | null;
|
|
22
|
-
/** Whether the sidebar is currently expanded. */
|
|
23
|
-
sidebarExpanded: boolean;
|
|
24
|
-
/** Whether configuration data is still loading from the dataset. */
|
|
25
|
-
loading: boolean;
|
|
26
|
-
/** Handbook configuration singleton fetched from the dataset. */
|
|
27
|
-
configuration: SanityHandbook | null;
|
|
28
|
-
/** Sets the active tab by identifier. */
|
|
29
|
-
setActiveTab: (id: string) => void;
|
|
30
|
-
/** Toggles the sidebar expanded state. */
|
|
31
|
-
setSidebarExpanded: (expanded: boolean) => void;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface HandbookProviderProps extends HandbookProviderConfig {
|
|
35
|
-
/** Child elements wrapped by the provider. */
|
|
36
|
-
children: ReactNode;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const HandbookContext = createContext<HandbookContextValues | undefined>(undefined);
|
|
40
|
-
|
|
41
|
-
export function HandbookProvider({
|
|
42
|
-
sidebarTitle,
|
|
43
|
-
groups,
|
|
44
|
-
blocks,
|
|
45
|
-
undocumentedFieldMessage,
|
|
46
|
-
children,
|
|
47
|
-
}: HandbookProviderProps) {
|
|
48
|
-
const [activeTab, setActiveTab] = useState<string | null>(null);
|
|
49
|
-
const [sidebarExpanded, setSidebarExpanded] = useState(true);
|
|
50
|
-
const { configuration, loading } = useHandbookConfiguration();
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<HandbookContext
|
|
54
|
-
value={{
|
|
55
|
-
sidebarTitle,
|
|
56
|
-
activeTab,
|
|
57
|
-
sidebarExpanded,
|
|
58
|
-
loading,
|
|
59
|
-
groups,
|
|
60
|
-
configuration,
|
|
61
|
-
blocks,
|
|
62
|
-
undocumentedFieldMessage,
|
|
63
|
-
setActiveTab,
|
|
64
|
-
setSidebarExpanded,
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
{children}
|
|
68
|
-
</HandbookContext>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Accesses the Handbook context for tab state and schema configuration.
|
|
74
|
-
*
|
|
75
|
-
* @returns Handbook context values including active tab, groups, and configuration.
|
|
76
|
-
* @throws Error if used outside HandbookProvider.
|
|
77
|
-
*/
|
|
78
|
-
export function useHandbookContext() {
|
|
79
|
-
const handbookContext = useContext(HandbookContext);
|
|
80
|
-
|
|
81
|
-
if (!isDefined(handbookContext)) throw new Error("useHandbookContext can only be used within <HandbookProvider>");
|
|
82
|
-
|
|
83
|
-
return handbookContext;
|
|
84
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { ChevronRightIcon } from "@sanity/icons";
|
|
2
|
-
import { Box, Card, Flex, Stack, Text } from "@sanity/ui";
|
|
3
|
-
|
|
4
|
-
import { useHandbookContext } from "../contexts/handbook-context";
|
|
5
|
-
import { isDefined } from "../lib/utils";
|
|
6
|
-
|
|
7
|
-
export function DocumentTypesOverview() {
|
|
8
|
-
const { groups, setActiveTab, setSidebarExpanded } = useHandbookContext();
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<div style={{ display: "flex", flexDirection: "column", gap: "2.5rem" }}>
|
|
12
|
-
{groups.map((group) => {
|
|
13
|
-
const { title, description, documents } = group;
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<Stack key={title} space={2}>
|
|
17
|
-
<Stack space={3}>
|
|
18
|
-
<Flex align="center" paddingY={1}>
|
|
19
|
-
<Text size={1} weight="medium">
|
|
20
|
-
{title}
|
|
21
|
-
</Text>
|
|
22
|
-
</Flex>
|
|
23
|
-
{isDefined(description) && (
|
|
24
|
-
<Text size={1} muted>
|
|
25
|
-
{description}
|
|
26
|
-
</Text>
|
|
27
|
-
)}
|
|
28
|
-
</Stack>
|
|
29
|
-
<Box marginTop={1}>
|
|
30
|
-
<Stack space={2}>
|
|
31
|
-
{documents.map((document) => {
|
|
32
|
-
const Icon = typeof document.icon === "function" ? document.icon : undefined;
|
|
33
|
-
const documentDescription =
|
|
34
|
-
document.handbook?.description ??
|
|
35
|
-
(typeof document.description === "string" ? document.description : undefined);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Card
|
|
39
|
-
key={document.name}
|
|
40
|
-
as="button"
|
|
41
|
-
padding={3}
|
|
42
|
-
radius={2}
|
|
43
|
-
sizing="border"
|
|
44
|
-
tone="inherit"
|
|
45
|
-
onClick={() => {
|
|
46
|
-
setActiveTab(document.name);
|
|
47
|
-
setSidebarExpanded(false);
|
|
48
|
-
}}
|
|
49
|
-
style={{
|
|
50
|
-
width: "calc(100% + 24px)",
|
|
51
|
-
marginLeft: "-12px",
|
|
52
|
-
textAlign: "left",
|
|
53
|
-
border: "none",
|
|
54
|
-
cursor: "pointer",
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
57
|
-
<Flex align="center" gap={3}>
|
|
58
|
-
<Box flex={1}>
|
|
59
|
-
<Stack space={3}>
|
|
60
|
-
<Flex align="center" gap={2} style={{ marginLeft: "-4px" }}>
|
|
61
|
-
{isDefined(Icon) && (
|
|
62
|
-
<div
|
|
63
|
-
style={{
|
|
64
|
-
display: "flex",
|
|
65
|
-
alignItems: "center",
|
|
66
|
-
justifyContent: "center",
|
|
67
|
-
width: "25px",
|
|
68
|
-
height: "25px",
|
|
69
|
-
color: "var(--card-icon-color)",
|
|
70
|
-
}}
|
|
71
|
-
>
|
|
72
|
-
<Icon />
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
<Text size={1} weight="medium">
|
|
76
|
-
{document.title ?? document.name}
|
|
77
|
-
</Text>
|
|
78
|
-
</Flex>
|
|
79
|
-
{isDefined(documentDescription) && (
|
|
80
|
-
<Box paddingBottom={1}>
|
|
81
|
-
<Text size={1} muted>
|
|
82
|
-
{documentDescription}
|
|
83
|
-
</Text>
|
|
84
|
-
</Box>
|
|
85
|
-
)}
|
|
86
|
-
</Stack>
|
|
87
|
-
</Box>
|
|
88
|
-
<Box flex="none" style={{ opacity: 0.5 }}>
|
|
89
|
-
<Text muted size={1}>
|
|
90
|
-
<ChevronRightIcon />
|
|
91
|
-
</Text>
|
|
92
|
-
</Box>
|
|
93
|
-
</Flex>
|
|
94
|
-
</Card>
|
|
95
|
-
);
|
|
96
|
-
})}
|
|
97
|
-
</Stack>
|
|
98
|
-
</Box>
|
|
99
|
-
</Stack>
|
|
100
|
-
);
|
|
101
|
-
})}
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { Box, Flex, Stack, Text } from "@sanity/ui";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
import { InfoIcon } from "../icons/info";
|
|
5
|
-
import { LightbulbIcon } from "../icons/lightbulb";
|
|
6
|
-
import { TriangleAlertIcon } from "../icons/triangle-alert";
|
|
7
|
-
import { defaultIconProps } from "../lib/icons";
|
|
8
|
-
|
|
9
|
-
function HintRow({ icon, children }: { icon: ReactNode; children: string }) {
|
|
10
|
-
return (
|
|
11
|
-
<Flex align="center" gap={3}>
|
|
12
|
-
<div
|
|
13
|
-
style={{
|
|
14
|
-
display: "flex",
|
|
15
|
-
alignItems: "center",
|
|
16
|
-
flexShrink: 0,
|
|
17
|
-
fontSize: "16px",
|
|
18
|
-
color: "var(--card-icon-color)",
|
|
19
|
-
}}
|
|
20
|
-
>
|
|
21
|
-
{icon}
|
|
22
|
-
</div>
|
|
23
|
-
<Text size={1} muted>
|
|
24
|
-
{children}
|
|
25
|
-
</Text>
|
|
26
|
-
</Flex>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function HowToUse() {
|
|
31
|
-
return (
|
|
32
|
-
<div style={{ display: "flex", flexDirection: "column", gap: "3rem" }}>
|
|
33
|
-
<Stack space={3}>
|
|
34
|
-
<Flex align="center" paddingY={1}>
|
|
35
|
-
<Text size={1} weight="medium">
|
|
36
|
-
Navigating the Handbook
|
|
37
|
-
</Text>
|
|
38
|
-
</Flex>
|
|
39
|
-
<Text size={1} muted>
|
|
40
|
-
Use the sidebar to browse document types and guides. Each entry opens a detailed view in the main panel
|
|
41
|
-
showing field descriptions, examples, and tips.
|
|
42
|
-
</Text>
|
|
43
|
-
</Stack>
|
|
44
|
-
|
|
45
|
-
<Stack space={3}>
|
|
46
|
-
<Flex align="center" paddingY={1}>
|
|
47
|
-
<Text size={1} weight="medium">
|
|
48
|
-
Understanding Fields
|
|
49
|
-
</Text>
|
|
50
|
-
</Flex>
|
|
51
|
-
<Text size={1} muted>
|
|
52
|
-
Each document type lists its fields with a name and type label. Below the name you will find a description
|
|
53
|
-
explaining what the field is for and how to use it. Some fields include an example value shown in italics.
|
|
54
|
-
</Text>
|
|
55
|
-
<Box marginTop={1}>
|
|
56
|
-
<Text size={1} muted style={{ fontStyle: "italic" }}>
|
|
57
|
-
e.g. Acme Inc.
|
|
58
|
-
</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
</Stack>
|
|
61
|
-
|
|
62
|
-
<Stack space={3}>
|
|
63
|
-
<Flex align="center" paddingY={1}>
|
|
64
|
-
<Text size={1} weight="medium">
|
|
65
|
-
Hints and Warnings
|
|
66
|
-
</Text>
|
|
67
|
-
</Flex>
|
|
68
|
-
<Text size={1} muted>
|
|
69
|
-
Look for these icons next to field names. Hover or click them to reveal additional guidance.
|
|
70
|
-
</Text>
|
|
71
|
-
<Stack space={3} marginTop={2}>
|
|
72
|
-
<HintRow icon={<LightbulbIcon {...defaultIconProps} />}>
|
|
73
|
-
Helpful tips and best practices for content editors.
|
|
74
|
-
</HintRow>
|
|
75
|
-
<HintRow icon={<InfoIcon {...defaultIconProps} />}>Additional context about how the field is used.</HintRow>
|
|
76
|
-
<HintRow icon={<TriangleAlertIcon {...defaultIconProps} />}>
|
|
77
|
-
Important warnings about constraints or potential issues.
|
|
78
|
-
</HintRow>
|
|
79
|
-
</Stack>
|
|
80
|
-
</Stack>
|
|
81
|
-
|
|
82
|
-
<Stack space={3}>
|
|
83
|
-
<Flex align="center" paddingY={1}>
|
|
84
|
-
<Text size={1} weight="medium">
|
|
85
|
-
Nested Fields
|
|
86
|
-
</Text>
|
|
87
|
-
</Flex>
|
|
88
|
-
<Text size={1} muted>
|
|
89
|
-
Some fields contain subfields — for example, a content block might include a heading and body text. These are
|
|
90
|
-
shown as collapsible sections you can expand to explore the full structure.
|
|
91
|
-
</Text>
|
|
92
|
-
</Stack>
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement } from "react";
|
|
2
|
-
|
|
3
|
-
import { ContentPanel } from "../components/content-panel";
|
|
4
|
-
import { SidebarGroup } from "../components/sidebar-group";
|
|
5
|
-
import { SidebarTab } from "../components/sidebar-tab";
|
|
6
|
-
import { BookTextIcon } from "../icons/book-text";
|
|
7
|
-
import { LayoutGridIcon } from "../icons/layout-grid";
|
|
8
|
-
import { createSanityIcon } from "../lib/icons";
|
|
9
|
-
import { DocumentTypesOverview } from "./document-types";
|
|
10
|
-
import { HowToUse } from "./how-to-use";
|
|
11
|
-
|
|
12
|
-
interface HandbookEntry {
|
|
13
|
-
/** Unique identifier used as the tab and panel key. */
|
|
14
|
-
name: string;
|
|
15
|
-
/** Display label in the sidebar. */
|
|
16
|
-
title: string;
|
|
17
|
-
/** Icon component shown beside the label. */
|
|
18
|
-
icon: ComponentType;
|
|
19
|
-
/** Optional description shown beneath the content heading. */
|
|
20
|
-
description?: string;
|
|
21
|
-
/** Component rendered in the content panel. */
|
|
22
|
-
component: () => ReactElement;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Built-in getting started pages shown at the top of the sidebar. */
|
|
26
|
-
const entries: HandbookEntry[] = [
|
|
27
|
-
{
|
|
28
|
-
name: "how-to-use-this-handbook",
|
|
29
|
-
title: "How to Use This Handbook",
|
|
30
|
-
icon: createSanityIcon(BookTextIcon),
|
|
31
|
-
description:
|
|
32
|
-
"A quick guide to navigating the Handbook, understanding the information presented for each field, and making the most of tips, hints, and warnings.",
|
|
33
|
-
component: HowToUse,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: "document-types",
|
|
37
|
-
title: "Document Types",
|
|
38
|
-
icon: createSanityIcon(LayoutGridIcon),
|
|
39
|
-
description:
|
|
40
|
-
"An overview of all document types, organised by group. Select a document type to view its fields, descriptions, and examples.",
|
|
41
|
-
component: DocumentTypesOverview,
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
export function GettingStartedTabs() {
|
|
46
|
-
return (
|
|
47
|
-
<SidebarGroup heading="Getting Started">
|
|
48
|
-
{entries.map((entry) => (
|
|
49
|
-
<SidebarTab key={entry.name} id={entry.name} label={entry.title} icon={entry.icon} />
|
|
50
|
-
))}
|
|
51
|
-
</SidebarGroup>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function GettingStartedPanels() {
|
|
56
|
-
return entries.map((entry) => {
|
|
57
|
-
const { name, title, description, component: Component } = entry;
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<ContentPanel key={name} id={name} title={title} description={description}>
|
|
61
|
-
<Component />
|
|
62
|
-
</ContentPanel>
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import type { RefObject } from "react";
|
|
3
|
-
|
|
4
|
-
import { isDefined } from "../lib/utils";
|
|
5
|
-
|
|
6
|
-
interface UseContainerWidthReturn<T extends HTMLElement> {
|
|
7
|
-
/** Ref to attach to the container element being measured. */
|
|
8
|
-
reference: RefObject<T | null>;
|
|
9
|
-
/** Measured width in pixels, or undefined before the first observation. */
|
|
10
|
-
width: number | undefined;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Measures the width of a container element using ResizeObserver.
|
|
15
|
-
*
|
|
16
|
-
* @returns An object containing a ref to attach and the measured width.
|
|
17
|
-
*/
|
|
18
|
-
export function useContainerWidth<T extends HTMLElement>(): UseContainerWidthReturn<T> {
|
|
19
|
-
const reference = useRef<T | null>(null);
|
|
20
|
-
const [width, setWidth] = useState<number | undefined>(undefined);
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const element = reference.current;
|
|
24
|
-
|
|
25
|
-
if (!isDefined(element)) return undefined;
|
|
26
|
-
|
|
27
|
-
const observer = new ResizeObserver((entries) => {
|
|
28
|
-
for (const entry of entries) {
|
|
29
|
-
setWidth(entry.contentRect.width);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
observer.observe(element);
|
|
34
|
-
|
|
35
|
-
return () => {
|
|
36
|
-
observer.disconnect();
|
|
37
|
-
};
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
return { reference, width };
|
|
41
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useClient } from "sanity";
|
|
3
|
-
|
|
4
|
-
import type { SanityHandbook } from "../types";
|
|
5
|
-
|
|
6
|
-
interface UseHandbookConfigurationReturn {
|
|
7
|
-
/** Handbook configuration singleton fetched from the dataset. */
|
|
8
|
-
configuration: SanityHandbook | null;
|
|
9
|
-
/** Whether configuration data is still loading from the dataset. */
|
|
10
|
-
loading: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** GROQ query that fetches the handbook singleton with expanded guide references. */
|
|
14
|
-
const query = `*[_type == "handbook.handbook"][0]{
|
|
15
|
-
groups[]{
|
|
16
|
-
_key,
|
|
17
|
-
title,
|
|
18
|
-
guides[]{
|
|
19
|
-
_key,
|
|
20
|
-
...@->{
|
|
21
|
-
_id,
|
|
22
|
-
_type,
|
|
23
|
-
title,
|
|
24
|
-
description,
|
|
25
|
-
content[]{
|
|
26
|
-
...,
|
|
27
|
-
_type == "handbook.image" => {
|
|
28
|
-
...,
|
|
29
|
-
"asset": { "url": asset.asset->url }
|
|
30
|
-
},
|
|
31
|
-
_type == "handbook.video" => {
|
|
32
|
-
...,
|
|
33
|
-
"asset": { "url": asset.asset->url }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}`;
|
|
40
|
-
|
|
41
|
-
/** GROQ filter used to subscribe to real-time changes on handbook documents. */
|
|
42
|
-
const listenQuery = '*[_type == "handbook.handbook" || _type == "handbook.guide"]';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Fetches the handbook configuration singleton from the dataset with real-time updates.
|
|
46
|
-
* Expands guide references within each group.
|
|
47
|
-
*
|
|
48
|
-
* @returns An object containing the configuration and a loading flag.
|
|
49
|
-
*/
|
|
50
|
-
export function useHandbookConfiguration(): UseHandbookConfigurationReturn {
|
|
51
|
-
const client = useClient({ apiVersion: "2026-01-01" });
|
|
52
|
-
const [configuration, setConfiguration] = useState<SanityHandbook | null>(null);
|
|
53
|
-
const [loading, setLoading] = useState(true);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
async function fetchConfiguration() {
|
|
57
|
-
const result = await client.fetch<SanityHandbook | null>(query);
|
|
58
|
-
setConfiguration(result);
|
|
59
|
-
setLoading(false);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
void fetchConfiguration();
|
|
63
|
-
|
|
64
|
-
const subscription = client.listen(listenQuery).subscribe(() => {
|
|
65
|
-
void fetchConfiguration();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
subscription.unsubscribe();
|
|
70
|
-
};
|
|
71
|
-
}, [client]);
|
|
72
|
-
|
|
73
|
-
return { configuration, loading };
|
|
74
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { useCurrentUser } from "sanity";
|
|
2
|
-
|
|
3
|
-
import { isPermittedEditor } from "../lib/utils";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Checks whether the current user is permitted to edit handbook content.
|
|
7
|
-
* Returns true if no editors list is provided (unrestricted) or if the
|
|
8
|
-
* current user's email is in the list.
|
|
9
|
-
*
|
|
10
|
-
* @public
|
|
11
|
-
* @param editors - Optional list of permitted email addresses.
|
|
12
|
-
* @returns Whether the current user can edit handbook documents.
|
|
13
|
-
*/
|
|
14
|
-
export function useIsHandbookEditor(editors?: string[]) {
|
|
15
|
-
const user = useCurrentUser();
|
|
16
|
-
|
|
17
|
-
return isPermittedEditor(editors, user?.email);
|
|
18
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function BookOpenTextIcon(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="M12 7v14" />
|
|
15
|
-
<path d="M16 12h2" />
|
|
16
|
-
<path d="M16 8h2" />
|
|
17
|
-
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
|
|
18
|
-
<path d="M6 12h2" />
|
|
19
|
-
<path d="M6 8h2" />
|
|
20
|
-
</svg>
|
|
21
|
-
);
|
|
22
|
-
}
|
package/src/icons/book-text.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function BookTextIcon(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="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
|
|
15
|
-
<path d="M8 11h8" />
|
|
16
|
-
<path d="M8 7h6" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
package/src/icons/code.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export function CodeIcon(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 18 6-6-6-6" />
|
|
15
|
-
<path d="m8 6-6 6 6 6" />
|
|
16
|
-
</svg>
|
|
17
|
-
);
|
|
18
|
-
}
|