@sanity/themer 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ import { t as CreateThemeOptions } from "./_chunks/types.cjs";
2
+ /**
3
+ * Options for the {@link themerTool} plugin.
4
+ *
5
+ * @public
6
+ */
7
+ interface ThemerToolOptions {
8
+ /**
9
+ * The colors that the Studio's configured theme was generated from — the
10
+ * themer starts editing from these, so pass the same object that the
11
+ * `theme` in the Studio config uses:
12
+ *
13
+ * ```ts
14
+ * const colors = {primary: '#2276fc'}
15
+ *
16
+ * export default defineConfig({
17
+ * theme: createTheme(colors),
18
+ * plugins: [themerTool({colors})],
19
+ * })
20
+ * ```
21
+ */
22
+ colors?: CreateThemeOptions;
23
+ }
24
+ /**
25
+ * A Studio plugin that adds a themer sidebar for generating Studio themes:
26
+ * a navbar toggle opens the sidebar next to the active tool, where presets
27
+ * and color pickers preview a `createTheme` theme live on the whole Studio
28
+ * while you browse around. Toggle between light and dark mode with the
29
+ * regular appearance menu — the preview follows it.
30
+ *
31
+ * ```ts
32
+ * import {themerTool} from '@sanity/themer/tool'
33
+ * import {defineConfig} from 'sanity'
34
+ *
35
+ * export default defineConfig({
36
+ * plugins: [themerTool()],
37
+ * // ...rest of the config
38
+ * })
39
+ * ```
40
+ *
41
+ * @public
42
+ */
43
+ declare const themerTool: import("sanity").Plugin<void | ThemerToolOptions>;
44
+ export { type ThemerToolOptions, themerTool };
45
+ //# sourceMappingURL=tool.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.d.cts","names":[],"sources":["../src/tool/plugin.tsx"],"mappings":";;;;;;UAYiB;;;;;;;;;;;;;;;EAef,SAAS;;;;;;;;;;;;;;;;;;;;;cAsBE,6BAAU,cAAA"}
package/dist/tool.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { t as CreateThemeOptions } from "./_chunks/types.js";
2
+ /**
3
+ * Options for the {@link themerTool} plugin.
4
+ *
5
+ * @public
6
+ */
7
+ interface ThemerToolOptions {
8
+ /**
9
+ * The colors that the Studio's configured theme was generated from — the
10
+ * themer starts editing from these, so pass the same object that the
11
+ * `theme` in the Studio config uses:
12
+ *
13
+ * ```ts
14
+ * const colors = {primary: '#2276fc'}
15
+ *
16
+ * export default defineConfig({
17
+ * theme: createTheme(colors),
18
+ * plugins: [themerTool({colors})],
19
+ * })
20
+ * ```
21
+ */
22
+ colors?: CreateThemeOptions;
23
+ }
24
+ /**
25
+ * A Studio plugin that adds a themer sidebar for generating Studio themes:
26
+ * a navbar toggle opens the sidebar next to the active tool, where presets
27
+ * and color pickers preview a `createTheme` theme live on the whole Studio
28
+ * while you browse around. Toggle between light and dark mode with the
29
+ * regular appearance menu — the preview follows it.
30
+ *
31
+ * ```ts
32
+ * import {themerTool} from '@sanity/themer/tool'
33
+ * import {defineConfig} from 'sanity'
34
+ *
35
+ * export default defineConfig({
36
+ * plugins: [themerTool()],
37
+ * // ...rest of the config
38
+ * })
39
+ * ```
40
+ *
41
+ * @public
42
+ */
43
+ declare const themerTool: import("sanity").Plugin<void | ThemerToolOptions>;
44
+ export { type ThemerToolOptions, themerTool };
45
+ //# sourceMappingURL=tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.d.ts","names":[],"sources":["../src/tool/plugin.tsx"],"mappings":";;;;;;UAYiB;;;;;;;;;;;;;;;EAef,SAAS;;;;;;;;;;;;;;;;;;;;;cAsBE,6BAAU,cAAA"}
package/dist/tool.js ADDED
@@ -0,0 +1,415 @@
1
+ import { t as isColor } from "./_chunks/mix.js";
2
+ import { n as createTheme, t as presets } from "./_chunks/presets.js";
3
+ import { color } from "@sanity/color";
4
+ import { definePlugin } from "sanity";
5
+ import { Box, Button, Card, Code, Flex, Layer, Select, Stack, Text, ThemeProvider, useToast } from "@sanity/ui";
6
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
7
+ import { ClipboardIcon } from "@sanity/icons/Clipboard";
8
+ import { CloseIcon } from "@sanity/icons/Close";
9
+ import { ResetIcon } from "@sanity/icons/Reset";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { ColorWheelIcon } from "@sanity/icons/ColorWheel";
12
+ /** @internal */
13
+ const ThemerContext = createContext(null);
14
+ /** @internal */
15
+ function useThemer() {
16
+ let context = useContext(ThemerContext);
17
+ if (!context) throw Error("useThemer must be used within the `themerTool` plugin");
18
+ return context;
19
+ }
20
+ /** @internal */
21
+ const THEMER_FIELDS = [
22
+ {
23
+ key: "primary",
24
+ title: "Primary",
25
+ description: "Buttons, focus rings and links",
26
+ defaultValue: color.blue[500].hex
27
+ },
28
+ {
29
+ key: "gray",
30
+ title: "Gray",
31
+ description: "Neutral surfaces, borders and text",
32
+ defaultValue: color.gray[500].hex
33
+ },
34
+ {
35
+ key: "positive",
36
+ title: "Positive",
37
+ description: "Success accents",
38
+ defaultValue: color.green[500].hex
39
+ },
40
+ {
41
+ key: "caution",
42
+ title: "Caution",
43
+ description: "Warning accents",
44
+ defaultValue: color.yellow[500].hex
45
+ },
46
+ {
47
+ key: "critical",
48
+ title: "Critical",
49
+ description: "Errors and destructive actions",
50
+ defaultValue: color.red[500].hex
51
+ },
52
+ {
53
+ key: "lightest",
54
+ title: "Lightest",
55
+ description: "Light mode background",
56
+ defaultValue: color.white.hex
57
+ },
58
+ {
59
+ key: "darkest",
60
+ title: "Darkest",
61
+ description: "Dark mode background",
62
+ defaultValue: color.black.hex
63
+ }
64
+ ];
65
+ /**
66
+ * Serializes colors into the `createTheme` call to paste into
67
+ * `sanity.config.ts`.
68
+ *
69
+ * @internal
70
+ */
71
+ function createThemeSnippet(colors) {
72
+ let entries = THEMER_FIELDS.filter(({ key }) => colors[key]).map(({ key }) => ` ${key}: '${colors[key]}',`);
73
+ return `import {createTheme} from '@sanity/themer'\n\nexport const theme = ${entries.length === 0 ? "createTheme()" : `createTheme({\n${entries.join("\n")}\n})`}\n`;
74
+ }
75
+ const swatchStyle = {
76
+ width: 33,
77
+ height: 33,
78
+ padding: 0,
79
+ border: "1px solid var(--card-border-color)",
80
+ borderRadius: 6,
81
+ background: "transparent",
82
+ cursor: "pointer",
83
+ flex: "none"
84
+ };
85
+ function sameColors(a, b) {
86
+ return THEMER_FIELDS.every(({ key }) => (a[key]?.toLowerCase() ?? void 0) === (b[key]?.toLowerCase() ?? void 0));
87
+ }
88
+ /** Expands `#abc` to `#aabbcc`, which is the only format `<input type="color">` accepts */
89
+ function expandHex(hex) {
90
+ return hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
91
+ }
92
+ /**
93
+ * The themer sidebar: a preset picker and a handful of color pickers that
94
+ * generate the previewed theme, plus the `createTheme` snippet to make it
95
+ * permanent.
96
+ *
97
+ * @internal
98
+ */
99
+ function ThemerSidebar() {
100
+ let { baseColors, colors, setColors, setOpen } = useThemer(), toast = useToast(), active = colors ?? baseColors, activePresetSlug = presets.find((preset) => sameColors(preset.colors, active))?.slug ?? "", snippet = createThemeSnippet(active), handleFieldChange = (key, value) => {
101
+ let next = { ...active };
102
+ value === void 0 ? delete next[key] : next[key] = value, setColors(next);
103
+ }, handlePresetChange = (slug) => {
104
+ let preset = presets.find((candidate) => candidate.slug === slug);
105
+ preset && setColors({ ...preset.colors });
106
+ }, handleCopy = async () => {
107
+ try {
108
+ await navigator.clipboard.writeText(snippet), toast.push({
109
+ status: "success",
110
+ title: "Copied theme to the clipboard"
111
+ });
112
+ } catch {
113
+ toast.push({
114
+ status: "error",
115
+ title: "Could not copy the theme"
116
+ });
117
+ }
118
+ };
119
+ return /* @__PURE__ */ jsx(Card, {
120
+ height: "fill",
121
+ children: /* @__PURE__ */ jsxs(Flex, {
122
+ direction: "column",
123
+ height: "fill",
124
+ children: [/* @__PURE__ */ jsx(Card, {
125
+ borderBottom: !0,
126
+ padding: 3,
127
+ children: /* @__PURE__ */ jsxs(Flex, {
128
+ align: "center",
129
+ gap: 2,
130
+ children: [/* @__PURE__ */ jsx(Box, {
131
+ flex: 1,
132
+ paddingLeft: 1,
133
+ children: /* @__PURE__ */ jsx(Text, {
134
+ size: 1,
135
+ weight: "semibold",
136
+ children: "Themer"
137
+ })
138
+ }), /* @__PURE__ */ jsx(Button, {
139
+ icon: CloseIcon,
140
+ mode: "bleed",
141
+ onClick: () => setOpen(!1),
142
+ padding: 2,
143
+ title: "Close themer"
144
+ })]
145
+ })
146
+ }), /* @__PURE__ */ jsx(Box, {
147
+ flex: 1,
148
+ overflow: "auto",
149
+ padding: 4,
150
+ children: /* @__PURE__ */ jsxs(Stack, {
151
+ gap: 5,
152
+ children: [
153
+ /* @__PURE__ */ jsxs(Stack, {
154
+ gap: 3,
155
+ children: [/* @__PURE__ */ jsx(Text, {
156
+ size: 1,
157
+ weight: "medium",
158
+ children: "Preset"
159
+ }), /* @__PURE__ */ jsxs(Select, {
160
+ onChange: (event) => handlePresetChange(event.currentTarget.value),
161
+ value: activePresetSlug,
162
+ children: [activePresetSlug === "" && /* @__PURE__ */ jsx("option", {
163
+ value: "",
164
+ children: "Custom"
165
+ }), presets.map((preset) => /* @__PURE__ */ jsx("option", {
166
+ value: preset.slug,
167
+ children: preset.title
168
+ }, preset.slug))]
169
+ })]
170
+ }),
171
+ /* @__PURE__ */ jsx(Stack, {
172
+ gap: 4,
173
+ children: THEMER_FIELDS.map((field) => /* @__PURE__ */ jsx(ColorField, {
174
+ field,
175
+ onChange: handleFieldChange,
176
+ value: active[field.key]
177
+ }, field.key))
178
+ }),
179
+ /* @__PURE__ */ jsxs(Stack, {
180
+ gap: 3,
181
+ children: [
182
+ /* @__PURE__ */ jsx(Text, {
183
+ size: 1,
184
+ weight: "medium",
185
+ children: "Add to your config"
186
+ }),
187
+ /* @__PURE__ */ jsx(Card, {
188
+ border: !0,
189
+ overflow: "auto",
190
+ padding: 3,
191
+ radius: 2,
192
+ tone: "transparent",
193
+ children: /* @__PURE__ */ jsx(Code, {
194
+ language: "ts",
195
+ size: 0,
196
+ children: snippet
197
+ })
198
+ }),
199
+ /* @__PURE__ */ jsxs(Flex, {
200
+ gap: 2,
201
+ children: [/* @__PURE__ */ jsx(Button, {
202
+ icon: ClipboardIcon,
203
+ mode: "ghost",
204
+ onClick: () => void handleCopy(),
205
+ text: "Copy"
206
+ }), /* @__PURE__ */ jsx(Button, {
207
+ disabled: colors === null,
208
+ icon: ResetIcon,
209
+ mode: "ghost",
210
+ onClick: () => setColors(null),
211
+ text: "Reset",
212
+ tone: "critical"
213
+ })]
214
+ })
215
+ ]
216
+ })
217
+ ]
218
+ })
219
+ })]
220
+ })
221
+ });
222
+ }
223
+ function ColorField(props) {
224
+ let { field, value, onChange } = props;
225
+ return /* @__PURE__ */ jsxs(Flex, {
226
+ align: "center",
227
+ gap: 3,
228
+ children: [
229
+ /* @__PURE__ */ jsxs(Stack, {
230
+ flex: 1,
231
+ gap: 2,
232
+ children: [/* @__PURE__ */ jsx(Text, {
233
+ size: 1,
234
+ weight: "medium",
235
+ children: field.title
236
+ }), /* @__PURE__ */ jsx(Text, {
237
+ muted: !0,
238
+ size: 0,
239
+ children: value ?? field.description
240
+ })]
241
+ }),
242
+ value !== void 0 && /* @__PURE__ */ jsx(Button, {
243
+ icon: CloseIcon,
244
+ mode: "bleed",
245
+ onClick: () => onChange(field.key, void 0),
246
+ padding: 2,
247
+ title: `Reset ${field.title}`
248
+ }),
249
+ /* @__PURE__ */ jsx("input", {
250
+ "aria-label": `${field.title} color`,
251
+ onChange: (event) => onChange(field.key, event.currentTarget.value),
252
+ style: swatchStyle,
253
+ type: "color",
254
+ value: expandHex(value ?? field.defaultValue)
255
+ })
256
+ ]
257
+ });
258
+ }
259
+ const sidebarStyle = {
260
+ width: 360,
261
+ flex: "none",
262
+ borderLeft: "1px solid var(--card-border-color)",
263
+ boxSizing: "border-box",
264
+ overflow: "hidden"
265
+ };
266
+ /**
267
+ * Renders the themer sidebar next to the active tool, so the user can browse
268
+ * around their own studio while tweaking the theme.
269
+ *
270
+ * @internal
271
+ */
272
+ function ThemerActiveToolLayout(props) {
273
+ let { open } = useThemer();
274
+ return /* @__PURE__ */ jsxs(Flex, {
275
+ height: "fill",
276
+ sizing: "border",
277
+ children: [/* @__PURE__ */ jsx(Box, {
278
+ flex: 1,
279
+ height: "fill",
280
+ overflow: "auto",
281
+ children: props.renderDefault(props)
282
+ }), open && /* @__PURE__ */ jsx(Layer, {
283
+ height: "fill",
284
+ style: sidebarStyle,
285
+ zOffset: 100,
286
+ children: /* @__PURE__ */ jsx(ThemerSidebar, {})
287
+ })]
288
+ });
289
+ }
290
+ const STORAGE_KEY = "sanityStudio:themer:colors";
291
+ /**
292
+ * Restores draft colors from localStorage, so theme drafts survive studio
293
+ * reloads.
294
+ *
295
+ * @internal
296
+ */
297
+ function readStoredColors() {
298
+ try {
299
+ if (typeof localStorage > "u") return null;
300
+ let raw = localStorage.getItem(STORAGE_KEY);
301
+ if (!raw) return null;
302
+ let parsed = JSON.parse(raw);
303
+ if (!parsed || typeof parsed != "object") return null;
304
+ let colors = {};
305
+ for (let { key } of THEMER_FIELDS) {
306
+ let value = Reflect.get(parsed, key);
307
+ typeof value == "string" && isColor(value) && (colors[key] = value);
308
+ }
309
+ return colors;
310
+ } catch {
311
+ return null;
312
+ }
313
+ }
314
+ /** @internal */
315
+ function writeStoredColors(colors) {
316
+ try {
317
+ if (typeof localStorage > "u") return;
318
+ colors === null ? localStorage.removeItem(STORAGE_KEY) : localStorage.setItem(STORAGE_KEY, JSON.stringify(colors));
319
+ } catch {}
320
+ }
321
+ /**
322
+ * Wraps the whole Studio so that draft themes generated by the themer sidebar
323
+ * apply everywhere while the user browses around, and hosts the state that the
324
+ * navbar toggle and the sidebar share.
325
+ *
326
+ * The draft theme provider inherits the color scheme from the Studio, so the
327
+ * preview follows the appearance setting (light/dark/system) like any other
328
+ * theme.
329
+ *
330
+ * @internal
331
+ */
332
+ function ThemerLayout(props) {
333
+ let { baseColors, ...layoutProps } = props, [open, setOpen] = useState(!1), [colors, setColors] = useState(readStoredColors);
334
+ useEffect(() => writeStoredColors(colors), [colors]);
335
+ let theme = useMemo(() => colors === null ? null : createTheme({
336
+ ...baseColors,
337
+ ...colors
338
+ }), [baseColors, colors]), context = useMemo(() => ({
339
+ baseColors,
340
+ colors,
341
+ setColors,
342
+ open,
343
+ setOpen
344
+ }), [
345
+ baseColors,
346
+ colors,
347
+ open
348
+ ]);
349
+ return /* @__PURE__ */ jsx(ThemerContext.Provider, {
350
+ value: context,
351
+ children: theme === null ? layoutProps.renderDefault(layoutProps) : /* @__PURE__ */ jsx(ThemeProvider, {
352
+ theme,
353
+ children: layoutProps.renderDefault(layoutProps)
354
+ })
355
+ });
356
+ }
357
+ /**
358
+ * Adds the toggle that opens and closes the themer sidebar to the Studio
359
+ * navbar.
360
+ *
361
+ * @internal
362
+ */
363
+ function ThemerNavbar(props) {
364
+ let { open, setOpen } = useThemer();
365
+ return props.renderDefault({
366
+ ...props,
367
+ __internal_actions: [...props.__internal_actions ?? [], {
368
+ icon: ColorWheelIcon,
369
+ location: "topbar",
370
+ name: "themer",
371
+ onAction: () => setOpen(!open),
372
+ selected: open,
373
+ title: "Themer"
374
+ }]
375
+ });
376
+ }
377
+ /**
378
+ * A Studio plugin that adds a themer sidebar for generating Studio themes:
379
+ * a navbar toggle opens the sidebar next to the active tool, where presets
380
+ * and color pickers preview a `createTheme` theme live on the whole Studio
381
+ * while you browse around. Toggle between light and dark mode with the
382
+ * regular appearance menu — the preview follows it.
383
+ *
384
+ * ```ts
385
+ * import {themerTool} from '@sanity/themer/tool'
386
+ * import {defineConfig} from 'sanity'
387
+ *
388
+ * export default defineConfig({
389
+ * plugins: [themerTool()],
390
+ * // ...rest of the config
391
+ * })
392
+ * ```
393
+ *
394
+ * @public
395
+ */
396
+ const themerTool = definePlugin((options) => {
397
+ let baseColors = options?.colors ?? {};
398
+ function ThemerLayoutWithOptions(props) {
399
+ return /* @__PURE__ */ jsx(ThemerLayout, {
400
+ ...props,
401
+ baseColors
402
+ });
403
+ }
404
+ return {
405
+ name: "@sanity/themer/tool",
406
+ studio: { components: {
407
+ layout: ThemerLayoutWithOptions,
408
+ navbar: ThemerNavbar,
409
+ activeToolLayout: ThemerActiveToolLayout
410
+ } }
411
+ };
412
+ });
413
+ export { themerTool };
414
+
415
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","names":[],"sources":["../src/tool/context.ts","../src/tool/fields.ts","../src/tool/snippet.ts","../src/tool/ThemerSidebar.tsx","../src/tool/ThemerActiveToolLayout.tsx","../src/tool/storage.ts","../src/tool/ThemerLayout.tsx","../src/tool/ThemerNavbar.tsx","../src/tool/plugin.tsx"],"sourcesContent":["import {createContext, useContext} from 'react'\n\nimport {CreateThemeOptions} from '../types'\n\n/** @internal */\nexport interface ThemerContextValue {\n /** The colors the Studio's configured theme was generated from */\n baseColors: CreateThemeOptions\n /** The draft colors, or `null` when the configured theme is untouched */\n colors: CreateThemeOptions | null\n setColors: (colors: CreateThemeOptions | null) => void\n /** Whether the themer sidebar is open */\n open: boolean\n setOpen: (open: boolean) => void\n}\n\n/** @internal */\nexport const ThemerContext = createContext<ThemerContextValue | null>(null)\n\n/** @internal */\nexport function useThemer(): ThemerContextValue {\n const context = useContext(ThemerContext)\n\n if (!context) {\n throw new Error('useThemer must be used within the `themerTool` plugin')\n }\n\n return context\n}\n","import {color} from '@sanity/color'\n\nimport {CreateThemeOptions} from '../types'\n\n/** @internal */\nexport interface ThemerField {\n key: keyof CreateThemeOptions\n title: string\n description: string\n /** The color the picker shows while the field is unset (6-digit hex) */\n defaultValue: string\n}\n\n/** @internal */\nexport const THEMER_FIELDS: ThemerField[] = [\n {\n key: 'primary',\n title: 'Primary',\n description: 'Buttons, focus rings and links',\n defaultValue: color.blue[500].hex,\n },\n {\n key: 'gray',\n title: 'Gray',\n description: 'Neutral surfaces, borders and text',\n defaultValue: color.gray[500].hex,\n },\n {\n key: 'positive',\n title: 'Positive',\n description: 'Success accents',\n defaultValue: color.green[500].hex,\n },\n {\n key: 'caution',\n title: 'Caution',\n description: 'Warning accents',\n defaultValue: color.yellow[500].hex,\n },\n {\n key: 'critical',\n title: 'Critical',\n description: 'Errors and destructive actions',\n defaultValue: color.red[500].hex,\n },\n {\n key: 'lightest',\n title: 'Lightest',\n description: 'Light mode background',\n defaultValue: color.white.hex,\n },\n {\n key: 'darkest',\n title: 'Darkest',\n description: 'Dark mode background',\n defaultValue: color.black.hex,\n },\n]\n","import {CreateThemeOptions} from '../types'\nimport {THEMER_FIELDS} from './fields'\n\n/**\n * Serializes colors into the `createTheme` call to paste into\n * `sanity.config.ts`.\n *\n * @internal\n */\nexport function createThemeSnippet(colors: CreateThemeOptions): string {\n const entries = THEMER_FIELDS.filter(({key}) => colors[key]).map(\n ({key}) => ` ${key}: '${colors[key]}',`,\n )\n\n const call = entries.length === 0 ? 'createTheme()' : `createTheme({\\n${entries.join('\\n')}\\n})`\n\n return `import {createTheme} from '@sanity/themer'\\n\\nexport const theme = ${call}\\n`\n}\n","import {ClipboardIcon} from '@sanity/icons/Clipboard'\nimport {CloseIcon} from '@sanity/icons/Close'\nimport {ResetIcon} from '@sanity/icons/Reset'\nimport {Box, Button, Card, Code, Flex, Select, Stack, Text, useToast} from '@sanity/ui'\n\nimport {presets} from '../presets'\nimport {CreateThemeOptions} from '../types'\nimport {useThemer} from './context'\nimport {THEMER_FIELDS, ThemerField} from './fields'\nimport {createThemeSnippet} from './snippet'\n\nconst swatchStyle: React.CSSProperties = {\n width: 33,\n height: 33,\n padding: 0,\n border: '1px solid var(--card-border-color)',\n borderRadius: 6,\n background: 'transparent',\n cursor: 'pointer',\n flex: 'none',\n}\n\nfunction sameColors(a: CreateThemeOptions, b: CreateThemeOptions): boolean {\n return THEMER_FIELDS.every(\n ({key}) => (a[key]?.toLowerCase() ?? undefined) === (b[key]?.toLowerCase() ?? undefined),\n )\n}\n\n/** Expands `#abc` to `#aabbcc`, which is the only format `<input type=\"color\">` accepts */\nfunction expandHex(hex: string): string {\n if (hex.length === 4) {\n return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`\n }\n\n return hex\n}\n\n/**\n * The themer sidebar: a preset picker and a handful of color pickers that\n * generate the previewed theme, plus the `createTheme` snippet to make it\n * permanent.\n *\n * @internal\n */\nexport function ThemerSidebar() {\n const {baseColors, colors, setColors, setOpen} = useThemer()\n const toast = useToast()\n\n const active = colors ?? baseColors\n const activePresetSlug = presets.find((preset) => sameColors(preset.colors, active))?.slug ?? ''\n const snippet = createThemeSnippet(active)\n\n const handleFieldChange = (key: keyof CreateThemeOptions, value: string | undefined) => {\n const next = {...active}\n\n if (value === undefined) {\n delete next[key]\n } else {\n next[key] = value\n }\n\n setColors(next)\n }\n\n const handlePresetChange = (slug: string) => {\n const preset = presets.find((candidate) => candidate.slug === slug)\n\n if (preset) {\n setColors({...preset.colors})\n }\n }\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(snippet)\n toast.push({status: 'success', title: 'Copied theme to the clipboard'})\n } catch {\n toast.push({status: 'error', title: 'Could not copy the theme'})\n }\n }\n\n return (\n <Card height=\"fill\">\n <Flex direction=\"column\" height=\"fill\">\n <Card borderBottom padding={3}>\n <Flex align=\"center\" gap={2}>\n <Box flex={1} paddingLeft={1}>\n <Text size={1} weight=\"semibold\">\n Themer\n </Text>\n </Box>\n <Button\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => setOpen(false)}\n padding={2}\n title=\"Close themer\"\n />\n </Flex>\n </Card>\n\n <Box flex={1} overflow=\"auto\" padding={4}>\n <Stack gap={5}>\n <Stack gap={3}>\n <Text size={1} weight=\"medium\">\n Preset\n </Text>\n <Select\n onChange={(event) => handlePresetChange(event.currentTarget.value)}\n value={activePresetSlug}\n >\n {activePresetSlug === '' && <option value=\"\">Custom</option>}\n {presets.map((preset) => (\n <option key={preset.slug} value={preset.slug}>\n {preset.title}\n </option>\n ))}\n </Select>\n </Stack>\n\n <Stack gap={4}>\n {THEMER_FIELDS.map((field) => (\n <ColorField\n field={field}\n key={field.key}\n onChange={handleFieldChange}\n value={active[field.key]}\n />\n ))}\n </Stack>\n\n <Stack gap={3}>\n <Text size={1} weight=\"medium\">\n Add to your config\n </Text>\n <Card border overflow=\"auto\" padding={3} radius={2} tone=\"transparent\">\n <Code language=\"ts\" size={0}>\n {snippet}\n </Code>\n </Card>\n <Flex gap={2}>\n <Button\n icon={ClipboardIcon}\n mode=\"ghost\"\n onClick={() => void handleCopy()}\n text=\"Copy\"\n />\n <Button\n disabled={colors === null}\n icon={ResetIcon}\n mode=\"ghost\"\n onClick={() => setColors(null)}\n text=\"Reset\"\n tone=\"critical\"\n />\n </Flex>\n </Stack>\n </Stack>\n </Box>\n </Flex>\n </Card>\n )\n}\n\nfunction ColorField(props: {\n field: ThemerField\n value: string | undefined\n onChange: (key: keyof CreateThemeOptions, value: string | undefined) => void\n}) {\n const {field, value, onChange} = props\n\n return (\n <Flex align=\"center\" gap={3}>\n <Stack flex={1} gap={2}>\n <Text size={1} weight=\"medium\">\n {field.title}\n </Text>\n <Text muted size={0}>\n {value ?? field.description}\n </Text>\n </Stack>\n {value !== undefined && (\n <Button\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => onChange(field.key, undefined)}\n padding={2}\n title={`Reset ${field.title}`}\n />\n )}\n <input\n aria-label={`${field.title} color`}\n onChange={(event) => onChange(field.key, event.currentTarget.value)}\n style={swatchStyle}\n type=\"color\"\n value={expandHex(value ?? field.defaultValue)}\n />\n </Flex>\n )\n}\n","import {Box, Flex, Layer} from '@sanity/ui'\nimport {type ActiveToolLayoutProps} from 'sanity'\n\nimport {useThemer} from './context'\nimport {ThemerSidebar} from './ThemerSidebar'\n\nconst sidebarStyle: React.CSSProperties = {\n width: 360,\n flex: 'none',\n borderLeft: '1px solid var(--card-border-color)',\n boxSizing: 'border-box',\n overflow: 'hidden',\n}\n\n/**\n * Renders the themer sidebar next to the active tool, so the user can browse\n * around their own studio while tweaking the theme.\n *\n * @internal\n */\nexport function ThemerActiveToolLayout(props: ActiveToolLayoutProps) {\n const {open} = useThemer()\n\n return (\n <Flex height=\"fill\" sizing=\"border\">\n <Box flex={1} height=\"fill\" overflow=\"auto\">\n {props.renderDefault(props)}\n </Box>\n\n {open && (\n <Layer height=\"fill\" style={sidebarStyle} zOffset={100}>\n <ThemerSidebar />\n </Layer>\n )}\n </Flex>\n )\n}\n","import {isColor} from '../lib/mix'\nimport {CreateThemeOptions} from '../types'\nimport {THEMER_FIELDS} from './fields'\n\nconst STORAGE_KEY = 'sanityStudio:themer:colors'\n\n/**\n * Restores draft colors from localStorage, so theme drafts survive studio\n * reloads.\n *\n * @internal\n */\nexport function readStoredColors(): CreateThemeOptions | null {\n try {\n if (typeof localStorage === 'undefined') return null\n\n const raw = localStorage.getItem(STORAGE_KEY)\n\n if (!raw) return null\n\n const parsed: unknown = JSON.parse(raw)\n\n if (!parsed || typeof parsed !== 'object') return null\n\n const colors: CreateThemeOptions = {}\n\n for (const {key} of THEMER_FIELDS) {\n const value: unknown = Reflect.get(parsed, key)\n\n if (typeof value === 'string' && isColor(value)) {\n colors[key] = value\n }\n }\n\n return colors\n } catch {\n return null\n }\n}\n\n/** @internal */\nexport function writeStoredColors(colors: CreateThemeOptions | null): void {\n try {\n if (typeof localStorage === 'undefined') return\n\n if (colors === null) {\n localStorage.removeItem(STORAGE_KEY)\n } else {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(colors))\n }\n } catch {\n // Storage can be unavailable (e.g. private browsing) — drafts just won't persist\n }\n}\n","import {ThemeProvider} from '@sanity/ui'\nimport {useEffect, useMemo, useState} from 'react'\nimport {type LayoutProps} from 'sanity'\n\nimport {createTheme} from '../createTheme'\nimport {CreateThemeOptions} from '../types'\nimport {ThemerContext, ThemerContextValue} from './context'\nimport {readStoredColors, writeStoredColors} from './storage'\n\n/**\n * Wraps the whole Studio so that draft themes generated by the themer sidebar\n * apply everywhere while the user browses around, and hosts the state that the\n * navbar toggle and the sidebar share.\n *\n * The draft theme provider inherits the color scheme from the Studio, so the\n * preview follows the appearance setting (light/dark/system) like any other\n * theme.\n *\n * @internal\n */\nexport function ThemerLayout(props: LayoutProps & {baseColors: CreateThemeOptions}) {\n const {baseColors, ...layoutProps} = props\n const [open, setOpen] = useState(false)\n const [colors, setColors] = useState<CreateThemeOptions | null>(readStoredColors)\n\n useEffect(() => writeStoredColors(colors), [colors])\n\n // The theme identity must be stable between renders: it feeds the\n // styled-components theme context for the whole Studio, and rebuilding it\n // would re-render everything\n const theme = useMemo(\n () => (colors === null ? null : createTheme({...baseColors, ...colors})),\n [baseColors, colors],\n )\n\n const context = useMemo<ThemerContextValue>(\n () => ({baseColors, colors, setColors, open, setOpen}),\n [baseColors, colors, open],\n )\n\n return (\n <ThemerContext.Provider value={context}>\n {theme === null ? (\n layoutProps.renderDefault(layoutProps)\n ) : (\n <ThemeProvider theme={theme}>{layoutProps.renderDefault(layoutProps)}</ThemeProvider>\n )}\n </ThemerContext.Provider>\n )\n}\n","import {ColorWheelIcon} from '@sanity/icons/ColorWheel'\nimport {type NavbarProps} from 'sanity'\n\nimport {useThemer} from './context'\n\n/**\n * Adds the toggle that opens and closes the themer sidebar to the Studio\n * navbar.\n *\n * @internal\n */\nexport function ThemerNavbar(props: NavbarProps) {\n const {open, setOpen} = useThemer()\n\n return props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\n {\n icon: ColorWheelIcon,\n location: 'topbar',\n name: 'themer',\n onAction: () => setOpen(!open),\n selected: open,\n title: 'Themer',\n },\n ],\n })\n}\n","import {definePlugin, type LayoutProps} from 'sanity'\n\nimport {CreateThemeOptions} from '../types'\nimport {ThemerActiveToolLayout} from './ThemerActiveToolLayout'\nimport {ThemerLayout} from './ThemerLayout'\nimport {ThemerNavbar} from './ThemerNavbar'\n\n/**\n * Options for the {@link themerTool} plugin.\n *\n * @public\n */\nexport interface ThemerToolOptions {\n /**\n * The colors that the Studio's configured theme was generated from — the\n * themer starts editing from these, so pass the same object that the\n * `theme` in the Studio config uses:\n *\n * ```ts\n * const colors = {primary: '#2276fc'}\n *\n * export default defineConfig({\n * theme: createTheme(colors),\n * plugins: [themerTool({colors})],\n * })\n * ```\n */\n colors?: CreateThemeOptions\n}\n\n/**\n * A Studio plugin that adds a themer sidebar for generating Studio themes:\n * a navbar toggle opens the sidebar next to the active tool, where presets\n * and color pickers preview a `createTheme` theme live on the whole Studio\n * while you browse around. Toggle between light and dark mode with the\n * regular appearance menu — the preview follows it.\n *\n * ```ts\n * import {themerTool} from '@sanity/themer/tool'\n * import {defineConfig} from 'sanity'\n *\n * export default defineConfig({\n * plugins: [themerTool()],\n * // ...rest of the config\n * })\n * ```\n *\n * @public\n */\nexport const themerTool = definePlugin<ThemerToolOptions | void>((options) => {\n const baseColors: CreateThemeOptions = options?.colors ?? {}\n\n function ThemerLayoutWithOptions(props: LayoutProps) {\n return <ThemerLayout {...props} baseColors={baseColors} />\n }\n\n return {\n name: '@sanity/themer/tool',\n studio: {\n components: {\n layout: ThemerLayoutWithOptions,\n navbar: ThemerNavbar,\n activeToolLayout: ThemerActiveToolLayout,\n },\n },\n }\n})\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@sanity/themer",
3
+ "version": "0.0.0",
4
+ "description": "Generate Sanity Studio themes, and preview them with a Studio tool.",
5
+ "keywords": [
6
+ "sanity",
7
+ "studio",
8
+ "theme",
9
+ "themer",
10
+ "ui"
11
+ ],
12
+ "homepage": "https://www.sanity.io/",
13
+ "bugs": {
14
+ "url": "https://github.com/sanity-io/ui/issues"
15
+ },
16
+ "license": "MIT",
17
+ "author": "Sanity.io <hello@sanity.io>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/sanity-io/ui.git",
21
+ "directory": "packages/themer"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "main": "./dist/index.cjs",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.cts",
31
+ "typesVersions": {
32
+ "*": {
33
+ "legacy": [
34
+ "./dist/legacy.d.cts"
35
+ ],
36
+ "tool": [
37
+ "./dist/tool.d.cts"
38
+ ]
39
+ }
40
+ },
41
+ "exports": {
42
+ ".": {
43
+ "import": "./dist/index.js",
44
+ "require": "./dist/index.cjs"
45
+ },
46
+ "./legacy": {
47
+ "import": "./dist/legacy.js",
48
+ "require": "./dist/legacy.cjs"
49
+ },
50
+ "./tool": {
51
+ "import": "./dist/tool.js",
52
+ "require": "./dist/tool.cjs"
53
+ },
54
+ "./package.json": "./package.json"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "dependencies": {
60
+ "@sanity/color": "^3.0.7",
61
+ "@sanity/ui": "^3.4.3",
62
+ "@sanity/icons": "^5.2.1"
63
+ },
64
+ "devDependencies": {
65
+ "@sanity/tsdown-config": "^0.19.6",
66
+ "@types/node": "^24.13.3",
67
+ "@types/react": "^19.2.17",
68
+ "react": "^19.2.7",
69
+ "rimraf": "^5.0.5",
70
+ "sanity": "^6.5.0",
71
+ "tsdown": "^0.22.12",
72
+ "typescript": "6.0.3",
73
+ "vitest": "^4.1.10"
74
+ },
75
+ "peerDependencies": {
76
+ "react": "^18 || >=19.0.0-0",
77
+ "sanity": "^5 || ^6"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "sanity": {
81
+ "optional": true
82
+ }
83
+ },
84
+ "engines": {
85
+ "node": ">=20.19 <22 || >=22.12"
86
+ },
87
+ "scripts": {
88
+ "build": "tsdown",
89
+ "clean": "rimraf dist",
90
+ "test": "vitest run",
91
+ "test:watch": "vitest",
92
+ "watch": "tsdown --watch"
93
+ }
94
+ }