sanity-plugin-references 1.0.0 → 1.0.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 +2 -0
- package/dist/index.js +74 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/pane.tsx +91 -84
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
Displays a badge with the reference count and a custom pane with the full list.
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
13
|
- **Reference Badge**: Shows count of documents referencing the current document
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { LinkIcon as LinkIcon2 } from "@sanity/icons";
|
|
|
4
4
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useRouter } from "sanity/router";
|
|
7
|
-
import { Flex, Spinner, Card, Text,
|
|
7
|
+
import { Flex, Spinner, Card, Text, Box, Select, Inline, Radio, Button, TextInput, Stack, Badge } from "@sanity/ui";
|
|
8
8
|
const ReferencesBadge = (props) => {
|
|
9
9
|
const { id, published, draft } = props, documentId = published?._id || draft?._id || id, [count, setCount] = useState(null), client = useClient({ apiVersion: "2026-01-05" });
|
|
10
10
|
return useEffect(() => {
|
|
@@ -73,87 +73,89 @@ const ReferencesBadge = (props) => {
|
|
|
73
73
|
/* @__PURE__ */ jsx(LinkIcon, { style: { fontSize: 32, opacity: 0.4 } }),
|
|
74
74
|
/* @__PURE__ */ jsx(Text, { muted: !0, children: "No document reference this one" })
|
|
75
75
|
] }) });
|
|
76
|
-
const useSelectForFilters = documentTypes.length
|
|
77
|
-
return /* @__PURE__ */ jsxs(
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
value: typeFilter,
|
|
83
|
-
onChange: (e) => setTypeFilter(e.currentTarget.value),
|
|
84
|
-
fontSize: 1,
|
|
85
|
-
padding: 2,
|
|
86
|
-
style: { minWidth: 150, flexShrink: 0 },
|
|
87
|
-
children: [
|
|
88
|
-
/* @__PURE__ */ jsx("option", { value: "all", children: "All types" }),
|
|
89
|
-
documentTypes.map((type) => {
|
|
90
|
-
const schemaType = getSchemaType(type);
|
|
91
|
-
return /* @__PURE__ */ jsx("option", { value: type, children: schemaType?.title || type }, type);
|
|
92
|
-
})
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
) : /* @__PURE__ */ jsxs(Inline, { space: 2, style: { flexShrink: 0 }, children: [
|
|
96
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1, as: "label", style: { cursor: "pointer", whiteSpace: "nowrap" }, children: [
|
|
97
|
-
/* @__PURE__ */ jsx(Radio, { checked: typeFilter === "all", onChange: () => setTypeFilter("all") }),
|
|
98
|
-
/* @__PURE__ */ jsx(Text, { size: 1, weight: typeFilter === "all" ? "semibold" : "regular", children: "All" })
|
|
99
|
-
] }),
|
|
100
|
-
documentTypes.map((type) => {
|
|
101
|
-
const schemaType = getSchemaType(type);
|
|
102
|
-
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1, as: "label", style: { cursor: "pointer", whiteSpace: "nowrap" }, children: [
|
|
103
|
-
/* @__PURE__ */ jsx(Radio, { checked: typeFilter === type, onChange: () => setTypeFilter(type) }),
|
|
104
|
-
/* @__PURE__ */ jsx(Text, { size: 1, weight: typeFilter === type ? "semibold" : "regular", children: schemaType?.title || type })
|
|
105
|
-
] }, type);
|
|
106
|
-
})
|
|
107
|
-
] }),
|
|
108
|
-
/* @__PURE__ */ jsx(Box, { flex: 1 }),
|
|
109
|
-
/* @__PURE__ */ jsxs(Flex, { gap: 1, style: { flexShrink: 0 }, children: [
|
|
110
|
-
/* @__PURE__ */ jsxs(Select, { value: sortBy, onChange: (e) => setSortBy(e.currentTarget.value), fontSize: 1, padding: 2, children: [
|
|
111
|
-
/* @__PURE__ */ jsx("option", { value: "updatedAt", children: "Updated" }),
|
|
112
|
-
/* @__PURE__ */ jsx("option", { value: "type", children: "Type" }),
|
|
113
|
-
/* @__PURE__ */ jsx("option", { value: "title", children: "Title" })
|
|
114
|
-
] }),
|
|
115
|
-
/* @__PURE__ */ jsx(
|
|
116
|
-
Button,
|
|
76
|
+
const useSelectForFilters = documentTypes.length >= 4;
|
|
77
|
+
return /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { height: "100%", overflow: "hidden" }, children: [
|
|
78
|
+
/* @__PURE__ */ jsxs(Box, { style: { flexShrink: 0 }, children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Card, { borderBottom: !0, padding: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", wrap: "nowrap", children: [
|
|
80
|
+
useSelectForFilters ? /* @__PURE__ */ jsx(Box, { style: { minWidth: 150, maxWidth: 250 }, children: /* @__PURE__ */ jsxs(
|
|
81
|
+
Select,
|
|
117
82
|
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
onClick: () => setSortDesc((d) => !d),
|
|
121
|
-
title: sortDesc ? "Descending" : "Ascending",
|
|
83
|
+
value: typeFilter,
|
|
84
|
+
onChange: (e) => setTypeFilter(e.currentTarget.value),
|
|
122
85
|
fontSize: 1,
|
|
123
|
-
padding: 2
|
|
86
|
+
padding: 2,
|
|
87
|
+
style: { width: "100%" },
|
|
88
|
+
children: [
|
|
89
|
+
/* @__PURE__ */ jsx("option", { value: "all", children: "All types" }),
|
|
90
|
+
documentTypes.map((type) => {
|
|
91
|
+
const schemaType = getSchemaType(type);
|
|
92
|
+
return /* @__PURE__ */ jsx("option", { value: type, children: schemaType?.title || type }, type);
|
|
93
|
+
})
|
|
94
|
+
]
|
|
124
95
|
}
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
96
|
+
) }) : /* @__PURE__ */ jsxs(Inline, { space: 2, style: { flexShrink: 0 }, children: [
|
|
97
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1, as: "label", style: { cursor: "pointer", whiteSpace: "nowrap" }, children: [
|
|
98
|
+
/* @__PURE__ */ jsx(Radio, { checked: typeFilter === "all", onChange: () => setTypeFilter("all") }),
|
|
99
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: typeFilter === "all" ? "semibold" : "regular", children: "All" })
|
|
100
|
+
] }),
|
|
101
|
+
documentTypes.map((type) => {
|
|
102
|
+
const schemaType = getSchemaType(type);
|
|
103
|
+
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1, as: "label", style: { cursor: "pointer", whiteSpace: "nowrap" }, children: [
|
|
104
|
+
/* @__PURE__ */ jsx(Radio, { checked: typeFilter === type, onChange: () => setTypeFilter(type) }),
|
|
105
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: typeFilter === type ? "semibold" : "regular", children: schemaType?.title || type })
|
|
106
|
+
] }, type);
|
|
107
|
+
})
|
|
108
|
+
] }),
|
|
109
|
+
/* @__PURE__ */ jsx(Box, { flex: 1 }),
|
|
110
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 1, style: { flexShrink: 0 }, children: [
|
|
111
|
+
/* @__PURE__ */ jsxs(Select, { value: sortBy, onChange: (e) => setSortBy(e.currentTarget.value), fontSize: 1, padding: 2, children: [
|
|
112
|
+
/* @__PURE__ */ jsx("option", { value: "updatedAt", children: "Updated" }),
|
|
113
|
+
/* @__PURE__ */ jsx("option", { value: "type", children: "Type" }),
|
|
114
|
+
/* @__PURE__ */ jsx("option", { value: "title", children: "Title" })
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ jsx(
|
|
117
|
+
Button,
|
|
118
|
+
{
|
|
119
|
+
icon: SortIcon,
|
|
120
|
+
mode: "ghost",
|
|
121
|
+
onClick: () => setSortDesc((d) => !d),
|
|
122
|
+
title: sortDesc ? "Descending" : "Ascending",
|
|
123
|
+
fontSize: 1,
|
|
124
|
+
padding: 2
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
] })
|
|
135
128
|
] }) }),
|
|
136
|
-
/* @__PURE__ */ jsx(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
129
|
+
/* @__PURE__ */ jsx(Card, { borderBottom: !0, paddingX: 3, paddingY: 0, children: /* @__PURE__ */ jsxs(Flex, { align: "stretch", style: { minHeight: 33 }, children: [
|
|
130
|
+
/* @__PURE__ */ jsx(Flex, { align: "center", paddingY: 2, style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
131
|
+
filteredDocuments.length,
|
|
132
|
+
" of ",
|
|
133
|
+
documents.length,
|
|
134
|
+
" document",
|
|
135
|
+
documents.length !== 1 ? "s" : ""
|
|
136
|
+
] }) }),
|
|
137
|
+
/* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 180, borderLeft: "1px solid var(--card-border-color)", paddingLeft: 4, marginLeft: 12, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx(
|
|
138
|
+
TextInput,
|
|
139
|
+
{
|
|
140
|
+
icon: SearchIcon,
|
|
141
|
+
placeholder: "Search...",
|
|
142
|
+
value: searchQuery,
|
|
143
|
+
onChange: (e) => setSearchQuery(e.currentTarget.value),
|
|
144
|
+
fontSize: 1,
|
|
145
|
+
padding: 2,
|
|
146
|
+
border: !1,
|
|
147
|
+
style: { boxShadow: "none", width: "100%" }
|
|
148
|
+
}
|
|
149
|
+
) })
|
|
150
|
+
] }) })
|
|
151
|
+
] }),
|
|
150
152
|
/* @__PURE__ */ jsx(Box, { style: { flex: 1, overflow: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 1, padding: 2, children: filteredDocuments.length === 0 ? /* @__PURE__ */ jsx(Card, { padding: 4, children: /* @__PURE__ */ jsx(Text, { muted: !0, align: "center", children: "No documents match your filters" }) }) : filteredDocuments.map((doc) => {
|
|
151
153
|
const schemaType = getSchemaType(doc._type);
|
|
152
154
|
return /* @__PURE__ */ jsx(
|
|
153
155
|
Card,
|
|
154
156
|
{
|
|
155
157
|
as: "button",
|
|
156
|
-
padding:
|
|
158
|
+
padding: 1,
|
|
157
159
|
radius: 2,
|
|
158
160
|
onClick: () => router.navigateIntent("edit", { id: doc._id, type: doc._type }),
|
|
159
161
|
style: { cursor: "pointer", textAlign: "left", width: "100%" },
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/badge.tsx","../src/pane.tsx","../src/index.ts"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport { type DocumentBadgeComponent, useClient } from 'sanity'\n\n/**\n * Badge component that displays the count of documents referencing the current document\n */\nexport const ReferencesBadge: DocumentBadgeComponent = (props) => {\n const { id, published, draft } = props\n const documentId = published?._id || draft?._id || id\n const [count, setCount] = useState<number | null>(null)\n\n const client = useClient({ apiVersion: '2026-01-05' })\n\n useEffect(() => {\n if (!documentId) return\n\n const cleanId = documentId.replace(/^drafts\\./, '')\n\n // Get count of documents referencing this document\n client.fetch<number>(\n /* groq */`count(*[references($id) && !(_id in path(\"drafts.**\"))])`,\n { id: cleanId }\n ).then(setCount)\n\n // Subscribe to changes\n const subscription = client.listen(\n /* groq */`*[references($id)]`,\n { id: cleanId }\n ).subscribe(() => {\n client.fetch<number>(\n /* groq */`count(*[references($id) && !(_id in path(\"drafts.**\"))])`,\n { id: cleanId }\n ).then(setCount)\n })\n\n return () => subscription.unsubscribe()\n }, [documentId, client])\n\n // Don't show badge if no references or still loading\n if (count === null || count === 0) {\n return null\n }\n\n return {\n label: `${count} reference${count > 1 ? 's' : ''}`,\n title: `${count} document${count > 1 ? 's' : ''} reference this. See \"References\" tab.`,\n color: 'primary',\n }\n}\n","import { useMemo, useState, useEffect, useCallback } from 'react'\nimport { useClient, useSchema, Preview } from 'sanity'\nimport { type UserViewComponent } from 'sanity/structure'\nimport { useRouter } from 'sanity/router'\nimport { Box, Card, Flex, Inline, Stack, Text, TextInput, Select, Spinner, Button, Badge, Radio } from '@sanity/ui'\nimport { SearchIcon, SortIcon, LinkIcon } from '@sanity/icons'\n\ninterface ReferencingDocument {\n _id: string\n _type: string\n _updatedAt: string\n title?: string\n}\ntype SortOption = 'updatedAt' | 'type' | 'title'\n\n/**\n * Structure Builder view component that displays documents referencing the current document.\n * Use this component with S.view.component() in your structure configuration.\n *\n * @example\n * ```ts\n * import { references, referencesView } from 'sanity-plugin-references'\n * import { LinkIcon } from '@sanity/icons'\n *\n * S.document().views([\n * S.view.form(),\n * referencesView(S),\n * ])\n * ```\n */\nexport const ReferencesPane: UserViewComponent = (props) => {\n const { documentId } = props\n const client = useClient({ apiVersion: '2026-01-05' })\n const schema = useSchema()\n const router = useRouter()\n\n const [documents, setDocuments] = useState<ReferencingDocument[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n const [searchQuery, setSearchQuery] = useState('')\n const [typeFilter, setTypeFilter] = useState('all')\n const [sortBy, setSortBy] = useState<SortOption>('updatedAt')\n const [sortDesc, setSortDesc] = useState(true)\n\n const cleanId = useMemo(() => documentId.replace(/^drafts\\./, ''), [documentId])\n\n const fetchDocuments = useCallback(async () => {\n if (!cleanId) return\n setLoading(true)\n setError(null)\n try {\n const results = await client.fetch<ReferencingDocument[]>(\n `*[references($id) && !(_id in path(\"drafts.**\"))] {\n _id, _type, _updatedAt, \"title\": coalesce(title, name, \"Untitled\")\n } | order(_updatedAt desc)`,\n { id: cleanId }\n )\n setDocuments(results)\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch references')\n } finally {\n setLoading(false)\n }\n }, [client, cleanId])\n\n useEffect(() => { fetchDocuments() }, [fetchDocuments])\n\n const documentTypes = useMemo(() =>\n [...new Set(documents.map((doc: ReferencingDocument) => doc._type))].sort(),\n [documents]\n )\n\n const filteredDocuments = useMemo(() => {\n const query = searchQuery.toLowerCase().trim()\n return documents\n .filter((doc: ReferencingDocument) => typeFilter === 'all' || doc._type === typeFilter)\n .filter((doc: ReferencingDocument) => !query || doc.title?.toLowerCase().includes(query) || doc._type.toLowerCase().includes(query))\n .sort((a: ReferencingDocument, b: ReferencingDocument) => {\n const cmp = sortBy === 'updatedAt'\n ? new Date(a._updatedAt).getTime() - new Date(b._updatedAt).getTime()\n : sortBy === 'type'\n ? a._type.localeCompare(b._type)\n : (a.title || '').localeCompare(b.title || '')\n return sortDesc ? -cmp : cmp\n })\n }, [documents, typeFilter, searchQuery, sortBy, sortDesc])\n\n const getSchemaType = useCallback((type: string) => schema.get(type), [schema])\n\n if (loading) {\n return <Flex align=\"center\" justify=\"center\" padding={5}><Spinner muted /></Flex>\n }\n\n if (error) {\n return <Card padding={4} tone=\"critical\"><Text>{error}</Text></Card>\n }\n\n if (documents.length === 0) {\n return (\n <Card padding={5}>\n <Flex align=\"center\" justify=\"center\" direction=\"column\" gap={3}>\n <LinkIcon style={{ fontSize: 32, opacity: 0.4 }} />\n <Text muted>No document reference this one</Text>\n </Flex>\n </Card>\n )\n }\n\n const useSelectForFilters = documentTypes.length > 5\n\n return (\n <Stack space={0} style={{ height: '100%' }}>\n {/* Toolbar */}\n <Card borderBottom padding={2}>\n <Flex gap={2} align=\"center\" wrap=\"nowrap\">\n {useSelectForFilters ? (\n <Select\n value={typeFilter}\n onChange={e => setTypeFilter(e.currentTarget.value)}\n fontSize={1}\n padding={2}\n style={{ minWidth: 150, flexShrink: 0 }}\n >\n <option value=\"all\">All types</option>\n {documentTypes.map((type: string) => {\n const schemaType = getSchemaType(type)\n return (\n <option key={type} value={type}>\n {schemaType?.title || type}\n </option>\n )\n })}\n </Select>\n ) : (\n <Inline space={2} style={{ flexShrink: 0 }}>\n <Flex align=\"center\" gap={1} as=\"label\" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>\n <Radio checked={typeFilter === 'all'} onChange={() => setTypeFilter('all')} />\n <Text size={1} weight={typeFilter === 'all' ? 'semibold' : 'regular'}>All</Text>\n </Flex>\n {documentTypes.map((type: string) => {\n const schemaType = getSchemaType(type)\n return (\n <Flex key={type} align=\"center\" gap={1} as=\"label\" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>\n <Radio checked={typeFilter === type} onChange={() => setTypeFilter(type)} />\n <Text size={1} weight={typeFilter === type ? 'semibold' : 'regular'}>\n {schemaType?.title || type}\n </Text>\n </Flex>\n )\n })}\n </Inline>\n )}\n <Box flex={1} />\n <Flex gap={1} style={{ flexShrink: 0 }}>\n <Select value={sortBy} onChange={e => setSortBy(e.currentTarget.value as SortOption)} fontSize={1} padding={2}>\n <option value=\"updatedAt\">Updated</option>\n <option value=\"type\">Type</option>\n <option value=\"title\">Title</option>\n </Select>\n <Button\n icon={SortIcon}\n mode=\"ghost\"\n onClick={() => setSortDesc((d: boolean) => !d)}\n title={sortDesc ? 'Descending' : 'Ascending'}\n fontSize={1}\n padding={2}\n />\n </Flex>\n </Flex>\n </Card>\n\n {/* Count + Search */}\n <Card borderBottom paddingX={3} paddingY={0}>\n <Flex align=\"stretch\" style={{ minHeight: 33 }}>\n <Flex align=\"center\" paddingY={2} style={{ flexShrink: 0 }}>\n <Text size={1} muted>\n {filteredDocuments.length} of {documents.length} document{documents.length !== 1 ? 's' : ''}\n </Text>\n </Flex>\n <Box style={{ flex: 1, minWidth: 180, borderLeft: '1px solid var(--card-border-color)', paddingLeft: 4, marginLeft: 12, display: 'flex', alignItems: 'center' }}>\n <TextInput\n icon={SearchIcon}\n placeholder=\"Search...\"\n value={searchQuery}\n onChange={e => setSearchQuery(e.currentTarget.value)}\n fontSize={1}\n padding={2}\n border={false}\n style={{ boxShadow: 'none', width: '100%' }}\n />\n </Box>\n </Flex>\n </Card>\n\n {/* Document list */}\n <Box style={{ flex: 1, overflow: 'auto' }}>\n <Stack space={1} padding={2}>\n {filteredDocuments.length === 0 ? (\n <Card padding={4}><Text muted align=\"center\">No documents match your filters</Text></Card>\n ) : (\n filteredDocuments.map((doc: ReferencingDocument) => {\n const schemaType = getSchemaType(doc._type)\n return (\n <Card\n key={doc._id}\n as=\"button\"\n padding={2}\n radius={2}\n onClick={() => router.navigateIntent('edit', { id: doc._id, type: doc._type })}\n style={{ cursor: 'pointer', textAlign: 'left', width: '100%' }}\n >\n <Flex gap={3} align=\"center\">\n <Box flex={1}>\n {schemaType ? (\n <Preview schemaType={schemaType} value={doc} layout=\"default\" />\n ) : (\n <Text weight=\"medium\">{doc.title}</Text>\n )}\n </Box>\n <Badge tone=\"primary\" fontSize={0}>{schemaType?.title || doc._type}</Badge>\n </Flex>\n </Card>\n )\n })\n )}\n </Stack>\n </Box>\n </Stack>\n )\n}\n","import { definePlugin } from 'sanity'\nimport type { StructureBuilder } from 'sanity/structure'\nimport { LinkIcon } from '@sanity/icons'\nimport type { ComponentType } from 'react'\nimport { ReferencesBadge } from './badge'\nimport { ReferencesPane } from './pane'\n\ninterface ReferencesConfig {\n /**\n * Document types to exclude from showing the References badge\n * @example ['media.tag', 'sanity.imageAsset']\n */\n exclude?: string[]\n}\n\n/**\n * Plugin that shows documents referencing the current document.\n * Displays a badge with the reference count.\n *\n * To add the References tab to your documents, use the Structure Builder API\n * with the exported `ReferencesPane` component.\n *\n * @example Basic usage (badge only)\n * ```ts\n * // sanity.config.ts\n * import { references } from 'sanity-plugin-references'\n *\n * export default defineConfig({\n * plugins: [references()],\n * })\n * ```\n *\n * @example Exclude specific document types from showing the badge\n * ```ts\n * references({ exclude: ['media.tag', 'sanity.imageAsset'] })\n * ```\n *\n * @example Adding the References tab with Structure Builder\n * ```ts\n * // sanity.config.ts\n * import { structureTool } from 'sanity/structure'\n * import { references, ReferencesPane } from 'sanity-plugin-references'\n * import { LinkIcon } from '@sanity/icons'\n *\n * export default defineConfig({\n * plugins: [\n * references(),\n * structureTool({\n * defaultDocumentNode: (S, { schemaType }) => {\n * return S.document().views([\n * S.view.form(),\n * S.view.component(ReferencesPane).title('References').icon(LinkIcon),\n * ])\n * },\n * }),\n * ],\n * })\n * ```\n *\n * @example Using the referencesView helper\n * ```ts\n * import { references, referencesView } from 'sanity-plugin-references'\n *\n * structureTool({\n * defaultDocumentNode: (S) => S.document().views([\n * S.view.form(),\n * referencesView(S),\n * ]),\n * })\n * ```\n */\nexport const references = definePlugin<ReferencesConfig | void>((config) => {\n const excludeTypes = config?.exclude || []\n\n return {\n name: 'references',\n document: {\n badges: (prev, context) => {\n // Don't show badge for excluded types\n if (excludeTypes.includes(context.schemaType)) {\n return prev\n }\n return [...prev, ReferencesBadge]\n },\n },\n }\n})\n\nexport type { ReferencesConfig }\n\n\n/**\n * Helper to create a References view for Structure Builder.\n *\n * @example\n * ```ts\n * S.document().views([\n * S.view.form(),\n * referencesView(S),\n * ])\n * ```\n *\n * @example With custom title and icon\n * ```ts\n * referencesView(S, { title: 'Incoming Links', icon: MyIcon })\n * ```\n */\nexport function referencesView(S: StructureBuilder, options?: {\n title?: string\n icon?: ComponentType\n}) {\n return S.view\n .component(ReferencesPane)\n .title(options?.title || 'References')\n .icon(options?.icon || LinkIcon)\n}\n\nexport { ReferencesPane } from './pane'\nexport { ReferencesBadge } from './badge'\nexport { LinkIcon as ReferencesIcon } from '@sanity/icons'\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/badge.tsx","../src/pane.tsx","../src/index.ts"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport { type DocumentBadgeComponent, useClient } from 'sanity'\n\n/**\n * Badge component that displays the count of documents referencing the current document\n */\nexport const ReferencesBadge: DocumentBadgeComponent = (props) => {\n const { id, published, draft } = props\n const documentId = published?._id || draft?._id || id\n const [count, setCount] = useState<number | null>(null)\n\n const client = useClient({ apiVersion: '2026-01-05' })\n\n useEffect(() => {\n if (!documentId) return\n\n const cleanId = documentId.replace(/^drafts\\./, '')\n\n // Get count of documents referencing this document\n client.fetch<number>(\n /* groq */`count(*[references($id) && !(_id in path(\"drafts.**\"))])`,\n { id: cleanId }\n ).then(setCount)\n\n // Subscribe to changes\n const subscription = client.listen(\n /* groq */`*[references($id)]`,\n { id: cleanId }\n ).subscribe(() => {\n client.fetch<number>(\n /* groq */`count(*[references($id) && !(_id in path(\"drafts.**\"))])`,\n { id: cleanId }\n ).then(setCount)\n })\n\n return () => subscription.unsubscribe()\n }, [documentId, client])\n\n // Don't show badge if no references or still loading\n if (count === null || count === 0) {\n return null\n }\n\n return {\n label: `${count} reference${count > 1 ? 's' : ''}`,\n title: `${count} document${count > 1 ? 's' : ''} reference this. See \"References\" tab.`,\n color: 'primary',\n }\n}\n","import { useMemo, useState, useEffect, useCallback } from 'react'\nimport { useClient, useSchema, Preview } from 'sanity'\nimport { type UserViewComponent } from 'sanity/structure'\nimport { useRouter } from 'sanity/router'\nimport { Box, Card, Flex, Inline, Stack, Text, TextInput, Select, Spinner, Button, Badge, Radio } from '@sanity/ui'\nimport { SearchIcon, SortIcon, LinkIcon } from '@sanity/icons'\n\ninterface ReferencingDocument {\n _id: string\n _type: string\n _updatedAt: string\n title?: string\n}\ntype SortOption = 'updatedAt' | 'type' | 'title'\n\n/**\n * Structure Builder view component that displays documents referencing the current document.\n * Use this component with S.view.component() in your structure configuration.\n *\n * @example\n * ```ts\n * import { references, referencesView } from 'sanity-plugin-references'\n * import { LinkIcon } from '@sanity/icons'\n *\n * S.document().views([\n * S.view.form(),\n * referencesView(S),\n * ])\n * ```\n */\nexport const ReferencesPane: UserViewComponent = (props) => {\n const { documentId } = props\n const client = useClient({ apiVersion: '2026-01-05' })\n const schema = useSchema()\n const router = useRouter()\n\n const [documents, setDocuments] = useState<ReferencingDocument[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n const [searchQuery, setSearchQuery] = useState('')\n const [typeFilter, setTypeFilter] = useState('all')\n const [sortBy, setSortBy] = useState<SortOption>('updatedAt')\n const [sortDesc, setSortDesc] = useState(true)\n\n const cleanId = useMemo(() => documentId.replace(/^drafts\\./, ''), [documentId])\n\n const fetchDocuments = useCallback(async () => {\n if (!cleanId) return\n setLoading(true)\n setError(null)\n try {\n const results = await client.fetch<ReferencingDocument[]>(\n `*[references($id) && !(_id in path(\"drafts.**\"))] {\n _id, _type, _updatedAt, \"title\": coalesce(title, name, \"Untitled\")\n } | order(_updatedAt desc)`,\n { id: cleanId }\n )\n setDocuments(results)\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch references')\n } finally {\n setLoading(false)\n }\n }, [client, cleanId])\n\n useEffect(() => { fetchDocuments() }, [fetchDocuments])\n\n const documentTypes = useMemo(() =>\n [...new Set(documents.map((doc: ReferencingDocument) => doc._type))].sort(),\n [documents]\n )\n\n const filteredDocuments = useMemo(() => {\n const query = searchQuery.toLowerCase().trim()\n return documents\n .filter((doc: ReferencingDocument) => typeFilter === 'all' || doc._type === typeFilter)\n .filter((doc: ReferencingDocument) => !query || doc.title?.toLowerCase().includes(query) || doc._type.toLowerCase().includes(query))\n .sort((a: ReferencingDocument, b: ReferencingDocument) => {\n const cmp = sortBy === 'updatedAt'\n ? new Date(a._updatedAt).getTime() - new Date(b._updatedAt).getTime()\n : sortBy === 'type'\n ? a._type.localeCompare(b._type)\n : (a.title || '').localeCompare(b.title || '')\n return sortDesc ? -cmp : cmp\n })\n }, [documents, typeFilter, searchQuery, sortBy, sortDesc])\n\n const getSchemaType = useCallback((type: string) => schema.get(type), [schema])\n\n if (loading) {\n return <Flex align=\"center\" justify=\"center\" padding={5}><Spinner muted /></Flex>\n }\n\n if (error) {\n return <Card padding={4} tone=\"critical\"><Text>{error}</Text></Card>\n }\n\n if (documents.length === 0) {\n return (\n <Card padding={5}>\n <Flex align=\"center\" justify=\"center\" direction=\"column\" gap={3}>\n <LinkIcon style={{ fontSize: 32, opacity: 0.4 }} />\n <Text muted>No document reference this one</Text>\n </Flex>\n </Card>\n )\n }\n\n const useSelectForFilters = documentTypes.length >= 4\n\n return (\n <Flex direction=\"column\" style={{ height: '100%', overflow: 'hidden' }}>\n {/* Sticky Filters and Search */}\n <Box style={{ flexShrink: 0 }}>\n {/* Toolbar */}\n <Card borderBottom padding={2}>\n <Flex gap={2} align=\"center\" wrap=\"nowrap\">\n {useSelectForFilters ? (\n <Box style={{ minWidth: 150, maxWidth: 250 }}>\n <Select\n value={typeFilter}\n onChange={e => setTypeFilter(e.currentTarget.value)}\n fontSize={1}\n padding={2}\n style={{ width: '100%' }}\n >\n <option value=\"all\">All types</option>\n {documentTypes.map((type: string) => {\n const schemaType = getSchemaType(type)\n return (\n <option key={type} value={type}>\n {schemaType?.title || type}\n </option>\n )\n })}\n </Select>\n </Box>\n ) : (\n <Inline space={2} style={{ flexShrink: 0 }}>\n <Flex align=\"center\" gap={1} as=\"label\" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>\n <Radio checked={typeFilter === 'all'} onChange={() => setTypeFilter('all')} />\n <Text size={1} weight={typeFilter === 'all' ? 'semibold' : 'regular'}>All</Text>\n </Flex>\n {documentTypes.map((type: string) => {\n const schemaType = getSchemaType(type)\n return (\n <Flex key={type} align=\"center\" gap={1} as=\"label\" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>\n <Radio checked={typeFilter === type} onChange={() => setTypeFilter(type)} />\n <Text size={1} weight={typeFilter === type ? 'semibold' : 'regular'}>\n {schemaType?.title || type}\n </Text>\n </Flex>\n )\n })}\n </Inline>\n )}\n <Box flex={1} />\n <Flex gap={1} style={{ flexShrink: 0 }}>\n <Select value={sortBy} onChange={e => setSortBy(e.currentTarget.value as SortOption)} fontSize={1} padding={2}>\n <option value=\"updatedAt\">Updated</option>\n <option value=\"type\">Type</option>\n <option value=\"title\">Title</option>\n </Select>\n <Button\n icon={SortIcon}\n mode=\"ghost\"\n onClick={() => setSortDesc((d: boolean) => !d)}\n title={sortDesc ? 'Descending' : 'Ascending'}\n fontSize={1}\n padding={2}\n />\n </Flex>\n </Flex>\n </Card>\n\n {/* Count + Search */}\n <Card borderBottom paddingX={3} paddingY={0}>\n <Flex align=\"stretch\" style={{ minHeight: 33 }}>\n <Flex align=\"center\" paddingY={2} style={{ flexShrink: 0 }}>\n <Text size={1} muted>\n {filteredDocuments.length} of {documents.length} document{documents.length !== 1 ? 's' : ''}\n </Text>\n </Flex>\n <Box style={{ flex: 1, minWidth: 180, borderLeft: '1px solid var(--card-border-color)', paddingLeft: 4, marginLeft: 12, display: 'flex', alignItems: 'center' }}>\n <TextInput\n icon={SearchIcon}\n placeholder=\"Search...\"\n value={searchQuery}\n onChange={e => setSearchQuery(e.currentTarget.value)}\n fontSize={1}\n padding={2}\n border={false}\n style={{ boxShadow: 'none', width: '100%' }}\n />\n </Box>\n </Flex>\n </Card>\n </Box>\n\n {/* Document list */}\n <Box style={{ flex: 1, overflow: 'auto' }}>\n <Stack space={1} padding={2}>\n {filteredDocuments.length === 0 ? (\n <Card padding={4}>\n <Text muted align=\"center\">No documents match your filters</Text>\n </Card>\n ) : (\n filteredDocuments.map((doc: ReferencingDocument) => {\n const schemaType = getSchemaType(doc._type)\n return (\n <Card\n key={doc._id}\n as=\"button\"\n padding={1}\n radius={2}\n onClick={() => router.navigateIntent('edit', { id: doc._id, type: doc._type })}\n style={{ cursor: 'pointer', textAlign: 'left', width: '100%' }}\n >\n <Flex gap={3} align=\"center\">\n <Box flex={1}>\n {schemaType ? (\n <Preview schemaType={schemaType} value={doc} layout=\"default\" />\n ) : (\n <Text weight=\"medium\">{doc.title}</Text>\n )}\n </Box>\n <Badge tone=\"primary\" fontSize={0}>{schemaType?.title || doc._type}</Badge>\n </Flex>\n </Card>\n )\n })\n )}\n </Stack>\n </Box>\n </Flex>\n )\n}\n","import { definePlugin } from 'sanity'\nimport type { StructureBuilder } from 'sanity/structure'\nimport { LinkIcon } from '@sanity/icons'\nimport type { ComponentType } from 'react'\nimport { ReferencesBadge } from './badge'\nimport { ReferencesPane } from './pane'\n\ninterface ReferencesConfig {\n /**\n * Document types to exclude from showing the References badge\n * @example ['media.tag', 'sanity.imageAsset']\n */\n exclude?: string[]\n}\n\n/**\n * Plugin that shows documents referencing the current document.\n * Displays a badge with the reference count.\n *\n * To add the References tab to your documents, use the Structure Builder API\n * with the exported `ReferencesPane` component.\n *\n * @example Basic usage (badge only)\n * ```ts\n * // sanity.config.ts\n * import { references } from 'sanity-plugin-references'\n *\n * export default defineConfig({\n * plugins: [references()],\n * })\n * ```\n *\n * @example Exclude specific document types from showing the badge\n * ```ts\n * references({ exclude: ['media.tag', 'sanity.imageAsset'] })\n * ```\n *\n * @example Adding the References tab with Structure Builder\n * ```ts\n * // sanity.config.ts\n * import { structureTool } from 'sanity/structure'\n * import { references, ReferencesPane } from 'sanity-plugin-references'\n * import { LinkIcon } from '@sanity/icons'\n *\n * export default defineConfig({\n * plugins: [\n * references(),\n * structureTool({\n * defaultDocumentNode: (S, { schemaType }) => {\n * return S.document().views([\n * S.view.form(),\n * S.view.component(ReferencesPane).title('References').icon(LinkIcon),\n * ])\n * },\n * }),\n * ],\n * })\n * ```\n *\n * @example Using the referencesView helper\n * ```ts\n * import { references, referencesView } from 'sanity-plugin-references'\n *\n * structureTool({\n * defaultDocumentNode: (S) => S.document().views([\n * S.view.form(),\n * referencesView(S),\n * ]),\n * })\n * ```\n */\nexport const references = definePlugin<ReferencesConfig | void>((config) => {\n const excludeTypes = config?.exclude || []\n\n return {\n name: 'references',\n document: {\n badges: (prev, context) => {\n // Don't show badge for excluded types\n if (excludeTypes.includes(context.schemaType)) {\n return prev\n }\n return [...prev, ReferencesBadge]\n },\n },\n }\n})\n\nexport type { ReferencesConfig }\n\n\n/**\n * Helper to create a References view for Structure Builder.\n *\n * @example\n * ```ts\n * S.document().views([\n * S.view.form(),\n * referencesView(S),\n * ])\n * ```\n *\n * @example With custom title and icon\n * ```ts\n * referencesView(S, { title: 'Incoming Links', icon: MyIcon })\n * ```\n */\nexport function referencesView(S: StructureBuilder, options?: {\n title?: string\n icon?: ComponentType\n}) {\n return S.view\n .component(ReferencesPane)\n .title(options?.title || 'References')\n .icon(options?.icon || LinkIcon)\n}\n\nexport { ReferencesPane } from './pane'\nexport { ReferencesBadge } from './badge'\nexport { LinkIcon as ReferencesIcon } from '@sanity/icons'\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/pane.tsx
CHANGED
|
@@ -106,97 +106,104 @@ export const ReferencesPane: UserViewComponent = (props) => {
|
|
|
106
106
|
)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const useSelectForFilters = documentTypes.length
|
|
109
|
+
const useSelectForFilters = documentTypes.length >= 4
|
|
110
110
|
|
|
111
111
|
return (
|
|
112
|
-
<
|
|
113
|
-
{/*
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
112
|
+
<Flex direction="column" style={{ height: '100%', overflow: 'hidden' }}>
|
|
113
|
+
{/* Sticky Filters and Search */}
|
|
114
|
+
<Box style={{ flexShrink: 0 }}>
|
|
115
|
+
{/* Toolbar */}
|
|
116
|
+
<Card borderBottom padding={2}>
|
|
117
|
+
<Flex gap={2} align="center" wrap="nowrap">
|
|
118
|
+
{useSelectForFilters ? (
|
|
119
|
+
<Box style={{ minWidth: 150, maxWidth: 250 }}>
|
|
120
|
+
<Select
|
|
121
|
+
value={typeFilter}
|
|
122
|
+
onChange={e => setTypeFilter(e.currentTarget.value)}
|
|
123
|
+
fontSize={1}
|
|
124
|
+
padding={2}
|
|
125
|
+
style={{ width: '100%' }}
|
|
126
|
+
>
|
|
127
|
+
<option value="all">All types</option>
|
|
128
|
+
{documentTypes.map((type: string) => {
|
|
129
|
+
const schemaType = getSchemaType(type)
|
|
130
|
+
return (
|
|
131
|
+
<option key={type} value={type}>
|
|
132
|
+
{schemaType?.title || type}
|
|
133
|
+
</option>
|
|
134
|
+
)
|
|
135
|
+
})}
|
|
136
|
+
</Select>
|
|
137
|
+
</Box>
|
|
138
|
+
) : (
|
|
139
|
+
<Inline space={2} style={{ flexShrink: 0 }}>
|
|
140
|
+
<Flex align="center" gap={1} as="label" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>
|
|
141
|
+
<Radio checked={typeFilter === 'all'} onChange={() => setTypeFilter('all')} />
|
|
142
|
+
<Text size={1} weight={typeFilter === 'all' ? 'semibold' : 'regular'}>All</Text>
|
|
143
|
+
</Flex>
|
|
144
|
+
{documentTypes.map((type: string) => {
|
|
145
|
+
const schemaType = getSchemaType(type)
|
|
146
|
+
return (
|
|
147
|
+
<Flex key={type} align="center" gap={1} as="label" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>
|
|
148
|
+
<Radio checked={typeFilter === type} onChange={() => setTypeFilter(type)} />
|
|
149
|
+
<Text size={1} weight={typeFilter === type ? 'semibold' : 'regular'}>
|
|
150
|
+
{schemaType?.title || type}
|
|
151
|
+
</Text>
|
|
152
|
+
</Flex>
|
|
153
|
+
)
|
|
154
|
+
})}
|
|
155
|
+
</Inline>
|
|
156
|
+
)}
|
|
157
|
+
<Box flex={1} />
|
|
158
|
+
<Flex gap={1} style={{ flexShrink: 0 }}>
|
|
159
|
+
<Select value={sortBy} onChange={e => setSortBy(e.currentTarget.value as SortOption)} fontSize={1} padding={2}>
|
|
160
|
+
<option value="updatedAt">Updated</option>
|
|
161
|
+
<option value="type">Type</option>
|
|
162
|
+
<option value="title">Title</option>
|
|
163
|
+
</Select>
|
|
164
|
+
<Button
|
|
165
|
+
icon={SortIcon}
|
|
166
|
+
mode="ghost"
|
|
167
|
+
onClick={() => setSortDesc((d: boolean) => !d)}
|
|
168
|
+
title={sortDesc ? 'Descending' : 'Ascending'}
|
|
169
|
+
fontSize={1}
|
|
170
|
+
padding={2}
|
|
171
|
+
/>
|
|
172
|
+
</Flex>
|
|
168
173
|
</Flex>
|
|
169
|
-
</
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
</
|
|
174
|
+
</Card>
|
|
175
|
+
|
|
176
|
+
{/* Count + Search */}
|
|
177
|
+
<Card borderBottom paddingX={3} paddingY={0}>
|
|
178
|
+
<Flex align="stretch" style={{ minHeight: 33 }}>
|
|
179
|
+
<Flex align="center" paddingY={2} style={{ flexShrink: 0 }}>
|
|
180
|
+
<Text size={1} muted>
|
|
181
|
+
{filteredDocuments.length} of {documents.length} document{documents.length !== 1 ? 's' : ''}
|
|
182
|
+
</Text>
|
|
183
|
+
</Flex>
|
|
184
|
+
<Box style={{ flex: 1, minWidth: 180, borderLeft: '1px solid var(--card-border-color)', paddingLeft: 4, marginLeft: 12, display: 'flex', alignItems: 'center' }}>
|
|
185
|
+
<TextInput
|
|
186
|
+
icon={SearchIcon}
|
|
187
|
+
placeholder="Search..."
|
|
188
|
+
value={searchQuery}
|
|
189
|
+
onChange={e => setSearchQuery(e.currentTarget.value)}
|
|
190
|
+
fontSize={1}
|
|
191
|
+
padding={2}
|
|
192
|
+
border={false}
|
|
193
|
+
style={{ boxShadow: 'none', width: '100%' }}
|
|
194
|
+
/>
|
|
195
|
+
</Box>
|
|
179
196
|
</Flex>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
icon={SearchIcon}
|
|
183
|
-
placeholder="Search..."
|
|
184
|
-
value={searchQuery}
|
|
185
|
-
onChange={e => setSearchQuery(e.currentTarget.value)}
|
|
186
|
-
fontSize={1}
|
|
187
|
-
padding={2}
|
|
188
|
-
border={false}
|
|
189
|
-
style={{ boxShadow: 'none', width: '100%' }}
|
|
190
|
-
/>
|
|
191
|
-
</Box>
|
|
192
|
-
</Flex>
|
|
193
|
-
</Card>
|
|
197
|
+
</Card>
|
|
198
|
+
</Box>
|
|
194
199
|
|
|
195
200
|
{/* Document list */}
|
|
196
201
|
<Box style={{ flex: 1, overflow: 'auto' }}>
|
|
197
202
|
<Stack space={1} padding={2}>
|
|
198
203
|
{filteredDocuments.length === 0 ? (
|
|
199
|
-
<Card padding={4}
|
|
204
|
+
<Card padding={4}>
|
|
205
|
+
<Text muted align="center">No documents match your filters</Text>
|
|
206
|
+
</Card>
|
|
200
207
|
) : (
|
|
201
208
|
filteredDocuments.map((doc: ReferencingDocument) => {
|
|
202
209
|
const schemaType = getSchemaType(doc._type)
|
|
@@ -204,7 +211,7 @@ export const ReferencesPane: UserViewComponent = (props) => {
|
|
|
204
211
|
<Card
|
|
205
212
|
key={doc._id}
|
|
206
213
|
as="button"
|
|
207
|
-
padding={
|
|
214
|
+
padding={1}
|
|
208
215
|
radius={2}
|
|
209
216
|
onClick={() => router.navigateIntent('edit', { id: doc._id, type: doc._type })}
|
|
210
217
|
style={{ cursor: 'pointer', textAlign: 'left', width: '100%' }}
|
|
@@ -225,6 +232,6 @@ export const ReferencesPane: UserViewComponent = (props) => {
|
|
|
225
232
|
)}
|
|
226
233
|
</Stack>
|
|
227
234
|
</Box>
|
|
228
|
-
</
|
|
235
|
+
</Flex>
|
|
229
236
|
)
|
|
230
237
|
}
|