@bison-lab/payload-core 3.7.0 → 3.9.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/README.md +57 -22
- package/dist/admin.d.mts +62 -6
- package/dist/admin.d.mts.map +1 -1
- package/dist/admin.mjs +1795 -172
- package/dist/admin.mjs.map +1 -1
- package/dist/identity-D1X0Om4Z.mjs +198 -0
- package/dist/identity-D1X0Om4Z.mjs.map +1 -0
- package/dist/index.d.mts +82 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +520 -66
- package/dist/index.mjs.map +1 -1
- package/dist/library-BGq4wGif.d.mts +189 -0
- package/dist/library-BGq4wGif.d.mts.map +1 -0
- package/dist/map-BUDkX8g1.mjs +111 -0
- package/dist/map-BUDkX8g1.mjs.map +1 -0
- package/dist/react.d.mts +32 -0
- package/dist/react.d.mts.map +1 -0
- package/dist/react.mjs +77 -0
- package/dist/react.mjs.map +1 -0
- package/dist/theme.d.mts +12 -7
- package/dist/theme.d.mts.map +1 -1
- package/dist/theme.mjs +19 -6
- package/dist/theme.mjs.map +1 -1
- package/package.json +17 -3
- package/dist/types-B3FC9sdF.d.mts +0 -89
- package/dist/types-B3FC9sdF.d.mts.map +0 -1
- package/dist/types-DWwL-JEr.mjs +0 -76
- package/dist/types-DWwL-JEr.mjs.map +0 -1
package/dist/admin.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { r as stateFromColorDoc, t as colorDocFromState } from "./map-BUDkX8g1.mjs";
|
|
3
|
+
import { FieldDescription, FieldError, FieldLabel, useField, useForm, useFormFields } from "@payloadcms/ui";
|
|
4
|
+
import { GREY_SCALES, SHADE_STEPS, contrastForeground, contrastRatio, createColorScale, hexToHSL, hslToString, includedShadeSteps, isThemeHex, presetHints, regenerateColorScale, setColorScaleInclude, updateColorScale } from "@bison-lab/tokens";
|
|
4
5
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { findFont, fontFaceCss } from "@bison-lab/fonts";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { findFont, fontFaceCss, isFontId } from "@bison-lab/fonts";
|
|
7
8
|
//#region src/admin/color-field.tsx
|
|
8
9
|
const HEX_ERROR = "Enter a six-digit hex colour like #1e3a5f";
|
|
9
10
|
/**
|
|
@@ -39,7 +40,7 @@ const ColorField = ({ field, path, readOnly }) => {
|
|
|
39
40
|
/* @__PURE__ */ jsx("style", {
|
|
40
41
|
href: "bl-color-field",
|
|
41
42
|
precedence: "default",
|
|
42
|
-
children: CSS$
|
|
43
|
+
children: CSS$3
|
|
43
44
|
}),
|
|
44
45
|
/* @__PURE__ */ jsx(FieldLabel, {
|
|
45
46
|
htmlFor: hexId,
|
|
@@ -98,7 +99,7 @@ const ColorField = ({ field, path, readOnly }) => {
|
|
|
98
99
|
]
|
|
99
100
|
});
|
|
100
101
|
};
|
|
101
|
-
const CSS$
|
|
102
|
+
const CSS$3 = `
|
|
102
103
|
.bl-color-field__control{display:flex;align-items:center;gap:calc(var(--base) * .4)}
|
|
103
104
|
.bl-color-field__swatch{flex:none;width:calc(var(--base) * 1.6);height:calc(var(--base) * 1.6);border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-elevation-50)}
|
|
104
105
|
.bl-color-field__picker{flex:none;width:calc(var(--base) * 2);height:calc(var(--base) * 2);padding:0;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:transparent;cursor:pointer}
|
|
@@ -108,11 +109,1007 @@ const CSS$2 = `
|
|
|
108
109
|
.bl-color-field__hex:disabled{color:var(--theme-elevation-400)}
|
|
109
110
|
`;
|
|
110
111
|
//#endregion
|
|
112
|
+
//#region src/theme/appearance-labels.ts
|
|
113
|
+
/**
|
|
114
|
+
* Named choices the Appearance child shows. Values stay the token presets;
|
|
115
|
+
* labels match the mock workflow (Soft → pill, Gentle → minimal, Balanced
|
|
116
|
+
* → default, Follow device → system). No rem, ms, or CSS variable tables.
|
|
117
|
+
*/
|
|
118
|
+
const APPEARANCE_CHOICES = {
|
|
119
|
+
defaultTheme: {
|
|
120
|
+
light: {
|
|
121
|
+
label: "Light",
|
|
122
|
+
hint: "Always opens in light mode"
|
|
123
|
+
},
|
|
124
|
+
dark: {
|
|
125
|
+
label: "Dark",
|
|
126
|
+
hint: "Always opens in dark mode"
|
|
127
|
+
},
|
|
128
|
+
system: {
|
|
129
|
+
label: "Follow device",
|
|
130
|
+
hint: "Matches the visitor’s system setting"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
radius: {
|
|
134
|
+
sharp: {
|
|
135
|
+
label: "Sharp",
|
|
136
|
+
hint: "Crisp and structured"
|
|
137
|
+
},
|
|
138
|
+
subtle: {
|
|
139
|
+
label: "Subtle",
|
|
140
|
+
hint: "Clean with a little softness"
|
|
141
|
+
},
|
|
142
|
+
rounded: {
|
|
143
|
+
label: "Rounded",
|
|
144
|
+
hint: "Friendly and contemporary"
|
|
145
|
+
},
|
|
146
|
+
pill: {
|
|
147
|
+
label: "Soft",
|
|
148
|
+
hint: "Very rounded and expressive"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
shadow: {
|
|
152
|
+
flat: {
|
|
153
|
+
label: "Flat",
|
|
154
|
+
hint: "Minimal visual depth"
|
|
155
|
+
},
|
|
156
|
+
subtle: {
|
|
157
|
+
label: "Subtle",
|
|
158
|
+
hint: "Light separation between surfaces"
|
|
159
|
+
},
|
|
160
|
+
elevated: {
|
|
161
|
+
label: "Elevated",
|
|
162
|
+
hint: "More noticeable layering and depth"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
motion: {
|
|
166
|
+
snappy: {
|
|
167
|
+
label: "Quick",
|
|
168
|
+
hint: "Fast and responsive"
|
|
169
|
+
},
|
|
170
|
+
smooth: {
|
|
171
|
+
label: "Smooth",
|
|
172
|
+
hint: "Balanced and natural"
|
|
173
|
+
},
|
|
174
|
+
minimal: {
|
|
175
|
+
label: "Gentle",
|
|
176
|
+
hint: "Slower and more relaxed"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
density: {
|
|
180
|
+
compact: {
|
|
181
|
+
label: "Compact",
|
|
182
|
+
hint: "Fits more content on screen"
|
|
183
|
+
},
|
|
184
|
+
default: {
|
|
185
|
+
label: "Balanced",
|
|
186
|
+
hint: "Comfortable for most sites"
|
|
187
|
+
},
|
|
188
|
+
spacious: {
|
|
189
|
+
label: "Spacious",
|
|
190
|
+
hint: "More breathing room and larger controls"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const APPEARANCE_FAMILY_COPY = {
|
|
195
|
+
defaultTheme: {
|
|
196
|
+
title: "Default theme",
|
|
197
|
+
hint: "Choose how the public site opens for visitors."
|
|
198
|
+
},
|
|
199
|
+
radius: {
|
|
200
|
+
title: "Corner style",
|
|
201
|
+
hint: "Controls the shape of buttons, inputs, cards, and panels across the site."
|
|
202
|
+
},
|
|
203
|
+
shadow: {
|
|
204
|
+
title: "Depth",
|
|
205
|
+
hint: "Choose how much cards and floating elements stand out from the page."
|
|
206
|
+
},
|
|
207
|
+
motion: {
|
|
208
|
+
title: "Motion",
|
|
209
|
+
hint: "Controls how quickly menus, cards, and interface transitions move."
|
|
210
|
+
},
|
|
211
|
+
density: {
|
|
212
|
+
title: "Spacing",
|
|
213
|
+
hint: "Choose how compact or roomy forms, buttons, cards, and page sections feel."
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const FONT_ROLE_COPY = {
|
|
217
|
+
heading: {
|
|
218
|
+
label: "Heading font",
|
|
219
|
+
hint: "Used for page titles, section headings, and cards"
|
|
220
|
+
},
|
|
221
|
+
body: {
|
|
222
|
+
label: "Body font",
|
|
223
|
+
hint: "Used for paragraphs, navigation, forms, and buttons"
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const SYSTEM_COLOR_COPY = {
|
|
227
|
+
primary: {
|
|
228
|
+
label: "Primary",
|
|
229
|
+
hint: "Primary actions and key links",
|
|
230
|
+
group: "brand"
|
|
231
|
+
},
|
|
232
|
+
secondary: {
|
|
233
|
+
label: "Secondary",
|
|
234
|
+
hint: "Supporting accents and eyebrows",
|
|
235
|
+
group: "brand"
|
|
236
|
+
},
|
|
237
|
+
accent: {
|
|
238
|
+
label: "Accent",
|
|
239
|
+
hint: "Dark brand surfaces",
|
|
240
|
+
group: "brand"
|
|
241
|
+
},
|
|
242
|
+
highlight: {
|
|
243
|
+
label: "Highlight",
|
|
244
|
+
hint: "Promotional and emphasis fills",
|
|
245
|
+
group: "brand"
|
|
246
|
+
},
|
|
247
|
+
success: {
|
|
248
|
+
label: "Success",
|
|
249
|
+
hint: "Theme chrome for confirmations. Not offered as a page-editor fill.",
|
|
250
|
+
group: "status"
|
|
251
|
+
},
|
|
252
|
+
destructive: {
|
|
253
|
+
label: "Destructive",
|
|
254
|
+
hint: "Theme chrome for errors and dangerous actions. Not offered as a page-editor fill.",
|
|
255
|
+
group: "status"
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/theme/readability.ts
|
|
260
|
+
/** Same threshold `deriveDarkPalette` uses for brand fills. */
|
|
261
|
+
const DARK_FILL_LIGHTEN_BELOW = 40;
|
|
262
|
+
const DARK_FILL_LIGHTEN_BY = 25;
|
|
263
|
+
function clamp(value, min, max) {
|
|
264
|
+
return Math.min(max, Math.max(min, value));
|
|
265
|
+
}
|
|
266
|
+
function darkFill(color) {
|
|
267
|
+
if (color.l < DARK_FILL_LIGHTEN_BELOW) return {
|
|
268
|
+
...color,
|
|
269
|
+
l: clamp(color.l + DARK_FILL_LIGHTEN_BY, 0, 100)
|
|
270
|
+
};
|
|
271
|
+
return color;
|
|
272
|
+
}
|
|
273
|
+
function sample(fill, target) {
|
|
274
|
+
const label = contrastForeground(fill);
|
|
275
|
+
const fillCss = hslToString(fill);
|
|
276
|
+
const ratio = contrastRatio(fillCss, label);
|
|
277
|
+
return {
|
|
278
|
+
ratio,
|
|
279
|
+
shortfall: Math.max(0, Math.round((target - ratio) * 100) / 100),
|
|
280
|
+
fill: fillCss,
|
|
281
|
+
label
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Editable fill + the automatic label, Light and Dark. Target 7:1.
|
|
286
|
+
* Meets only when both pass. Warnings do not block save.
|
|
287
|
+
*/
|
|
288
|
+
function fillLabelWarning(id, name, hex, target = 7) {
|
|
289
|
+
if (!isThemeHex(hex)) return null;
|
|
290
|
+
const lightFill = hexToHSL(hex);
|
|
291
|
+
const light = sample(lightFill, target);
|
|
292
|
+
const dark = sample(darkFill(lightFill), target);
|
|
293
|
+
return {
|
|
294
|
+
id,
|
|
295
|
+
name,
|
|
296
|
+
light,
|
|
297
|
+
dark,
|
|
298
|
+
meets: light.ratio >= target && dark.ratio >= target
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/** Green/teal family. A warning at admin time, not a schema block. */
|
|
302
|
+
function successHueWarning(hex) {
|
|
303
|
+
if (!isThemeHex(hex)) return null;
|
|
304
|
+
const { h } = hexToHSL(hex);
|
|
305
|
+
if (h >= 80 && h <= 180) return null;
|
|
306
|
+
return "Keep success colors recognizable — stay in a green or teal family.";
|
|
307
|
+
}
|
|
308
|
+
/** Red family. A warning at admin time, not a schema block. */
|
|
309
|
+
function destructiveHueWarning(hex) {
|
|
310
|
+
if (!isThemeHex(hex)) return null;
|
|
311
|
+
const { h } = hexToHSL(hex);
|
|
312
|
+
if (h <= 30 || h >= 330) return null;
|
|
313
|
+
return "Keep destructive colors recognizable — stay in a red family.";
|
|
314
|
+
}
|
|
315
|
+
function automaticLabel(hex) {
|
|
316
|
+
if (!isThemeHex(hex)) return null;
|
|
317
|
+
return contrastForeground(hexToHSL(hex));
|
|
318
|
+
}
|
|
319
|
+
function hslCss(value) {
|
|
320
|
+
return `hsl(${value})`;
|
|
321
|
+
}
|
|
322
|
+
/** Which appearances fail, in words. Empty when both pass. */
|
|
323
|
+
function hardToReadModes(item) {
|
|
324
|
+
const light = item.light.shortfall > 0;
|
|
325
|
+
const dark = item.dark.shortfall > 0;
|
|
326
|
+
if (light && dark) return "in Light and Dark";
|
|
327
|
+
if (light) return "in Light";
|
|
328
|
+
if (dark) return "in Dark";
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
function readabilityHeadline(item) {
|
|
332
|
+
return item.meets ? `Text on ${item.name} looks readable` : `Text on ${item.name} may be hard to read`;
|
|
333
|
+
}
|
|
334
|
+
function readabilityModesLine(item) {
|
|
335
|
+
const modes = hardToReadModes(item);
|
|
336
|
+
return modes ? `Hard to read ${modes}.` : "Looks good in Light and Dark.";
|
|
337
|
+
}
|
|
338
|
+
function readabilityGapPercent(sample, target = 7) {
|
|
339
|
+
if (sample.shortfall <= 0) return 0;
|
|
340
|
+
return Math.round(sample.shortfall / target * 100);
|
|
341
|
+
}
|
|
342
|
+
function readabilityRatioLine(sample, target = 7) {
|
|
343
|
+
const percent = readabilityGapPercent(sample, target);
|
|
344
|
+
if (percent <= 0) return "Meets the contrast we want";
|
|
345
|
+
if (percent < 10) return "A little short of the contrast we want";
|
|
346
|
+
return `About ${percent}% short of the contrast we want`;
|
|
347
|
+
}
|
|
348
|
+
const READABILITY_FIX = "Darken or lighten the hex, then regenerate the scale. The site picks light or dark type for you.";
|
|
349
|
+
const READABILITY_PUBLISH_NOTE = "You can still publish. To improve a color, darken or lighten it and regenerate.";
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/admin/preview-mode.tsx
|
|
352
|
+
const ATTR = "data-bl-theme-preview";
|
|
353
|
+
const EVENT = "bl-theme-preview";
|
|
354
|
+
function readMode() {
|
|
355
|
+
if (typeof document === "undefined") return "light";
|
|
356
|
+
return document.documentElement.getAttribute(ATTR) === "dark" ? "dark" : "light";
|
|
357
|
+
}
|
|
358
|
+
function setThemePreviewMode(mode) {
|
|
359
|
+
if (typeof document === "undefined") return;
|
|
360
|
+
document.documentElement.setAttribute(ATTR, mode);
|
|
361
|
+
window.dispatchEvent(new Event(EVENT));
|
|
362
|
+
}
|
|
363
|
+
function useThemePreviewMode() {
|
|
364
|
+
const [mode, setMode] = useState(readMode);
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
const sync = () => setMode(readMode());
|
|
367
|
+
window.addEventListener(EVENT, sync);
|
|
368
|
+
return () => window.removeEventListener(EVENT, sync);
|
|
369
|
+
}, []);
|
|
370
|
+
return mode;
|
|
371
|
+
}
|
|
372
|
+
function ThemePreviewToggle() {
|
|
373
|
+
const mode = useThemePreviewMode();
|
|
374
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
375
|
+
className: "bl-preview-toggle",
|
|
376
|
+
role: "group",
|
|
377
|
+
"aria-label": "Preview colors in",
|
|
378
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
379
|
+
type: "button",
|
|
380
|
+
"data-on": mode === "light" ? "" : void 0,
|
|
381
|
+
onClick: () => setThemePreviewMode("light"),
|
|
382
|
+
children: "Light"
|
|
383
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
384
|
+
type: "button",
|
|
385
|
+
"data-on": mode === "dark" ? "" : void 0,
|
|
386
|
+
onClick: () => setThemePreviewMode("dark"),
|
|
387
|
+
children: "Dark"
|
|
388
|
+
})]
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
const THEME_PREVIEW_TOGGLE_CSS = `
|
|
392
|
+
.bl-preview-toggle{display:inline-flex;padding:3px;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:10px;background:var(--theme-elevation-0,#fff)}
|
|
393
|
+
.bl-preview-toggle button{min-height:2rem;padding:.25rem .75rem;border:0;border-radius:7px;background:transparent;color:var(--theme-elevation-600,#475569);font:inherit;font-size:13px;font-weight:600;cursor:pointer}
|
|
394
|
+
.bl-preview-toggle button[data-on]{background:var(--theme-elevation-800,#0f172a);color:#fff}
|
|
395
|
+
`;
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/admin/readability-list.tsx
|
|
398
|
+
function ReadabilityDetail({ item, target = 7 }) {
|
|
399
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
400
|
+
className: "bl-readability-detail",
|
|
401
|
+
children: [
|
|
402
|
+
/* @__PURE__ */ jsx("p", {
|
|
403
|
+
className: "bl-readability-detail__lead",
|
|
404
|
+
children: readabilityModesLine(item)
|
|
405
|
+
}),
|
|
406
|
+
/* @__PURE__ */ jsx("div", {
|
|
407
|
+
className: "bl-readability-detail__modes",
|
|
408
|
+
children: [["Light", item.light], ["Dark", item.dark]].map(([mode, sample]) => /* @__PURE__ */ jsxs("figure", {
|
|
409
|
+
"data-failing": sample.shortfall > 0 ? "" : void 0,
|
|
410
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
411
|
+
"aria-hidden": "true",
|
|
412
|
+
className: "bl-readability__swatch",
|
|
413
|
+
style: {
|
|
414
|
+
background: hslCss(sample.fill),
|
|
415
|
+
color: hslCss(sample.label)
|
|
416
|
+
},
|
|
417
|
+
children: "Aa"
|
|
418
|
+
}), /* @__PURE__ */ jsxs("figcaption", { children: [
|
|
419
|
+
/* @__PURE__ */ jsx("strong", { children: mode }),
|
|
420
|
+
/* @__PURE__ */ jsx("span", { children: sample.shortfall > 0 ? "Hard to read" : "Looks good" }),
|
|
421
|
+
/* @__PURE__ */ jsx("span", { children: readabilityRatioLine(sample, target) })
|
|
422
|
+
] })]
|
|
423
|
+
}, mode))
|
|
424
|
+
}),
|
|
425
|
+
item.meets ? null : /* @__PURE__ */ jsx("p", {
|
|
426
|
+
className: "bl-readability-detail__fix",
|
|
427
|
+
children: READABILITY_FIX
|
|
428
|
+
})
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function ReadabilityList({ warnings, target = 7 }) {
|
|
433
|
+
const failing = warnings.filter((item) => !item.meets).length;
|
|
434
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
435
|
+
className: "bl-readability",
|
|
436
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
437
|
+
className: "bl-readability__head",
|
|
438
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Readability checks" }), /* @__PURE__ */ jsx("p", { children: "Text the site puts on each fill. Warnings do not block save." })] }), /* @__PURE__ */ jsx("span", {
|
|
439
|
+
"data-attention": failing ? "" : void 0,
|
|
440
|
+
children: failing === 0 ? "All look readable" : `${failing} may be hard to read`
|
|
441
|
+
})]
|
|
442
|
+
}), /* @__PURE__ */ jsx("ul", { children: warnings.map((item) => /* @__PURE__ */ jsxs("li", {
|
|
443
|
+
"data-failing": item.meets ? void 0 : "",
|
|
444
|
+
children: [/* @__PURE__ */ jsx("strong", { children: readabilityHeadline(item) }), /* @__PURE__ */ jsx(ReadabilityDetail, {
|
|
445
|
+
item,
|
|
446
|
+
target
|
|
447
|
+
})]
|
|
448
|
+
}, item.id)) })]
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
const READABILITY_LIST_CSS = `
|
|
452
|
+
.bl-readability{padding:calc(var(--base,16px) * .8);border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:var(--style-radius-m,12px);background:var(--theme-elevation-0,#fff)}
|
|
453
|
+
.bl-readability__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}
|
|
454
|
+
.bl-readability__head h2{margin:0;font-size:1rem}
|
|
455
|
+
.bl-readability__head p{margin:.35rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
456
|
+
.bl-readability__head span{flex:none;font-size:12px;font-weight:600;padding:.2rem .55rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9)}
|
|
457
|
+
.bl-readability__head span[data-attention]{background:#fffbeb;color:#92400e}
|
|
458
|
+
.bl-readability ul{margin:.85rem 0 0;padding:0;list-style:none}
|
|
459
|
+
.bl-readability li{padding:.65rem 0;border-top:1px solid var(--theme-elevation-100,#f1f5f9)}
|
|
460
|
+
.bl-readability li:first-child{border-top:0}
|
|
461
|
+
.bl-readability li[data-failing]{background:#fff7ed;margin:0 -.4rem;padding:.65rem .4rem;border-radius:8px}
|
|
462
|
+
.bl-readability__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2rem;border-radius:6px;font-weight:700;font-size:13px}
|
|
463
|
+
.bl-readability strong{display:block;font-size:13px}
|
|
464
|
+
.bl-readability-detail__lead{margin:0 0 .65rem;font-size:13px;color:var(--theme-elevation-600,#475569)}
|
|
465
|
+
.bl-readability-detail__modes{display:grid;grid-template-columns:1fr 1fr;gap:.65rem}
|
|
466
|
+
.bl-readability-detail__modes figure{display:flex;gap:.6rem;align-items:center;margin:0;padding:.55rem .6rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:10px;background:#fff}
|
|
467
|
+
.bl-readability-detail__modes figure[data-failing]{border-color:#fdba74;background:#fff7ed}
|
|
468
|
+
.bl-readability-detail__modes figcaption{display:flex;flex-direction:column;gap:.1rem;min-width:0}
|
|
469
|
+
.bl-readability-detail__modes figcaption strong{font-size:12px}
|
|
470
|
+
.bl-readability-detail__modes figcaption span{font-size:11px;color:var(--theme-elevation-500,#64748b)}
|
|
471
|
+
.bl-readability-detail__fix{margin:.7rem 0 0;font-size:13px;color:var(--theme-elevation-600,#475569)}
|
|
472
|
+
`;
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/admin/color-scale-card.tsx
|
|
475
|
+
function ColorScaleCard({ id, name, hint, required, custom, state, onChange, onDelete, readOnly, role }) {
|
|
476
|
+
const label = automaticLabel(state.hex);
|
|
477
|
+
const warning = fillLabelWarning(id ?? name.toLowerCase(), name, state.hex);
|
|
478
|
+
const previewMode = useThemePreviewMode();
|
|
479
|
+
const preview = warning ? previewMode === "dark" ? warning.dark : warning.light : null;
|
|
480
|
+
const [open, setOpen] = useState(false);
|
|
481
|
+
const titleId = useId();
|
|
482
|
+
const hueWarning = role === "success" ? successHueWarning(state.hex) : role === "destructive" ? destructiveHueWarning(state.hex) : null;
|
|
483
|
+
const included = new Set(includedShadeSteps(state));
|
|
484
|
+
function setHex(hex) {
|
|
485
|
+
onChange(updateColorScale(state, { hex }));
|
|
486
|
+
}
|
|
487
|
+
function setStep(step) {
|
|
488
|
+
onChange(updateColorScale(state, { sourceStep: step }));
|
|
489
|
+
}
|
|
490
|
+
function toggleStep(step) {
|
|
491
|
+
if (step === state.sourceStep) return;
|
|
492
|
+
const next = new Set(included);
|
|
493
|
+
if (next.has(step)) next.delete(step);
|
|
494
|
+
else next.add(step);
|
|
495
|
+
onChange(setColorScaleInclude(state, SHADE_STEPS.filter((item) => next.has(item))));
|
|
496
|
+
}
|
|
497
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
498
|
+
className: "bl-scale-card",
|
|
499
|
+
"data-preview": previewMode,
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ jsxs("header", {
|
|
502
|
+
className: "bl-scale-card__head",
|
|
503
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
504
|
+
className: "bl-scale-card__title-row",
|
|
505
|
+
children: [
|
|
506
|
+
/* @__PURE__ */ jsx("h3", {
|
|
507
|
+
className: "bl-scale-card__title",
|
|
508
|
+
children: name
|
|
509
|
+
}),
|
|
510
|
+
required ? /* @__PURE__ */ jsx("span", {
|
|
511
|
+
className: "bl-scale-card__badge",
|
|
512
|
+
children: "Required"
|
|
513
|
+
}) : null,
|
|
514
|
+
custom ? /* @__PURE__ */ jsx("span", {
|
|
515
|
+
className: "bl-scale-card__badge",
|
|
516
|
+
children: "Optional"
|
|
517
|
+
}) : null,
|
|
518
|
+
warning ? warning.meets ? /* @__PURE__ */ jsxs("span", {
|
|
519
|
+
className: "bl-scale-card__status",
|
|
520
|
+
"data-ok": "",
|
|
521
|
+
title: `Text on ${name} looks readable`,
|
|
522
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
523
|
+
"aria-hidden": "true",
|
|
524
|
+
children: "✓"
|
|
525
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
526
|
+
className: "bl-scale-card__sr",
|
|
527
|
+
children: [
|
|
528
|
+
"Text on ",
|
|
529
|
+
name,
|
|
530
|
+
" looks readable"
|
|
531
|
+
]
|
|
532
|
+
})]
|
|
533
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
534
|
+
type: "button",
|
|
535
|
+
className: "bl-scale-card__badge bl-scale-card__badge--attention",
|
|
536
|
+
"aria-haspopup": "dialog",
|
|
537
|
+
onClick: () => setOpen(true),
|
|
538
|
+
children: "Needs attention"
|
|
539
|
+
}) : null
|
|
540
|
+
]
|
|
541
|
+
}), hint ? /* @__PURE__ */ jsx("p", {
|
|
542
|
+
className: "bl-scale-card__hint",
|
|
543
|
+
children: hint
|
|
544
|
+
}) : null] }), onDelete ? /* @__PURE__ */ jsx("button", {
|
|
545
|
+
type: "button",
|
|
546
|
+
className: "bl-scale-card__delete",
|
|
547
|
+
disabled: readOnly,
|
|
548
|
+
onClick: onDelete,
|
|
549
|
+
children: "Delete"
|
|
550
|
+
}) : null]
|
|
551
|
+
}),
|
|
552
|
+
/* @__PURE__ */ jsxs("div", {
|
|
553
|
+
className: "bl-scale-card__row",
|
|
554
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
555
|
+
className: "bl-scale-card__field",
|
|
556
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Brand hex" }), /* @__PURE__ */ jsxs("span", {
|
|
557
|
+
className: "bl-scale-card__hex-wrap",
|
|
558
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
559
|
+
type: "color",
|
|
560
|
+
"aria-label": `${name} picker`,
|
|
561
|
+
disabled: readOnly,
|
|
562
|
+
value: state.hex,
|
|
563
|
+
onChange: (event) => setHex(event.target.value)
|
|
564
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
565
|
+
type: "text",
|
|
566
|
+
spellCheck: false,
|
|
567
|
+
autoComplete: "off",
|
|
568
|
+
disabled: readOnly,
|
|
569
|
+
value: state.hex,
|
|
570
|
+
onChange: (event) => {
|
|
571
|
+
const next = event.target.value.trim();
|
|
572
|
+
if (next) setHex(next);
|
|
573
|
+
}
|
|
574
|
+
})]
|
|
575
|
+
})]
|
|
576
|
+
}), /* @__PURE__ */ jsxs("label", {
|
|
577
|
+
className: "bl-scale-card__field",
|
|
578
|
+
children: [/* @__PURE__ */ jsx("span", { children: "This color is" }), /* @__PURE__ */ jsx("select", {
|
|
579
|
+
disabled: readOnly,
|
|
580
|
+
value: String(state.sourceStep),
|
|
581
|
+
onChange: (event) => setStep(Number(event.target.value)),
|
|
582
|
+
children: SHADE_STEPS.map((step) => /* @__PURE__ */ jsx("option", {
|
|
583
|
+
value: step,
|
|
584
|
+
children: step
|
|
585
|
+
}, step))
|
|
586
|
+
})]
|
|
587
|
+
})]
|
|
588
|
+
}),
|
|
589
|
+
/* @__PURE__ */ jsxs("div", {
|
|
590
|
+
className: "bl-scale-card__label-sample",
|
|
591
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
592
|
+
className: "bl-scale-card__swatch",
|
|
593
|
+
style: {
|
|
594
|
+
background: preview ? hslCss(preview.fill) : state.hex,
|
|
595
|
+
color: preview ? hslCss(preview.label) : label ? hslCss(label) : void 0
|
|
596
|
+
},
|
|
597
|
+
children: "Aa"
|
|
598
|
+
}), /* @__PURE__ */ jsx("p", { children: previewMode === "dark" ? "Previewing this fill in Dark. A dark-enough hex lightens; otherwise it stays the typed color." : "The site chooses light or dark type on this fill. Darken or lighten the hex and regenerate to flip it. There is no ink picker." })]
|
|
599
|
+
}),
|
|
600
|
+
hueWarning ? /* @__PURE__ */ jsx("p", {
|
|
601
|
+
className: "bl-scale-card__warn",
|
|
602
|
+
children: hueWarning
|
|
603
|
+
}) : null,
|
|
604
|
+
state.stale ? /* @__PURE__ */ jsx("p", {
|
|
605
|
+
className: "bl-scale-card__stale",
|
|
606
|
+
children: "This scale is out of date. Regenerate to rebuild 50–950 around the hex."
|
|
607
|
+
}) : null,
|
|
608
|
+
/* @__PURE__ */ jsx("button", {
|
|
609
|
+
type: "button",
|
|
610
|
+
className: "bl-scale-card__regen",
|
|
611
|
+
disabled: readOnly || !state.stale,
|
|
612
|
+
onClick: () => onChange(regenerateColorScale(state)),
|
|
613
|
+
children: "Regenerate scale"
|
|
614
|
+
}),
|
|
615
|
+
/* @__PURE__ */ jsxs("div", {
|
|
616
|
+
className: "bl-scale-card__scale-head",
|
|
617
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Generated scale" }), /* @__PURE__ */ jsxs("span", { children: [
|
|
618
|
+
/* @__PURE__ */ jsx("button", {
|
|
619
|
+
type: "button",
|
|
620
|
+
disabled: readOnly,
|
|
621
|
+
onClick: () => onChange(setColorScaleInclude(state, "all")),
|
|
622
|
+
children: "Include all"
|
|
623
|
+
}),
|
|
624
|
+
" · ",
|
|
625
|
+
/* @__PURE__ */ jsx("button", {
|
|
626
|
+
type: "button",
|
|
627
|
+
disabled: readOnly,
|
|
628
|
+
onClick: () => onChange(setColorScaleInclude(state, "source")),
|
|
629
|
+
children: "Source only"
|
|
630
|
+
})
|
|
631
|
+
] })]
|
|
632
|
+
}),
|
|
633
|
+
/* @__PURE__ */ jsx("div", {
|
|
634
|
+
className: "bl-scale-card__steps",
|
|
635
|
+
role: "group",
|
|
636
|
+
"aria-label": `${name} scale steps`,
|
|
637
|
+
children: SHADE_STEPS.map((step) => {
|
|
638
|
+
const source = step === state.sourceStep;
|
|
639
|
+
const on = included.has(step);
|
|
640
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
641
|
+
type: "button",
|
|
642
|
+
className: "bl-scale-card__step",
|
|
643
|
+
"data-source": source ? "" : void 0,
|
|
644
|
+
"data-excluded": on ? void 0 : "",
|
|
645
|
+
disabled: readOnly || source,
|
|
646
|
+
title: source ? `Source step ${step} cannot be excluded` : on ? `Exclude ${step} from page editors` : `Include ${step} for page editors`,
|
|
647
|
+
onClick: () => toggleStep(step),
|
|
648
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
649
|
+
className: "bl-scale-card__chip",
|
|
650
|
+
style: { background: hslCss(state.scale[step]) }
|
|
651
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
652
|
+
className: "bl-scale-card__step-label",
|
|
653
|
+
children: step
|
|
654
|
+
})]
|
|
655
|
+
}, step);
|
|
656
|
+
})
|
|
657
|
+
}),
|
|
658
|
+
open && warning ? /* @__PURE__ */ jsx("div", {
|
|
659
|
+
className: "bl-scale-card__modal",
|
|
660
|
+
role: "dialog",
|
|
661
|
+
"aria-modal": "true",
|
|
662
|
+
"aria-labelledby": titleId,
|
|
663
|
+
onClick: (event) => {
|
|
664
|
+
if (event.target === event.currentTarget) setOpen(false);
|
|
665
|
+
},
|
|
666
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
667
|
+
className: "bl-scale-card__panel",
|
|
668
|
+
children: [
|
|
669
|
+
/* @__PURE__ */ jsxs("div", {
|
|
670
|
+
className: "bl-scale-card__panel-head",
|
|
671
|
+
children: [/* @__PURE__ */ jsx("h4", {
|
|
672
|
+
id: titleId,
|
|
673
|
+
children: readabilityHeadline(warning)
|
|
674
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
675
|
+
type: "button",
|
|
676
|
+
onClick: () => setOpen(false),
|
|
677
|
+
"aria-label": "Close",
|
|
678
|
+
children: "×"
|
|
679
|
+
})]
|
|
680
|
+
}),
|
|
681
|
+
/* @__PURE__ */ jsx(ReadabilityDetail, { item: warning }),
|
|
682
|
+
/* @__PURE__ */ jsx("button", {
|
|
683
|
+
type: "button",
|
|
684
|
+
className: "bl-scale-card__panel-done",
|
|
685
|
+
onClick: () => setOpen(false),
|
|
686
|
+
children: "Keep editing this color"
|
|
687
|
+
})
|
|
688
|
+
]
|
|
689
|
+
})
|
|
690
|
+
}) : null
|
|
691
|
+
]
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
const COLOR_SCALE_CARD_CSS = `
|
|
695
|
+
.bl-scale-card{padding:calc(var(--base,16px) * .8);border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:var(--style-radius-m,12px);background:var(--theme-elevation-0,#fff)}
|
|
696
|
+
.bl-scale-card__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}
|
|
697
|
+
.bl-scale-card__title-row{position:relative;display:flex;align-items:center;flex-wrap:wrap;gap:.5rem}
|
|
698
|
+
.bl-scale-card__title{margin:0;font-size:1rem}
|
|
699
|
+
.bl-scale-card__badge{font:inherit;font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;border:0;background:var(--theme-elevation-100,#f1f5f9);color:var(--theme-elevation-500,#64748b)}
|
|
700
|
+
.bl-scale-card__badge--attention{background:#fff1f2;color:#be123c;cursor:pointer}
|
|
701
|
+
.bl-scale-card__status{display:inline-grid;place-items:center;width:1.4rem;height:1.4rem;border-radius:999px;border:0;font:inherit;font-weight:700;line-height:1}
|
|
702
|
+
.bl-scale-card__status[data-ok]{background:#ecfdf5;color:#047857}
|
|
703
|
+
.bl-scale-card__sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
704
|
+
.bl-scale-card__hint{margin:.35rem 0 0;color:var(--theme-elevation-500,#64748b);font-size:12px}
|
|
705
|
+
.bl-scale-card__delete{border:0;background:none;color:#be123c;font:inherit;font-size:13px;font-weight:600;cursor:pointer}
|
|
706
|
+
.bl-scale-card__row{display:grid;grid-template-columns:1fr 8rem;gap:.75rem;margin-top:1rem}
|
|
707
|
+
.bl-scale-card__field{display:flex;flex-direction:column;gap:.35rem;font-size:12px;font-weight:600;color:var(--theme-elevation-600,#475569)}
|
|
708
|
+
.bl-scale-card__hex-wrap{display:flex;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px;overflow:hidden;background:var(--theme-input-bg,#fff)}
|
|
709
|
+
.bl-scale-card__hex-wrap input[type=color]{width:2.75rem;height:2.25rem;padding:0;border:0;border-right:1px solid var(--theme-elevation-150,#e2e8f0);background:transparent;cursor:pointer}
|
|
710
|
+
.bl-scale-card__hex-wrap input[type=text]{flex:1;min-width:0;border:0;padding:.4rem .7rem;font-family:var(--font-mono,ui-monospace,monospace);font-size:13px;background:transparent;color:inherit}
|
|
711
|
+
.bl-scale-card__field select{min-height:2.25rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px;padding:.35rem .5rem;background:var(--theme-input-bg,#fff);color:inherit}
|
|
712
|
+
.bl-scale-card__label-sample{display:flex;gap:.75rem;align-items:center;margin-top:1rem}
|
|
713
|
+
.bl-scale-card__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:3rem;height:2.25rem;border-radius:8px;border:1px solid var(--theme-elevation-150,#e2e8f0);font-weight:700}
|
|
714
|
+
.bl-scale-card__label-sample p{margin:0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
715
|
+
.bl-scale-card__warn,.bl-scale-card__stale{margin:.75rem 0 0;padding:.65rem .75rem;border-radius:8px;font-size:12px}
|
|
716
|
+
.bl-scale-card__warn{border:1px solid #a7f3d0;background:#ecfdf5;color:#065f46}
|
|
717
|
+
.bl-scale-card__stale{border:1px solid #fde68a;background:#fffbeb;color:#92400e}
|
|
718
|
+
.bl-scale-card__regen{width:100%;margin-top:.85rem;min-height:2.25rem;border:0;border-radius:8px;background:var(--theme-elevation-800,#0f172a);color:#fff;font:inherit;font-weight:600;cursor:pointer}
|
|
719
|
+
.bl-scale-card__regen:disabled{opacity:.45;cursor:not-allowed}
|
|
720
|
+
.bl-scale-card__scale-head{display:flex;justify-content:space-between;margin:1rem 0 .4rem;font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
721
|
+
.bl-scale-card__scale-head button{border:0;background:none;padding:0;font:inherit;color:inherit;text-decoration:underline;cursor:pointer}
|
|
722
|
+
.bl-scale-card__steps{display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:.45rem 3px}
|
|
723
|
+
.bl-scale-card__step{display:flex;flex-direction:column;align-items:center;gap:.3rem;border:0;padding:0;background:transparent;cursor:pointer}
|
|
724
|
+
.bl-scale-card__chip{display:block;width:100%;height:2.15rem;border-radius:6px}
|
|
725
|
+
.bl-scale-card__step-label{font-size:10px;line-height:1;color:var(--theme-elevation-600,#475569)}
|
|
726
|
+
.bl-scale-card__step[data-source] .bl-scale-card__chip{box-shadow:0 0 0 2px #0f172a}
|
|
727
|
+
.bl-scale-card__step[data-excluded]{opacity:.35}
|
|
728
|
+
.bl-scale-card__step:disabled{cursor:default}
|
|
729
|
+
.bl-scale-card__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
|
|
730
|
+
.bl-scale-card__panel{width:min(32rem,100%);padding:1.15rem 1.2rem;border-radius:16px;background:#fff;color:#0f172a}
|
|
731
|
+
.bl-scale-card__panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
|
|
732
|
+
.bl-scale-card__panel-head h4{margin:0;font-size:1rem}
|
|
733
|
+
.bl-scale-card__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;line-height:1;color:#64748b;cursor:pointer}
|
|
734
|
+
.bl-scale-card__panel-done{margin-top:1rem;min-height:2.1rem;padding:.35rem .85rem;border:0;border-radius:8px;background:#0f172a;color:#fff;font:inherit;font-weight:600;cursor:pointer}
|
|
735
|
+
[data-bl-theme-preview=dark] .bl-scale-card{background:#0f172a;border-color:#334155;color:#f8fafc}
|
|
736
|
+
[data-bl-theme-preview=dark] .bl-scale-card__hint,[data-bl-theme-preview=dark] .bl-scale-card__label-sample p,[data-bl-theme-preview=dark] .bl-scale-card__scale-head,[data-bl-theme-preview=dark] .bl-scale-card__step-label,[data-bl-theme-preview=dark] .bl-scale-card__field{color:#94a3b8}
|
|
737
|
+
[data-bl-theme-preview=dark] .bl-scale-card__hex-wrap,[data-bl-theme-preview=dark] .bl-scale-card__field select{background:#1e293b;border-color:#334155;color:#f8fafc}
|
|
738
|
+
[data-bl-theme-preview=dark] .bl-scale-card__step[data-source] .bl-scale-card__chip{box-shadow:0 0 0 2px #f8fafc}
|
|
739
|
+
` + READABILITY_LIST_CSS;
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/admin/color-scale-field.tsx
|
|
742
|
+
const SYSTEM_ROLES = new Set(Object.keys(SYSTEM_COLOR_COPY));
|
|
743
|
+
/**
|
|
744
|
+
* One system (or library row) color: hex, source step, regenerate, stale,
|
|
745
|
+
* include/exclude, automatic label. Writes the group's stored fields.
|
|
746
|
+
*/
|
|
747
|
+
const ColorScaleField = ({ field, path, readOnly }) => {
|
|
748
|
+
const hex = useField({ path: `${path}.hex` });
|
|
749
|
+
const sourceStep = useField({ path: `${path}.sourceStep` });
|
|
750
|
+
const scale = useField({ path: `${path}.scale` });
|
|
751
|
+
const stale = useField({ path: `${path}.stale` });
|
|
752
|
+
const include = useField({ path: `${path}.include` });
|
|
753
|
+
const includedSteps = useField({ path: `${path}.includedSteps` });
|
|
754
|
+
const key = path.split(".").pop() ?? "";
|
|
755
|
+
const copy = SYSTEM_ROLES.has(key) ? SYSTEM_COLOR_COPY[key] : void 0;
|
|
756
|
+
const state = stateFromColorDoc({
|
|
757
|
+
hex: hex.value,
|
|
758
|
+
sourceStep: sourceStep.value,
|
|
759
|
+
scale: scale.value,
|
|
760
|
+
stale: stale.value,
|
|
761
|
+
include: include.value === "source" || include.value === "custom" || include.value === "all" ? include.value : "all",
|
|
762
|
+
includedSteps: includedSteps.value
|
|
763
|
+
});
|
|
764
|
+
function write(next) {
|
|
765
|
+
const doc = colorDocFromState(next);
|
|
766
|
+
hex.setValue(doc.hex);
|
|
767
|
+
sourceStep.setValue(String(doc.sourceStep));
|
|
768
|
+
scale.setValue(doc.scale);
|
|
769
|
+
stale.setValue(doc.stale);
|
|
770
|
+
include.setValue(doc.include);
|
|
771
|
+
includedSteps.setValue(doc.includedSteps ?? (doc.include === "source" ? [doc.sourceStep] : [...SHADE_STEPS]));
|
|
772
|
+
}
|
|
773
|
+
const label = copy?.label ?? (typeof field.label === "string" ? field.label : key);
|
|
774
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
775
|
+
className: "field-type bl-scale-field",
|
|
776
|
+
children: [/* @__PURE__ */ jsx("style", {
|
|
777
|
+
href: "bl-scale-card",
|
|
778
|
+
precedence: "default",
|
|
779
|
+
children: COLOR_SCALE_CARD_CSS
|
|
780
|
+
}), /* @__PURE__ */ jsx(ColorScaleCard, {
|
|
781
|
+
id: key,
|
|
782
|
+
name: label,
|
|
783
|
+
hint: copy?.hint,
|
|
784
|
+
required: Boolean(copy) || Boolean("required" in field && field.required),
|
|
785
|
+
state: isThemeHex(state.hex) ? state : {
|
|
786
|
+
...state,
|
|
787
|
+
hex: "#000000"
|
|
788
|
+
},
|
|
789
|
+
onChange: write,
|
|
790
|
+
readOnly,
|
|
791
|
+
role: key === "success" || key === "destructive" ? key : null
|
|
792
|
+
})]
|
|
793
|
+
});
|
|
794
|
+
};
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/theme/types.ts
|
|
797
|
+
const SYSTEM_COLOR_KEYS = [
|
|
798
|
+
"primary",
|
|
799
|
+
"secondary",
|
|
800
|
+
"accent",
|
|
801
|
+
"highlight",
|
|
802
|
+
"success",
|
|
803
|
+
"destructive"
|
|
804
|
+
];
|
|
805
|
+
function themeColorKeys(doc) {
|
|
806
|
+
const custom = (doc?.colors?.library ?? []).map((row) => row.key?.trim()).filter((key) => Boolean(key));
|
|
807
|
+
return [...SYSTEM_COLOR_KEYS, ...custom];
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Remove a custom color. Unused: omit `replacement` and the row is gone.
|
|
811
|
+
* In use: pass another system or custom key; the caller rewrites page
|
|
812
|
+
* tokens with `rewriteColorToken`. After this, no library row still
|
|
813
|
+
* has `key`.
|
|
814
|
+
*/
|
|
815
|
+
function deleteLibraryColor(doc, key, replacement) {
|
|
816
|
+
const library = [...doc.colors?.library ?? []];
|
|
817
|
+
const index = library.findIndex((row) => row.key === key);
|
|
818
|
+
if (index === -1) throw new Error(`No custom color named ${JSON.stringify(key)}`);
|
|
819
|
+
library.splice(index, 1);
|
|
820
|
+
const next = {
|
|
821
|
+
...doc,
|
|
822
|
+
colors: {
|
|
823
|
+
...doc.colors,
|
|
824
|
+
library
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
if (replacement !== void 0) {
|
|
828
|
+
if (replacement === key) throw new Error("Replacement must be a different color");
|
|
829
|
+
if (!themeColorKeys(next).includes(replacement)) throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);
|
|
830
|
+
}
|
|
831
|
+
return next;
|
|
832
|
+
}
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region src/admin/library-field.tsx
|
|
835
|
+
function slugify(value) {
|
|
836
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Custom colors: the same scale controls as system colors, plus add and
|
|
840
|
+
* in-use delete (one replacement scale; unused rows delete immediately).
|
|
841
|
+
*/
|
|
842
|
+
const LibraryField = ({ path, readOnly }) => {
|
|
843
|
+
const { value, setValue } = useField({ path });
|
|
844
|
+
const rows = value ?? [];
|
|
845
|
+
const [adding, setAdding] = useState(false);
|
|
846
|
+
const [name, setName] = useState("");
|
|
847
|
+
const [hex, setHex] = useState("#E07A5F");
|
|
848
|
+
const [pendingDelete, setPendingDelete] = useState(null);
|
|
849
|
+
const [replacement, setReplacement] = useState("");
|
|
850
|
+
function write(next) {
|
|
851
|
+
setValue(next);
|
|
852
|
+
}
|
|
853
|
+
function add() {
|
|
854
|
+
const key = slugify(name);
|
|
855
|
+
if (!key || rows.some((row) => row.key === key)) return;
|
|
856
|
+
write([...rows, {
|
|
857
|
+
key,
|
|
858
|
+
label: name.trim(),
|
|
859
|
+
...colorDocFromState(createColorScale(hex, 500))
|
|
860
|
+
}]);
|
|
861
|
+
setName("");
|
|
862
|
+
setAdding(false);
|
|
863
|
+
}
|
|
864
|
+
function remove(row, replaceWith) {
|
|
865
|
+
write(deleteLibraryColor({ colors: { library: rows } }, row.key ?? "", replaceWith).colors?.library ?? []);
|
|
866
|
+
setPendingDelete(null);
|
|
867
|
+
setReplacement("");
|
|
868
|
+
}
|
|
869
|
+
const replacements = [...SYSTEM_COLOR_KEYS.map((key) => ({
|
|
870
|
+
value: key,
|
|
871
|
+
label: key[0].toUpperCase() + key.slice(1)
|
|
872
|
+
})), ...rows.filter((row) => row.key && row.key !== pendingDelete?.key).map((row) => ({
|
|
873
|
+
value: row.key ?? "",
|
|
874
|
+
label: row.label ?? row.key ?? ""
|
|
875
|
+
}))];
|
|
876
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
877
|
+
className: "field-type bl-library",
|
|
878
|
+
children: [
|
|
879
|
+
/* @__PURE__ */ jsx("style", {
|
|
880
|
+
href: "bl-scale-card",
|
|
881
|
+
precedence: "default",
|
|
882
|
+
children: COLOR_SCALE_CARD_CSS
|
|
883
|
+
}),
|
|
884
|
+
/* @__PURE__ */ jsx("style", {
|
|
885
|
+
href: "bl-library",
|
|
886
|
+
precedence: "default",
|
|
887
|
+
children: CSS$2
|
|
888
|
+
}),
|
|
889
|
+
/* @__PURE__ */ jsxs("div", {
|
|
890
|
+
className: "bl-library__head",
|
|
891
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Custom colors" }), /* @__PURE__ */ jsx("p", { children: "Named scales that sit with the house colors. Page editors pick them as keys (`coral-400`)." })] }), /* @__PURE__ */ jsx("button", {
|
|
892
|
+
type: "button",
|
|
893
|
+
disabled: readOnly,
|
|
894
|
+
onClick: () => setAdding(true),
|
|
895
|
+
children: "Add color"
|
|
896
|
+
})]
|
|
897
|
+
}),
|
|
898
|
+
adding ? /* @__PURE__ */ jsxs("div", {
|
|
899
|
+
className: "bl-library__add",
|
|
900
|
+
children: [
|
|
901
|
+
/* @__PURE__ */ jsxs("label", { children: ["Name", /* @__PURE__ */ jsx("input", {
|
|
902
|
+
value: name,
|
|
903
|
+
onChange: (event) => setName(event.target.value)
|
|
904
|
+
})] }),
|
|
905
|
+
/* @__PURE__ */ jsxs("label", { children: ["Hex", /* @__PURE__ */ jsx("input", {
|
|
906
|
+
value: hex,
|
|
907
|
+
onChange: (event) => setHex(event.target.value)
|
|
908
|
+
})] }),
|
|
909
|
+
/* @__PURE__ */ jsx("button", {
|
|
910
|
+
type: "button",
|
|
911
|
+
onClick: () => setAdding(false),
|
|
912
|
+
children: "Cancel"
|
|
913
|
+
}),
|
|
914
|
+
/* @__PURE__ */ jsx("button", {
|
|
915
|
+
type: "button",
|
|
916
|
+
disabled: !slugify(name),
|
|
917
|
+
onClick: add,
|
|
918
|
+
children: "Create color scale"
|
|
919
|
+
})
|
|
920
|
+
]
|
|
921
|
+
}) : null,
|
|
922
|
+
/* @__PURE__ */ jsx("div", {
|
|
923
|
+
className: "bl-library__list",
|
|
924
|
+
children: rows.map((row, index) => /* @__PURE__ */ jsx(ColorScaleCard, {
|
|
925
|
+
id: row.key ?? `custom-${index}`,
|
|
926
|
+
name: row.label ?? row.key ?? "Color",
|
|
927
|
+
hint: row.key ? `Key: ${row.key}` : void 0,
|
|
928
|
+
custom: true,
|
|
929
|
+
state: stateFromColorDoc(row),
|
|
930
|
+
onChange: (next) => {
|
|
931
|
+
const copy = [...rows];
|
|
932
|
+
copy[index] = {
|
|
933
|
+
...row,
|
|
934
|
+
...colorDocFromState(next)
|
|
935
|
+
};
|
|
936
|
+
write(copy);
|
|
937
|
+
},
|
|
938
|
+
onDelete: readOnly ? void 0 : () => setPendingDelete(row),
|
|
939
|
+
readOnly
|
|
940
|
+
}, row.key ?? index))
|
|
941
|
+
}),
|
|
942
|
+
pendingDelete ? /* @__PURE__ */ jsx("div", {
|
|
943
|
+
className: "bl-library__modal",
|
|
944
|
+
role: "dialog",
|
|
945
|
+
"aria-modal": "true",
|
|
946
|
+
"aria-labelledby": "bl-library-delete-title",
|
|
947
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
948
|
+
className: "bl-library__panel",
|
|
949
|
+
children: [
|
|
950
|
+
/* @__PURE__ */ jsxs("h2", {
|
|
951
|
+
id: "bl-library-delete-title",
|
|
952
|
+
children: ["Delete ", pendingDelete.label ?? pendingDelete.key]
|
|
953
|
+
}),
|
|
954
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
955
|
+
"Unused custom colors delete immediately. If this scale is in use, pick one replacement — every",
|
|
956
|
+
" ",
|
|
957
|
+
/* @__PURE__ */ jsxs("code", { children: [pendingDelete.key, "-400"] }),
|
|
958
|
+
" becomes ",
|
|
959
|
+
/* @__PURE__ */ jsxs("code", { children: [replacement || "…", "-400"] }),
|
|
960
|
+
"."
|
|
961
|
+
] }),
|
|
962
|
+
/* @__PURE__ */ jsxs("label", { children: ["Replacement scale", /* @__PURE__ */ jsxs("select", {
|
|
963
|
+
value: replacement,
|
|
964
|
+
onChange: (event) => setReplacement(event.target.value),
|
|
965
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
966
|
+
value: "",
|
|
967
|
+
children: "Choose a scale…"
|
|
968
|
+
}), replacements.map((option) => /* @__PURE__ */ jsx("option", {
|
|
969
|
+
value: option.value,
|
|
970
|
+
children: option.label
|
|
971
|
+
}, option.value))]
|
|
972
|
+
})] }),
|
|
973
|
+
/* @__PURE__ */ jsxs("div", {
|
|
974
|
+
className: "bl-library__actions",
|
|
975
|
+
children: [
|
|
976
|
+
/* @__PURE__ */ jsx("button", {
|
|
977
|
+
type: "button",
|
|
978
|
+
onClick: () => setPendingDelete(null),
|
|
979
|
+
children: "Cancel"
|
|
980
|
+
}),
|
|
981
|
+
/* @__PURE__ */ jsx("button", {
|
|
982
|
+
type: "button",
|
|
983
|
+
onClick: () => remove(pendingDelete),
|
|
984
|
+
children: "Delete immediately"
|
|
985
|
+
}),
|
|
986
|
+
/* @__PURE__ */ jsx("button", {
|
|
987
|
+
type: "button",
|
|
988
|
+
disabled: !replacement,
|
|
989
|
+
onClick: () => remove(pendingDelete, replacement),
|
|
990
|
+
children: "Replace & delete"
|
|
991
|
+
})
|
|
992
|
+
]
|
|
993
|
+
})
|
|
994
|
+
]
|
|
995
|
+
})
|
|
996
|
+
}) : null
|
|
997
|
+
]
|
|
998
|
+
});
|
|
999
|
+
};
|
|
1000
|
+
const CSS$2 = `
|
|
1001
|
+
.bl-library__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start;margin-bottom:.85rem}
|
|
1002
|
+
.bl-library__head h2{margin:0;font-size:1.1rem}
|
|
1003
|
+
.bl-library__head p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b)}
|
|
1004
|
+
.bl-library__head button,.bl-library__add button,.bl-library__actions button{min-height:2.1rem;padding:.35rem .75rem;border-radius:8px;border:1px solid var(--theme-elevation-150,#e2e8f0);background:var(--theme-elevation-0,#fff);font:inherit;cursor:pointer}
|
|
1005
|
+
.bl-library__list{display:grid;gap:.85rem}
|
|
1006
|
+
.bl-library__add{display:flex;flex-wrap:wrap;gap:.6rem;align-items:end;margin-bottom:.85rem;padding:.85rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px}
|
|
1007
|
+
.bl-library__add label{display:flex;flex-direction:column;gap:.3rem;font-size:12px;font-weight:600}
|
|
1008
|
+
.bl-library__add input{min-height:2.1rem;padding:.3rem .55rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px}
|
|
1009
|
+
.bl-library__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
|
|
1010
|
+
.bl-library__panel{width:min(28rem,100%);padding:1.2rem;border-radius:16px;background:#fff;color:#0f172a}
|
|
1011
|
+
.bl-library__panel h2{margin:0}
|
|
1012
|
+
.bl-library__panel p{margin:.5rem 0 0;color:#475569;font-size:14px}
|
|
1013
|
+
.bl-library__panel label{display:flex;flex-direction:column;gap:.4rem;margin-top:1rem;font-size:13px;font-weight:600}
|
|
1014
|
+
.bl-library__panel select{min-height:2.25rem;border:1px solid #cbd5e1;border-radius:8px;padding:.35rem .5rem}
|
|
1015
|
+
.bl-library__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;margin-top:1rem}
|
|
1016
|
+
`;
|
|
1017
|
+
//#endregion
|
|
1018
|
+
//#region src/admin/typography-pairing.tsx
|
|
1019
|
+
function FontRoleCard({ title, hint, required, family, preview, children }) {
|
|
1020
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
1021
|
+
className: "bl-font-role",
|
|
1022
|
+
children: [
|
|
1023
|
+
/* @__PURE__ */ jsxs("header", {
|
|
1024
|
+
className: "bl-font-role__head",
|
|
1025
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", { children: title }), /* @__PURE__ */ jsx("p", { children: hint })] }), required ? /* @__PURE__ */ jsx("span", {
|
|
1026
|
+
className: "bl-font-role__badge",
|
|
1027
|
+
children: "Required"
|
|
1028
|
+
}) : null]
|
|
1029
|
+
}),
|
|
1030
|
+
children,
|
|
1031
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1032
|
+
className: "bl-font-role__preview",
|
|
1033
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Preview" }), preview === "heading" ? /* @__PURE__ */ jsx("p", {
|
|
1034
|
+
className: "bl-font-role__heading",
|
|
1035
|
+
style: { fontFamily: `"${family}"` },
|
|
1036
|
+
children: "Advancing the work with clarity."
|
|
1037
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
1038
|
+
className: "bl-font-role__body",
|
|
1039
|
+
style: { fontFamily: `"${family}"` },
|
|
1040
|
+
children: "Clear, comfortable text for longer reading across the site."
|
|
1041
|
+
})]
|
|
1042
|
+
})
|
|
1043
|
+
]
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
function TypographyPairing({ headingFamily, bodyFamily }) {
|
|
1047
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
1048
|
+
className: "bl-pairing",
|
|
1049
|
+
"aria-label": "Font pairing sample",
|
|
1050
|
+
children: [/* @__PURE__ */ jsxs("header", { children: [/* @__PURE__ */ jsx("h2", { children: "Pairing sample" }), /* @__PURE__ */ jsx("p", { children: "Heading and body from the approved catalogue. Type scale stays with the library." })] }), /* @__PURE__ */ jsxs("div", {
|
|
1051
|
+
className: "bl-pairing__page",
|
|
1052
|
+
children: [
|
|
1053
|
+
/* @__PURE__ */ jsx("p", {
|
|
1054
|
+
className: "bl-pairing__eyebrow",
|
|
1055
|
+
style: { fontFamily: `"${bodyFamily}"` },
|
|
1056
|
+
children: "For visitors"
|
|
1057
|
+
}),
|
|
1058
|
+
/* @__PURE__ */ jsx("p", {
|
|
1059
|
+
className: "bl-pairing__title",
|
|
1060
|
+
style: { fontFamily: `"${headingFamily}"` },
|
|
1061
|
+
children: "Stay in motion."
|
|
1062
|
+
}),
|
|
1063
|
+
/* @__PURE__ */ jsx("p", {
|
|
1064
|
+
className: "bl-pairing__copy",
|
|
1065
|
+
style: { fontFamily: `"${bodyFamily}"` },
|
|
1066
|
+
children: "Heading and body come from the catalogue. This is the pairing, not a full site preview."
|
|
1067
|
+
}),
|
|
1068
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1069
|
+
className: "bl-pairing__actions",
|
|
1070
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1071
|
+
className: "bl-pairing__btn",
|
|
1072
|
+
style: { fontFamily: `"${bodyFamily}"` },
|
|
1073
|
+
children: "Primary action"
|
|
1074
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1075
|
+
className: "bl-pairing__btn bl-pairing__btn--ghost",
|
|
1076
|
+
style: { fontFamily: `"${bodyFamily}"` },
|
|
1077
|
+
children: "Learn more"
|
|
1078
|
+
})]
|
|
1079
|
+
})
|
|
1080
|
+
]
|
|
1081
|
+
})]
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
const TYPOGRAPHY_PAIRING_CSS = `
|
|
1085
|
+
.bl-font-role{padding:1.1rem 1.15rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff)}
|
|
1086
|
+
.bl-font-role__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
|
|
1087
|
+
.bl-font-role__head h3{margin:0;font-size:1rem}
|
|
1088
|
+
.bl-font-role__head p{margin:.3rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
1089
|
+
.bl-font-role__badge{font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9);color:var(--theme-elevation-500,#64748b)}
|
|
1090
|
+
.bl-font-role__preview{margin-top:1rem;padding:.85rem .9rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-50,#f8fafc)}
|
|
1091
|
+
.bl-font-role__preview > span{display:block;margin-bottom:.4rem;font-size:11px;color:var(--theme-elevation-500,#64748b)}
|
|
1092
|
+
.bl-font-role__heading{margin:0;font-size:1.5rem;line-height:1.2;font-weight:600}
|
|
1093
|
+
.bl-font-role__body{margin:0;font-size:1rem;line-height:1.6;color:var(--theme-elevation-600,#475569)}
|
|
1094
|
+
.bl-pairing{padding:0;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);overflow:hidden}
|
|
1095
|
+
.bl-pairing > header{padding:1rem 1.15rem;border-bottom:1px solid var(--theme-elevation-150,#e2e8f0)}
|
|
1096
|
+
.bl-pairing > header h2{margin:0;font-size:1rem}
|
|
1097
|
+
.bl-pairing > header p{margin:.3rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
1098
|
+
.bl-pairing__page{padding:1.35rem 1.4rem 1.5rem}
|
|
1099
|
+
.bl-pairing__eyebrow{margin:0;font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#7c1fa2}
|
|
1100
|
+
.bl-pairing__title{margin:.5rem 0 0;font-size:2rem;line-height:1.1;font-weight:600}
|
|
1101
|
+
.bl-pairing__copy{margin:.75rem 0 0;max-width:36rem;font-size:1rem;line-height:1.6;color:var(--theme-elevation-600,#475569)}
|
|
1102
|
+
.bl-pairing__actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1.1rem}
|
|
1103
|
+
.bl-pairing__btn{display:inline-flex;align-items:center;min-height:2.15rem;padding:.4rem .85rem;border-radius:8px;background:#db0082;color:#fff;font-size:14px;font-weight:600}
|
|
1104
|
+
.bl-pairing__btn--ghost{background:#fff;color:#0f172a;border:1px solid #cbd5e1}
|
|
1105
|
+
`;
|
|
1106
|
+
//#endregion
|
|
111
1107
|
//#region src/admin/font-field.tsx
|
|
112
1108
|
/**
|
|
113
1109
|
* Listbox over the catalogue ids in `admin.custom.ids`. Each option is the
|
|
114
1110
|
* family name in that face, with the catalogue hint beneath. Faces load
|
|
115
|
-
* from the site's font route the
|
|
1111
|
+
* from the site's font route for the selected family, and for every
|
|
1112
|
+
* catalogue id once the picker opens.
|
|
116
1113
|
*/
|
|
117
1114
|
const FontField = ({ field, path, readOnly }) => {
|
|
118
1115
|
const { value, setValue, showError, errorMessage } = useField({ path });
|
|
@@ -142,6 +1139,8 @@ const FontField = ({ field, path, readOnly }) => {
|
|
|
142
1139
|
const [open, setOpen] = useState(false);
|
|
143
1140
|
const [faces, setFaces] = useState(false);
|
|
144
1141
|
const [active, setActive] = useState(-1);
|
|
1142
|
+
const [query, setQuery] = useState("");
|
|
1143
|
+
const [category, setCategory] = useState("all");
|
|
145
1144
|
const typed = useRef("");
|
|
146
1145
|
const typedAt = useRef(0);
|
|
147
1146
|
const id = useId();
|
|
@@ -152,10 +1151,18 @@ const FontField = ({ field, path, readOnly }) => {
|
|
|
152
1151
|
if (open) setFaces(true);
|
|
153
1152
|
}, [open]);
|
|
154
1153
|
const selected = options.find((option) => option.value === (value ?? "")) ?? options[0];
|
|
1154
|
+
const visible = options.filter((option) => {
|
|
1155
|
+
if (query && !option.family.toLowerCase().includes(query.trim().toLowerCase())) return false;
|
|
1156
|
+
if (category === "all" || !option.specimen) return true;
|
|
1157
|
+
return findFont(option.value)?.category === category;
|
|
1158
|
+
});
|
|
1159
|
+
const role = sameAsBody ? FONT_ROLE_COPY.heading : FONT_ROLE_COPY.body;
|
|
155
1160
|
function choose(option) {
|
|
156
1161
|
setValue(option.value);
|
|
157
1162
|
setOpen(false);
|
|
158
1163
|
setActive(-1);
|
|
1164
|
+
setQuery("");
|
|
1165
|
+
setCategory("all");
|
|
159
1166
|
}
|
|
160
1167
|
function onKeyDown(event) {
|
|
161
1168
|
if (event.key === "ArrowDown") {
|
|
@@ -198,26 +1205,35 @@ const FontField = ({ field, path, readOnly }) => {
|
|
|
198
1205
|
}
|
|
199
1206
|
}
|
|
200
1207
|
const activeId = active >= 0 && open ? `${id}-option-${active}` : void 0;
|
|
1208
|
+
const faceIds = faces ? ids : isFontId(value) ? [value] : [];
|
|
201
1209
|
return /* @__PURE__ */ jsxs("div", {
|
|
202
1210
|
className: "field-type bl-font-field",
|
|
203
1211
|
children: [
|
|
1212
|
+
/* @__PURE__ */ jsx("style", {
|
|
1213
|
+
href: "bl-pairing",
|
|
1214
|
+
precedence: "default",
|
|
1215
|
+
children: TYPOGRAPHY_PAIRING_CSS
|
|
1216
|
+
}),
|
|
204
1217
|
/* @__PURE__ */ jsx("style", {
|
|
205
1218
|
href: "bl-font-field",
|
|
206
1219
|
precedence: "default",
|
|
207
1220
|
children: CSS$1
|
|
208
1221
|
}),
|
|
209
|
-
|
|
1222
|
+
faceIds.length > 0 ? /* @__PURE__ */ jsx("style", {
|
|
210
1223
|
"data-bl-font-faces": "",
|
|
211
|
-
children: fontFaceCss(
|
|
1224
|
+
children: fontFaceCss(faceIds, fontsBaseUrl)
|
|
212
1225
|
}) : null,
|
|
213
|
-
/* @__PURE__ */
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
required
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
1226
|
+
/* @__PURE__ */ jsxs(FontRoleCard, {
|
|
1227
|
+
title: typeof field.label === "string" ? field.label : role.label,
|
|
1228
|
+
hint: role.hint,
|
|
1229
|
+
required,
|
|
1230
|
+
family: selected?.family ?? "Inter",
|
|
1231
|
+
preview: sameAsBody ? "heading" : "body",
|
|
1232
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
1233
|
+
htmlFor: buttonId,
|
|
1234
|
+
label: field.label,
|
|
1235
|
+
required
|
|
1236
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
221
1237
|
id: buttonId,
|
|
222
1238
|
type: "button",
|
|
223
1239
|
className: "bl-font-field__trigger",
|
|
@@ -227,34 +1243,94 @@ const FontField = ({ field, path, readOnly }) => {
|
|
|
227
1243
|
"aria-controls": listId,
|
|
228
1244
|
"aria-activedescendant": activeId,
|
|
229
1245
|
onClick: () => setOpen((was) => !was),
|
|
230
|
-
onBlur: () => setOpen(false),
|
|
231
1246
|
onKeyDown,
|
|
232
|
-
children:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"aria-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
choose(option);
|
|
245
|
-
},
|
|
246
|
-
onMouseEnter: () => setActive(i),
|
|
247
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
248
|
-
className: "bl-font-field__family",
|
|
249
|
-
style: option.specimen ? { fontFamily: `"${option.family}"` } : void 0,
|
|
250
|
-
children: option.family
|
|
251
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
252
|
-
className: "bl-font-field__hint",
|
|
253
|
-
children: option.hint
|
|
254
|
-
})]
|
|
255
|
-
}, option.value || "same"))
|
|
256
|
-
}) : null]
|
|
1247
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
|
|
1248
|
+
className: "bl-font-field__selected-label",
|
|
1249
|
+
children: "Selected"
|
|
1250
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1251
|
+
className: "bl-font-field__selected-name",
|
|
1252
|
+
style: selected?.specimen ? { fontFamily: `"${selected.family}"` } : void 0,
|
|
1253
|
+
children: selected?.family ?? "Choose a font"
|
|
1254
|
+
})] }), /* @__PURE__ */ jsx("span", {
|
|
1255
|
+
"aria-hidden": "true",
|
|
1256
|
+
children: "⌄"
|
|
1257
|
+
})]
|
|
1258
|
+
})]
|
|
257
1259
|
}),
|
|
1260
|
+
open ? /* @__PURE__ */ jsx("div", {
|
|
1261
|
+
className: "bl-font-field__modal",
|
|
1262
|
+
onClick: (event) => {
|
|
1263
|
+
if (event.target === event.currentTarget) setOpen(false);
|
|
1264
|
+
},
|
|
1265
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1266
|
+
className: "bl-font-field__panel",
|
|
1267
|
+
children: [
|
|
1268
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1269
|
+
className: "bl-font-field__panel-head",
|
|
1270
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { children: "Choose a font" }), /* @__PURE__ */ jsx("h2", { children: role.label })] }), /* @__PURE__ */ jsx("button", {
|
|
1271
|
+
type: "button",
|
|
1272
|
+
onClick: () => setOpen(false),
|
|
1273
|
+
"aria-label": "Close",
|
|
1274
|
+
children: "×"
|
|
1275
|
+
})]
|
|
1276
|
+
}),
|
|
1277
|
+
/* @__PURE__ */ jsx("input", {
|
|
1278
|
+
type: "search",
|
|
1279
|
+
className: "bl-font-field__search",
|
|
1280
|
+
placeholder: "Search fonts…",
|
|
1281
|
+
value: query,
|
|
1282
|
+
onChange: (event) => setQuery(event.target.value)
|
|
1283
|
+
}),
|
|
1284
|
+
/* @__PURE__ */ jsx("div", {
|
|
1285
|
+
className: "bl-font-field__filters",
|
|
1286
|
+
children: [
|
|
1287
|
+
"all",
|
|
1288
|
+
"sans",
|
|
1289
|
+
"serif",
|
|
1290
|
+
"display",
|
|
1291
|
+
"mono"
|
|
1292
|
+
].map((item) => /* @__PURE__ */ jsx("button", {
|
|
1293
|
+
type: "button",
|
|
1294
|
+
"data-on": category === item ? "" : void 0,
|
|
1295
|
+
onClick: () => setCategory(item),
|
|
1296
|
+
children: item === "all" ? "All" : item === "sans" ? "Sans serif" : item === "serif" ? "Serif" : item === "display" ? "Display" : "Mono"
|
|
1297
|
+
}, item))
|
|
1298
|
+
}),
|
|
1299
|
+
/* @__PURE__ */ jsx("ul", {
|
|
1300
|
+
id: listId,
|
|
1301
|
+
role: "listbox",
|
|
1302
|
+
className: "bl-font-field__menu",
|
|
1303
|
+
children: visible.map((option, i) => /* @__PURE__ */ jsxs("li", {
|
|
1304
|
+
id: `${id}-option-${i}`,
|
|
1305
|
+
role: "option",
|
|
1306
|
+
"aria-selected": option.value === (value ?? ""),
|
|
1307
|
+
className: "bl-font-field__option",
|
|
1308
|
+
onMouseDown: (event) => {
|
|
1309
|
+
event.preventDefault();
|
|
1310
|
+
choose(option);
|
|
1311
|
+
},
|
|
1312
|
+
onMouseEnter: () => setActive(i),
|
|
1313
|
+
children: [
|
|
1314
|
+
/* @__PURE__ */ jsx("span", {
|
|
1315
|
+
className: "bl-font-field__family",
|
|
1316
|
+
style: option.specimen ? { fontFamily: `"${option.family}"` } : void 0,
|
|
1317
|
+
children: option.family
|
|
1318
|
+
}),
|
|
1319
|
+
/* @__PURE__ */ jsx("span", {
|
|
1320
|
+
className: "bl-font-field__hint",
|
|
1321
|
+
children: option.hint
|
|
1322
|
+
}),
|
|
1323
|
+
/* @__PURE__ */ jsx("span", {
|
|
1324
|
+
className: "bl-font-field__specimen",
|
|
1325
|
+
style: option.specimen ? { fontFamily: `"${option.family}"` } : void 0,
|
|
1326
|
+
children: "The quick brown fox jumps over the lazy dog."
|
|
1327
|
+
})
|
|
1328
|
+
]
|
|
1329
|
+
}, option.value || "same"))
|
|
1330
|
+
})
|
|
1331
|
+
]
|
|
1332
|
+
})
|
|
1333
|
+
}) : null,
|
|
258
1334
|
/* @__PURE__ */ jsx(FieldDescription, {
|
|
259
1335
|
description: field.admin?.description,
|
|
260
1336
|
path
|
|
@@ -268,166 +1344,713 @@ const FontField = ({ field, path, readOnly }) => {
|
|
|
268
1344
|
});
|
|
269
1345
|
};
|
|
270
1346
|
const CSS$1 = `
|
|
271
|
-
.bl-font-
|
|
272
|
-
.bl-font-
|
|
273
|
-
.bl-font-field__trigger:
|
|
274
|
-
.bl-font-field__trigger:
|
|
275
|
-
.bl-font-
|
|
276
|
-
.bl-font-
|
|
277
|
-
.bl-font-
|
|
278
|
-
.bl-font-
|
|
279
|
-
.bl-font-
|
|
1347
|
+
.bl-font-field{margin-bottom:1rem}
|
|
1348
|
+
.bl-font-field .field-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
1349
|
+
.bl-font-field__trigger{display:flex;align-items:center;justify-content:space-between;width:100%;margin-top:1rem;min-height:3.25rem;padding:.7rem 1rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-input-bg,#fff);color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
1350
|
+
.bl-font-field__trigger:focus{border-color:var(--theme-elevation-400,#94a3b8);outline:none;box-shadow:0 0 0 1px var(--theme-elevation-400,#94a3b8)}
|
|
1351
|
+
.bl-font-field__trigger:disabled{color:var(--theme-elevation-400,#94a3b8);cursor:not-allowed}
|
|
1352
|
+
.bl-font-field__selected-label{display:block;font-size:11px;color:var(--theme-elevation-500,#64748b)}
|
|
1353
|
+
.bl-font-field__selected-name{display:block;margin-top:.15rem;font-weight:600}
|
|
1354
|
+
.bl-font-field__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
|
|
1355
|
+
.bl-font-field__panel{width:min(36rem,100%);max-height:min(36rem,90vh);display:flex;flex-direction:column;padding:1.15rem 1.2rem;border-radius:16px;background:#fff;color:#0f172a}
|
|
1356
|
+
.bl-font-field__panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
|
|
1357
|
+
.bl-font-field__panel-head p{margin:0;font-size:12px;color:#64748b}
|
|
1358
|
+
.bl-font-field__panel-head h2{margin:.2rem 0 0;font-size:1.15rem}
|
|
1359
|
+
.bl-font-field__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;color:#64748b;cursor:pointer}
|
|
1360
|
+
.bl-font-field__search{margin-top:.9rem;min-height:2.5rem;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:10px;font:inherit}
|
|
1361
|
+
.bl-font-field__filters{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.75rem}
|
|
1362
|
+
.bl-font-field__filters button{min-height:1.8rem;padding:.2rem .7rem;border:0;border-radius:999px;background:#f1f5f9;color:#475569;font:inherit;font-size:12px;cursor:pointer}
|
|
1363
|
+
.bl-font-field__filters button[data-on]{background:#0f172a;color:#fff}
|
|
1364
|
+
.bl-font-field__menu{margin:.75rem 0 0;padding:0;list-style:none;overflow:auto;max-height:18rem}
|
|
1365
|
+
.bl-font-field__option{display:flex;flex-direction:column;gap:2px;margin-bottom:.5rem;padding:.75rem .85rem;border:1px solid #e2e8f0;border-radius:12px;cursor:pointer}
|
|
1366
|
+
.bl-font-field__option[aria-selected="true"]{border-color:#7c1fa2;background:#f5f3ff}
|
|
1367
|
+
.bl-font-field__family{font-weight:600}
|
|
1368
|
+
.bl-font-field__hint{color:#64748b;font-size:12px}
|
|
1369
|
+
.bl-font-field__specimen{margin-top:.45rem;font-size:1.25rem;line-height:1.3}
|
|
280
1370
|
`;
|
|
281
1371
|
//#endregion
|
|
282
|
-
//#region src/admin/
|
|
283
|
-
|
|
284
|
-
|
|
1372
|
+
//#region src/admin/pairing-field.tsx
|
|
1373
|
+
/**
|
|
1374
|
+
* Pairing sample only — no weight, letter-spacing, line-height, or health
|
|
1375
|
+
* scores. Faces load from the site's font route.
|
|
1376
|
+
*/
|
|
1377
|
+
const PairingField = ({ field }) => {
|
|
1378
|
+
const fontsBaseUrl = (field.admin?.custom ?? {}).fontsBaseUrl ?? "/fonts";
|
|
1379
|
+
const { body, heading } = useFormFields(([fields]) => {
|
|
1380
|
+
const record = fields;
|
|
1381
|
+
return {
|
|
1382
|
+
body: String(record["typography.body"]?.value ?? ""),
|
|
1383
|
+
heading: String(record["typography.heading"]?.value ?? "")
|
|
1384
|
+
};
|
|
1385
|
+
});
|
|
1386
|
+
const headingId = heading || body;
|
|
1387
|
+
const bodyFont = findFont(body);
|
|
1388
|
+
const headingFont = findFont(headingId);
|
|
1389
|
+
const ids = [body, headingId].filter(Boolean);
|
|
1390
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1391
|
+
className: "field-type bl-pairing-field",
|
|
1392
|
+
children: [
|
|
1393
|
+
/* @__PURE__ */ jsx("style", {
|
|
1394
|
+
href: "bl-pairing",
|
|
1395
|
+
precedence: "default",
|
|
1396
|
+
children: TYPOGRAPHY_PAIRING_CSS
|
|
1397
|
+
}),
|
|
1398
|
+
ids.length > 0 ? /* @__PURE__ */ jsx("style", {
|
|
1399
|
+
"data-bl-font-faces": "",
|
|
1400
|
+
children: fontFaceCss(ids, fontsBaseUrl)
|
|
1401
|
+
}) : null,
|
|
1402
|
+
/* @__PURE__ */ jsx(TypographyPairing, {
|
|
1403
|
+
headingFamily: headingFont?.family ?? "Georgia",
|
|
1404
|
+
bodyFamily: bodyFont?.family ?? "system-ui"
|
|
1405
|
+
})
|
|
1406
|
+
]
|
|
1407
|
+
});
|
|
1408
|
+
};
|
|
1409
|
+
//#endregion
|
|
1410
|
+
//#region src/admin/appearance-choices.tsx
|
|
1411
|
+
function ChoicePreview({ family, value }) {
|
|
1412
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1413
|
+
className: "bl-appearance__preview",
|
|
1414
|
+
"data-family": family,
|
|
1415
|
+
"data-value": value,
|
|
1416
|
+
"aria-hidden": "true",
|
|
1417
|
+
children: family === "defaultTheme" && value === "system" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
1418
|
+
className: "bl-appearance__pane bl-appearance__pane--light",
|
|
1419
|
+
children: [/* @__PURE__ */ jsx("i", {}), /* @__PURE__ */ jsx("b", {})]
|
|
1420
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1421
|
+
className: "bl-appearance__pane bl-appearance__pane--dark",
|
|
1422
|
+
children: [/* @__PURE__ */ jsx("i", {}), /* @__PURE__ */ jsx("b", {})]
|
|
1423
|
+
})] }) : family === "motion" ? /* @__PURE__ */ jsx("span", { className: "bl-appearance__dot" }) : family === "shadow" ? /* @__PURE__ */ jsx("div", { className: "bl-appearance__lift" }) : family === "radius" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1424
|
+
/* @__PURE__ */ jsx("b", { children: "Button" }),
|
|
1425
|
+
/* @__PURE__ */ jsx("i", { className: "bl-appearance__input" }),
|
|
1426
|
+
/* @__PURE__ */ jsx("i", { className: "bl-appearance__card" })
|
|
1427
|
+
] }) : family === "density" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1428
|
+
/* @__PURE__ */ jsx("b", {}),
|
|
1429
|
+
/* @__PURE__ */ jsx("i", { className: "bl-appearance__input" }),
|
|
1430
|
+
/* @__PURE__ */ jsx("i", { className: "bl-appearance__card" })
|
|
1431
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1432
|
+
/* @__PURE__ */ jsx("i", {}),
|
|
1433
|
+
/* @__PURE__ */ jsx("i", {}),
|
|
1434
|
+
/* @__PURE__ */ jsx("b", {})
|
|
1435
|
+
] })
|
|
1436
|
+
});
|
|
285
1437
|
}
|
|
286
|
-
function
|
|
287
|
-
return
|
|
1438
|
+
function AppearanceChoices({ values, onChange, readOnly }) {
|
|
1439
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1440
|
+
className: "bl-appearance",
|
|
1441
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
1442
|
+
className: "bl-appearance__intro",
|
|
1443
|
+
children: [/* @__PURE__ */ jsx("h2", { children: "Set the overall feel of your site" }), /* @__PURE__ */ jsx("p", { children: "Choose how rounded, elevated, animated, and spacious the interface feels. You don’t manage the technical values — the system handles those." })]
|
|
1444
|
+
}), Object.keys(APPEARANCE_CHOICES).map((family) => {
|
|
1445
|
+
const choices = APPEARANCE_CHOICES[family];
|
|
1446
|
+
const copy = APPEARANCE_FAMILY_COPY[family];
|
|
1447
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1448
|
+
className: "bl-appearance__family",
|
|
1449
|
+
children: [
|
|
1450
|
+
/* @__PURE__ */ jsxs("legend", { children: [/* @__PURE__ */ jsx("strong", { children: copy.title }), /* @__PURE__ */ jsx("span", { children: copy.hint })] }),
|
|
1451
|
+
/* @__PURE__ */ jsx("div", {
|
|
1452
|
+
className: "bl-appearance__choices",
|
|
1453
|
+
"data-family": family,
|
|
1454
|
+
children: Object.entries(choices).map(([value, item]) => {
|
|
1455
|
+
const selected = values[family] === value;
|
|
1456
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
1457
|
+
className: "bl-appearance__choice",
|
|
1458
|
+
"data-selected": selected ? "" : void 0,
|
|
1459
|
+
children: [
|
|
1460
|
+
/* @__PURE__ */ jsx("input", {
|
|
1461
|
+
type: "radio",
|
|
1462
|
+
name: family,
|
|
1463
|
+
value,
|
|
1464
|
+
checked: selected,
|
|
1465
|
+
disabled: readOnly,
|
|
1466
|
+
onChange: () => onChange(family, value)
|
|
1467
|
+
}),
|
|
1468
|
+
/* @__PURE__ */ jsx(ChoicePreview, {
|
|
1469
|
+
family,
|
|
1470
|
+
value
|
|
1471
|
+
}),
|
|
1472
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1473
|
+
className: "bl-appearance__copy",
|
|
1474
|
+
children: [/* @__PURE__ */ jsxs("strong", { children: [item.label, selected ? /* @__PURE__ */ jsx("em", {
|
|
1475
|
+
"aria-hidden": "true",
|
|
1476
|
+
children: "✓"
|
|
1477
|
+
}) : null] }), /* @__PURE__ */ jsx("span", { children: item.hint })]
|
|
1478
|
+
})
|
|
1479
|
+
]
|
|
1480
|
+
}, value);
|
|
1481
|
+
})
|
|
1482
|
+
}),
|
|
1483
|
+
family === "motion" ? /* @__PURE__ */ jsx("p", {
|
|
1484
|
+
className: "bl-appearance__note",
|
|
1485
|
+
children: "Visitors who prefer reduced motion will still see fewer animations automatically."
|
|
1486
|
+
}) : null
|
|
1487
|
+
]
|
|
1488
|
+
}, family);
|
|
1489
|
+
})]
|
|
1490
|
+
});
|
|
288
1491
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1492
|
+
const APPEARANCE_CHOICES_CSS = `
|
|
1493
|
+
.bl-appearance{display:flex;flex-direction:column;gap:1.25rem}
|
|
1494
|
+
.bl-appearance__intro h2{margin:0;font-size:1.1rem}
|
|
1495
|
+
.bl-appearance__intro p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b);max-width:40rem}
|
|
1496
|
+
.bl-appearance__family{margin:0;padding:1rem 1.1rem 1.15rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff)}
|
|
1497
|
+
.bl-appearance__family legend{padding:0;margin-bottom:.85rem}
|
|
1498
|
+
.bl-appearance__family legend strong{display:block;font-size:14px}
|
|
1499
|
+
.bl-appearance__family legend span{display:block;margin-top:.25rem;font-size:12px;color:var(--theme-elevation-500,#64748b);font-weight:400}
|
|
1500
|
+
.bl-appearance__choices{display:grid;gap:.6rem}
|
|
1501
|
+
.bl-appearance__choices[data-family=defaultTheme],.bl-appearance__choices[data-family=shadow],.bl-appearance__choices[data-family=motion],.bl-appearance__choices[data-family=density]{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
1502
|
+
.bl-appearance__choices[data-family=radius]{grid-template-columns:repeat(4,minmax(0,1fr))}
|
|
1503
|
+
.bl-appearance__choice{display:flex;flex-direction:column;gap:.65rem;padding:.75rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);cursor:pointer}
|
|
1504
|
+
.bl-appearance__choice[data-selected]{box-shadow:inset 0 0 0 2px #0f172a;border-color:#0f172a}
|
|
1505
|
+
.bl-appearance__choice input{position:absolute;opacity:0;pointer-events:none}
|
|
1506
|
+
.bl-appearance__copy{display:flex;flex-direction:column;gap:.2rem}
|
|
1507
|
+
.bl-appearance__copy strong{display:flex;justify-content:space-between;align-items:center;gap:.4rem;font-size:14px}
|
|
1508
|
+
.bl-appearance__copy em{font-style:normal}
|
|
1509
|
+
.bl-appearance__copy span{font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
1510
|
+
.bl-appearance__note{margin:.75rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
1511
|
+
.bl-appearance__preview{--bl-preview-brand:#db0082;--bl-preview-radius:8px;border-radius:10px;background:#f8fafc;overflow:hidden}
|
|
1512
|
+
.bl-appearance__preview[data-family=defaultTheme]{display:grid;min-height:5.5rem;padding:.65rem}
|
|
1513
|
+
.bl-appearance__preview[data-family=defaultTheme][data-value=system]{grid-template-columns:1fr 1fr;padding:0}
|
|
1514
|
+
.bl-appearance__preview[data-family=defaultTheme][data-value=dark]{background:#020617}
|
|
1515
|
+
.bl-appearance__pane{display:flex;flex-direction:column;gap:.45rem;padding:.65rem}
|
|
1516
|
+
.bl-appearance__pane--dark{background:#020617}
|
|
1517
|
+
.bl-appearance__pane i,.bl-appearance__preview[data-family=defaultTheme] > i{display:block;height:.4rem;width:42%;border-radius:99px;background:#0f172a}
|
|
1518
|
+
.bl-appearance__pane i + i,.bl-appearance__preview[data-family=defaultTheme] > i + i{width:64%;background:#cbd5e1}
|
|
1519
|
+
.bl-appearance__pane--dark i{background:#fff}
|
|
1520
|
+
.bl-appearance__pane--dark i + i{background:#334155}
|
|
1521
|
+
.bl-appearance__preview[data-family=defaultTheme][data-value=dark] > i{background:#fff}
|
|
1522
|
+
.bl-appearance__preview[data-family=defaultTheme][data-value=dark] > i + i{background:#334155}
|
|
1523
|
+
.bl-appearance__pane b,.bl-appearance__preview[data-family=defaultTheme] > b{display:block;height:1.6rem;margin-top:auto;border-radius:6px;background:var(--bl-preview-brand)}
|
|
1524
|
+
.bl-appearance__pane--dark b{background:var(--bl-preview-brand)}
|
|
1525
|
+
.bl-appearance__preview[data-family=radius]{display:flex;flex-direction:column;gap:.45rem;padding:.65rem;background:#fff}
|
|
1526
|
+
.bl-appearance__preview[data-family=radius] b{display:grid;place-items:center;height:1.85rem;background:var(--bl-preview-brand);color:#fff;font-size:10px;font-weight:600;border-radius:var(--bl-preview-radius)}
|
|
1527
|
+
.bl-appearance__input{display:block;height:1.85rem;border:1px solid #cbd5e1;background:#f8fafc;border-radius:var(--bl-preview-radius)}
|
|
1528
|
+
.bl-appearance__card{display:block;height:2.4rem;border:1px solid #e2e8f0;background:#fff;border-radius:var(--bl-preview-radius)}
|
|
1529
|
+
.bl-appearance__preview[data-value=sharp]{--bl-preview-radius:0}
|
|
1530
|
+
.bl-appearance__preview[data-value=subtle]{--bl-preview-radius:6px}
|
|
1531
|
+
.bl-appearance__preview[data-value=rounded]{--bl-preview-radius:12px}
|
|
1532
|
+
.bl-appearance__preview[data-value=pill]{--bl-preview-radius:999px}
|
|
1533
|
+
.bl-appearance__preview[data-value=pill] .bl-appearance__card{--bl-preview-radius:1.25rem}
|
|
1534
|
+
.bl-appearance__preview[data-family=shadow]{display:grid;place-items:center;min-height:6.5rem;padding:.85rem}
|
|
1535
|
+
.bl-appearance__lift{width:100%;height:3.5rem;border-radius:10px;background:#fff}
|
|
1536
|
+
.bl-appearance__preview[data-value=flat] .bl-appearance__lift{border:1px solid #e2e8f0}
|
|
1537
|
+
.bl-appearance__preview[data-value=subtle] .bl-appearance__lift{box-shadow:0 1px 2px rgba(15,23,42,.06),0 4px 12px rgba(15,23,42,.06)}
|
|
1538
|
+
.bl-appearance__preview[data-value=elevated] .bl-appearance__lift{box-shadow:0 10px 30px rgba(15,23,42,.12)}
|
|
1539
|
+
.bl-appearance__preview[data-family=motion]{display:flex;align-items:center;min-height:4.5rem;padding:0 .85rem}
|
|
1540
|
+
.bl-appearance__dot{width:1.15rem;height:1.15rem;border-radius:999px;background:var(--bl-preview-brand);animation:bl-appear-dot 1.8s ease-in-out infinite}
|
|
1541
|
+
.bl-appearance__preview[data-value=snappy] .bl-appearance__dot{animation-duration:1s}
|
|
1542
|
+
.bl-appearance__preview[data-value=minimal] .bl-appearance__dot{animation-duration:2.5s}
|
|
1543
|
+
@keyframes bl-appear-dot{0%,100%{transform:translateX(0)}50%{transform:translateX(4.5rem)}}
|
|
1544
|
+
.bl-appearance__preview[data-family=density]{display:flex;flex-direction:column;background:#f8fafc}
|
|
1545
|
+
.bl-appearance__preview[data-value=compact]{gap:.4rem;padding:.55rem}
|
|
1546
|
+
.bl-appearance__preview[data-value=default]{gap:.65rem;padding:.8rem}
|
|
1547
|
+
.bl-appearance__preview[data-value=spacious]{gap:.9rem;padding:1.05rem}
|
|
1548
|
+
.bl-appearance__preview[data-family=density] b{display:block;height:1.7rem;background:var(--bl-preview-brand);border-radius:6px}
|
|
1549
|
+
.bl-appearance__preview[data-value=compact] b,.bl-appearance__preview[data-value=compact] .bl-appearance__input{height:1.45rem}
|
|
1550
|
+
.bl-appearance__preview[data-value=spacious] b,.bl-appearance__preview[data-value=spacious] .bl-appearance__input{height:2.1rem}
|
|
1551
|
+
@media (max-width:720px){
|
|
1552
|
+
.bl-appearance__choices[data-family=defaultTheme],.bl-appearance__choices[data-family=shadow],.bl-appearance__choices[data-family=motion],.bl-appearance__choices[data-family=density],.bl-appearance__choices[data-family=radius]{grid-template-columns:1fr}
|
|
1553
|
+
}
|
|
1554
|
+
@media (prefers-reduced-motion:reduce){
|
|
1555
|
+
.bl-appearance__dot{animation:none}
|
|
1556
|
+
}
|
|
1557
|
+
`;
|
|
1558
|
+
//#endregion
|
|
1559
|
+
//#region src/admin/appearance-field.tsx
|
|
1560
|
+
/**
|
|
1561
|
+
* Named appearance choices. Soft → pill, Gentle → minimal, Balanced →
|
|
1562
|
+
* default, Follow device → system. No rem or ms.
|
|
1563
|
+
*/
|
|
1564
|
+
const AppearanceField = ({ path, readOnly }) => {
|
|
1565
|
+
const defaultTheme = useField({ path: `${path}.defaultTheme` });
|
|
1566
|
+
const radius = useField({ path: `${path}.radius` });
|
|
1567
|
+
const shadow = useField({ path: `${path}.shadow` });
|
|
1568
|
+
const motion = useField({ path: `${path}.motion` });
|
|
1569
|
+
const density = useField({ path: `${path}.density` });
|
|
1570
|
+
const fields = {
|
|
1571
|
+
defaultTheme,
|
|
1572
|
+
radius,
|
|
1573
|
+
shadow,
|
|
1574
|
+
motion,
|
|
1575
|
+
density
|
|
1576
|
+
};
|
|
1577
|
+
function onChange(name, value) {
|
|
1578
|
+
fields[name].setValue(value);
|
|
1579
|
+
}
|
|
1580
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1581
|
+
className: "field-type bl-appearance-field",
|
|
1582
|
+
children: [/* @__PURE__ */ jsx("style", {
|
|
1583
|
+
href: "bl-appearance",
|
|
1584
|
+
precedence: "default",
|
|
1585
|
+
children: APPEARANCE_CHOICES_CSS
|
|
1586
|
+
}), /* @__PURE__ */ jsx(AppearanceChoices, {
|
|
1587
|
+
values: {
|
|
1588
|
+
defaultTheme: defaultTheme.value ?? "system",
|
|
1589
|
+
radius: radius.value ?? "rounded",
|
|
1590
|
+
shadow: shadow.value ?? "subtle",
|
|
1591
|
+
motion: motion.value ?? "smooth",
|
|
1592
|
+
density: density.value ?? "default"
|
|
1593
|
+
},
|
|
1594
|
+
onChange,
|
|
1595
|
+
readOnly
|
|
1596
|
+
})]
|
|
299
1597
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
1598
|
+
};
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region src/admin/grey-scale-field.tsx
|
|
1601
|
+
const FAMILIES = Object.keys(presetHints.greyScale);
|
|
1602
|
+
const SWATCHES = [
|
|
1603
|
+
"50",
|
|
1604
|
+
"200",
|
|
1605
|
+
"400",
|
|
1606
|
+
"700",
|
|
1607
|
+
"950"
|
|
1608
|
+
];
|
|
1609
|
+
function GreyScaleChoices({ value, onChange, readOnly }) {
|
|
1610
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
1611
|
+
className: "bl-grey",
|
|
1612
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
1613
|
+
className: "bl-grey__head",
|
|
1614
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Gray family" }), /* @__PURE__ */ jsx("p", { children: "Choose the neutral family used for page backgrounds, cards, borders, inputs, and muted text." })] }), /* @__PURE__ */ jsx("span", {
|
|
1615
|
+
className: "bl-grey__badge",
|
|
1616
|
+
children: "Required"
|
|
1617
|
+
})]
|
|
1618
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1619
|
+
className: "bl-grey__choices",
|
|
1620
|
+
children: FAMILIES.map((id) => {
|
|
1621
|
+
const copy = presetHints.greyScale[id];
|
|
1622
|
+
const selected = value === id;
|
|
1623
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
1624
|
+
className: "bl-grey__choice",
|
|
1625
|
+
"data-selected": selected ? "" : void 0,
|
|
1626
|
+
children: [
|
|
1627
|
+
/* @__PURE__ */ jsx("input", {
|
|
1628
|
+
type: "radio",
|
|
1629
|
+
name: "greyScale",
|
|
1630
|
+
value: id,
|
|
1631
|
+
checked: selected,
|
|
1632
|
+
disabled: readOnly,
|
|
1633
|
+
onChange: () => onChange(id)
|
|
1634
|
+
}),
|
|
1635
|
+
/* @__PURE__ */ jsx("span", {
|
|
1636
|
+
className: "bl-grey__ramp",
|
|
1637
|
+
"aria-hidden": "true",
|
|
1638
|
+
children: SWATCHES.map((step) => /* @__PURE__ */ jsx("i", { style: { background: `hsl(${GREY_SCALES[id][step]})` } }, step))
|
|
1639
|
+
}),
|
|
1640
|
+
/* @__PURE__ */ jsxs("strong", { children: [copy.label, selected ? /* @__PURE__ */ jsx("em", {
|
|
1641
|
+
"aria-hidden": "true",
|
|
1642
|
+
children: "✓"
|
|
1643
|
+
}) : null] }),
|
|
1644
|
+
/* @__PURE__ */ jsx("span", { children: copy.hint })
|
|
1645
|
+
]
|
|
1646
|
+
}, id);
|
|
1647
|
+
})
|
|
1648
|
+
})]
|
|
309
1649
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Visual picker over the five grey-scale presets. Replaces the stock select
|
|
1653
|
+
* on Colors; the stored value is still `colors.greyScale`.
|
|
1654
|
+
*/
|
|
1655
|
+
const GreyScaleField = ({ path, readOnly }) => {
|
|
1656
|
+
const { value, setValue } = useField({ path });
|
|
1657
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1658
|
+
className: "field-type bl-grey-field",
|
|
1659
|
+
children: [/* @__PURE__ */ jsx("style", {
|
|
1660
|
+
href: "bl-grey",
|
|
1661
|
+
precedence: "default",
|
|
1662
|
+
children: GREY_SCALE_FIELD_CSS
|
|
1663
|
+
}), /* @__PURE__ */ jsx(GreyScaleChoices, {
|
|
1664
|
+
value: value ?? "slate",
|
|
1665
|
+
onChange: setValue,
|
|
1666
|
+
readOnly
|
|
1667
|
+
})]
|
|
319
1668
|
});
|
|
1669
|
+
};
|
|
1670
|
+
const GREY_SCALE_FIELD_CSS = `
|
|
1671
|
+
.bl-grey{margin:0 0 1.75rem}
|
|
1672
|
+
.bl-grey__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:.85rem}
|
|
1673
|
+
.bl-grey__head h2{margin:0;font-size:1.1rem}
|
|
1674
|
+
.bl-grey__head p{margin:.35rem 0 0;max-width:40rem;font-size:13px;color:var(--theme-elevation-500,#64748b)}
|
|
1675
|
+
.bl-grey__badge{font:inherit;font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9);color:var(--theme-elevation-500,#64748b)}
|
|
1676
|
+
.bl-grey__choices{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:.6rem}
|
|
1677
|
+
.bl-grey__choice{display:flex;flex-direction:column;gap:.45rem;padding:.75rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);cursor:pointer}
|
|
1678
|
+
.bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px #0f172a;border-color:#0f172a}
|
|
1679
|
+
.bl-grey__choice input{position:absolute;opacity:0;pointer-events:none}
|
|
1680
|
+
.bl-grey__ramp{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:3px}
|
|
1681
|
+
.bl-grey__ramp i{display:block;height:2rem;border-radius:6px}
|
|
1682
|
+
.bl-grey__choice strong{display:flex;justify-content:space-between;align-items:center;gap:.4rem;font-size:14px}
|
|
1683
|
+
.bl-grey__choice em{font-style:normal}
|
|
1684
|
+
.bl-grey__choice > span{font-size:12px;color:var(--theme-elevation-500,#64748b)}
|
|
1685
|
+
[data-bl-theme-preview=dark] .bl-grey__head p,[data-bl-theme-preview=dark] .bl-grey__choice > span{color:#94a3b8}
|
|
1686
|
+
[data-bl-theme-preview=dark] .bl-grey__choice{background:#0f172a;border-color:#334155;color:#f8fafc}
|
|
1687
|
+
[data-bl-theme-preview=dark] .bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px #f8fafc;border-color:#f8fafc}
|
|
1688
|
+
[data-bl-theme-preview=dark] .bl-grey__badge{background:#1e293b;color:#94a3b8}
|
|
1689
|
+
@media (max-width:720px){.bl-grey__choices{grid-template-columns:1fr}}
|
|
1690
|
+
`;
|
|
1691
|
+
//#endregion
|
|
1692
|
+
//#region src/admin/contrast-report.tsx
|
|
1693
|
+
function hexAt(fields, path) {
|
|
1694
|
+
const value = fields[path]?.value;
|
|
1695
|
+
return typeof value === "string" ? value : "";
|
|
320
1696
|
}
|
|
321
1697
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* here touches validity.
|
|
1698
|
+
* Fill + automatic label only. Target 7:1, Light and Dark. No gray
|
|
1699
|
+
* ContrastStrip. Warnings do not block save.
|
|
325
1700
|
*/
|
|
326
1701
|
const ContrastReport = ({ field }) => {
|
|
327
|
-
const target = Number((field.admin?.custom)?.target) ||
|
|
328
|
-
const
|
|
1702
|
+
const target = Number((field.admin?.custom)?.target) || 7;
|
|
1703
|
+
const hexes = useFormFields(([fields]) => {
|
|
329
1704
|
const record = fields;
|
|
1705
|
+
const system = Object.fromEntries(SYSTEM_COLOR_KEYS.map((key) => [key, hexAt(record, `colors.brand.${key}.hex`)]));
|
|
1706
|
+
const library = record["colors.library"]?.value;
|
|
330
1707
|
return {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
1708
|
+
system,
|
|
1709
|
+
custom: Array.isArray(library) ? library.flatMap((row, index) => {
|
|
1710
|
+
const hex = typeof row === "object" && row && "hex" in row ? String(row.hex ?? "") : "";
|
|
1711
|
+
const name = typeof row === "object" && row && "label" in row ? String(row.label ?? row.key ?? "Custom") : "Custom";
|
|
1712
|
+
return hex ? [{
|
|
1713
|
+
id: `library-${index}`,
|
|
1714
|
+
name,
|
|
1715
|
+
hex
|
|
1716
|
+
}] : [];
|
|
1717
|
+
}) : []
|
|
337
1718
|
};
|
|
338
1719
|
});
|
|
339
1720
|
const last = useRef(null);
|
|
340
|
-
const complete =
|
|
341
|
-
if (complete) last.current =
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
brandAccent: values.accent,
|
|
345
|
-
brandHighlight: values.highlight,
|
|
346
|
-
brandSuccess: values.success,
|
|
347
|
-
greyScale: values.greyScale
|
|
348
|
-
}, { target });
|
|
349
|
-
const checks = last.current;
|
|
350
|
-
const stale = Boolean(checks) && !complete;
|
|
351
|
-
const failing = checks?.filter((check) => !check.meetsTarget).length ?? 0;
|
|
352
|
-
const summary = !checks ? "Enter five hex colours to measure contrast." : failing === 0 ? `Every pairing meets ${target}:1` : `${failing} of ${checks.length} pairings below ${target}:1`;
|
|
1721
|
+
const complete = SYSTEM_COLOR_KEYS.every((key) => isThemeHex(hexes.system[key]));
|
|
1722
|
+
if (complete) last.current = [...SYSTEM_COLOR_KEYS.map((key) => fillLabelWarning(key, SYSTEM_COLOR_COPY[key].label, hexes.system[key], target)), ...hexes.custom.map((row) => fillLabelWarning(row.id, row.name, row.hex, target))].filter((item) => item !== null);
|
|
1723
|
+
const warnings = last.current ?? [];
|
|
1724
|
+
const stale = Boolean(last.current) && !complete;
|
|
353
1725
|
return /* @__PURE__ */ jsxs("div", {
|
|
354
1726
|
className: "field-type bl-contrast-report",
|
|
355
1727
|
children: [
|
|
356
1728
|
/* @__PURE__ */ jsx("style", {
|
|
357
|
-
href: "bl-
|
|
1729
|
+
href: "bl-readability",
|
|
358
1730
|
precedence: "default",
|
|
359
|
-
children:
|
|
360
|
-
}),
|
|
361
|
-
/* @__PURE__ */ jsx("p", {
|
|
362
|
-
className: "bl-contrast-report__summary",
|
|
363
|
-
children: summary
|
|
1731
|
+
children: READABILITY_LIST_CSS
|
|
364
1732
|
}),
|
|
365
1733
|
stale ? /* @__PURE__ */ jsx("p", {
|
|
366
1734
|
className: "bl-contrast-report__stale",
|
|
367
1735
|
children: "Showing the last complete audit while a colour is being typed."
|
|
368
1736
|
}) : null,
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
children: [/* @__PURE__ */ jsx(LevelIcon, { level: sample.level }), contrastLevelLabel(sample.level)]
|
|
395
|
-
}),
|
|
396
|
-
/* @__PURE__ */ jsx("span", {
|
|
397
|
-
className: "bl-contrast-report__verdict",
|
|
398
|
-
children: verdict.text
|
|
399
|
-
}),
|
|
400
|
-
/* @__PURE__ */ jsxs("span", {
|
|
401
|
-
className: "bl-contrast-report__label",
|
|
402
|
-
children: [
|
|
403
|
-
check.label,
|
|
404
|
-
" (",
|
|
405
|
-
mode === "light" ? "Light" : "Dark",
|
|
406
|
-
")"
|
|
407
|
-
]
|
|
408
|
-
})
|
|
409
|
-
]
|
|
410
|
-
}, `${check.id}-${mode}`);
|
|
411
|
-
}))
|
|
412
|
-
}) : null
|
|
1737
|
+
warnings.length > 0 ? /* @__PURE__ */ jsx(ReadabilityList, {
|
|
1738
|
+
warnings,
|
|
1739
|
+
target
|
|
1740
|
+
}) : /* @__PURE__ */ jsx("p", { children: "Enter hex colours to measure the automatic label on each fill." })
|
|
1741
|
+
]
|
|
1742
|
+
});
|
|
1743
|
+
};
|
|
1744
|
+
//#endregion
|
|
1745
|
+
//#region src/admin/section-heading.tsx
|
|
1746
|
+
/**
|
|
1747
|
+
* Unnamed UI heading between Theme fields. No document path.
|
|
1748
|
+
*/
|
|
1749
|
+
const SectionHeading = ({ field }) => {
|
|
1750
|
+
const custom = field.admin?.custom ?? {};
|
|
1751
|
+
if (!custom.title) return null;
|
|
1752
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
1753
|
+
className: "bl-section-heading",
|
|
1754
|
+
children: [
|
|
1755
|
+
/* @__PURE__ */ jsx("style", {
|
|
1756
|
+
href: "bl-section-heading",
|
|
1757
|
+
precedence: "default",
|
|
1758
|
+
children: CSS
|
|
1759
|
+
}),
|
|
1760
|
+
/* @__PURE__ */ jsx("h2", { children: custom.title }),
|
|
1761
|
+
custom.hint ? /* @__PURE__ */ jsx("p", { children: custom.hint }) : null
|
|
413
1762
|
]
|
|
414
1763
|
});
|
|
415
1764
|
};
|
|
416
1765
|
const CSS = `
|
|
417
|
-
.bl-
|
|
418
|
-
.bl-
|
|
419
|
-
.bl-
|
|
420
|
-
.bl-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
1766
|
+
.bl-section-heading{margin:1.25rem 0 .65rem}
|
|
1767
|
+
.bl-section-heading:first-child{margin-top:0}
|
|
1768
|
+
.bl-section-heading h2{margin:0;font-size:1.1rem}
|
|
1769
|
+
.bl-section-heading p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b);max-width:40rem}
|
|
1770
|
+
`;
|
|
1771
|
+
//#endregion
|
|
1772
|
+
//#region src/theme/changes.ts
|
|
1773
|
+
function colorLine(name, before, after) {
|
|
1774
|
+
const a = before && typeof before === "object" ? before : { hex: before };
|
|
1775
|
+
const b = after && typeof after === "object" ? after : { hex: after };
|
|
1776
|
+
const left = a;
|
|
1777
|
+
const right = b;
|
|
1778
|
+
if (left.hex === right.hex && String(left.sourceStep) === String(right.sourceStep)) return null;
|
|
1779
|
+
return `${name}: ${String(left.hex ?? "—")} → ${String(right.hex ?? "—")}`;
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Human lines for the Publish confirmation. Only this child's
|
|
1783
|
+
* differences; other responsibilities are not listed.
|
|
1784
|
+
*/
|
|
1785
|
+
function themeChildChanges(child, published, incoming) {
|
|
1786
|
+
if (child === "colors") {
|
|
1787
|
+
const lines = [];
|
|
1788
|
+
for (const key of SYSTEM_COLOR_KEYS) {
|
|
1789
|
+
const line = colorLine(key[0].toUpperCase() + key.slice(1), published.colors?.brand?.[key], incoming.colors?.brand?.[key]);
|
|
1790
|
+
if (line) lines.push(line);
|
|
1791
|
+
}
|
|
1792
|
+
const beforeKeys = (published.colors?.library ?? []).map((row) => row.key).join(", ");
|
|
1793
|
+
const afterKeys = (incoming.colors?.library ?? []).map((row) => row.key).join(", ");
|
|
1794
|
+
if (beforeKeys !== afterKeys) lines.push(`Custom colors: ${beforeKeys || "none"} → ${afterKeys || "none"}`);
|
|
1795
|
+
if ((published.colors?.greyScale ?? "") !== (incoming.colors?.greyScale ?? "")) lines.push(`Gray family: ${published.colors?.greyScale ?? "—"} → ${incoming.colors?.greyScale ?? "—"}`);
|
|
1796
|
+
return lines;
|
|
1797
|
+
}
|
|
1798
|
+
if (child === "typography") {
|
|
1799
|
+
const lines = [];
|
|
1800
|
+
if ((published.typography?.heading ?? "") !== (incoming.typography?.heading ?? "")) lines.push(`Heading: ${published.typography?.heading || "same as body"} → ${incoming.typography?.heading || "same as body"}`);
|
|
1801
|
+
if ((published.typography?.body ?? "") !== (incoming.typography?.body ?? "")) lines.push(`Body: ${published.typography?.body ?? "—"} → ${incoming.typography?.body ?? "—"}`);
|
|
1802
|
+
return lines;
|
|
1803
|
+
}
|
|
1804
|
+
if (child === "appearance") return [
|
|
1805
|
+
"defaultTheme",
|
|
1806
|
+
"radius",
|
|
1807
|
+
"shadow",
|
|
1808
|
+
"motion",
|
|
1809
|
+
"density"
|
|
1810
|
+
].flatMap((key) => {
|
|
1811
|
+
const left = published.appearance?.[key] ?? "";
|
|
1812
|
+
const right = incoming.appearance?.[key] ?? "";
|
|
1813
|
+
return left === right ? [] : [`${key}: ${left || "—"} → ${right || "—"}`];
|
|
1814
|
+
});
|
|
1815
|
+
const lines = [];
|
|
1816
|
+
if (uploadId(published.logo) !== uploadId(incoming.logo)) lines.push("Logo updated");
|
|
1817
|
+
if (uploadId(published.favicon) !== uploadId(incoming.favicon)) lines.push("Favicon updated");
|
|
1818
|
+
if (uploadId(published.logoMark) !== uploadId(incoming.logoMark)) lines.push("Mobile menu logo updated");
|
|
1819
|
+
return lines;
|
|
1820
|
+
}
|
|
1821
|
+
function uploadId(value) {
|
|
1822
|
+
if (value && typeof value === "object" && value.id != null) return String(value.id);
|
|
1823
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
1824
|
+
return "";
|
|
1825
|
+
}
|
|
1826
|
+
//#endregion
|
|
1827
|
+
//#region src/theme/publish.ts
|
|
1828
|
+
const THEME_CHILDREN = [
|
|
1829
|
+
"colors",
|
|
1830
|
+
"typography",
|
|
1831
|
+
"appearance",
|
|
1832
|
+
"identity"
|
|
1833
|
+
];
|
|
1834
|
+
function isThemeChild(value) {
|
|
1835
|
+
return typeof value === "string" && THEME_CHILDREN.includes(value);
|
|
1836
|
+
}
|
|
1837
|
+
//#endregion
|
|
1838
|
+
//#region src/admin/publish-confirm.tsx
|
|
1839
|
+
const OTHER_CHILDREN = {
|
|
1840
|
+
Colors: "Typography, Appearance, and Identity stay as they are.",
|
|
1841
|
+
Typography: "Colors, Appearance, and Identity stay as they are.",
|
|
1842
|
+
Appearance: "Colors, Typography, and Identity stay as they are.",
|
|
1843
|
+
Identity: "Colors, Typography, and Appearance stay as they are."
|
|
1844
|
+
};
|
|
1845
|
+
function PublishConfirm({ childLabel, changes, shorts, onKeepEditing, onPublish, busy }) {
|
|
1846
|
+
const remaining = shorts?.filter((item) => !item.meets) ?? [];
|
|
1847
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1848
|
+
className: "bl-publish",
|
|
1849
|
+
role: "dialog",
|
|
1850
|
+
"aria-modal": "true",
|
|
1851
|
+
"aria-labelledby": "bl-publish-title",
|
|
1852
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1853
|
+
className: "bl-publish__panel",
|
|
1854
|
+
children: [
|
|
1855
|
+
/* @__PURE__ */ jsxs("h2", {
|
|
1856
|
+
id: "bl-publish-title",
|
|
1857
|
+
children: ["Publish ", childLabel]
|
|
1858
|
+
}),
|
|
1859
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
1860
|
+
"These ",
|
|
1861
|
+
childLabel.toLowerCase(),
|
|
1862
|
+
" changes go live now.",
|
|
1863
|
+
" ",
|
|
1864
|
+
OTHER_CHILDREN[childLabel] ?? "The other Theme sections stay as they are."
|
|
1865
|
+
] }),
|
|
1866
|
+
changes.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
1867
|
+
className: "bl-publish__empty",
|
|
1868
|
+
children: "No changes to publish."
|
|
1869
|
+
}) : /* @__PURE__ */ jsx("ul", { children: changes.map((change) => /* @__PURE__ */ jsx("li", { children: change }, change)) }),
|
|
1870
|
+
remaining.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
1871
|
+
className: "bl-publish__shorts",
|
|
1872
|
+
children: [
|
|
1873
|
+
/* @__PURE__ */ jsx("strong", { children: "Some colors may be hard to read" }),
|
|
1874
|
+
/* @__PURE__ */ jsx("ul", { children: remaining.map((item) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
|
|
1875
|
+
"aria-hidden": "true",
|
|
1876
|
+
className: "bl-publish__swatch",
|
|
1877
|
+
style: {
|
|
1878
|
+
background: hslCss(item.light.fill),
|
|
1879
|
+
color: hslCss(item.light.label)
|
|
1880
|
+
},
|
|
1881
|
+
children: "Aa"
|
|
1882
|
+
}), /* @__PURE__ */ jsxs("span", { children: [
|
|
1883
|
+
readabilityHeadline(item).replace(/^Text on /, ""),
|
|
1884
|
+
" ",
|
|
1885
|
+
hardToReadModes(item),
|
|
1886
|
+
"."
|
|
1887
|
+
] })] }, item.id)) }),
|
|
1888
|
+
/* @__PURE__ */ jsx("p", { children: READABILITY_PUBLISH_NOTE })
|
|
1889
|
+
]
|
|
1890
|
+
}) : null,
|
|
1891
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1892
|
+
className: "bl-publish__actions",
|
|
1893
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1894
|
+
type: "button",
|
|
1895
|
+
onClick: onKeepEditing,
|
|
1896
|
+
children: "Keep editing"
|
|
1897
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1898
|
+
type: "button",
|
|
1899
|
+
disabled: busy || changes.length === 0,
|
|
1900
|
+
onClick: onPublish,
|
|
1901
|
+
children: remaining.length > 0 ? "Publish anyway" : "Publish"
|
|
1902
|
+
})]
|
|
1903
|
+
})
|
|
1904
|
+
]
|
|
1905
|
+
})
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
const PUBLISH_CONFIRM_CSS = `
|
|
1909
|
+
.bl-publish{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
|
|
1910
|
+
.bl-publish__panel{width:min(36rem,100%);padding:1.25rem 1.35rem;border-radius:16px;background:#fff;color:#0f172a;box-shadow:0 24px 60px rgba(15,23,42,.2)}
|
|
1911
|
+
.bl-publish__panel h2{margin:0}
|
|
1912
|
+
.bl-publish__panel p{margin:.5rem 0 0;color:#475569}
|
|
1913
|
+
.bl-publish__empty{font-style:italic}
|
|
1914
|
+
.bl-publish__panel > ul{margin:.75rem 0 0;padding-left:1.1rem}
|
|
1915
|
+
.bl-publish__shorts{margin-top:1rem;padding:.85rem .9rem;border-radius:10px;background:#fffbeb}
|
|
1916
|
+
.bl-publish__shorts strong{display:block}
|
|
1917
|
+
.bl-publish__shorts ul{margin:.65rem 0 0;padding:0;list-style:none}
|
|
1918
|
+
.bl-publish__shorts li{display:flex;gap:.65rem;align-items:center;margin-top:.45rem;font-size:13px}
|
|
1919
|
+
.bl-publish__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:1.6rem;border-radius:6px;font-weight:700;font-size:12px}
|
|
1920
|
+
.bl-publish__shorts p{margin:.65rem 0 0;font-size:13px}
|
|
1921
|
+
.bl-publish__actions{display:flex;justify-content:flex-end;gap:.6rem;margin-top:1.1rem}
|
|
1922
|
+
.bl-publish__actions button{min-height:2.25rem;padding:.4rem .9rem;border-radius:8px;border:1px solid #cbd5e1;background:#fff;font:inherit;cursor:pointer}
|
|
1923
|
+
.bl-publish__actions button:last-child{border:0;background:#0f172a;color:#fff;font-weight:600}
|
|
1924
|
+
.bl-publish__actions button:disabled{opacity:.45;cursor:not-allowed}
|
|
429
1925
|
`;
|
|
430
1926
|
//#endregion
|
|
431
|
-
|
|
1927
|
+
//#region src/admin/publish-child.tsx
|
|
1928
|
+
const LABELS = {
|
|
1929
|
+
colors: "Colors",
|
|
1930
|
+
typography: "Typography",
|
|
1931
|
+
appearance: "Appearance",
|
|
1932
|
+
identity: "Identity"
|
|
1933
|
+
};
|
|
1934
|
+
function asColor(value) {
|
|
1935
|
+
if (value && typeof value === "object") return value;
|
|
1936
|
+
if (typeof value === "string") return { hex: value };
|
|
1937
|
+
}
|
|
1938
|
+
function formDoc(fields) {
|
|
1939
|
+
return {
|
|
1940
|
+
colors: {
|
|
1941
|
+
brand: Object.fromEntries(SYSTEM_COLOR_KEYS.map((key) => [key, {
|
|
1942
|
+
hex: fields[`colors.brand.${key}.hex`]?.value,
|
|
1943
|
+
sourceStep: fields[`colors.brand.${key}.sourceStep`]?.value
|
|
1944
|
+
}])),
|
|
1945
|
+
library: fields["colors.library"]?.value ?? [],
|
|
1946
|
+
greyScale: fields["colors.greyScale"]?.value
|
|
1947
|
+
},
|
|
1948
|
+
typography: {
|
|
1949
|
+
body: fields["typography.body"]?.value,
|
|
1950
|
+
heading: fields["typography.heading"]?.value
|
|
1951
|
+
},
|
|
1952
|
+
appearance: {
|
|
1953
|
+
defaultTheme: fields["appearance.defaultTheme"]?.value,
|
|
1954
|
+
radius: fields["appearance.radius"]?.value,
|
|
1955
|
+
shadow: fields["appearance.shadow"]?.value,
|
|
1956
|
+
motion: fields["appearance.motion"]?.value,
|
|
1957
|
+
density: fields["appearance.density"]?.value
|
|
1958
|
+
},
|
|
1959
|
+
logo: fields.logo?.value,
|
|
1960
|
+
favicon: fields.favicon?.value,
|
|
1961
|
+
logoMark: fields.logoMark?.value
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
function publishedDoc(fields) {
|
|
1965
|
+
return formDoc(Object.fromEntries(Object.entries(fields).map(([path, field]) => [path, { value: field?.initialValue }])));
|
|
1966
|
+
}
|
|
1967
|
+
function shortsFrom(doc) {
|
|
1968
|
+
const brand = doc.colors?.brand ?? {};
|
|
1969
|
+
const custom = doc.colors?.library ?? [];
|
|
1970
|
+
return [...SYSTEM_COLOR_KEYS.flatMap((key) => {
|
|
1971
|
+
const hex = asColor(brand[key])?.hex;
|
|
1972
|
+
const warning = typeof hex === "string" ? fillLabelWarning(key, SYSTEM_COLOR_COPY[key].label, hex) : null;
|
|
1973
|
+
return warning ? [warning] : [];
|
|
1974
|
+
}), ...custom.flatMap((row, index) => {
|
|
1975
|
+
const warning = row.hex ? fillLabelWarning(`library-${index}`, row.label ?? row.key ?? "Custom", row.hex) : null;
|
|
1976
|
+
return warning ? [warning] : [];
|
|
1977
|
+
})];
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Per-child Publish. Sets `publishChild` then submits; `beforeChange`
|
|
1981
|
+
* writes only that slice. Confirmation lists this child's changes and,
|
|
1982
|
+
* on Colors, remaining automatic-label shorts.
|
|
1983
|
+
*/
|
|
1984
|
+
const PublishChild = ({ field }) => {
|
|
1985
|
+
const child = (field.admin?.custom)?.child;
|
|
1986
|
+
const label = isThemeChild(child) ? LABELS[child] : "Theme";
|
|
1987
|
+
const { submit } = useForm();
|
|
1988
|
+
const publishChild = useField({ path: "publishChild" });
|
|
1989
|
+
const snapshot = useFormFields(([fields]) => {
|
|
1990
|
+
const record = fields;
|
|
1991
|
+
return {
|
|
1992
|
+
incoming: formDoc(record),
|
|
1993
|
+
published: publishedDoc(record)
|
|
1994
|
+
};
|
|
1995
|
+
});
|
|
1996
|
+
const [open, setOpen] = useState(false);
|
|
1997
|
+
const [busy, setBusy] = useState(false);
|
|
1998
|
+
const changes = useMemo(() => isThemeChild(child) ? themeChildChanges(child, snapshot.published, snapshot.incoming) : [], [child, snapshot]);
|
|
1999
|
+
const shorts = child === "colors" ? shortsFrom(snapshot.incoming) : void 0;
|
|
2000
|
+
async function publish() {
|
|
2001
|
+
if (!isThemeChild(child)) return;
|
|
2002
|
+
setBusy(true);
|
|
2003
|
+
publishChild.setValue(child);
|
|
2004
|
+
try {
|
|
2005
|
+
await submit();
|
|
2006
|
+
setOpen(false);
|
|
2007
|
+
} finally {
|
|
2008
|
+
setBusy(false);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2012
|
+
className: "field-type bl-publish-child",
|
|
2013
|
+
children: [
|
|
2014
|
+
/* @__PURE__ */ jsx("style", {
|
|
2015
|
+
href: "bl-publish",
|
|
2016
|
+
precedence: "default",
|
|
2017
|
+
children: PUBLISH_CONFIRM_CSS
|
|
2018
|
+
}),
|
|
2019
|
+
/* @__PURE__ */ jsx("style", {
|
|
2020
|
+
href: "bl-publish-child",
|
|
2021
|
+
precedence: "default",
|
|
2022
|
+
children: `${THEME_PREVIEW_TOGGLE_CSS}.bl-publish-child{position:sticky;top:0;z-index:15;display:flex;justify-content:flex-end;align-items:center;gap:.75rem;padding:.65rem 0;background:var(--theme-bg,#fff)}.bl-publish-child__button{min-height:2.25rem;padding:.4rem 1rem;border:0;border-radius:8px;background:var(--theme-elevation-800,#0f172a);color:#fff;font:inherit;font-weight:600;cursor:pointer}`
|
|
2023
|
+
}),
|
|
2024
|
+
child === "colors" ? /* @__PURE__ */ jsx(ThemePreviewToggle, {}) : null,
|
|
2025
|
+
/* @__PURE__ */ jsxs("button", {
|
|
2026
|
+
type: "button",
|
|
2027
|
+
className: "bl-publish-child__button",
|
|
2028
|
+
onClick: () => setOpen(true),
|
|
2029
|
+
children: ["Publish ", label]
|
|
2030
|
+
}),
|
|
2031
|
+
open ? /* @__PURE__ */ jsx(PublishConfirm, {
|
|
2032
|
+
childLabel: label,
|
|
2033
|
+
changes,
|
|
2034
|
+
shorts,
|
|
2035
|
+
busy,
|
|
2036
|
+
onKeepEditing: () => setOpen(false),
|
|
2037
|
+
onPublish: () => {
|
|
2038
|
+
publish();
|
|
2039
|
+
}
|
|
2040
|
+
}) : null
|
|
2041
|
+
]
|
|
2042
|
+
});
|
|
2043
|
+
};
|
|
2044
|
+
//#endregion
|
|
2045
|
+
//#region src/admin/save-button.tsx
|
|
2046
|
+
/**
|
|
2047
|
+
* Theme children publish themselves. The stock Save would write every
|
|
2048
|
+
* dirty tab at once, so it is hidden.
|
|
2049
|
+
*/
|
|
2050
|
+
function HiddenSaveButton() {
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
//#endregion
|
|
2054
|
+
export { AppearanceField, ColorField, ColorScaleField, ContrastReport, FontField, GreyScaleField, HiddenSaveButton, LibraryField, PairingField, PublishChild, SectionHeading };
|
|
432
2055
|
|
|
433
2056
|
//# sourceMappingURL=admin.mjs.map
|