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 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
+ ![screenshot](https://github.com/user-attachments/assets/5a2256a8-da35-4d9f-b0ca-8416aa87194d)
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, Stack, Select, Inline, Radio, Box, Button, TextInput, Badge } from "@sanity/ui";
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 > 5;
77
- return /* @__PURE__ */ jsxs(Stack, { space: 0, style: { height: "100%" }, children: [
78
- /* @__PURE__ */ jsx(Card, { borderBottom: !0, padding: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", wrap: "nowrap", children: [
79
- useSelectForFilters ? /* @__PURE__ */ jsxs(
80
- Select,
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
- icon: SortIcon,
119
- mode: "ghost",
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
- /* @__PURE__ */ jsx(Card, { borderBottom: !0, paddingX: 3, paddingY: 0, children: /* @__PURE__ */ jsxs(Flex, { align: "stretch", style: { minHeight: 33 }, children: [
129
- /* @__PURE__ */ jsx(Flex, { align: "center", paddingY: 2, style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
130
- filteredDocuments.length,
131
- " of ",
132
- documents.length,
133
- " document",
134
- documents.length !== 1 ? "s" : ""
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(Box, { style: { flex: 1, minWidth: 180, borderLeft: "1px solid var(--card-border-color)", paddingLeft: 4, marginLeft: 12, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx(
137
- TextInput,
138
- {
139
- icon: SearchIcon,
140
- placeholder: "Search...",
141
- value: searchQuery,
142
- onChange: (e) => setSearchQuery(e.currentTarget.value),
143
- fontSize: 1,
144
- padding: 2,
145
- border: !1,
146
- style: { boxShadow: "none", width: "100%" }
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: 2,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-references",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "See which documents reference the current document in Sanity Studio",
5
5
  "keywords": [
6
6
  "sanity",
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 > 5
109
+ const useSelectForFilters = documentTypes.length >= 4
110
110
 
111
111
  return (
112
- <Stack space={0} style={{ height: '100%' }}>
113
- {/* Toolbar */}
114
- <Card borderBottom padding={2}>
115
- <Flex gap={2} align="center" wrap="nowrap">
116
- {useSelectForFilters ? (
117
- <Select
118
- value={typeFilter}
119
- onChange={e => setTypeFilter(e.currentTarget.value)}
120
- fontSize={1}
121
- padding={2}
122
- style={{ minWidth: 150, flexShrink: 0 }}
123
- >
124
- <option value="all">All types</option>
125
- {documentTypes.map((type: string) => {
126
- const schemaType = getSchemaType(type)
127
- return (
128
- <option key={type} value={type}>
129
- {schemaType?.title || type}
130
- </option>
131
- )
132
- })}
133
- </Select>
134
- ) : (
135
- <Inline space={2} style={{ flexShrink: 0 }}>
136
- <Flex align="center" gap={1} as="label" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>
137
- <Radio checked={typeFilter === 'all'} onChange={() => setTypeFilter('all')} />
138
- <Text size={1} weight={typeFilter === 'all' ? 'semibold' : 'regular'}>All</Text>
139
- </Flex>
140
- {documentTypes.map((type: string) => {
141
- const schemaType = getSchemaType(type)
142
- return (
143
- <Flex key={type} align="center" gap={1} as="label" style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}>
144
- <Radio checked={typeFilter === type} onChange={() => setTypeFilter(type)} />
145
- <Text size={1} weight={typeFilter === type ? 'semibold' : 'regular'}>
146
- {schemaType?.title || type}
147
- </Text>
148
- </Flex>
149
- )
150
- })}
151
- </Inline>
152
- )}
153
- <Box flex={1} />
154
- <Flex gap={1} style={{ flexShrink: 0 }}>
155
- <Select value={sortBy} onChange={e => setSortBy(e.currentTarget.value as SortOption)} fontSize={1} padding={2}>
156
- <option value="updatedAt">Updated</option>
157
- <option value="type">Type</option>
158
- <option value="title">Title</option>
159
- </Select>
160
- <Button
161
- icon={SortIcon}
162
- mode="ghost"
163
- onClick={() => setSortDesc((d: boolean) => !d)}
164
- title={sortDesc ? 'Descending' : 'Ascending'}
165
- fontSize={1}
166
- padding={2}
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
- </Flex>
170
- </Card>
171
-
172
- {/* Count + Search */}
173
- <Card borderBottom paddingX={3} paddingY={0}>
174
- <Flex align="stretch" style={{ minHeight: 33 }}>
175
- <Flex align="center" paddingY={2} style={{ flexShrink: 0 }}>
176
- <Text size={1} muted>
177
- {filteredDocuments.length} of {documents.length} document{documents.length !== 1 ? 's' : ''}
178
- </Text>
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
- <Box style={{ flex: 1, minWidth: 180, borderLeft: '1px solid var(--card-border-color)', paddingLeft: 4, marginLeft: 12, display: 'flex', alignItems: 'center' }}>
181
- <TextInput
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}><Text muted align="center">No documents match your filters</Text></Card>
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={2}
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
- </Stack>
235
+ </Flex>
229
236
  )
230
237
  }