sanity-context 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +188 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # sanity-context
2
+
3
+ A [Sanity Studio](https://www.sanity.io) plugin that adds a persistent context switcher to the navbar — letting editors switch between dimensions like brand, locale, market, or environment without changing the URL or dataset.
4
+
5
+ The selected context is stored in `localStorage` and made available to any part of your Studio (document lists, previews, custom tools) via a simple subscription API.
6
+
7
+ ![Demo of the context switcher in the Studio navbar](./demo.gif)
8
+
9
+ ## Features
10
+
11
+ - Adds a toggle button to the Studio navbar
12
+ - Supports any number of named context dimensions (brand, locale, market, etc.)
13
+ - Each dimension has a list of options and can be independently enabled/toggled
14
+ - Selection persists across sessions via `localStorage`
15
+ - Resolver function API: derive available options from the current user's roles and workspace at runtime
16
+ - Subscribe to context changes from anywhere in your Studio
17
+
18
+ ## Installation
19
+
20
+ ```sh
21
+ npm install sanity-context
22
+ ```
23
+
24
+ ## Basic usage
25
+
26
+ Static options — all editors see the same choices:
27
+
28
+ ```ts
29
+ // sanity.config.ts
30
+ import {defineConfig} from 'sanity'
31
+ import {contextPlugin} from 'sanity-context'
32
+
33
+ export default defineConfig({
34
+ // ...
35
+ plugins: [
36
+ contextPlugin({
37
+ contexts: [
38
+ {
39
+ id: 'brand',
40
+ title: 'Brand',
41
+ options: [
42
+ {value: 'acme', title: 'Acme'},
43
+ {value: 'globex', title: 'Globex'},
44
+ ],
45
+ defaultValue: 'acme',
46
+ },
47
+ {
48
+ id: 'locale',
49
+ title: 'Locale',
50
+ options: [
51
+ {value: 'en-US', title: 'English (US)'},
52
+ {value: 'de-DE', title: 'German'},
53
+ {value: 'fr-FR', title: 'French'},
54
+ ],
55
+ defaultValue: 'en-US',
56
+ },
57
+ ],
58
+ }),
59
+ ],
60
+ })
61
+ ```
62
+
63
+ ## Role-based options
64
+
65
+ Pass a resolver function instead of a static array to derive options from the current user and workspace. The resolver is called once after the user authenticates.
66
+
67
+ ```ts
68
+ import {contextPlugin} from 'sanity-context'
69
+ import type {ContextsResolver} from 'sanity-context'
70
+
71
+ const BRAND_ROLES: Record<string, string[]> = {
72
+ acme: ['administrator', 'editor-acme'],
73
+ globex: ['administrator', 'editor-globex'],
74
+ }
75
+
76
+ const resolver: ContextsResolver = ({currentUser}) => {
77
+ const userRoles = currentUser?.roles.map((r) => r.name) ?? []
78
+
79
+ const allowedBrands = Object.entries(BRAND_ROLES)
80
+ .filter(([, roles]) => roles.some((r) => userRoles.includes(r)))
81
+ .map(([brand]) => brand)
82
+
83
+ return [
84
+ {
85
+ id: 'brand',
86
+ title: 'Brand',
87
+ options: allowedBrands.map((b) => ({value: b, title: b})),
88
+ defaultValue: allowedBrands[0] ?? 'acme',
89
+ },
90
+ ]
91
+ }
92
+
93
+ export default defineConfig({
94
+ plugins: [contextPlugin({contexts: resolver})],
95
+ })
96
+ ```
97
+
98
+ The resolver receives `{ currentUser, workspace }`:
99
+
100
+ | Property | Type | Description |
101
+ |---|---|---|
102
+ | `currentUser` | `CurrentUser \| null` | The authenticated Sanity user, including `roles` |
103
+ | `workspace` | `Workspace` | The active Studio workspace, including `name`, `dataset`, `projectId` |
104
+
105
+ ## Reading context in your Studio
106
+
107
+ Use `getContext()` and `subscribeToContext()` to read the current context from document views, custom components, or structure builders.
108
+
109
+ ### In a React component
110
+
111
+ ```tsx
112
+ import {useSyncExternalStore} from 'react'
113
+ import {getContext, subscribeToContext} from 'sanity-context'
114
+
115
+ function useStudioContext() {
116
+ return useSyncExternalStore(subscribeToContext, getContext, getContext)
117
+ }
118
+
119
+ export function MyPreview() {
120
+ const ctx = useStudioContext()
121
+
122
+ if (!ctx.brand?.enabled) return <DefaultPreview />
123
+
124
+ return <BrandPreview brand={ctx.brand.value} locale={ctx.locale?.value} />
125
+ }
126
+ ```
127
+
128
+ ### In a structure builder
129
+
130
+ ```ts
131
+ import {getContext} from 'sanity-context'
132
+
133
+ export function createStructure(S) {
134
+ const ctx = getContext()
135
+ const brand = ctx.brand?.enabled ? ctx.brand.value : null
136
+
137
+ return S.list()
138
+ .title('Content')
139
+ .items(
140
+ brand
141
+ ? [S.documentTypeListItem('article').filter(`brand == "${brand}"`)]
142
+ : S.documentTypeListItems()
143
+ )
144
+ }
145
+ ```
146
+
147
+ > Note: structure builders run once on load. For reactive filtering, read context inside document list components instead.
148
+
149
+ ## `ContextState` shape
150
+
151
+ `getContext()` returns a `Record<string, ContextEntry>` keyed by context `id`:
152
+
153
+ ```ts
154
+ interface ContextEntry {
155
+ enabled: boolean // whether the user has switched this context on
156
+ value: string // the selected option value
157
+ }
158
+ ```
159
+
160
+ A context must be **enabled** to be considered active. This lets editors see the full unfiltered Studio when the toggle is off.
161
+
162
+ ```ts
163
+ const ctx = getContext()
164
+
165
+ const brand = ctx.brand?.enabled ? ctx.brand.value : null
166
+ // null → show all content
167
+ // 'acme' → filter to Acme brand
168
+ ```
169
+
170
+ ## Options
171
+
172
+ | Option | Type | Required | Description |
173
+ |---|---|---|---|
174
+ | `contexts` | `ContextDefinition[] \| ContextsResolver` | Yes | Static list or resolver function |
175
+ | `storageKey` | `string` | No | `localStorage` key (default: `"sanity-context"`) |
176
+
177
+ Each `ContextDefinition`:
178
+
179
+ | Field | Type | Description |
180
+ |---|---|---|
181
+ | `id` | `string` | Unique identifier, used as the key in `ContextState` |
182
+ | `title` | `string` | Label shown in the navbar UI |
183
+ | `options` | `ContextOption[]` | Available choices (`{value, title}`) |
184
+ | `defaultValue` | `string` | Selected value when no stored preference exists |
185
+
186
+ ## License
187
+
188
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-context",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Sanity Studio plugin for managing studio-wide context (brand, locale, market, etc.)",
5
5
  "keywords": [
6
6
  "sanity",
@@ -12,9 +12,9 @@
12
12
  "sideEffects": false,
13
13
  "exports": {
14
14
  ".": {
15
- "source": "./src/index.ts",
16
15
  "import": "./dist/index.js",
17
16
  "require": "./dist/index.cjs",
17
+ "source": "./src/index.ts",
18
18
  "default": "./dist/index.js"
19
19
  },
20
20
  "./package.json": "./package.json"