@sanity/themer 0.1.0 → 0.2.1-next.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.
package/dist/tool.js CHANGED
@@ -1,11 +1,10 @@
1
- import { a as parseHuesFromUrl, c as isColor, i as createTheme, l as applyHues, o as createTonesFromHues, s as presets, t as hues } from "./defaults-DJtuhI2W.js";
1
+ import { a as DEFAULT_BACKGROUND_DARK, c as resolveThemeOptions, i as DEFAULT_ACCENT, n as buildTheme, o as DEFAULT_BACKGROUND_LIGHT, r as buildPalette, s as deriveTextColor, t as presets } from "./presets-DWzYcsLg.js";
2
+ import { t as isColor } from "./mix-BfV6_Ke4.js";
2
3
  import { COLOR_TINTS } from "@sanity/color";
3
4
  import { c } from "react-compiler-runtime";
4
5
  import { definePlugin } from "sanity";
5
- import { Box, Button, Card, Code, Flex, Grid, Layer, Select, Stack, Text, TextInput, ThemeProvider, Tooltip, useToast } from "@sanity/ui";
6
+ import { Box, Button, Card, Code, Flex, Grid, Layer, Stack, Text, ThemeProvider, Tooltip, useToast } from "@sanity/ui";
6
7
  import { createContext, useContext, useEffect, useState } from "react";
7
- import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
8
- import { ChevronRightIcon } from "@sanity/icons/ChevronRight";
9
8
  import { ClipboardIcon } from "@sanity/icons/Clipboard";
10
9
  import { CloseIcon } from "@sanity/icons/Close";
11
10
  import { ResetIcon } from "@sanity/icons/Reset";
@@ -23,192 +22,111 @@ function useThemer() {
23
22
  return context;
24
23
  }
25
24
  /**
26
- * The six hues in the order the hosted Themer service listed them, used for
27
- * the sidebar's editors and the generated snippet alike.
25
+ * Reduces theme options to the minimal object that recreates them through
26
+ * `buildTheme`: the text color is dropped when it matches the one derived
27
+ * from the accent, and backgrounds and contrast are dropped when they match
28
+ * the defaults. Options that boil down to the stock Studio theme reduce to
29
+ * `null` — they need nothing from this package.
28
30
  *
29
31
  * @internal
30
32
  */
31
- const HUE_KEYS = [
32
- "default",
33
- "primary",
34
- "transparent",
35
- "positive",
36
- "caution",
37
- "critical"
38
- ], HUE_FIELDS = [
39
- {
40
- key: "default",
41
- title: "Default",
42
- description: "Text, icons and most surfaces"
43
- },
44
- {
45
- key: "primary",
46
- title: "Primary",
47
- description: "Focus rings, links and primary buttons"
48
- },
49
- {
50
- key: "transparent",
51
- title: "Transparent",
52
- description: "The backdrop behind panes"
53
- },
54
- {
55
- key: "positive",
56
- title: "Positive",
57
- description: "Success badges and prompts"
58
- },
59
- {
60
- key: "caution",
61
- title: "Caution",
62
- description: "Warnings and draft indicators"
63
- },
64
- {
65
- key: "critical",
66
- title: "Critical",
67
- description: "Errors and destructive actions"
68
- }
69
- ], MID_POINTS = [
70
- 50,
71
- 100,
72
- 200,
73
- 300,
74
- 400,
75
- 500,
76
- 600,
77
- 700,
78
- 800,
79
- 900,
80
- 950
81
- ], HUE_PROPERTIES = [
82
- "mid",
83
- "midPoint",
84
- "lightest",
85
- "darkest"
86
- ];
87
- /** @internal */
88
- function sameHues(a, b) {
89
- return HUE_KEYS.every((key) => HUE_PROPERTIES.every((property) => {
90
- let left = a[key][property], right = b[key][property];
91
- return typeof left == "string" && typeof right == "string" ? left.toLowerCase() === right.toLowerCase() : left === right;
92
- }));
33
+ function minimizeOptions(options) {
34
+ let resolved = resolveThemeOptions(options), minimized = { accent: resolved.accent };
35
+ resolved.text !== deriveTextColor(resolved.accent) && (minimized.text = resolved.text);
36
+ let dark = resolved.background.dark === DEFAULT_BACKGROUND_DARK ? null : resolved.background.dark, light = resolved.background.light === DEFAULT_BACKGROUND_LIGHT ? null : resolved.background.light;
37
+ return (dark !== null || light !== null) && (minimized.background = {}, dark !== null && (minimized.background.dark = dark), light !== null && (minimized.background.light = light)), resolved.contrast !== 85 && (minimized.contrast = resolved.contrast), minimized.accent === DEFAULT_ACCENT && Object.keys(minimized).length === 1 ? null : minimized;
93
38
  }
94
39
  /**
95
- * Reduces resolved hues to the minimal `PartialHues` that recreates them
96
- * through the legacy `createTheme`. The subtlety is `applyHues`' quirk of
97
- * resetting a customized `mid`'s mid point to 500: whenever `mid` is included,
98
- * the baseline for `midPoint` is 500 rather than the hue's default.
40
+ * Whether two sets of theme options generate the same theme, comparing their
41
+ * resolved forms so that derived and explicit values (and hex casing) don't
42
+ * matter.
99
43
  *
100
44
  * @internal
101
45
  */
102
- function diffHues(hues$1) {
103
- let diff = {};
104
- for (let key of HUE_KEYS) {
105
- let hue = hues$1[key], base = hues[key], patch = {};
106
- hue.mid.toLowerCase() !== base.mid && (patch.mid = hue.mid.toLowerCase());
107
- let impliedMidPoint = patch.mid === void 0 ? base.midPoint : 500;
108
- hue.midPoint !== impliedMidPoint && (patch.midPoint = hue.midPoint), hue.lightest.toLowerCase() !== base.lightest && (patch.lightest = hue.lightest.toLowerCase()), hue.darkest.toLowerCase() !== base.darkest && (patch.darkest = hue.darkest.toLowerCase()), Object.keys(patch).length > 0 && (diff[key] = patch);
109
- }
110
- return diff;
111
- }
112
- function serializeHue(hue) {
113
- let parts = [];
114
- return hue.mid !== void 0 && parts.push(`mid: '${hue.mid}'`), hue.midPoint !== void 0 && parts.push(`midPoint: ${hue.midPoint}`), hue.lightest !== void 0 && parts.push(`lightest: '${hue.lightest}'`), hue.darkest !== void 0 && parts.push(`darkest: '${hue.darkest}'`), `{${parts.join(", ")}}`;
46
+ function sameOptions(a, b) {
47
+ let left = resolveThemeOptions(a), right = resolveThemeOptions(b);
48
+ return left.accent === right.accent && left.text === right.text && left.background.dark === right.background.dark && left.background.light === right.background.light && left.contrast === right.contrast;
115
49
  }
116
50
  /**
117
- * Serializes hues into the `createTheme` call to paste into
118
- * `sanity.config.ts`, keeping only what differs from the default hues.
51
+ * Serializes theme options into the `buildTheme` call to paste into
52
+ * `sanity.config.ts`, keeping only what differs from the derived defaults.
119
53
  *
120
- * Hues that match the defaults entirely are the stock Studio theme, which
121
- * needs nothing from this package — so they serialize to a bare `buildTheme()`
54
+ * Options that boil down to the stock Studio theme need nothing from this
55
+ * package — so they serialize to a bare `buildTheme()` from `@sanity/ui/theme`
122
56
  * instead.
123
57
  *
124
58
  * @internal
125
59
  */
126
- function createThemeSnippet(hues) {
127
- let diff = diffHues(hues), entries = [];
128
- for (let key of HUE_KEYS) {
129
- let patch = diff[key];
130
- patch && entries.push(` ${key}: ${serializeHue(patch)},`);
60
+ function createThemeSnippet(options) {
61
+ let minimized = minimizeOptions(options);
62
+ if (minimized === null) return "import {buildTheme} from '@sanity/ui/theme'\n\nexport const theme = buildTheme()\n";
63
+ let entries = [` accent: '${minimized.accent}',`];
64
+ if (minimized.text !== void 0 && entries.push(` text: '${minimized.text}',`), minimized.background) {
65
+ let parts = [];
66
+ minimized.background.dark !== void 0 && parts.push(`dark: '${minimized.background.dark}'`), minimized.background.light !== void 0 && parts.push(`light: '${minimized.background.light}'`), entries.push(` background: {${parts.join(", ")}},`);
131
67
  }
132
- return entries.length === 0 ? "import {buildTheme} from '@sanity/ui/theme'\n\nexport const theme = buildTheme()\n" : `import {createTheme} from '@sanity/themer/legacy'\n\nexport const theme = createTheme({\n${entries.join("\n")}\n})\n`;
68
+ return minimized.contrast !== void 0 && entries.push(` contrast: ${minimized.contrast},`), `import {buildTheme} from '@sanity/themer'\n\nexport const theme = buildTheme({\n${entries.join("\n")}\n})\n`;
133
69
  }
134
70
  registerLanguage(typescript);
135
71
  /**
136
- * The tool's take on the hosted service's presets: Tailwind Cyan is hidden and
137
- * the default "Studio v3" preset reads just "Studio". The legacy `presets`
138
- * export itself keeps both untouched, for parity with
139
- * `https://themer.sanity.build/api/hues`.
140
- */
141
- const TOOL_PRESETS = [];
142
- for (let preset of presets) preset.slug !== "tw-cyan" && TOOL_PRESETS.push(preset.slug === "default" ? {
143
- ...preset,
144
- title: "Studio"
145
- } : preset);
146
- /**
147
72
  * `<input type="color">` paints the color into a shadow-DOM swatch that brings
148
73
  * its own border and padding, which then sits inside ours as a second border.
149
74
  * Stripping that chrome leaves the themed border as the only one.
150
75
  */
151
76
  const Swatch = styled.input.withConfig({
152
77
  displayName: "Swatch",
153
- componentId: "sc-g4qzjc-0"
154
- })`box-sizing:border-box;flex:none;width:33px;height:33px;padding:0;border:1px solid var(--card-border-color);border-radius:4px;background:none;cursor:pointer;&::-webkit-color-swatch-wrapper{padding:0;}&::-webkit-color-swatch{border:none;border-radius:3px;}&::-moz-color-swatch{border:none;border-radius:3px;}`;
155
- /** Expands `#abc` to `#aabbcc`, which is the only format `<input type="color">` accepts */
156
- function expandHex(hex) {
157
- return hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
158
- }
159
- /** Prefixes bare query strings so `parseHuesFromUrl` accepts them too */
160
- function normalizeImportUrl(input) {
161
- let trimmed = input.trim();
162
- return trimmed.startsWith("http") || trimmed.startsWith("?") ? trimmed : `?${trimmed}`;
163
- }
78
+ componentId: "sc-lj4grb-0"
79
+ })`box-sizing:border-box;flex:none;width:33px;height:33px;padding:0;border:1px solid var(--card-border-color);border-radius:4px;background:none;cursor:pointer;&::-webkit-color-swatch-wrapper{padding:0;}&::-webkit-color-swatch{border:none;border-radius:3px;}&::-moz-color-swatch{border:none;border-radius:3px;}`, Range = styled.input.withConfig({
80
+ displayName: "Range",
81
+ componentId: "sc-lj4grb-1"
82
+ })`display:block;width:100%;margin:0;accent-color:var(--card-focus-ring-color);`;
164
83
  /**
165
- * The themer sidebar: presets, a themer.sanity.build URL importer and the
166
- * per-hue editors of the hosted Themer service — mid color, mid point,
167
- * lightest and darkest that generate the previewed legacy theme, plus the
168
- * `createTheme` snippet to make it permanent.
84
+ * The themer sidebar: presets, the accent/text/background pickers and the
85
+ * contrast slider that generate the previewed `buildTheme` theme, plus the
86
+ * `buildTheme` snippet to make it permanent.
169
87
  *
170
88
  * @internal
171
89
  */
172
90
  function ThemerSidebar() {
173
- let $ = c(71), { baseHues, hues, setHues, setOpen } = useThemer(), toast = useToast(), [expandedHue, setExpandedHue] = useState(null), [importUrl, setImportUrl] = useState(""), active = hues ?? baseHues, t0;
174
- $[0] === active ? t0 = $[1] : (t0 = createTonesFromHues(active), $[0] = active, $[1] = t0);
175
- let tones = t0, t1;
176
- $[2] === active ? t1 = $[3] : (t1 = TOOL_PRESETS.find((preset) => sameHues(preset.hues, active))?.slug ?? "", $[2] = active, $[3] = t1);
177
- let activePresetSlug = t1, t2;
178
- $[4] === active ? t2 = $[5] : (t2 = createThemeSnippet(active), $[4] = active, $[5] = t2);
179
- let snippet = t2, t3;
180
- $[6] !== active || $[7] !== setHues ? (t3 = (key, patch) => {
181
- setHues({
91
+ let $ = c(117), { baseOptions, options, setOptions, setOpen } = useThemer(), toast = useToast(), active = options ?? baseOptions, t0;
92
+ $[0] === active ? t0 = $[1] : (t0 = resolveThemeOptions(active), $[0] = active, $[1] = t0);
93
+ let resolved = t0, t1;
94
+ $[2] === active ? t1 = $[3] : (t1 = buildPalette(active), $[2] = active, $[3] = t1);
95
+ let palette = t1, t2;
96
+ $[4] === active ? t2 = $[5] : (t2 = presets.find((preset) => sameOptions(preset.options, active))?.slug ?? "", $[4] = active, $[5] = t2);
97
+ let activePresetSlug = t2, t3;
98
+ $[6] === active ? t3 = $[7] : (t3 = createThemeSnippet(active), $[6] = active, $[7] = t3);
99
+ let snippet = t3, t4;
100
+ $[8] !== active || $[9] !== setOptions ? (t4 = (changes) => {
101
+ setOptions({
182
102
  ...active,
183
- [key]: {
184
- ...active[key],
185
- ...patch
103
+ ...changes
104
+ });
105
+ }, $[8] = active, $[9] = setOptions, $[10] = t4) : t4 = $[10];
106
+ let patch = t4, t5;
107
+ $[11] !== active || $[12] !== setOptions ? (t5 = (changes_0) => {
108
+ setOptions({
109
+ ...active,
110
+ background: {
111
+ ...active.background,
112
+ ...changes_0
186
113
  }
187
114
  });
188
- }, $[6] = active, $[7] = setHues, $[8] = t3) : t3 = $[8];
189
- let handleHueChange = t3, t4;
190
- $[9] === setHues ? t4 = $[10] : (t4 = (slug) => {
191
- let preset_0 = TOOL_PRESETS.find((candidate) => candidate.slug === slug);
192
- preset_0 && setHues(preset_0.hues);
193
- }, $[9] = setHues, $[10] = t4);
194
- let handlePresetChange = t4, t5;
195
- $[11] !== importUrl || $[12] !== setHues || $[13] !== toast ? (t5 = () => {
196
- if (importUrl.trim()) try {
197
- setHues(parseHuesFromUrl(normalizeImportUrl(importUrl))), setImportUrl(""), toast.push({
198
- status: "success",
199
- title: "Imported theme from URL"
200
- });
201
- } catch (t6) {
202
- let error = t6;
203
- toast.push({
204
- status: "error",
205
- title: "Could not import the URL",
206
- description: error instanceof Error ? error.message : String(error)
207
- });
208
- }
209
- }, $[11] = importUrl, $[12] = setHues, $[13] = toast, $[14] = t5) : t5 = $[14];
210
- let handleImport = t5, t6;
211
- $[15] !== snippet || $[16] !== toast ? (t6 = async () => {
115
+ }, $[11] = active, $[12] = setOptions, $[13] = t5) : t5 = $[13];
116
+ let patchBackground = t5, t6;
117
+ $[14] !== active || $[15] !== setOptions ? (t6 = (field) => {
118
+ let next = { ...active };
119
+ delete next[field], setOptions(next);
120
+ }, $[14] = active, $[15] = setOptions, $[16] = t6) : t6 = $[16];
121
+ let clearField = t6, t7;
122
+ $[17] !== active || $[18] !== setOptions ? (t7 = (key) => {
123
+ let background = { ...active.background };
124
+ delete background[key];
125
+ let next_0 = { ...active };
126
+ background.dark === void 0 && background.light === void 0 ? delete next_0.background : next_0.background = background, setOptions(next_0);
127
+ }, $[17] = active, $[18] = setOptions, $[19] = t7) : t7 = $[19];
128
+ let clearBackground = t7, t8;
129
+ $[20] !== snippet || $[21] !== toast ? (t8 = async () => {
212
130
  try {
213
131
  await navigator.clipboard.writeText(snippet), toast.push({
214
132
  status: "success",
@@ -220,9 +138,9 @@ function ThemerSidebar() {
220
138
  title: "Could not copy the theme"
221
139
  });
222
140
  }
223
- }, $[15] = snippet, $[16] = toast, $[17] = t6) : t6 = $[17];
224
- let handleCopy = t6, t7;
225
- $[18] === Symbol.for("react.memo_cache_sentinel") ? (t7 = /* @__PURE__ */ jsx(Box, {
141
+ }, $[20] = snippet, $[21] = toast, $[22] = t8) : t8 = $[22];
142
+ let handleCopy = t8, t9;
143
+ $[23] === Symbol.for("react.memo_cache_sentinel") ? (t9 = /* @__PURE__ */ jsx(Box, {
226
144
  flex: 1,
227
145
  paddingLeft: 1,
228
146
  children: /* @__PURE__ */ jsx(Text, {
@@ -230,15 +148,15 @@ function ThemerSidebar() {
230
148
  weight: "semibold",
231
149
  children: "Themer"
232
150
  })
233
- }), $[18] = t7) : t7 = $[18];
234
- let t8;
235
- $[19] === setOpen ? t8 = $[20] : (t8 = /* @__PURE__ */ jsx(Card, {
151
+ }), $[23] = t9) : t9 = $[23];
152
+ let t10;
153
+ $[24] === setOpen ? t10 = $[25] : (t10 = /* @__PURE__ */ jsx(Card, {
236
154
  borderBottom: !0,
237
155
  padding: 3,
238
156
  children: /* @__PURE__ */ jsxs(Flex, {
239
157
  align: "center",
240
158
  gap: 2,
241
- children: [t7, /* @__PURE__ */ jsx(Button, {
159
+ children: [t9, /* @__PURE__ */ jsx(Button, {
242
160
  icon: CloseIcon,
243
161
  mode: "bleed",
244
162
  onClick: () => setOpen(!1),
@@ -246,103 +164,160 @@ function ThemerSidebar() {
246
164
  title: "Close themer"
247
165
  })]
248
166
  })
249
- }), $[19] = setOpen, $[20] = t8);
250
- let t9;
251
- $[21] === Symbol.for("react.memo_cache_sentinel") ? (t9 = /* @__PURE__ */ jsx(Text, {
167
+ }), $[24] = setOpen, $[25] = t10);
168
+ let t11;
169
+ $[26] === Symbol.for("react.memo_cache_sentinel") ? (t11 = /* @__PURE__ */ jsx(Text, {
252
170
  size: 1,
253
171
  weight: "medium",
254
172
  children: "Presets"
255
- }), $[21] = t9) : t9 = $[21];
256
- let t10;
257
- $[22] !== activePresetSlug || $[23] !== handlePresetChange ? (t10 = TOOL_PRESETS.map((preset_1) => /* @__PURE__ */ jsx(PresetButton, {
258
- active: preset_1.slug === activePresetSlug,
259
- onClick: handlePresetChange,
260
- preset: preset_1
261
- }, preset_1.slug)), $[22] = activePresetSlug, $[23] = handlePresetChange, $[24] = t10) : t10 = $[24];
262
- let t11;
263
- $[25] === t10 ? t11 = $[26] : (t11 = /* @__PURE__ */ jsxs(Stack, {
173
+ }), $[26] = t11) : t11 = $[26];
174
+ let t12;
175
+ $[27] !== activePresetSlug || $[28] !== setOptions ? (t12 = presets.map((preset_0) => /* @__PURE__ */ jsx(PresetButton, {
176
+ active: preset_0.slug === activePresetSlug,
177
+ onClick: () => setOptions(preset_0.options),
178
+ preset: preset_0
179
+ }, preset_0.slug)), $[27] = activePresetSlug, $[28] = setOptions, $[29] = t12) : t12 = $[29];
180
+ let t13;
181
+ $[30] === t12 ? t13 = $[31] : (t13 = /* @__PURE__ */ jsxs(Stack, {
264
182
  gap: 3,
265
- children: [t9, /* @__PURE__ */ jsx(Grid, {
183
+ children: [t11, /* @__PURE__ */ jsx(Grid, {
266
184
  gap: 2,
267
185
  gridTemplateColumns: 2,
268
- children: t10
186
+ children: t12
269
187
  })]
270
- }), $[25] = t10, $[26] = t11);
271
- let t12;
272
- $[27] === Symbol.for("react.memo_cache_sentinel") ? (t12 = /* @__PURE__ */ jsx(Text, {
188
+ }), $[30] = t12, $[31] = t13);
189
+ let t14;
190
+ $[32] === Symbol.for("react.memo_cache_sentinel") ? (t14 = /* @__PURE__ */ jsx(Text, {
273
191
  size: 1,
274
192
  weight: "medium",
275
- children: "Import"
276
- }), $[27] = t12) : t12 = $[27];
277
- let t13;
278
- $[28] === handleImport ? t13 = $[29] : (t13 = (event) => {
279
- event.preventDefault(), handleImport();
280
- }, $[28] = handleImport, $[29] = t13);
281
- let t14;
282
- $[30] === Symbol.for("react.memo_cache_sentinel") ? (t14 = (event_0) => setImportUrl(event_0.currentTarget.value), $[30] = t14) : t14 = $[30];
193
+ children: "Colors"
194
+ }), $[32] = t14) : t14 = $[32];
283
195
  let t15;
284
- $[31] === importUrl ? t15 = $[32] : (t15 = /* @__PURE__ */ jsx(Box, {
285
- flex: 1,
286
- children: /* @__PURE__ */ jsx(TextInput, {
287
- "aria-label": "Hosted Themer URL",
288
- fontSize: 1,
289
- onChange: t14,
290
- padding: 2,
291
- placeholder: "themer.sanity.build URL",
292
- value: importUrl
293
- })
294
- }), $[31] = importUrl, $[32] = t15);
196
+ $[33] === patch ? t15 = $[34] : (t15 = (accent) => patch({ accent }), $[33] = patch, $[34] = t15);
295
197
  let t16;
296
- $[33] === Symbol.for("react.memo_cache_sentinel") ? (t16 = /* @__PURE__ */ jsx(Button, {
297
- mode: "ghost",
298
- padding: 2,
299
- text: "Import",
300
- type: "submit"
301
- }), $[33] = t16) : t16 = $[33];
302
- let t17;
303
- $[34] === t15 ? t17 = $[35] : (t17 = /* @__PURE__ */ jsxs(Flex, {
304
- gap: 2,
305
- children: [t15, t16]
306
- }), $[34] = t15, $[35] = t17);
307
- let t18;
308
- $[36] !== t13 || $[37] !== t17 ? (t18 = /* @__PURE__ */ jsxs(Stack, {
198
+ $[35] !== palette.blue || $[36] !== resolved.accent || $[37] !== t15 ? (t16 = /* @__PURE__ */ jsx(ColorRow, {
199
+ onChange: t15,
200
+ tints: palette.blue,
201
+ title: "Accent",
202
+ value: resolved.accent
203
+ }), $[35] = palette.blue, $[36] = resolved.accent, $[37] = t15, $[38] = t16) : t16 = $[38];
204
+ let t17 = active.text === void 0, t18;
205
+ $[39] === patch ? t18 = $[40] : (t18 = (text) => patch({ text }), $[39] = patch, $[40] = t18);
206
+ let t19;
207
+ $[41] === clearField ? t19 = $[42] : (t19 = () => clearField("text"), $[41] = clearField, $[42] = t19);
208
+ let t20;
209
+ $[43] !== palette.gray || $[44] !== resolved.text || $[45] !== t17 || $[46] !== t18 || $[47] !== t19 ? (t20 = /* @__PURE__ */ jsx(ColorRow, {
210
+ auto: t17,
211
+ onChange: t18,
212
+ onClear: t19,
213
+ tints: palette.gray,
214
+ title: "Text",
215
+ value: resolved.text
216
+ }), $[43] = palette.gray, $[44] = resolved.text, $[45] = t17, $[46] = t18, $[47] = t19, $[48] = t20) : t20 = $[48];
217
+ let t21 = active.background?.dark === void 0, t22;
218
+ $[49] === patchBackground ? t22 = $[50] : (t22 = (dark) => patchBackground({ dark }), $[49] = patchBackground, $[50] = t22);
219
+ let t23;
220
+ $[51] === clearBackground ? t23 = $[52] : (t23 = () => clearBackground("dark"), $[51] = clearBackground, $[52] = t23);
221
+ let t24;
222
+ $[53] !== palette.black || $[54] !== resolved.background.dark || $[55] !== t21 || $[56] !== t22 || $[57] !== t23 ? (t24 = /* @__PURE__ */ jsx(ColorRow, {
223
+ adjusted: palette.black,
224
+ auto: t21,
225
+ onChange: t22,
226
+ onClear: t23,
227
+ title: "Background · dark",
228
+ value: resolved.background.dark
229
+ }), $[53] = palette.black, $[54] = resolved.background.dark, $[55] = t21, $[56] = t22, $[57] = t23, $[58] = t24) : t24 = $[58];
230
+ let t25 = active.background?.light === void 0, t26;
231
+ $[59] === patchBackground ? t26 = $[60] : (t26 = (light) => patchBackground({ light }), $[59] = patchBackground, $[60] = t26);
232
+ let t27;
233
+ $[61] === clearBackground ? t27 = $[62] : (t27 = () => clearBackground("light"), $[61] = clearBackground, $[62] = t27);
234
+ let t28;
235
+ $[63] !== palette.white || $[64] !== resolved.background.light || $[65] !== t25 || $[66] !== t26 || $[67] !== t27 ? (t28 = /* @__PURE__ */ jsx(ColorRow, {
236
+ adjusted: palette.white,
237
+ auto: t25,
238
+ onChange: t26,
239
+ onClear: t27,
240
+ title: "Background · light",
241
+ value: resolved.background.light
242
+ }), $[63] = palette.white, $[64] = resolved.background.light, $[65] = t25, $[66] = t26, $[67] = t27, $[68] = t28) : t28 = $[68];
243
+ let t29;
244
+ $[69] !== t16 || $[70] !== t20 || $[71] !== t24 || $[72] !== t28 ? (t29 = /* @__PURE__ */ jsxs(Stack, {
309
245
  gap: 3,
310
- children: [t12, /* @__PURE__ */ jsx("form", {
311
- onSubmit: t13,
312
- children: t17
246
+ children: [t14, /* @__PURE__ */ jsxs(Stack, {
247
+ gap: 4,
248
+ children: [
249
+ t16,
250
+ t20,
251
+ t24,
252
+ t28
253
+ ]
313
254
  })]
314
- }), $[36] = t13, $[37] = t17, $[38] = t18) : t18 = $[38];
315
- let t19;
316
- $[39] === Symbol.for("react.memo_cache_sentinel") ? (t19 = /* @__PURE__ */ jsx(Text, {
255
+ }), $[69] = t16, $[70] = t20, $[71] = t24, $[72] = t28, $[73] = t29) : t29 = $[73];
256
+ let t30;
257
+ $[74] === Symbol.for("react.memo_cache_sentinel") ? (t30 = /* @__PURE__ */ jsx(Text, {
317
258
  size: 1,
318
259
  weight: "medium",
319
- children: "Hues"
320
- }), $[39] = t19) : t19 = $[39];
321
- let t20;
322
- $[40] !== active || $[41] !== expandedHue || $[42] !== handleHueChange || $[43] !== tones ? (t20 = HUE_FIELDS.map((field) => /* @__PURE__ */ jsx(HueSection, {
323
- expanded: expandedHue === field.key,
324
- field,
325
- hue: active[field.key],
326
- onChange: handleHueChange,
327
- onToggle: () => setExpandedHue(expandedHue === field.key ? null : field.key),
328
- tints: tones[field.key]
329
- }, field.key)), $[40] = active, $[41] = expandedHue, $[42] = handleHueChange, $[43] = tones, $[44] = t20) : t20 = $[44];
330
- let t21;
331
- $[45] === t20 ? t21 = $[46] : (t21 = /* @__PURE__ */ jsxs(Stack, {
332
- gap: 3,
333
- children: [t19, /* @__PURE__ */ jsx(Stack, {
334
- gap: 1,
335
- children: t20
260
+ children: "Contrast"
261
+ }), $[74] = t30) : t30 = $[74];
262
+ let t31 = active.contrast === void 0 ? " · auto" : "", t32;
263
+ $[75] !== resolved.contrast || $[76] !== t31 ? (t32 = /* @__PURE__ */ jsxs(Stack, {
264
+ flex: 1,
265
+ gap: 2,
266
+ children: [t30, /* @__PURE__ */ jsxs(Text, {
267
+ muted: !0,
268
+ size: 0,
269
+ children: [resolved.contrast, t31]
336
270
  })]
337
- }), $[45] = t20, $[46] = t21);
338
- let t22;
339
- $[47] === Symbol.for("react.memo_cache_sentinel") ? (t22 = /* @__PURE__ */ jsx(Text, {
271
+ }), $[75] = resolved.contrast, $[76] = t31, $[77] = t32) : t32 = $[77];
272
+ let t33;
273
+ $[78] !== active.contrast || $[79] !== clearField ? (t33 = active.contrast !== void 0 && /* @__PURE__ */ jsx(Button, {
274
+ icon: ResetIcon,
275
+ mode: "bleed",
276
+ onClick: () => clearField("contrast"),
277
+ padding: 2,
278
+ title: "Reset to auto"
279
+ }), $[78] = active.contrast, $[79] = clearField, $[80] = t33) : t33 = $[80];
280
+ let t34;
281
+ $[81] !== t32 || $[82] !== t33 ? (t34 = /* @__PURE__ */ jsxs(Flex, {
282
+ align: "center",
283
+ gap: 2,
284
+ children: [t32, t33]
285
+ }), $[81] = t32, $[82] = t33, $[83] = t34) : t34 = $[83];
286
+ let t35;
287
+ $[84] === patch ? t35 = $[85] : (t35 = (event) => patch({ contrast: Number(event.currentTarget.value) }), $[84] = patch, $[85] = t35);
288
+ let t36;
289
+ $[86] !== resolved.contrast || $[87] !== t35 ? (t36 = /* @__PURE__ */ jsx(Range, {
290
+ "aria-label": "Contrast",
291
+ max: 100,
292
+ min: 15,
293
+ onChange: t35,
294
+ step: 1,
295
+ type: "range",
296
+ value: resolved.contrast
297
+ }), $[86] = resolved.contrast, $[87] = t35, $[88] = t36) : t36 = $[88];
298
+ let t37;
299
+ $[89] === Symbol.for("react.memo_cache_sentinel") ? (t37 = /* @__PURE__ */ jsx(Text, {
300
+ muted: !0,
301
+ size: 0,
302
+ children: "100 keeps text and borders neutral — lower values blend in the accent"
303
+ }), $[89] = t37) : t37 = $[89];
304
+ let t38;
305
+ $[90] !== t34 || $[91] !== t36 ? (t38 = /* @__PURE__ */ jsxs(Stack, {
306
+ gap: 3,
307
+ children: [
308
+ t34,
309
+ t36,
310
+ t37
311
+ ]
312
+ }), $[90] = t34, $[91] = t36, $[92] = t38) : t38 = $[92];
313
+ let t39;
314
+ $[93] === Symbol.for("react.memo_cache_sentinel") ? (t39 = /* @__PURE__ */ jsx(Text, {
340
315
  size: 1,
341
316
  weight: "medium",
342
317
  children: "Add to your config"
343
- }), $[47] = t22) : t22 = $[47];
344
- let t23;
345
- $[48] === snippet ? t23 = $[49] : (t23 = /* @__PURE__ */ jsx(Card, {
318
+ }), $[93] = t39) : t39 = $[93];
319
+ let t40;
320
+ $[94] === snippet ? t40 = $[95] : (t40 = /* @__PURE__ */ jsx(Card, {
346
321
  border: !0,
347
322
  overflow: "auto",
348
323
  padding: 2,
@@ -353,63 +328,63 @@ function ThemerSidebar() {
353
328
  size: 0,
354
329
  children: snippet
355
330
  })
356
- }), $[48] = snippet, $[49] = t23);
357
- let t24;
358
- $[50] === handleCopy ? t24 = $[51] : (t24 = /* @__PURE__ */ jsx(Button, {
331
+ }), $[94] = snippet, $[95] = t40);
332
+ let t41;
333
+ $[96] === handleCopy ? t41 = $[97] : (t41 = /* @__PURE__ */ jsx(Button, {
359
334
  icon: ClipboardIcon,
360
335
  mode: "ghost",
361
336
  onClick: () => void handleCopy(),
362
337
  text: "Copy"
363
- }), $[50] = handleCopy, $[51] = t24);
364
- let t25 = hues === null, t26;
365
- $[52] === setHues ? t26 = $[53] : (t26 = () => setHues(null), $[52] = setHues, $[53] = t26);
366
- let t27;
367
- $[54] !== t25 || $[55] !== t26 ? (t27 = /* @__PURE__ */ jsx(Button, {
368
- disabled: t25,
338
+ }), $[96] = handleCopy, $[97] = t41);
339
+ let t42 = options === null, t43;
340
+ $[98] === setOptions ? t43 = $[99] : (t43 = () => setOptions(null), $[98] = setOptions, $[99] = t43);
341
+ let t44;
342
+ $[100] !== t42 || $[101] !== t43 ? (t44 = /* @__PURE__ */ jsx(Button, {
343
+ disabled: t42,
369
344
  icon: ResetIcon,
370
345
  mode: "ghost",
371
- onClick: t26,
346
+ onClick: t43,
372
347
  text: "Reset",
373
348
  tone: "critical"
374
- }), $[54] = t25, $[55] = t26, $[56] = t27) : t27 = $[56];
375
- let t28;
376
- $[57] !== t24 || $[58] !== t27 ? (t28 = /* @__PURE__ */ jsxs(Flex, {
349
+ }), $[100] = t42, $[101] = t43, $[102] = t44) : t44 = $[102];
350
+ let t45;
351
+ $[103] !== t41 || $[104] !== t44 ? (t45 = /* @__PURE__ */ jsxs(Flex, {
377
352
  gap: 2,
378
- children: [t24, t27]
379
- }), $[57] = t24, $[58] = t27, $[59] = t28) : t28 = $[59];
380
- let t29;
381
- $[60] !== t23 || $[61] !== t28 ? (t29 = /* @__PURE__ */ jsxs(Stack, {
353
+ children: [t41, t44]
354
+ }), $[103] = t41, $[104] = t44, $[105] = t45) : t45 = $[105];
355
+ let t46;
356
+ $[106] !== t40 || $[107] !== t45 ? (t46 = /* @__PURE__ */ jsxs(Stack, {
382
357
  gap: 3,
383
358
  children: [
384
- t22,
385
- t23,
386
- t28
359
+ t39,
360
+ t40,
361
+ t45
387
362
  ]
388
- }), $[60] = t23, $[61] = t28, $[62] = t29) : t29 = $[62];
389
- let t30;
390
- $[63] !== t11 || $[64] !== t18 || $[65] !== t21 || $[66] !== t29 ? (t30 = /* @__PURE__ */ jsx(Box, {
363
+ }), $[106] = t40, $[107] = t45, $[108] = t46) : t46 = $[108];
364
+ let t47;
365
+ $[109] !== t13 || $[110] !== t29 || $[111] !== t38 || $[112] !== t46 ? (t47 = /* @__PURE__ */ jsx(Box, {
391
366
  flex: 1,
392
367
  overflow: "auto",
393
368
  padding: 3,
394
369
  children: /* @__PURE__ */ jsxs(Stack, {
395
370
  gap: 4,
396
371
  children: [
397
- t11,
398
- t18,
399
- t21,
400
- t29
372
+ t13,
373
+ t29,
374
+ t38,
375
+ t46
401
376
  ]
402
377
  })
403
- }), $[63] = t11, $[64] = t18, $[65] = t21, $[66] = t29, $[67] = t30) : t30 = $[67];
404
- let t31;
405
- return $[68] !== t30 || $[69] !== t8 ? (t31 = /* @__PURE__ */ jsx(Card, {
378
+ }), $[109] = t13, $[110] = t29, $[111] = t38, $[112] = t46, $[113] = t47) : t47 = $[113];
379
+ let t48;
380
+ return $[114] !== t10 || $[115] !== t47 ? (t48 = /* @__PURE__ */ jsx(Card, {
406
381
  height: "fill",
407
382
  children: /* @__PURE__ */ jsxs(Flex, {
408
383
  direction: "column",
409
384
  height: "fill",
410
- children: [t8, t30]
385
+ children: [t10, t47]
411
386
  })
412
- }), $[68] = t30, $[69] = t8, $[70] = t31) : t31 = $[70], t31;
387
+ }), $[114] = t10, $[115] = t47, $[116] = t48) : t48 = $[116], t48;
413
388
  }
414
389
  const paletteStyle = {
415
390
  display: "flex",
@@ -420,211 +395,132 @@ const paletteStyle = {
420
395
  overflow: "hidden",
421
396
  boxShadow: "inset 0 0 0 1px var(--card-border-color)"
422
397
  };
423
- /** A little color palette of the hue mid colors a preset would apply */
398
+ /** A little palette of the colors a preset would apply */
424
399
  function PresetButton(props) {
425
- let $ = c(17), { active, onClick, preset } = props, t0;
426
- $[0] !== onClick || $[1] !== preset.slug ? (t0 = () => onClick(preset.slug), $[0] = onClick, $[1] = preset.slug, $[2] = t0) : t0 = $[2];
427
- let t1 = preset.title, t2;
428
- $[3] === preset.hues ? t2 = $[4] : (t2 = HUE_KEYS.map((key) => /* @__PURE__ */ jsx("span", { style: {
429
- flex: 1,
430
- background: preset.hues[key].mid
431
- } }, key)), $[3] = preset.hues, $[4] = t2);
400
+ let $ = c(27), { active, onClick, preset } = props, t0;
401
+ $[0] === preset.options ? t0 = $[1] : (t0 = resolveThemeOptions(preset.options), $[0] = preset.options, $[1] = t0);
402
+ let resolved = t0, t1;
403
+ $[2] === resolved.accent ? t1 = $[3] : (t1 = ["accent", resolved.accent], $[2] = resolved.accent, $[3] = t1);
404
+ let t2;
405
+ $[4] === resolved.text ? t2 = $[5] : (t2 = ["text", resolved.text], $[4] = resolved.text, $[5] = t2);
432
406
  let t3;
433
- $[5] === t2 ? t3 = $[6] : (t3 = /* @__PURE__ */ jsx("span", {
434
- style: paletteStyle,
435
- children: t2
436
- }), $[5] = t2, $[6] = t3);
407
+ $[6] === resolved.background.dark ? t3 = $[7] : (t3 = ["dark", resolved.background.dark], $[6] = resolved.background.dark, $[7] = t3);
437
408
  let t4;
438
- $[7] === preset.title ? t4 = $[8] : (t4 = /* @__PURE__ */ jsx(Text, {
409
+ $[8] === resolved.background.light ? t4 = $[9] : (t4 = ["light", resolved.background.light], $[8] = resolved.background.light, $[9] = t4);
410
+ let t5;
411
+ $[10] !== t1 || $[11] !== t2 || $[12] !== t3 || $[13] !== t4 ? (t5 = [
412
+ t1,
413
+ t2,
414
+ t3,
415
+ t4
416
+ ], $[10] = t1, $[11] = t2, $[12] = t3, $[13] = t4, $[14] = t5) : t5 = $[14];
417
+ let swatches = t5, t6;
418
+ $[15] === swatches ? t6 = $[16] : (t6 = /* @__PURE__ */ jsx("span", {
419
+ style: paletteStyle,
420
+ children: swatches.map(_temp$1)
421
+ }), $[15] = swatches, $[16] = t6);
422
+ let t7;
423
+ $[17] === preset.title ? t7 = $[18] : (t7 = /* @__PURE__ */ jsx(Text, {
439
424
  align: "left",
440
425
  size: 1,
441
426
  textOverflow: "ellipsis",
442
427
  children: preset.title
443
- }), $[7] = preset.title, $[8] = t4);
444
- let t5;
445
- $[9] !== t3 || $[10] !== t4 ? (t5 = /* @__PURE__ */ jsxs(Stack, {
428
+ }), $[17] = preset.title, $[18] = t7);
429
+ let t8;
430
+ $[19] !== t6 || $[20] !== t7 ? (t8 = /* @__PURE__ */ jsxs(Stack, {
446
431
  as: "span",
447
432
  gap: 2,
448
- children: [t3, t4]
449
- }), $[9] = t3, $[10] = t4, $[11] = t5) : t5 = $[11];
450
- let t6;
451
- return $[12] !== active || $[13] !== preset.title || $[14] !== t0 || $[15] !== t5 ? (t6 = /* @__PURE__ */ jsx(Button, {
433
+ children: [t6, t7]
434
+ }), $[19] = t6, $[20] = t7, $[21] = t8) : t8 = $[21];
435
+ let t9;
436
+ return $[22] !== active || $[23] !== onClick || $[24] !== preset.title || $[25] !== t8 ? (t9 = /* @__PURE__ */ jsx(Button, {
452
437
  mode: "ghost",
453
- onClick: t0,
438
+ onClick,
454
439
  padding: 2,
455
440
  selected: active,
456
- title: t1,
457
- children: t5
458
- }), $[12] = active, $[13] = preset.title, $[14] = t0, $[15] = t5, $[16] = t6) : t6 = $[16], t6;
441
+ title: preset.title,
442
+ children: t8
443
+ }), $[22] = active, $[23] = onClick, $[24] = preset.title, $[25] = t8, $[26] = t9) : t9 = $[26], t9;
444
+ }
445
+ function _temp$1(t0) {
446
+ let [key, background] = t0;
447
+ return /* @__PURE__ */ jsx("span", { style: {
448
+ flex: 1,
449
+ background
450
+ } }, key);
459
451
  }
460
452
  const rampStyle = {
461
453
  ...paletteStyle,
462
454
  height: 13,
463
455
  borderRadius: 2
464
456
  };
465
- /** One hue of the theme: a collapsible header with the generated tint ramp */
466
- function HueSection(props) {
467
- let $ = c(35), { expanded, field, hue, onChange, onToggle, tints } = props, t0 = expanded ? "transparent" : void 0, t1 = expanded ? 3 : 0, t2 = expanded ? 3 : 0, t3 = field.description, t4;
468
- $[0] === expanded ? t4 = $[1] : (t4 = /* @__PURE__ */ jsx(Text, {
469
- size: 1,
470
- children: jsx(expanded ? ChevronDownIcon : ChevronRightIcon, {})
471
- }), $[0] = expanded, $[1] = t4);
472
- let t5;
473
- $[2] === Symbol.for("react.memo_cache_sentinel") ? (t5 = { width: 76 }, $[2] = t5) : t5 = $[2];
474
- let t6;
475
- $[3] === field.title ? t6 = $[4] : (t6 = /* @__PURE__ */ jsx(Box, {
476
- as: "span",
477
- style: t5,
478
- children: /* @__PURE__ */ jsx(Text, {
479
- size: 1,
480
- textOverflow: "ellipsis",
481
- weight: "medium",
482
- children: field.title
483
- })
484
- }), $[3] = field.title, $[4] = t6);
485
- let t7;
486
- $[5] === Symbol.for("react.memo_cache_sentinel") ? (t7 = {
487
- ...rampStyle,
488
- flex: 1
489
- }, $[5] = t7) : t7 = $[5];
490
- let t8;
491
- $[6] === tints ? t8 = $[7] : (t8 = COLOR_TINTS.map((tint) => /* @__PURE__ */ jsx("span", { style: {
492
- flex: 1,
493
- background: tints[tint].hex
494
- } }, tint)), $[6] = tints, $[7] = t8);
495
- let t9;
496
- $[8] === t8 ? t9 = $[9] : (t9 = /* @__PURE__ */ jsx("span", {
497
- style: t7,
498
- children: t8
499
- }), $[8] = t8, $[9] = t9);
500
- let t10;
501
- $[10] !== t4 || $[11] !== t6 || $[12] !== t9 ? (t10 = /* @__PURE__ */ jsxs(Flex, {
502
- align: "center",
503
- as: "span",
504
- gap: 2,
505
- children: [
506
- t4,
507
- t6,
508
- t9
509
- ]
510
- }), $[10] = t4, $[11] = t6, $[12] = t9, $[13] = t10) : t10 = $[13];
511
- let t11;
512
- $[14] !== expanded || $[15] !== field.description || $[16] !== onToggle || $[17] !== t10 ? (t11 = /* @__PURE__ */ jsx(Button, {
513
- "aria-expanded": expanded,
514
- mode: "bleed",
515
- onClick: onToggle,
516
- padding: 2,
517
- title: t3,
518
- children: t10
519
- }), $[14] = expanded, $[15] = field.description, $[16] = onToggle, $[17] = t10, $[18] = t11) : t11 = $[18];
520
- let t12;
521
- $[19] !== expanded || $[20] !== field.description || $[21] !== field.key || $[22] !== field.title || $[23] !== hue || $[24] !== onChange ? (t12 = expanded && /* @__PURE__ */ jsxs(Stack, {
522
- gap: 3,
523
- paddingX: 3,
524
- children: [
525
- /* @__PURE__ */ jsx(Text, {
526
- muted: !0,
527
- size: 0,
528
- children: field.description
529
- }),
530
- /* @__PURE__ */ jsx(ColorRow, {
531
- onChange: (mid) => onChange(field.key, { mid }),
532
- title: "Mid",
533
- value: hue.mid
534
- }),
535
- /* @__PURE__ */ jsxs(Flex, {
536
- align: "center",
537
- gap: 2,
538
- children: [/* @__PURE__ */ jsxs(Stack, {
539
- flex: 1,
540
- gap: 2,
541
- children: [/* @__PURE__ */ jsx(Text, {
542
- size: 1,
543
- children: "Mid point"
544
- }), /* @__PURE__ */ jsx(Text, {
545
- muted: !0,
546
- size: 0,
547
- children: "The tint the mid color sits at"
548
- })]
549
- }), /* @__PURE__ */ jsx(Select, {
550
- "aria-label": `${field.title} mid point`,
551
- fontSize: 1,
552
- onChange: (event) => {
553
- let value = Number(event.currentTarget.value), midPoint = MID_POINTS.find((candidate) => candidate === value);
554
- midPoint !== void 0 && onChange(field.key, { midPoint });
555
- },
556
- padding: 2,
557
- value: hue.midPoint,
558
- children: MID_POINTS.map(_temp$1)
559
- })]
560
- }),
561
- /* @__PURE__ */ jsx(ColorRow, {
562
- onChange: (lightest) => onChange(field.key, { lightest }),
563
- title: "Lightest",
564
- value: hue.lightest
565
- }),
566
- /* @__PURE__ */ jsx(ColorRow, {
567
- onChange: (darkest) => onChange(field.key, { darkest }),
568
- title: "Darkest",
569
- value: hue.darkest
570
- })
571
- ]
572
- }), $[19] = expanded, $[20] = field.description, $[21] = field.key, $[22] = field.title, $[23] = hue, $[24] = onChange, $[25] = t12) : t12 = $[25];
573
- let t13;
574
- $[26] !== t1 || $[27] !== t11 || $[28] !== t12 || $[29] !== t2 ? (t13 = /* @__PURE__ */ jsxs(Stack, {
575
- gap: t1,
576
- paddingBottom: t2,
577
- children: [t11, t12]
578
- }), $[26] = t1, $[27] = t11, $[28] = t12, $[29] = t2, $[30] = t13) : t13 = $[30];
579
- let t14;
580
- return $[31] !== expanded || $[32] !== t0 || $[33] !== t13 ? (t14 = /* @__PURE__ */ jsx(Card, {
581
- border: expanded,
582
- radius: 2,
583
- tone: t0,
584
- children: t13
585
- }), $[31] = expanded, $[32] = t0, $[33] = t13, $[34] = t14) : t14 = $[34], t14;
586
- }
587
- function _temp$1(midPoint_0) {
588
- return /* @__PURE__ */ jsx("option", {
589
- value: midPoint_0,
590
- children: midPoint_0
591
- }, midPoint_0);
592
- }
457
+ /**
458
+ * One color of the theme: a swatch with its value, an optional reset-to-auto
459
+ * button, and the generated 50–950 tint ramp for the scales it anchors.
460
+ */
593
461
  function ColorRow(props) {
594
- let $ = c(18), { onChange, title, value } = props, t0;
462
+ let $ = c(27), { adjusted, auto, onChange, onClear, tints, title, value } = props, t0;
595
463
  $[0] === title ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(Text, {
596
464
  size: 1,
597
465
  children: title
598
466
  }), $[0] = title, $[1] = t0);
599
- let t1;
600
- $[2] === value ? t1 = $[3] : (t1 = /* @__PURE__ */ jsx(Text, {
467
+ let t1 = adjusted !== void 0 && adjusted !== value ? ` → ${adjusted}` : "", t2 = auto ? " · auto" : "", t3;
468
+ $[2] !== t1 || $[3] !== t2 || $[4] !== value ? (t3 = /* @__PURE__ */ jsxs(Text, {
601
469
  muted: !0,
602
470
  size: 0,
603
- children: value
604
- }), $[2] = value, $[3] = t1);
605
- let t2;
606
- $[4] !== t0 || $[5] !== t1 ? (t2 = /* @__PURE__ */ jsxs(Stack, {
471
+ textOverflow: "ellipsis",
472
+ children: [
473
+ value,
474
+ t1,
475
+ t2
476
+ ]
477
+ }), $[2] = t1, $[3] = t2, $[4] = value, $[5] = t3) : t3 = $[5];
478
+ let t4;
479
+ $[6] !== t0 || $[7] !== t3 ? (t4 = /* @__PURE__ */ jsxs(Stack, {
607
480
  flex: 1,
608
481
  gap: 2,
609
- children: [t0, t1]
610
- }), $[4] = t0, $[5] = t1, $[6] = t2) : t2 = $[6];
611
- let t3 = `${title} color`, t4;
612
- $[7] === onChange ? t4 = $[8] : (t4 = (event) => onChange(event.currentTarget.value), $[7] = onChange, $[8] = t4);
482
+ children: [t0, t3]
483
+ }), $[6] = t0, $[7] = t3, $[8] = t4) : t4 = $[8];
613
484
  let t5;
614
- $[9] === value ? t5 = $[10] : (t5 = expandHex(value), $[9] = value, $[10] = t5);
615
- let t6;
616
- $[11] !== t3 || $[12] !== t4 || $[13] !== t5 ? (t6 = /* @__PURE__ */ jsx(Swatch, {
617
- "aria-label": t3,
618
- onChange: t4,
485
+ $[9] !== auto || $[10] !== onClear ? (t5 = onClear && !auto && /* @__PURE__ */ jsx(Button, {
486
+ icon: ResetIcon,
487
+ mode: "bleed",
488
+ onClick: onClear,
489
+ padding: 2,
490
+ title: "Reset to auto"
491
+ }), $[9] = auto, $[10] = onClear, $[11] = t5) : t5 = $[11];
492
+ let t6 = `${title} color`, t7;
493
+ $[12] === onChange ? t7 = $[13] : (t7 = (event) => onChange(event.currentTarget.value), $[12] = onChange, $[13] = t7);
494
+ let t8;
495
+ $[14] !== t6 || $[15] !== t7 || $[16] !== value ? (t8 = /* @__PURE__ */ jsx(Swatch, {
496
+ "aria-label": t6,
497
+ onChange: t7,
619
498
  type: "color",
620
- value: t5
621
- }), $[11] = t3, $[12] = t4, $[13] = t5, $[14] = t6) : t6 = $[14];
622
- let t7;
623
- return $[15] !== t2 || $[16] !== t6 ? (t7 = /* @__PURE__ */ jsxs(Flex, {
499
+ value
500
+ }), $[14] = t6, $[15] = t7, $[16] = value, $[17] = t8) : t8 = $[17];
501
+ let t9;
502
+ $[18] !== t4 || $[19] !== t5 || $[20] !== t8 ? (t9 = /* @__PURE__ */ jsxs(Flex, {
624
503
  align: "center",
625
504
  gap: 2,
626
- children: [t2, t6]
627
- }), $[15] = t2, $[16] = t6, $[17] = t7) : t7 = $[17], t7;
505
+ children: [
506
+ t4,
507
+ t5,
508
+ t8
509
+ ]
510
+ }), $[18] = t4, $[19] = t5, $[20] = t8, $[21] = t9) : t9 = $[21];
511
+ let t10;
512
+ $[22] === tints ? t10 = $[23] : (t10 = tints && /* @__PURE__ */ jsx("span", {
513
+ style: rampStyle,
514
+ children: COLOR_TINTS.map((tint) => /* @__PURE__ */ jsx("span", { style: {
515
+ flex: 1,
516
+ background: tints[tint]
517
+ } }, tint))
518
+ }), $[22] = tints, $[23] = t10);
519
+ let t11;
520
+ return $[24] !== t10 || $[25] !== t9 ? (t11 = /* @__PURE__ */ jsxs(Stack, {
521
+ gap: 2,
522
+ children: [t9, t10]
523
+ }), $[24] = t10, $[25] = t9, $[26] = t11) : t11 = $[26], t11;
628
524
  }
629
525
  /**
630
526
  * Narrow enough to leave the studio preview as much room as possible: it fits
@@ -668,46 +564,44 @@ function ThemerActiveToolLayout(props) {
668
564
  children: [t1, t2]
669
565
  }), $[6] = t1, $[7] = t2, $[8] = t3) : t3 = $[8], t3;
670
566
  }
671
- const STORAGE_KEY = "sanityStudio:themer:hues";
672
- function sanitizeHue(value) {
567
+ const STORAGE_KEY = "sanityStudio:themer:options";
568
+ function sanitizeColor(value) {
569
+ return typeof value == "string" && isColor(value) ? value.toLowerCase() : null;
570
+ }
571
+ function sanitizeOptions(value) {
673
572
  if (!value || typeof value != "object") return null;
674
- let mid = Reflect.get(value, "mid"), midPoint = Reflect.get(value, "midPoint"), lightest = Reflect.get(value, "lightest"), darkest = Reflect.get(value, "darkest");
675
- return typeof mid != "string" || !isColor(mid) || typeof lightest != "string" || !isColor(lightest) || typeof darkest != "string" || !isColor(darkest) || typeof midPoint != "number" || !MID_POINTS.includes(midPoint) ? null : {
676
- mid: mid.toLowerCase(),
677
- midPoint,
678
- lightest: lightest.toLowerCase(),
679
- darkest: darkest.toLowerCase()
680
- };
573
+ let accent = sanitizeColor(Reflect.get(value, "accent"));
574
+ if (!accent) return null;
575
+ let options = { accent }, text = sanitizeColor(Reflect.get(value, "text"));
576
+ text && (options.text = text);
577
+ let background = Reflect.get(value, "background");
578
+ if (background && typeof background == "object") {
579
+ let dark = sanitizeColor(Reflect.get(background, "dark")), light = sanitizeColor(Reflect.get(background, "light"));
580
+ (dark || light) && (options.background = {}, dark && (options.background.dark = dark), light && (options.background.light = light));
581
+ }
582
+ let contrast = Reflect.get(value, "contrast");
583
+ return typeof contrast == "number" && Number.isFinite(contrast) && (options.contrast = Math.min(100, Math.max(15, contrast))), options;
681
584
  }
682
585
  /**
683
- * Restores draft hues from localStorage, so theme drafts survive studio
684
- * reloads.
586
+ * Restores draft theme options from localStorage, so theme drafts survive
587
+ * studio reloads.
685
588
  *
686
589
  * @internal
687
590
  */
688
- function readStoredHues() {
591
+ function readStoredOptions() {
689
592
  try {
690
593
  if (typeof localStorage > "u") return null;
691
594
  let raw = localStorage.getItem(STORAGE_KEY);
692
- if (!raw) return null;
693
- let parsed = JSON.parse(raw);
694
- if (!parsed || typeof parsed != "object") return null;
695
- let hues = {};
696
- for (let key of HUE_KEYS) {
697
- let hue = sanitizeHue(Reflect.get(parsed, key));
698
- if (!hue) return null;
699
- hues[key] = hue;
700
- }
701
- return hues;
595
+ return raw ? sanitizeOptions(JSON.parse(raw)) : null;
702
596
  } catch {
703
597
  return null;
704
598
  }
705
599
  }
706
600
  /** @internal */
707
- function writeStoredHues(hues) {
601
+ function writeStoredOptions(options) {
708
602
  try {
709
603
  if (typeof localStorage > "u") return;
710
- hues === null ? localStorage.removeItem(STORAGE_KEY) : localStorage.setItem(STORAGE_KEY, JSON.stringify(hues));
604
+ options === null ? localStorage.removeItem(STORAGE_KEY) : localStorage.setItem(STORAGE_KEY, JSON.stringify(options));
711
605
  } catch {}
712
606
  }
713
607
  /**
@@ -722,18 +616,18 @@ function writeStoredHues(hues) {
722
616
  * @internal
723
617
  */
724
618
  function ThemerLayout(props) {
725
- let $ = c(13), { baseHues, ...layoutProps } = props, [open, setOpen] = useState(!1), [hues, setHues] = useState(readStoredHues), t0, t1;
726
- $[0] === hues ? (t0 = $[1], t1 = $[2]) : (t0 = () => writeStoredHues(hues), t1 = [hues], $[0] = hues, $[1] = t0, $[2] = t1), useEffect(t0, t1);
619
+ let $ = c(13), { baseOptions, ...layoutProps } = props, [open, setOpen] = useState(!1), [options, setOptions] = useState(readStoredOptions), t0, t1;
620
+ $[0] === options ? (t0 = $[1], t1 = $[2]) : (t0 = () => writeStoredOptions(options), t1 = [options], $[0] = options, $[1] = t0, $[2] = t1), useEffect(t0, t1);
727
621
  let t2;
728
- $[3] === hues ? t2 = $[4] : (t2 = hues === null ? null : createTheme(hues), $[3] = hues, $[4] = t2);
622
+ $[3] === options ? t2 = $[4] : (t2 = options === null ? null : buildTheme(options), $[3] = options, $[4] = t2);
729
623
  let theme = t2, t3;
730
- $[5] !== baseHues || $[6] !== hues || $[7] !== open ? (t3 = {
731
- baseHues,
732
- hues,
733
- setHues,
624
+ $[5] !== baseOptions || $[6] !== open || $[7] !== options ? (t3 = {
625
+ baseOptions,
626
+ options,
627
+ setOptions,
734
628
  open,
735
629
  setOpen
736
- }, $[5] = baseHues, $[6] = hues, $[7] = open, $[8] = t3) : t3 = $[8];
630
+ }, $[5] = baseOptions, $[6] = open, $[7] = options, $[8] = t3) : t3 = $[8];
737
631
  let context = t3, t4 = ThemerContext, t5 = theme === null ? layoutProps.renderDefault(layoutProps) : /* @__PURE__ */ jsx(ThemeProvider, {
738
632
  theme,
739
633
  children: layoutProps.renderDefault(layoutProps)
@@ -802,10 +696,10 @@ function _temp() {
802
696
  return /* @__PURE__ */ jsx(ThemerNavbarButton, {});
803
697
  }
804
698
  /**
805
- * A Studio plugin that adds a themer sidebar for the legacy Themer themes:
806
- * a navbar toggle opens the sidebar next to the active tool, where presets,
807
- * per-hue editors and pasted themer.sanity.build URLs preview a legacy
808
- * `createTheme` theme live on the whole Studio while you browse around.
699
+ * A Studio plugin that adds a themer sidebar for `buildTheme` themes: a
700
+ * navbar toggle opens the sidebar next to the active tool, where presets, the
701
+ * accent/text/background pickers and the contrast slider preview a
702
+ * `buildTheme` theme live on the whole Studio while you browse around.
809
703
  * Toggle between light and dark mode with the regular appearance menu — the
810
704
  * preview follows it.
811
705
  *
@@ -825,12 +719,12 @@ function _temp() {
825
719
  * @alpha
826
720
  */
827
721
  const themerTool = definePlugin((options) => {
828
- let baseHues = applyHues(options?.hues ?? {});
722
+ let baseOptions = options?.config ?? { accent: DEFAULT_ACCENT };
829
723
  function ThemerLayoutWithOptions(props) {
830
724
  let $ = c(2), t0;
831
725
  return $[0] === props ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(ThemerLayout, {
832
726
  ...props,
833
- baseHues
727
+ baseOptions
834
728
  }), $[0] = props, $[1] = t0), t0;
835
729
  }
836
730
  return {