editor-shell 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ML5SEFU4.js → chunk-EZKBKK3E.js} +10 -2
- package/dist/chunk-EZKBKK3E.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/panel/index.d.ts +92 -15
- package/dist/panel/index.js +29 -1
- package/dist/panel/index.js.map +1 -1
- package/dist/panel/panel.css +252 -92
- package/dist/shell/index.d.ts +7 -0
- package/dist/shell/index.js +1 -1
- package/dist/shell/shell.css +1 -0
- package/package.json +1 -1
- package/dist/chunk-ML5SEFU4.js.map +0 -1
|
@@ -122,6 +122,13 @@ var shellTokens = {
|
|
|
122
122
|
panelShadow: "0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)",
|
|
123
123
|
/** `--es-topbar-shadow`. */
|
|
124
124
|
topbarShadow: "0 1px 3px rgba(0, 0, 0, 0.06)",
|
|
125
|
+
/**
|
|
126
|
+
* `--es-raised-shadow` — what a small surface sitting ON another one casts:
|
|
127
|
+
* the chip in the settings tab strip's track (card 66138). Tighter and darker
|
|
128
|
+
* than `panelShadow`, which is a whole panel floating over the app; this one
|
|
129
|
+
* has to read across 3px of track without looking like a second panel.
|
|
130
|
+
*/
|
|
131
|
+
raisedShadow: "0 1px 2px rgba(16, 24, 40, 0.1)",
|
|
125
132
|
// ── Type ──────────────────────────────────────────────────────────────────
|
|
126
133
|
/** `--es-font` — system sans stack (also fed to `--puck-font-family`). */
|
|
127
134
|
font: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
|
|
@@ -179,6 +186,7 @@ var shellCssVars = {
|
|
|
179
186
|
panelGap: "--es-panel-gap",
|
|
180
187
|
panelShadow: "--es-panel-shadow",
|
|
181
188
|
topbarShadow: "--es-topbar-shadow",
|
|
189
|
+
raisedShadow: "--es-raised-shadow",
|
|
182
190
|
font: "--es-font"
|
|
183
191
|
};
|
|
184
192
|
var puckAzureRamp = {
|
|
@@ -197,5 +205,5 @@ var puckAzureRamp = {
|
|
|
197
205
|
};
|
|
198
206
|
|
|
199
207
|
export { puckAzureRamp, shellCssVars, shellTokens };
|
|
200
|
-
//# sourceMappingURL=chunk-
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
208
|
+
//# sourceMappingURL=chunk-EZKBKK3E.js.map
|
|
209
|
+
//# sourceMappingURL=chunk-EZKBKK3E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shell/tokens.ts"],"names":[],"mappings":"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","file":"chunk-EZKBKK3E.js","sourcesContent":["/**\n * Editor SHELL design tokens — the ONE source both editors' chrome converge on.\n *\n * Where `rail/tokens.ts` owns the slim icon-rail, this owns the rest of the\n * editor chrome theme: accent, surfaces, fields, text, lines, chips, named\n * chrome literals, danger/warning/success, radii, gap, shadows and font. Every\n * value bar one is taken verbatim from the storefront editor's `--sf-*` block\n * (`efficient-shop/components/storefront-editor.css`), the visual reference the\n * campaign designer (`--cd-*`) and the storefront's own TS mirror (`tk`) had\n * each re-copied. After card 66024 they all resolve to these values instead.\n * The one exception is `success`, which that block has no counterpart for; see\n * its own note below.\n *\n * Exposed BOTH ways, per the same value (mirrors the rail):\n * - `shellTokens` — this JS object, for code that needs the numbers/colours\n * (e.g. the storefront's `floating-editor/tokens.ts` inline-style mirror);\n * - `shell.css` — the stylesheet, which declares the identical values as CSS\n * custom properties on `.es-editor` (names listed in `shellCssVars`).\n *\n * Numeric fields are pixel magnitudes (unitless) so they compose in code; the\n * CSS applies the `px`. Both editing surfaces are LIGHT only (no dark mode), so\n * there is a single palette here — no dark counterpart.\n */\nexport const shellTokens = {\n // ── Accent (react-os-shell blue) ──────────────────────────────────────────\n /** `--es-accent` — blue-600. */\n accent: '#2563eb',\n /** `--es-accent-tint` — blue-50. */\n accentTint: '#eff6ff',\n /** `--es-accent-tint-strong` — blue-100. */\n accentTintStrong: '#dbeafe',\n /** `--es-accent-text` — blue-700. */\n accentText: '#1d4ed8',\n /** `--es-accent-ring` — focus ring wash. */\n accentRing: 'rgba(37, 99, 235, 0.25)',\n /** `--es-accent-shadow` — accent button shadow. */\n accentShadow: 'rgba(37, 99, 235, 0.35)',\n /** `--es-on-accent` — text/icon over the solid accent. */\n onAccent: '#ffffff',\n\n // ── Surfaces (cool gray ramp) ─────────────────────────────────────────────\n /** `--es-app-bg` — gray-50, sunken canvas. */\n appBg: '#f9fafb',\n /** `--es-preview-bg` — gray-100, preview well. */\n previewBg: '#f3f4f6',\n /** `--es-panel` — raised cards / panels. */\n panel: '#ffffff',\n /** `--es-raised` — active seg / chip. */\n raised: '#ffffff',\n /** `--es-topbar-bg`. */\n topbarBg: '#ffffff',\n /** `--es-frame-bg` — device \"paper\" behind the preview iframe. */\n frameBg: '#ffffff',\n /** `--es-hover` — gray-100 row / icon hover. */\n hover: '#f3f4f6',\n /** `--es-hatch` — 45° upload-slot texture. */\n hatch: 'rgba(0, 0, 0, 0.018)',\n /** `--es-addcard-veil` — add-section card hover wash. */\n addcardVeil: 'rgba(248, 249, 250, 0.66)',\n\n // ── Fields ────────────────────────────────────────────────────────────────\n /** `--es-field-bg`. */\n fieldBg: '#ffffff',\n /** `--es-track-bg` — gray-100 segmented / search track. */\n trackBg: '#f3f4f6',\n /** `--es-input-border` — gray-300. */\n inputBorder: '#d1d5db',\n /** `--es-field-text` — gray-800. */\n fieldText: '#1f2937',\n /** `--es-field-placeholder` — gray-400. */\n fieldPlaceholder: '#9ca3af',\n /** `--es-knob` — toggle knob. */\n knob: '#ffffff',\n\n // ── Text ────────────────────────────────────────────────────────────────\n /** `--es-text` — gray-900. */\n text: '#111827',\n /** `--es-text-strong` — gray-950 active labels. */\n textStrong: '#030712',\n /** `--es-text-secondary` — gray-700. */\n textSecondary: '#374151',\n /** `--es-muted` — gray-500. */\n muted: '#6b7280',\n /** `--es-icon` — gray-500 icon buttons. */\n icon: '#6b7280',\n /** `--es-disabled` — gray-400 disabled / faint glyphs. */\n disabled: '#9ca3af',\n\n // ── Lines / chips / buttons ──────────────────────────────────────────────\n /** `--es-divider` — gray-200. */\n divider: '#e5e7eb',\n /** `--es-line` — gray-200 hairlines. */\n line: '#e5e7eb',\n /** `--es-btn-border` — gray-300. */\n btnBorder: '#d1d5db',\n /** `--es-btn-text` — gray-700. */\n btnText: '#374151',\n /** `--es-chip-bg` — gray-100. */\n chipBg: '#f3f4f6',\n /** `--es-chip-fg` — gray-500. */\n chipFg: '#6b7280',\n\n // ── Named chrome literals ─────────────────────────────────────────────────\n /** `--es-brand-square` — gray-900 wordmark square. */\n brandSquare: '#111827',\n /** `--es-device-border` — gray-200 tablet/mobile frame. */\n deviceBorder: '#e5e7eb',\n /** `--es-toast-bg` — gray-900 dark chip / seam pill. */\n toastBg: '#111827',\n /** `--es-toggle-off` — gray-300 toggle track. */\n toggleOff: '#d1d5db',\n /** `--es-scrollbar` — gray-300. */\n scrollbar: '#d1d5db',\n /** `--es-scrollbar-hover` — gray-400. */\n scrollbarHover: '#9ca3af',\n\n // ── Danger / warning / success (shell red / amber / green) ────────────────\n /** `--es-danger` — red-600. */\n danger: '#dc2626',\n /** `--es-danger-strong` — red-700. */\n dangerStrong: '#b91c1c',\n /** `--es-danger-tint` — red-50. */\n dangerTint: '#fef2f2',\n /** `--es-warning` — amber-700. */\n warning: '#b45309',\n /** `--es-preview-tint` — amber-100 \"Preview\" pill. */\n previewTint: '#fef3c7',\n /**\n * `--es-success` — green-700. THE ONE VALUE HERE WITH NO `--sf-*` BEHIND IT:\n * the storefront editor's block has a red and an amber and no green, and card\n * 8802.g needs one for a green tick. So it is picked to sit with the block\n * rather than invented next to it — the same Tailwind ramp every other colour\n * in this file comes from, at the 700 step `--es-warning` already sits on.\n * 700 and not 600 for the reason amber went there: it is the darkest step\n * still plainly the hue, and it clears WCAG AA (5.0:1) on the shell's white\n * panels, so a tick may carry a label without needing a second token.\n */\n success: '#15803d',\n\n // ── Radii / shape / gap ───────────────────────────────────────────────────\n /** `--es-panel-radius`. */\n panelRadius: 12,\n /** `--es-row-radius`. */\n rowRadius: 8,\n /** `--es-field-radius`. */\n fieldRadius: 6,\n /** `--es-panel-gap` — inter-pane gutter + outer frame padding, ONE token. */\n panelGap: 10,\n\n // ── Shadows ───────────────────────────────────────────────────────────────\n /** `--es-panel-shadow`. */\n panelShadow: '0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)',\n /** `--es-topbar-shadow`. */\n topbarShadow: '0 1px 3px rgba(0, 0, 0, 0.06)',\n /**\n * `--es-raised-shadow` — what a small surface sitting ON another one casts:\n * the chip in the settings tab strip's track (card 66138). Tighter and darker\n * than `panelShadow`, which is a whole panel floating over the app; this one\n * has to read across 3px of track without looking like a second panel.\n */\n raisedShadow: '0 1px 2px rgba(16, 24, 40, 0.1)',\n\n // ── Type ──────────────────────────────────────────────────────────────────\n /** `--es-font` — system sans stack (also fed to `--puck-font-family`). */\n font: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif',\n} as const;\n\nexport type ShellTokens = typeof shellTokens;\n\n/**\n * The CSS custom-property name behind each `shellTokens` key. `shell.css` sets\n * these on `.es-editor`; override any of them on (or above) the editor root to\n * re-theme without shipping new CSS. The `shell-tokens.test.ts` parity gate\n * proves this map, `shellTokens` and `shell.css` never drift apart.\n */\nexport const shellCssVars: Record<keyof ShellTokens, string> = {\n accent: '--es-accent',\n accentTint: '--es-accent-tint',\n accentTintStrong: '--es-accent-tint-strong',\n accentText: '--es-accent-text',\n accentRing: '--es-accent-ring',\n accentShadow: '--es-accent-shadow',\n onAccent: '--es-on-accent',\n appBg: '--es-app-bg',\n previewBg: '--es-preview-bg',\n panel: '--es-panel',\n raised: '--es-raised',\n topbarBg: '--es-topbar-bg',\n frameBg: '--es-frame-bg',\n hover: '--es-hover',\n hatch: '--es-hatch',\n addcardVeil: '--es-addcard-veil',\n fieldBg: '--es-field-bg',\n trackBg: '--es-track-bg',\n inputBorder: '--es-input-border',\n fieldText: '--es-field-text',\n fieldPlaceholder: '--es-field-placeholder',\n knob: '--es-knob',\n text: '--es-text',\n textStrong: '--es-text-strong',\n textSecondary: '--es-text-secondary',\n muted: '--es-muted',\n icon: '--es-icon',\n disabled: '--es-disabled',\n divider: '--es-divider',\n line: '--es-line',\n btnBorder: '--es-btn-border',\n btnText: '--es-btn-text',\n chipBg: '--es-chip-bg',\n chipFg: '--es-chip-fg',\n brandSquare: '--es-brand-square',\n deviceBorder: '--es-device-border',\n toastBg: '--es-toast-bg',\n toggleOff: '--es-toggle-off',\n scrollbar: '--es-scrollbar',\n scrollbarHover: '--es-scrollbar-hover',\n danger: '--es-danger',\n dangerStrong: '--es-danger-strong',\n dangerTint: '--es-danger-tint',\n warning: '--es-warning',\n previewTint: '--es-preview-tint',\n success: '--es-success',\n panelRadius: '--es-panel-radius',\n rowRadius: '--es-row-radius',\n fieldRadius: '--es-field-radius',\n panelGap: '--es-panel-gap',\n panelShadow: '--es-panel-shadow',\n topbarShadow: '--es-topbar-shadow',\n raisedShadow: '--es-raised-shadow',\n font: '--es-font',\n};\n\n/**\n * Puck's own accent ramp + font, re-tinted to the shell blue. Puck reads these\n * fixed variable names (`--puck-color-azure-NN`, `--puck-font-family`) directly,\n * so they are NOT part of the `--es-*` set — but they had been copied verbatim\n * into BOTH editors' CSS, so `shell.css` now declares them once on `.es-editor`.\n * Kept here as the JS record so the parity gate can prove `shell.css` matches.\n */\nexport const puckAzureRamp: Record<string, string> = {\n '--puck-color-azure-01': '#172554',\n '--puck-color-azure-02': '#1e3a8a',\n '--puck-color-azure-03': '#1d4ed8',\n '--puck-color-azure-04': '#2563eb',\n '--puck-color-azure-05': '#3b82f6',\n '--puck-color-azure-06': '#60a5fa',\n '--puck-color-azure-07': '#93c5fd',\n '--puck-color-azure-08': '#bfdbfe',\n '--puck-color-azure-09': '#dbeafe',\n '--puck-color-azure-10': '#eff6ff',\n '--puck-color-azure-11': '#f5f9ff',\n '--puck-color-azure-12': '#fafcff',\n};\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
export { puckAzureRamp, shellCssVars, shellTokens } from './chunk-EZKBKK3E.js';
|
|
1
2
|
export { EditorRail, EditorRailButton, railCssVars, railTokens } from './chunk-3VMFPQGZ.js';
|
|
2
3
|
export { AddIcon, LayersIcon, MediaIcon, PageIcon as PagesIcon, SitemapIcon, StylesIcon } from './chunk-M7B5VTWH.js';
|
|
3
|
-
export { puckAzureRamp, shellCssVars, shellTokens } from './chunk-ML5SEFU4.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/panel/index.d.ts
CHANGED
|
@@ -169,6 +169,48 @@ interface SettingsSwitchBaseProps {
|
|
|
169
169
|
className?: string;
|
|
170
170
|
}
|
|
171
171
|
type SettingsSwitchProps = SettingsSwitchBaseProps & SettingsSwitchLabel;
|
|
172
|
+
/**
|
|
173
|
+
* A DRILL-IN ROW — a name, a one-line preview, and a chevron that says pressing
|
|
174
|
+
* it takes the panel down a level (card 8819).
|
|
175
|
+
*
|
|
176
|
+
* The storefront editor's text editing becomes a drill-down: the section screen
|
|
177
|
+
* lists a section's text parts as these rows, and pressing one scopes the
|
|
178
|
+
* inspector to that one part's editor. It lives in the shell for the reason the
|
|
179
|
+
* rest of this grammar does — the campaign designer draws the same list, and a
|
|
180
|
+
* second hand-rolled copy is exactly how the two editors drift.
|
|
181
|
+
*
|
|
182
|
+
* CONTROLLED and hook-free like the other four: it holds no open/closed state of
|
|
183
|
+
* its own. Pressing it calls `onOpen`, and the HOST decides what drilling down
|
|
184
|
+
* means, because only the host owns the panel's navigation.
|
|
185
|
+
*/
|
|
186
|
+
interface SettingsDrillProps {
|
|
187
|
+
/** The part's name — the loud primary line ("Heading", "Slide name"). */
|
|
188
|
+
name: string;
|
|
189
|
+
/**
|
|
190
|
+
* One line of what the part reads as, shown quiet under the name and clipped
|
|
191
|
+
* with an ellipsis. Optional: a row can be a name alone, and an absent preview
|
|
192
|
+
* draws no empty second line rather than a blank one.
|
|
193
|
+
*/
|
|
194
|
+
preview?: ReactNode;
|
|
195
|
+
/**
|
|
196
|
+
* Mark the PREVIEW as belonging to a part with no place on the page — an
|
|
197
|
+
* identifier the eye never draws (a `Slide name`), reachable only from this
|
|
198
|
+
* list. It reads quiet and italic. The mark is on the preview, not the row:
|
|
199
|
+
* the name still reads as a name.
|
|
200
|
+
*/
|
|
201
|
+
offPage?: boolean;
|
|
202
|
+
/**
|
|
203
|
+
* A trailing glyph before the chevron — the "hidden on the page" eye is the
|
|
204
|
+
* standing case. A ReactNode, not a boolean, so the host owns the icon and its
|
|
205
|
+
* accessible name (a bare glyph says nothing to a screen reader on its own).
|
|
206
|
+
*/
|
|
207
|
+
status?: ReactNode;
|
|
208
|
+
/** Asked to open. The host owns what drilling down does. */
|
|
209
|
+
onOpen: () => void;
|
|
210
|
+
/** A DOM id for the row button, so a host can point a label at it. */
|
|
211
|
+
id?: string;
|
|
212
|
+
className?: string;
|
|
213
|
+
}
|
|
172
214
|
|
|
173
215
|
/**
|
|
174
216
|
* The tab strip under the panel head.
|
|
@@ -189,30 +231,41 @@ type SettingsSwitchProps = SettingsSwitchBaseProps & SettingsSwitchLabel;
|
|
|
189
231
|
* `aria-controls` is worse than a missing one, because it names an element
|
|
190
232
|
* that does not exist instead of admitting there is none.
|
|
191
233
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
234
|
+
* A SEGMENTED CONTROL (card 66138). The strip is a grey track and the chosen
|
|
235
|
+
* bucket is a raised white chip on it. Lewis, looking at a real panel: the tab
|
|
236
|
+
* he is NOT on does not invite a click — it does not even look like a button.
|
|
237
|
+
* The track is the answer to that, because it makes every segment look
|
|
238
|
+
* pressable, where a mark under the chosen tab only ever made the chosen tab
|
|
239
|
+
* louder. It replaces the underline strip and card 8811's tint ground; see the
|
|
240
|
+
* long note on `.es-tab.is-active` in `panel.css`.
|
|
241
|
+
*
|
|
242
|
+
* A segment takes the width its OWN label needs and shares what is left over,
|
|
243
|
+
* rather than every segment taking the width of the widest label. Equal
|
|
244
|
+
* segments were measured and cost too much: three of them need a 267px track
|
|
245
|
+
* and four need 355px, against the 281px the shop's default panel gives.
|
|
196
246
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
247
|
+
* THE WORD DROPS WHERE IT STOPS FITTING. Natural widths fit two and three
|
|
248
|
+
* buckets across the whole panel-drag band; they do not fit four, where the
|
|
249
|
+
* last segment ends 39px past the panel edge. So given an `icon`, a segment can
|
|
250
|
+
* spend less width instead of more — the word goes and the glyph stays.
|
|
200
251
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* one row — drawing them at full width would wrap the strip at the ordinary
|
|
204
|
-
* panel width, which is the bug this is here to remove.
|
|
252
|
+
* Where that happens depends on HOW MANY buckets a section has, because with
|
|
253
|
+
* natural widths the question is about the sum of the labels:
|
|
205
254
|
*
|
|
206
|
-
*
|
|
255
|
+
* two buckets both words, at every width the panel drags to.
|
|
256
|
+
* three buckets words down to 240px, then glyph plus the chosen word.
|
|
257
|
+
* four buckets glyph plus the chosen word under 320px; glyphs under 200px.
|
|
258
|
+
*
|
|
259
|
+
* Which state is a CSS decision, taken against the PANEL's width — see the
|
|
207
260
|
* `@container` blocks in `panel.css`, and the wrapper below that gives them
|
|
208
261
|
* something to measure. React cannot know the answer at render time, and it
|
|
209
262
|
* does not need to: BOTH parts are always in the markup, and CSS shows the one
|
|
210
263
|
* that fits. The word is only ever hidden, never dropped, and the label rides
|
|
211
|
-
* along as `title` and `aria-label` on every tab in every
|
|
264
|
+
* along as `title` and `aria-label` on every tab in every state, so the glyph
|
|
212
265
|
* never has to speak for the tab.
|
|
213
266
|
*
|
|
214
267
|
* A tab with no icon is untouched at every width — it has nothing to fall back
|
|
215
|
-
* to, so it keeps its word
|
|
268
|
+
* to, so it keeps its word.
|
|
216
269
|
*/
|
|
217
270
|
declare function SettingsTabs({ items, activeId, onSelect, ariaLabel, className, }: SettingsTabsProps): react.JSX.Element;
|
|
218
271
|
|
|
@@ -316,4 +369,28 @@ declare function SettingsField({ label, labelId, icon, value, hint, changed, htm
|
|
|
316
369
|
*/
|
|
317
370
|
declare function SettingsSwitch({ checked, onChange, disabled, id, ariaLabel, ariaLabelledBy, className, }: SettingsSwitchProps): react.JSX.Element;
|
|
318
371
|
|
|
319
|
-
|
|
372
|
+
/**
|
|
373
|
+
* A DRILL-IN ROW — a name, a one-line preview, and a chevron (card 8819).
|
|
374
|
+
*
|
|
375
|
+
* The storefront editor's text editing becomes a panel drill-down: the section
|
|
376
|
+
* screen no longer holds a text box for every part; it LISTS the parts as these
|
|
377
|
+
* rows, and pressing one takes the inspector down a level to that part's own
|
|
378
|
+
* editor. A part the eye hid — or one that never draws on the page at all, like
|
|
379
|
+
* a `Slide name` — has no words on the canvas to click, so this list is the only
|
|
380
|
+
* way back to it. That is why the list exists, and why the row is the shell's,
|
|
381
|
+
* not the shop's: the campaign designer draws the same list.
|
|
382
|
+
*
|
|
383
|
+
* IT IS ONE BUTTON, not a control beside some text. The name and the preview are
|
|
384
|
+
* the button's own content, so they ARE its accessible name — a chevron floating
|
|
385
|
+
* next to unlabelled text would be a press a screen reader could not describe.
|
|
386
|
+
* The chevron is `aria-hidden` (pure direction); the status slot is NOT, because
|
|
387
|
+
* its meaning is the host's to name — it passes an icon carrying its own label
|
|
388
|
+
* (a `title`, a visually-hidden word), and hiding the slot would bury that.
|
|
389
|
+
*
|
|
390
|
+
* CONTROLLED and hook-free like the other four primitives: it owns no open state.
|
|
391
|
+
* Pressing it calls `onOpen`, and the host decides what drilling down means —
|
|
392
|
+
* which keeps this a directive-free leaf a Next-16 server component can import.
|
|
393
|
+
*/
|
|
394
|
+
declare function SettingsDrill({ name, preview, offPage, status, onOpen, id, className, }: SettingsDrillProps): react.JSX.Element;
|
|
395
|
+
|
|
396
|
+
export { SettingsDrill, type SettingsDrillProps, SettingsField, type SettingsFieldProps, SettingsGroup, SettingsGroupHead, type SettingsGroupHeadProps, type SettingsGroupProps, SettingsSwitch, type SettingsSwitchBaseProps, type SettingsSwitchLabel, type SettingsSwitchProps, type SettingsTabItem, SettingsTabs, type SettingsTabsProps };
|
package/dist/panel/index.js
CHANGED
|
@@ -156,7 +156,35 @@ function SettingsField({
|
|
|
156
156
|
hint ? /* @__PURE__ */ jsx("p", { className: "es-field-hint", children: hint }) : null
|
|
157
157
|
] });
|
|
158
158
|
}
|
|
159
|
+
function SettingsDrill({
|
|
160
|
+
name,
|
|
161
|
+
preview,
|
|
162
|
+
offPage,
|
|
163
|
+
status,
|
|
164
|
+
onOpen,
|
|
165
|
+
id,
|
|
166
|
+
className
|
|
167
|
+
}) {
|
|
168
|
+
const hasPreview = preview !== void 0 && preview !== null;
|
|
169
|
+
return /* @__PURE__ */ jsxs(
|
|
170
|
+
"button",
|
|
171
|
+
{
|
|
172
|
+
type: "button",
|
|
173
|
+
id,
|
|
174
|
+
className: className ? `es-drill ${className}` : "es-drill",
|
|
175
|
+
onClick: onOpen,
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ jsxs("span", { className: "es-drill-text", children: [
|
|
178
|
+
/* @__PURE__ */ jsx("span", { className: "es-drill-name", children: name }),
|
|
179
|
+
hasPreview ? /* @__PURE__ */ jsx("span", { className: offPage ? "es-drill-preview is-offpage" : "es-drill-preview", children: preview }) : null
|
|
180
|
+
] }),
|
|
181
|
+
status ? /* @__PURE__ */ jsx("span", { className: "es-drill-status", children: status }) : null,
|
|
182
|
+
/* @__PURE__ */ jsx("span", { className: "es-drill-chev", "aria-hidden": "true" })
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
159
187
|
|
|
160
|
-
export { SettingsField, SettingsGroup, SettingsGroupHead, SettingsSwitch, SettingsTabs };
|
|
188
|
+
export { SettingsDrill, SettingsField, SettingsGroup, SettingsGroupHead, SettingsSwitch, SettingsTabs };
|
|
161
189
|
//# sourceMappingURL=index.js.map
|
|
162
190
|
//# sourceMappingURL=index.js.map
|
package/dist/panel/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/panel/SettingsTabs.tsx","../../src/panel/SettingsGroup.tsx","../../src/panel/SettingsGroupHead.tsx","../../src/panel/SettingsSwitch.tsx","../../src/panel/SettingsField.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { SettingsTabsProps } from './types';\n\n/**\n * The tab strip under the panel head.\n *\n * It replaces the old stack of fold headers a merchant had to open and hunt\n * through: the buckets a section actually has become tabs, in one fixed order,\n * so the panel keeps the same shape on every section. A section shows only the\n * tabs it has — pass three items and three tabs render.\n *\n * Controlled: the host decides `activeId`. No hooks, no browser globals.\n *\n * A tab can be WIRED to what it shows (`panelId` / `htmlId` on the item). That\n * is not decoration: `role=\"tab\"` promises a panel, and a tab strip that names\n * none announces \"tab, 1 of 4, selected\" over nothing. The shell cannot supply\n * those ids — only the host knows the element the settings land in — but until\n * card 8802.d it could not ACCEPT them either, so no host could fix it. A tab\n * with nothing to point at renders no attribute at all; an empty\n * `aria-controls` is worse than a missing one, because it names an element\n * that does not exist instead of admitting there is none.\n *\n * THE THREE TIERS (card 8810). A tab keeps the width its label needs — it may\n * not shrink and it may not truncate — so a narrow panel wraps the strip onto a\n * second row, and at the 220px minimum the last bucket goes there alone. Given\n * an `icon`, a tab can spend less width instead of more:\n *\n * 270px and up words, and no glyph at all. Exactly what shipped.\n * 240 – 269px the glyphs come out; only the ACTIVE tab keeps its word.\n * below 240px glyphs only.\n *\n * The glyph is absent from the widest tier on purpose, not by oversight: it\n * costs a tab 22px, and four tabs carrying word AND glyph need 356px to hold\n * one row — drawing them at full width would wrap the strip at the ordinary\n * panel width, which is the bug this is here to remove.\n *\n * Which tier is a CSS decision, taken against the PANEL's width — see the\n * `@container` blocks in `panel.css`, and the wrapper below that gives them\n * something to measure. React cannot know the answer at render time, and it\n * does not need to: BOTH parts are always in the markup, and CSS shows the one\n * that fits. The word is only ever hidden, never dropped, and the label rides\n * along as `title` and `aria-label` on every tab in every tier, so the glyph\n * never has to speak for the tab.\n *\n * A tab with no icon is untouched at every width — it has nothing to fall back\n * to, so it keeps its word and the strip wraps for it exactly as before.\n */\nexport function SettingsTabs({\n items,\n activeId,\n onSelect,\n ariaLabel = 'Settings',\n className,\n}: SettingsTabsProps) {\n return (\n // The container context is the SHELL's, not a host's to remember. A panel\n // that forgot to declare one would silently pin the strip to one tier.\n <div className=\"es-tabs-fit\">\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation=\"horizontal\"\n className={className ? `es-tabs ${className}` : 'es-tabs'}\n >\n {items.map((item) => {\n const active = item.id === activeId;\n const cls = `es-tab${item.icon ? ' has-icon' : ''}${active ? ' is-active' : ''}`;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"tab\"\n id={item.htmlId}\n title={item.label}\n aria-label={item.label}\n aria-selected={active}\n aria-controls={item.panelId}\n className={cls}\n onClick={() => onSelect(item.id)}\n >\n {item.icon ? (\n <span className=\"es-tab-icon\" aria-hidden=\"true\">\n {item.icon}\n </span>\n ) : null}\n <span className=\"es-tab-word\">{item.label}</span>\n {typeof item.badge === 'number' && item.badge > 0 ? (\n <span className=\"es-tab-badge\">{item.badge}</span>\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","import type { SettingsGroupProps } from './types';\n\n/**\n * A group of settings under a soft pill row that folds with − / +.\n *\n * The sign is drawn by `panel.css` from `aria-expanded`, so the open state has\n * exactly one home (the attribute assistive tech already reads) instead of a\n * second copy in the markup.\n *\n * `count` is the reason a fold here is safe: a folded group that holds changes\n * still says so on its pill, so nothing a merchant changed can hide.\n */\nexport function SettingsGroup({\n title,\n open,\n onToggle,\n count,\n id,\n children,\n className,\n}: SettingsGroupProps) {\n const bodyId = id ? `${id}-body` : undefined;\n return (\n <div className={className ? `es-group ${className}` : 'es-group'}>\n <button\n type=\"button\"\n id={id}\n aria-expanded={open}\n aria-controls={bodyId}\n className=\"es-group-head\"\n onClick={() => onToggle(!open)}\n >\n {title}\n {typeof count === 'number' && count > 0 ? (\n <span className=\"es-group-count\">{count}</span>\n ) : null}\n <span className=\"es-group-sign\" aria-hidden=\"true\" />\n </button>\n <div id={bodyId} className=\"es-group-body\" hidden={!open}>\n {children}\n </div>\n </div>\n );\n}\n","import type { SettingsGroupHeadProps } from './types';\n\n/**\n * A group heading with NO group under it — the pill on its own.\n *\n * It exists because Puck lays the inspector out as a FLAT sibling list: a field\n * renders next to the heading above it, never inside it, so nothing here can\n * wrap the rows that follow. Heading a run of settings is therefore a job for a\n * component that heads and nothing else.\n *\n * `SettingsGroup` cannot do that job. It always renders its body div, and its\n * pill is always a <button> carrying `aria-expanded`, `aria-controls` and the\n * − / + sign. Used header-only it would draw a control that claims to expand\n * something, does nothing when clicked, and tells a screen reader the same lie.\n * A component that folds and a component that titles are two different things,\n * and this is the one that titles.\n *\n * So it is a HEADING, not a control: a real <h3> (the level is the host's, since\n * only the host knows the page outline around it) with no role bolted on, no\n * expanded state, no sign, no body. In a flat list the heading list is the only\n * structure a screen reader has left to navigate by, which is the whole reason\n * this renders a heading element rather than a styled <div>.\n *\n * It wears `.es-group-head` — the same pill as a real group's — so a section\n * that folds and a section that does not read as the same panel. `panel.css`\n * scopes the cursor and the hover tint to `button.es-group-head`, so the\n * heading does not offer a press it cannot honour.\n *\n * NO `count`. A folded group needs one because a fold can hide a change; a\n * heading folds nothing, so nothing can hide behind it.\n */\nexport function SettingsGroupHead({\n title,\n level = 3,\n id,\n className,\n}: SettingsGroupHeadProps) {\n const Tag = `h${level}` as const;\n return (\n <Tag id={id} className={className ? `es-group-head ${className}` : 'es-group-head'}>\n {title}\n </Tag>\n );\n}\n","import type { SettingsSwitchProps } from './types';\n\n/**\n * The yes-or-no control — ONE shape, both editors (card 8801).\n *\n * Point 5 of the approved settings design says one switch style everywhere.\n * Before this, the storefront editor drew a tick box and the campaign designer\n * drew a green switch, so a merchant met two shapes for the same question and\n * had to learn the control twice. Both now import this file, which is the only\n * thing that stops them drifting apart again.\n *\n * It is a real <button>, and that is the whole keyboard story: a button is\n * activated by Space and by Enter in every browser, and that activation IS a\n * click — so the one `onClick` below serves mouse and keyboard alike. A div\n * wearing `role=\"switch\"` would need a key handler of our own. Do not add one.\n * `type=\"button\"` keeps it from submitting a form it happens to sit in.\n *\n * It cannot exist without a NAME. `SettingsSwitchProps` demands `ariaLabel` or\n * `ariaLabelledBy` and forbids both at once, so an unnamed switch — which a\n * screen reader reads out as \"switch, on\" and nothing more — is a compile error\n * instead of something a docstring asks you to remember.\n *\n * ONE-WAY DEPENDENCY. `SettingsField` imports this module so it can recognise a\n * switch in its `action` slot and hand it the field's label as a name. This\n * module must NEVER import `SettingsField` back — that would close a cycle\n * between two siblings in the same folder. It imports `./types` and nothing\n * else, and `tests/settings-switch.test.tsx` (o) keeps it that way.\n *\n * CONTROLLED and hook-free like the other three: `checked` comes from the host\n * and the switch only ever ASKS to be flipped. That is what keeps this module a\n * directive-free leaf a Next-16 server component can import.\n */\nexport function SettingsSwitch({\n checked,\n onChange,\n disabled,\n id,\n ariaLabel,\n ariaLabelledBy,\n className,\n}: SettingsSwitchProps) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n id={id}\n aria-checked={checked}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n disabled={disabled}\n className={className ? `es-switch ${className}` : 'es-switch'}\n onClick={() => onChange(!checked)}\n />\n );\n}\n","import { cloneElement, isValidElement } from 'react';\nimport type { ReactElement, ReactNode } from 'react';\n\nimport { SettingsSwitch } from './SettingsSwitch';\nimport type { SettingsFieldProps } from './types';\n\n/**\n * Does this slot hold a control?\n *\n * The rule is ELEMENT-OR-NOTHING, deliberately not a list of falsy values: a\n * list rots, and the next odd value nobody thought of walks straight through.\n * `{flag && <Slider/>}` yields `false`, `{count && <Slider/>}` yields `0` when\n * the count is zero — and React RENDERS that zero, a stray 0 sitting in the\n * panel where a control belongs. A bare string or number in a control slot is a\n * caller mistake in every case any of us can name.\n *\n * Arrays are walked rather than trusted: `Array.isArray([])` is true, so an\n * empty array — or one holding only `false` and `null`, which is what a list of\n * conditional controls collapses to — would otherwise draw the empty row this\n * check exists to prevent.\n */\nfunction hasControlIn(slot: ReactNode): boolean {\n if (Array.isArray(slot)) return slot.some(hasControlIn);\n return isValidElement(slot);\n}\n\n/** What a switch is named by. Read off an unknown element, so it stays loose. */\ninterface SwitchName {\n ariaLabel?: string;\n ariaLabelledBy?: string;\n}\n\n/**\n * Give a switch in the `action` slot the field's own label as its name.\n *\n * A switch carries no text, and `htmlFor` cannot bridge a <label> to a\n * <button>, so in a boolean row nothing connects the visible label to the\n * control unless something does it explicitly. Doing it HERE means the common\n * case is right with the caller saying nothing: the accessible name and the\n * visible name are the same string and cannot drift apart.\n *\n * It only ever fills a gap. A switch that names itself keeps its own name, and\n * anything that is not a switch — a reset dot, a badge — is left alone, since\n * labelling those with the field's name would be worse than not labelling them.\n */\nfunction nameSwitch(action: ReactNode, label: string, labelId?: string): ReactNode {\n if (!isValidElement(action) || action.type !== SettingsSwitch) return action;\n\n const named = action.props as SwitchName;\n if (named.ariaLabel || named.ariaLabelledBy) return action;\n\n return cloneElement(\n action as ReactElement<SwitchName>,\n labelId ? { ariaLabelledBy: labelId } : { ariaLabel: label },\n );\n}\n\n/**\n * One setting: a label row, then its control.\n *\n * The row is stacked rather than side-by-side so the control gets the panel's\n * full width — a 308px panel cannot afford a label column AND a usable slider.\n * The label reads small and quiet; the VALUE sits at the end of the same line in\n * the text colour, which is what a merchant scans for. That contrast is the\n * point: before this, label and value looked alike and the panel read as noise.\n *\n * `hint` is where a clause-long explanation goes. A label is a name, never a\n * sentence.\n *\n * A BOOLEAN row is the one exception to \"control on its own line\" (card 8801,\n * Option A, approved). A switch is small and it is the whole control, so it\n * rides the label line in the `action` slot and the row costs one line instead\n * of two — which is what lets a 308px panel show four settings where it used to\n * show three. That is the only reason `children` is optional: a field with no\n * children draws no control row, and its label line IS the row.\n *\n * A field with no children AND no action has no control on either slot. That is\n * a mistake at the call site, so it renders NOTHING — an empty row would hide\n * the mistake behind 20px of blank panel.\n */\nexport function SettingsField({\n label,\n labelId,\n icon,\n value,\n hint,\n changed,\n htmlFor,\n action,\n children,\n className,\n}: SettingsFieldProps) {\n const hasControl = hasControlIn(children);\n const hasAction = hasControlIn(action);\n if (!hasControl && !hasAction) return null;\n\n const classes = ['es-field'];\n if (!hasControl) classes.push('is-inline');\n if (changed) classes.push('is-changed');\n if (className) classes.push(className);\n\n const labelBody = (\n <>\n {icon ? (\n <span className=\"es-field-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n ) : null}\n <span className=\"es-field-label\" id={labelId}>\n {label}\n </span>\n </>\n );\n\n return (\n <div className={classes.join(' ')}>\n <div className=\"es-field-head\">\n {htmlFor ? (\n <label className=\"es-field-name\" htmlFor={htmlFor}>\n {labelBody}\n </label>\n ) : (\n <span className=\"es-field-name\">{labelBody}</span>\n )}\n {value !== undefined && value !== null ? (\n <span className=\"es-field-value\">{value}</span>\n ) : null}\n {hasAction ? nameSwitch(action, label, labelId) : null}\n </div>\n {hasControl ? <div className=\"es-field-ctl\">{children}</div> : null}\n {hint ? <p className=\"es-field-hint\">{hint}</p> : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/panel/SettingsTabs.tsx","../../src/panel/SettingsGroup.tsx","../../src/panel/SettingsGroupHead.tsx","../../src/panel/SettingsSwitch.tsx","../../src/panel/SettingsField.tsx","../../src/panel/SettingsDrill.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { SettingsTabsProps } from './types';\n\n/**\n * The tab strip under the panel head.\n *\n * It replaces the old stack of fold headers a merchant had to open and hunt\n * through: the buckets a section actually has become tabs, in one fixed order,\n * so the panel keeps the same shape on every section. A section shows only the\n * tabs it has — pass three items and three tabs render.\n *\n * Controlled: the host decides `activeId`. No hooks, no browser globals.\n *\n * A tab can be WIRED to what it shows (`panelId` / `htmlId` on the item). That\n * is not decoration: `role=\"tab\"` promises a panel, and a tab strip that names\n * none announces \"tab, 1 of 4, selected\" over nothing. The shell cannot supply\n * those ids — only the host knows the element the settings land in — but until\n * card 8802.d it could not ACCEPT them either, so no host could fix it. A tab\n * with nothing to point at renders no attribute at all; an empty\n * `aria-controls` is worse than a missing one, because it names an element\n * that does not exist instead of admitting there is none.\n *\n * A SEGMENTED CONTROL (card 66138). The strip is a grey track and the chosen\n * bucket is a raised white chip on it. Lewis, looking at a real panel: the tab\n * he is NOT on does not invite a click — it does not even look like a button.\n * The track is the answer to that, because it makes every segment look\n * pressable, where a mark under the chosen tab only ever made the chosen tab\n * louder. It replaces the underline strip and card 8811's tint ground; see the\n * long note on `.es-tab.is-active` in `panel.css`.\n *\n * A segment takes the width its OWN label needs and shares what is left over,\n * rather than every segment taking the width of the widest label. Equal\n * segments were measured and cost too much: three of them need a 267px track\n * and four need 355px, against the 281px the shop's default panel gives.\n *\n * THE WORD DROPS WHERE IT STOPS FITTING. Natural widths fit two and three\n * buckets across the whole panel-drag band; they do not fit four, where the\n * last segment ends 39px past the panel edge. So given an `icon`, a segment can\n * spend less width instead of more — the word goes and the glyph stays.\n *\n * Where that happens depends on HOW MANY buckets a section has, because with\n * natural widths the question is about the sum of the labels:\n *\n * two buckets both words, at every width the panel drags to.\n * three buckets words down to 240px, then glyph plus the chosen word.\n * four buckets glyph plus the chosen word under 320px; glyphs under 200px.\n *\n * Which state is a CSS decision, taken against the PANEL's width — see the\n * `@container` blocks in `panel.css`, and the wrapper below that gives them\n * something to measure. React cannot know the answer at render time, and it\n * does not need to: BOTH parts are always in the markup, and CSS shows the one\n * that fits. The word is only ever hidden, never dropped, and the label rides\n * along as `title` and `aria-label` on every tab in every state, so the glyph\n * never has to speak for the tab.\n *\n * A tab with no icon is untouched at every width — it has nothing to fall back\n * to, so it keeps its word.\n */\nexport function SettingsTabs({\n items,\n activeId,\n onSelect,\n ariaLabel = 'Settings',\n className,\n}: SettingsTabsProps) {\n return (\n // The container context is the SHELL's, not a host's to remember. A panel\n // that forgot to declare one would silently pin the strip to one tier.\n <div className=\"es-tabs-fit\">\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation=\"horizontal\"\n className={className ? `es-tabs ${className}` : 'es-tabs'}\n >\n {items.map((item) => {\n const active = item.id === activeId;\n const cls = `es-tab${item.icon ? ' has-icon' : ''}${active ? ' is-active' : ''}`;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"tab\"\n id={item.htmlId}\n title={item.label}\n aria-label={item.label}\n aria-selected={active}\n aria-controls={item.panelId}\n className={cls}\n onClick={() => onSelect(item.id)}\n >\n {item.icon ? (\n <span className=\"es-tab-icon\" aria-hidden=\"true\">\n {item.icon}\n </span>\n ) : null}\n <span className=\"es-tab-word\">{item.label}</span>\n {typeof item.badge === 'number' && item.badge > 0 ? (\n <span className=\"es-tab-badge\">{item.badge}</span>\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","import type { SettingsGroupProps } from './types';\n\n/**\n * A group of settings under a soft pill row that folds with − / +.\n *\n * The sign is drawn by `panel.css` from `aria-expanded`, so the open state has\n * exactly one home (the attribute assistive tech already reads) instead of a\n * second copy in the markup.\n *\n * `count` is the reason a fold here is safe: a folded group that holds changes\n * still says so on its pill, so nothing a merchant changed can hide.\n */\nexport function SettingsGroup({\n title,\n open,\n onToggle,\n count,\n id,\n children,\n className,\n}: SettingsGroupProps) {\n const bodyId = id ? `${id}-body` : undefined;\n return (\n <div className={className ? `es-group ${className}` : 'es-group'}>\n <button\n type=\"button\"\n id={id}\n aria-expanded={open}\n aria-controls={bodyId}\n className=\"es-group-head\"\n onClick={() => onToggle(!open)}\n >\n {title}\n {typeof count === 'number' && count > 0 ? (\n <span className=\"es-group-count\">{count}</span>\n ) : null}\n <span className=\"es-group-sign\" aria-hidden=\"true\" />\n </button>\n <div id={bodyId} className=\"es-group-body\" hidden={!open}>\n {children}\n </div>\n </div>\n );\n}\n","import type { SettingsGroupHeadProps } from './types';\n\n/**\n * A group heading with NO group under it — the pill on its own.\n *\n * It exists because Puck lays the inspector out as a FLAT sibling list: a field\n * renders next to the heading above it, never inside it, so nothing here can\n * wrap the rows that follow. Heading a run of settings is therefore a job for a\n * component that heads and nothing else.\n *\n * `SettingsGroup` cannot do that job. It always renders its body div, and its\n * pill is always a <button> carrying `aria-expanded`, `aria-controls` and the\n * − / + sign. Used header-only it would draw a control that claims to expand\n * something, does nothing when clicked, and tells a screen reader the same lie.\n * A component that folds and a component that titles are two different things,\n * and this is the one that titles.\n *\n * So it is a HEADING, not a control: a real <h3> (the level is the host's, since\n * only the host knows the page outline around it) with no role bolted on, no\n * expanded state, no sign, no body. In a flat list the heading list is the only\n * structure a screen reader has left to navigate by, which is the whole reason\n * this renders a heading element rather than a styled <div>.\n *\n * It wears `.es-group-head` — the same pill as a real group's — so a section\n * that folds and a section that does not read as the same panel. `panel.css`\n * scopes the cursor and the hover tint to `button.es-group-head`, so the\n * heading does not offer a press it cannot honour.\n *\n * NO `count`. A folded group needs one because a fold can hide a change; a\n * heading folds nothing, so nothing can hide behind it.\n */\nexport function SettingsGroupHead({\n title,\n level = 3,\n id,\n className,\n}: SettingsGroupHeadProps) {\n const Tag = `h${level}` as const;\n return (\n <Tag id={id} className={className ? `es-group-head ${className}` : 'es-group-head'}>\n {title}\n </Tag>\n );\n}\n","import type { SettingsSwitchProps } from './types';\n\n/**\n * The yes-or-no control — ONE shape, both editors (card 8801).\n *\n * Point 5 of the approved settings design says one switch style everywhere.\n * Before this, the storefront editor drew a tick box and the campaign designer\n * drew a green switch, so a merchant met two shapes for the same question and\n * had to learn the control twice. Both now import this file, which is the only\n * thing that stops them drifting apart again.\n *\n * It is a real <button>, and that is the whole keyboard story: a button is\n * activated by Space and by Enter in every browser, and that activation IS a\n * click — so the one `onClick` below serves mouse and keyboard alike. A div\n * wearing `role=\"switch\"` would need a key handler of our own. Do not add one.\n * `type=\"button\"` keeps it from submitting a form it happens to sit in.\n *\n * It cannot exist without a NAME. `SettingsSwitchProps` demands `ariaLabel` or\n * `ariaLabelledBy` and forbids both at once, so an unnamed switch — which a\n * screen reader reads out as \"switch, on\" and nothing more — is a compile error\n * instead of something a docstring asks you to remember.\n *\n * ONE-WAY DEPENDENCY. `SettingsField` imports this module so it can recognise a\n * switch in its `action` slot and hand it the field's label as a name. This\n * module must NEVER import `SettingsField` back — that would close a cycle\n * between two siblings in the same folder. It imports `./types` and nothing\n * else, and `tests/settings-switch.test.tsx` (o) keeps it that way.\n *\n * CONTROLLED and hook-free like the other three: `checked` comes from the host\n * and the switch only ever ASKS to be flipped. That is what keeps this module a\n * directive-free leaf a Next-16 server component can import.\n */\nexport function SettingsSwitch({\n checked,\n onChange,\n disabled,\n id,\n ariaLabel,\n ariaLabelledBy,\n className,\n}: SettingsSwitchProps) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n id={id}\n aria-checked={checked}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n disabled={disabled}\n className={className ? `es-switch ${className}` : 'es-switch'}\n onClick={() => onChange(!checked)}\n />\n );\n}\n","import { cloneElement, isValidElement } from 'react';\nimport type { ReactElement, ReactNode } from 'react';\n\nimport { SettingsSwitch } from './SettingsSwitch';\nimport type { SettingsFieldProps } from './types';\n\n/**\n * Does this slot hold a control?\n *\n * The rule is ELEMENT-OR-NOTHING, deliberately not a list of falsy values: a\n * list rots, and the next odd value nobody thought of walks straight through.\n * `{flag && <Slider/>}` yields `false`, `{count && <Slider/>}` yields `0` when\n * the count is zero — and React RENDERS that zero, a stray 0 sitting in the\n * panel where a control belongs. A bare string or number in a control slot is a\n * caller mistake in every case any of us can name.\n *\n * Arrays are walked rather than trusted: `Array.isArray([])` is true, so an\n * empty array — or one holding only `false` and `null`, which is what a list of\n * conditional controls collapses to — would otherwise draw the empty row this\n * check exists to prevent.\n */\nfunction hasControlIn(slot: ReactNode): boolean {\n if (Array.isArray(slot)) return slot.some(hasControlIn);\n return isValidElement(slot);\n}\n\n/** What a switch is named by. Read off an unknown element, so it stays loose. */\ninterface SwitchName {\n ariaLabel?: string;\n ariaLabelledBy?: string;\n}\n\n/**\n * Give a switch in the `action` slot the field's own label as its name.\n *\n * A switch carries no text, and `htmlFor` cannot bridge a <label> to a\n * <button>, so in a boolean row nothing connects the visible label to the\n * control unless something does it explicitly. Doing it HERE means the common\n * case is right with the caller saying nothing: the accessible name and the\n * visible name are the same string and cannot drift apart.\n *\n * It only ever fills a gap. A switch that names itself keeps its own name, and\n * anything that is not a switch — a reset dot, a badge — is left alone, since\n * labelling those with the field's name would be worse than not labelling them.\n */\nfunction nameSwitch(action: ReactNode, label: string, labelId?: string): ReactNode {\n if (!isValidElement(action) || action.type !== SettingsSwitch) return action;\n\n const named = action.props as SwitchName;\n if (named.ariaLabel || named.ariaLabelledBy) return action;\n\n return cloneElement(\n action as ReactElement<SwitchName>,\n labelId ? { ariaLabelledBy: labelId } : { ariaLabel: label },\n );\n}\n\n/**\n * One setting: a label row, then its control.\n *\n * The row is stacked rather than side-by-side so the control gets the panel's\n * full width — a 308px panel cannot afford a label column AND a usable slider.\n * The label reads small and quiet; the VALUE sits at the end of the same line in\n * the text colour, which is what a merchant scans for. That contrast is the\n * point: before this, label and value looked alike and the panel read as noise.\n *\n * `hint` is where a clause-long explanation goes. A label is a name, never a\n * sentence.\n *\n * A BOOLEAN row is the one exception to \"control on its own line\" (card 8801,\n * Option A, approved). A switch is small and it is the whole control, so it\n * rides the label line in the `action` slot and the row costs one line instead\n * of two — which is what lets a 308px panel show four settings where it used to\n * show three. That is the only reason `children` is optional: a field with no\n * children draws no control row, and its label line IS the row.\n *\n * A field with no children AND no action has no control on either slot. That is\n * a mistake at the call site, so it renders NOTHING — an empty row would hide\n * the mistake behind 20px of blank panel.\n */\nexport function SettingsField({\n label,\n labelId,\n icon,\n value,\n hint,\n changed,\n htmlFor,\n action,\n children,\n className,\n}: SettingsFieldProps) {\n const hasControl = hasControlIn(children);\n const hasAction = hasControlIn(action);\n if (!hasControl && !hasAction) return null;\n\n const classes = ['es-field'];\n if (!hasControl) classes.push('is-inline');\n if (changed) classes.push('is-changed');\n if (className) classes.push(className);\n\n const labelBody = (\n <>\n {icon ? (\n <span className=\"es-field-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n ) : null}\n <span className=\"es-field-label\" id={labelId}>\n {label}\n </span>\n </>\n );\n\n return (\n <div className={classes.join(' ')}>\n <div className=\"es-field-head\">\n {htmlFor ? (\n <label className=\"es-field-name\" htmlFor={htmlFor}>\n {labelBody}\n </label>\n ) : (\n <span className=\"es-field-name\">{labelBody}</span>\n )}\n {value !== undefined && value !== null ? (\n <span className=\"es-field-value\">{value}</span>\n ) : null}\n {hasAction ? nameSwitch(action, label, labelId) : null}\n </div>\n {hasControl ? <div className=\"es-field-ctl\">{children}</div> : null}\n {hint ? <p className=\"es-field-hint\">{hint}</p> : null}\n </div>\n );\n}\n","import type { SettingsDrillProps } from './types';\n\n/**\n * A DRILL-IN ROW — a name, a one-line preview, and a chevron (card 8819).\n *\n * The storefront editor's text editing becomes a panel drill-down: the section\n * screen no longer holds a text box for every part; it LISTS the parts as these\n * rows, and pressing one takes the inspector down a level to that part's own\n * editor. A part the eye hid — or one that never draws on the page at all, like\n * a `Slide name` — has no words on the canvas to click, so this list is the only\n * way back to it. That is why the list exists, and why the row is the shell's,\n * not the shop's: the campaign designer draws the same list.\n *\n * IT IS ONE BUTTON, not a control beside some text. The name and the preview are\n * the button's own content, so they ARE its accessible name — a chevron floating\n * next to unlabelled text would be a press a screen reader could not describe.\n * The chevron is `aria-hidden` (pure direction); the status slot is NOT, because\n * its meaning is the host's to name — it passes an icon carrying its own label\n * (a `title`, a visually-hidden word), and hiding the slot would bury that.\n *\n * CONTROLLED and hook-free like the other four primitives: it owns no open state.\n * Pressing it calls `onOpen`, and the host decides what drilling down means —\n * which keeps this a directive-free leaf a Next-16 server component can import.\n */\nexport function SettingsDrill({\n name,\n preview,\n offPage,\n status,\n onOpen,\n id,\n className,\n}: SettingsDrillProps) {\n const hasPreview = preview !== undefined && preview !== null;\n return (\n <button\n type=\"button\"\n id={id}\n className={className ? `es-drill ${className}` : 'es-drill'}\n onClick={onOpen}\n >\n <span className=\"es-drill-text\">\n <span className=\"es-drill-name\">{name}</span>\n {hasPreview ? (\n <span className={offPage ? 'es-drill-preview is-offpage' : 'es-drill-preview'}>\n {preview}\n </span>\n ) : null}\n </span>\n {status ? <span className=\"es-drill-status\">{status}</span> : null}\n <span className=\"es-drill-chev\" aria-hidden=\"true\" />\n </button>\n );\n}\n"]}
|
package/dist/panel/panel.css
CHANGED
|
@@ -16,93 +16,132 @@
|
|
|
16
16
|
* contrast existed, a merchant could not tell a name from a number.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
/* ── the tab strip
|
|
19
|
+
/* ── the tab strip: a segmented control ────────────────────────────────────── */
|
|
20
20
|
|
|
21
21
|
/* The strip's own container context (card 8810), and the reason `SettingsTabs`
|
|
22
22
|
renders a wrapper at all.
|
|
23
23
|
|
|
24
|
-
The
|
|
25
|
-
only thing that can ask for it. A media query asks the WINDOW, and the
|
|
26
|
-
is the wrong box: the inspector is a column the merchant drags, so a
|
|
27
|
-
panel on a 2560px screen is the ordinary case, not an edge one — a
|
|
28
|
-
query would
|
|
29
|
-
|
|
24
|
+
The drop rules further down turn on the width of the PANEL, and `@container`
|
|
25
|
+
is the only thing that can ask for it. A media query asks the WINDOW, and the
|
|
26
|
+
window is the wrong box: the inspector is a column the merchant drags, so a
|
|
27
|
+
220px panel on a 2560px screen is the ordinary case, not an edge one — a
|
|
28
|
+
media query would answer for that panel with the width of the screen behind
|
|
29
|
+
it.
|
|
30
30
|
|
|
31
31
|
The context lives HERE rather than in a host's own panel rule so the shell is
|
|
32
32
|
self-contained: a host that never declares `container-type` would silently
|
|
33
|
-
pin the strip to one
|
|
33
|
+
pin the strip to one state with nothing to see and nothing to fail. */
|
|
34
34
|
.es-tabs-fit {
|
|
35
35
|
container-type: inline-size;
|
|
36
|
-
/* The wrapper is now the flex item the strip used to be — see `.es-tabs`. */
|
|
37
36
|
flex: none;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
/*
|
|
41
|
-
on
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
/* THE TRACK (card 66138). Lewis, looking at a real settings panel: the tab he
|
|
40
|
+
is NOT on does not invite a click — it does not even look like a button. He
|
|
41
|
+
was shown six treatments in `mockups/settings-tab-strip-options.html` and
|
|
42
|
+
picked B, the segmented control.
|
|
43
|
+
|
|
44
|
+
This REPLACES the underline strip, and with it card 8811's accent-tint ground
|
|
45
|
+
(see `.es-tab.is-active` below). The grey track is the part doing the work
|
|
46
|
+
Lewis asked for: it is what makes every segment look pressable, where a mark
|
|
47
|
+
under the chosen tab only ever made the chosen tab louder.
|
|
48
|
+
|
|
49
|
+
FULL WIDTH, no side inset. The old strip carried `padding: 0 14px` because
|
|
50
|
+
underline tabs must start inset from the panel edge. A track is a FILLED row,
|
|
51
|
+
and every other filled row here — `.es-group-head` at `width: 100%` — runs
|
|
52
|
+
the full width of the body's content box; a track inset 14px further than the
|
|
53
|
+
rows beneath it is visibly out of line. It is also worth 28px of room, which
|
|
54
|
+
is the difference between three segments fitting the shop's default panel and
|
|
55
|
+
not.
|
|
56
|
+
|
|
57
|
+
NOWRAP, unlike the strip it replaces. A segmented control is one row by
|
|
58
|
+
definition — a second row of track under the first is not a wrapped control,
|
|
59
|
+
it is two controls. The guarantee that no bucket is ever out of reach is
|
|
60
|
+
re-made out of different parts: segments that share the leftover space, and
|
|
61
|
+
the drop rules below. */
|
|
53
62
|
.es-tabs {
|
|
54
63
|
display: flex;
|
|
55
|
-
flex-wrap:
|
|
56
|
-
gap:
|
|
57
|
-
padding:
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
flex-wrap: nowrap;
|
|
65
|
+
gap: 3px;
|
|
66
|
+
padding: 3px;
|
|
67
|
+
background: var(--es-track-bg);
|
|
68
|
+
border-radius: var(--es-row-radius);
|
|
60
69
|
flex: none;
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
/* A SEGMENT TAKES ITS OWN WIDTH, THEN SHARES WHAT IS LEFT.
|
|
73
|
+
|
|
74
|
+
`1 1 auto` and not `1 1 0`: equal segments were measured and are too
|
|
75
|
+
expensive. Every equal segment takes the width of the WIDEST label, and
|
|
76
|
+
"Content" is 85.36px at 12px/600, so three equal segments need a 267px track
|
|
77
|
+
and four need 355px — against the 281px the shop's default panel gives. With
|
|
78
|
+
natural widths the track still reaches both edges, and "Style" is not forced
|
|
79
|
+
to be as wide as "Content" to get there.
|
|
80
|
+
|
|
81
|
+
`box-sizing` for the same reason the switch sets it: this file assumes no
|
|
82
|
+
reset of the host's. */
|
|
63
83
|
.es-tab {
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
flex: 1 1 auto;
|
|
64
86
|
border: 0;
|
|
65
87
|
background: transparent;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
clips it — a bucket out of reach in a different way. */
|
|
69
|
-
flex: none;
|
|
70
|
-
white-space: nowrap;
|
|
88
|
+
border-radius: 6px;
|
|
89
|
+
padding: 6px 8px;
|
|
71
90
|
font: inherit;
|
|
72
91
|
font-size: 12px;
|
|
73
92
|
font-weight: 600;
|
|
74
|
-
color: var(--es-
|
|
75
|
-
|
|
93
|
+
color: var(--es-text-secondary);
|
|
94
|
+
white-space: nowrap;
|
|
76
95
|
cursor: pointer;
|
|
77
|
-
/* The active rule sits ON the strip's own border, not under it. */
|
|
78
|
-
border-bottom: 2px solid transparent;
|
|
79
|
-
margin-bottom: -1px;
|
|
80
96
|
display: inline-flex;
|
|
81
97
|
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
82
99
|
gap: 6px;
|
|
83
|
-
transition: color 0.12s;
|
|
100
|
+
transition: background 0.12s, color 0.12s, box-shadow 0.12s;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* A quiet segment is READABLE, not faint. It was `--es-muted`, which on the new
|
|
104
|
+
grey track sits at about 4.3:1 and misses AA; this is about 8.9:1. The track
|
|
105
|
+
is what says "pressable" now, so the label no longer has to whisper to stay
|
|
106
|
+
out of the chosen tab's way. */
|
|
107
|
+
.es-tab:hover:not(.is-active) { color: var(--es-text); }
|
|
108
|
+
|
|
109
|
+
/* Keyboard focus, themed rather than left to the UA. Same ring as the switch,
|
|
110
|
+
so the panel answers a keyboard the one way. */
|
|
111
|
+
.es-tab:focus-visible {
|
|
112
|
+
outline: none;
|
|
113
|
+
box-shadow: 0 0 0 3px var(--es-accent-ring);
|
|
84
114
|
}
|
|
85
115
|
|
|
86
|
-
|
|
116
|
+
/* THE CHOSEN TAB IS A RAISED CHIP — AND THIS REPLACES CARD 8811.
|
|
87
117
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
8811 gave the chosen tab a filled ground, `--es-accent-tint` rounded at the
|
|
119
|
+
top only, because colour alone stopped being enough once the strip could
|
|
120
|
+
reduce a tab to a 16px glyph. Lewis picked the softest of three weights he
|
|
121
|
+
was shown, and the rule carried a note asking the next reader not to tidy it
|
|
122
|
+
away for looking almost absent in the stylesheet. That note did its job — it
|
|
123
|
+
is why this replacement is written down instead of silent.
|
|
93
124
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
What changed is the QUESTION. 8811 marked the chosen tab; Lewis's complaint
|
|
126
|
+
here is about the other one, which a tint under the chosen tab cannot reach.
|
|
127
|
+
The segmented control answers it the other way round: the track makes every
|
|
128
|
+
segment look pressable, the chip says which one he is on. Both marks survive
|
|
129
|
+
at every width, which is the property 8811 was buying.
|
|
98
130
|
|
|
99
|
-
|
|
100
|
-
|
|
131
|
+
So the tint goes, the top-only corners go, and the 2px accent rule goes with
|
|
132
|
+
them. The chip is told apart by ELEVATION now — a lit rule under a raised
|
|
133
|
+
chip is a second answer to a question that already has one. */
|
|
101
134
|
.es-tab.is-active {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
border-radius:
|
|
135
|
+
background: var(--es-raised);
|
|
136
|
+
color: var(--es-text);
|
|
137
|
+
box-shadow: var(--es-raised-shadow);
|
|
138
|
+
border-radius: 6px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Focused AND chosen: keep the lift, or the chip drops flat the moment a
|
|
142
|
+
keyboard reaches it. */
|
|
143
|
+
.es-tab.is-active:focus-visible {
|
|
144
|
+
box-shadow: var(--es-raised-shadow), 0 0 0 3px var(--es-accent-ring);
|
|
106
145
|
}
|
|
107
146
|
|
|
108
147
|
.es-tab-badge {
|
|
@@ -119,26 +158,25 @@
|
|
|
119
158
|
justify-content: center;
|
|
120
159
|
}
|
|
121
160
|
|
|
122
|
-
/* The glyph
|
|
123
|
-
|
|
161
|
+
/* The glyph in the chip: 16px of drawing, the same size a field's icon takes,
|
|
162
|
+
standing in an 18px box.
|
|
124
163
|
|
|
125
|
-
DRAWN
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
and the strip wraps at the shop's ordinary panel width — the bug this card
|
|
130
|
-
exists to remove, moved somewhere worse. Measured, `tabs-tiers.html` below.
|
|
164
|
+
DRAWN AT EVERY WIDTH, unlike the strip this replaces, where the glyph only
|
|
165
|
+
appeared once the words started failing. In a segmented control the glyph is
|
|
166
|
+
part of the chip — it is what the chip still says when its word has gone, and
|
|
167
|
+
mockup B draws it in both states.
|
|
131
168
|
|
|
132
169
|
The 18 is not a second opinion about the glyph — it is the WORD's own line
|
|
133
170
|
box (12px/600 in `--es-font` lays out an 18px line). Give the box that height
|
|
134
|
-
and a
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
and a chip is the same height whether it is showing a word, a glyph, or both,
|
|
172
|
+
so the track keeps one height as the merchant drags the panel past a
|
|
173
|
+
threshold. It is also what keeps a badge from moving anything: the badge is
|
|
174
|
+
16px, so it is never the tallest thing in the chip.
|
|
137
175
|
|
|
138
|
-
`flex: none`
|
|
139
|
-
|
|
176
|
+
`flex: none` because a glyph that shrinks is a squeezed segment in a smaller
|
|
177
|
+
box. */
|
|
140
178
|
.es-tab-icon {
|
|
141
|
-
display:
|
|
179
|
+
display: inline-flex;
|
|
142
180
|
width: 16px;
|
|
143
181
|
height: 18px;
|
|
144
182
|
flex: none;
|
|
@@ -148,39 +186,63 @@
|
|
|
148
186
|
|
|
149
187
|
.es-tab-icon svg { width: 16px; height: 16px; display: block; }
|
|
150
188
|
|
|
151
|
-
/* ── the
|
|
189
|
+
/* ── where the word stops fitting ──────────────────────────────────────────── */
|
|
152
190
|
|
|
153
|
-
/* MEASURED in Chromium, `tests/fixtures/tabs-
|
|
154
|
-
|
|
191
|
+
/* MEASURED in Chromium, `tests/fixtures/tabs-segmented-fit.html`, against these
|
|
192
|
+
* stylesheets and the shop's four buckets Content / Media / Style / Layout, by
|
|
193
|
+
* EDGE GEOMETRY — the right edge of the last segment against the right content
|
|
194
|
+
* edge of the track. Not `scrollWidth`: a flex button reports a `scrollWidth`
|
|
195
|
+
* equal to its `clientWidth` while its own content is drawn outside it, so it
|
|
196
|
+
* says "fits" about a strip that visibly does not.
|
|
197
|
+
*
|
|
198
|
+
* Smallest container width with nothing drawn past the track edge:
|
|
155
199
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* 270px 1 row 1 row 1 row
|
|
200
|
+
* tabs words glyph + active word glyphs only
|
|
201
|
+
* 2 168px 126px 120px
|
|
202
|
+
* 3 239px 161px 120px
|
|
203
|
+
* 4 320px 196px 143px
|
|
161
204
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
205
|
+
* The drag band gives containers from 181px (a 220px panel, the minimum) to
|
|
206
|
+
* 281px (a 320px panel, the default — the panel minus 28px of body padding
|
|
207
|
+
* minus the 11px reserved scrollbar gutter). Read against it:
|
|
165
208
|
*
|
|
166
|
-
*
|
|
209
|
+
* · TWO tabs never drop a word. 168px is under the 181px floor, so both words
|
|
210
|
+
* survive the whole band — and this is the case Lewis is looking at.
|
|
211
|
+
* · THREE tabs carry their words down to 239px.
|
|
212
|
+
* · FOUR tabs need 320px, which the band never reaches, so a four-bucket
|
|
213
|
+
* section is glyph-plus-one-word at the default panel, and below 196px even
|
|
214
|
+
* that one word stops fitting.
|
|
215
|
+
*
|
|
216
|
+
* WHY THE RULES ARE KEYED ON THE TAB COUNT. With natural widths, whether the
|
|
217
|
+
* words fit is a question about the SUM of the labels, so the answer moves with
|
|
218
|
+
* how many buckets a section has. A width-only rule would have to answer for
|
|
219
|
+
* the worst case — four tabs — and would then strip the word off a TWO-tab
|
|
220
|
+
* strip at the default panel width. That is the exact bug this card exists to
|
|
221
|
+
* remove, so it cannot be the fix for it.
|
|
167
222
|
*
|
|
168
223
|
* Only `.has-icon` is ever touched. A tab with no glyph has nothing to fall
|
|
169
|
-
* back to, so it keeps its word at every width
|
|
170
|
-
*
|
|
171
|
-
|
|
224
|
+
* back to, so it keeps its word at every width — a button holding neither word
|
|
225
|
+
* nor glyph is not a narrower segment, it is an empty one. */
|
|
226
|
+
|
|
227
|
+
/* THREE OR MORE, under 240px: the words on the tabs he is not reading go. The
|
|
228
|
+
NAME does not go with them — it is still on the button as `title` and
|
|
229
|
+
`aria-label`, in every state. */
|
|
230
|
+
@container (width < 240px) {
|
|
231
|
+
.es-tabs:has(.es-tab:nth-child(3)) .es-tab.has-icon:not(.is-active) .es-tab-word { display: none; }
|
|
232
|
+
}
|
|
172
233
|
|
|
173
|
-
/*
|
|
174
|
-
|
|
175
|
-
@container (width <
|
|
176
|
-
.es-tab.has-icon .es-tab-
|
|
177
|
-
.es-tab.has-icon:not(.is-active) .es-tab-word { display: none; }
|
|
234
|
+
/* FOUR OR MORE: a fourth label is another 78px of track, so the same words stop
|
|
235
|
+
fitting a long way sooner. */
|
|
236
|
+
@container (width < 320px) {
|
|
237
|
+
.es-tabs:has(.es-tab:nth-child(4)) .es-tab.has-icon:not(.is-active) .es-tab-word { display: none; }
|
|
178
238
|
}
|
|
179
239
|
|
|
180
|
-
/*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
/* FOUR OR MORE at the narrow end: the last word goes as well, and the strip is
|
|
241
|
+
four glyphs on a track. It reads — the track and the chips are what say
|
|
242
|
+
"these are buttons", which is more than the bare glyphs it replaces ever
|
|
243
|
+
said. */
|
|
244
|
+
@container (width < 200px) {
|
|
245
|
+
.es-tabs:has(.es-tab:nth-child(4)) .es-tab.has-icon.is-active .es-tab-word { display: none; }
|
|
184
246
|
}
|
|
185
247
|
|
|
186
248
|
/* ── a group: the soft pill row that folds ─────────────────────────────────── */
|
|
@@ -392,11 +454,109 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
|
|
|
392
454
|
.es-switch:disabled[aria-checked='true'] { background: var(--es-input-border); }
|
|
393
455
|
.es-switch:disabled::after { box-shadow: none; border: 1px solid var(--es-divider); }
|
|
394
456
|
|
|
457
|
+
/* ── a drill-in row: name, preview, chevron (card 8819) ────────────────────── */
|
|
458
|
+
|
|
459
|
+
/* The row a section lists a text part as. Pressing it drills the inspector down
|
|
460
|
+
a level to that part's own editor.
|
|
461
|
+
|
|
462
|
+
A FULL-WIDTH LIST ROW, not the raised pill a group's head is. These stack into
|
|
463
|
+
a list at the bottom of the section panel, and a list reads as a list: flat
|
|
464
|
+
rows with a hairline between them, a quiet hover tint, no chip elevation. The
|
|
465
|
+
separator is drawn BETWEEN rows (`.es-drill + .es-drill`) rather than as a
|
|
466
|
+
bottom border on each, so the last row needs no `:last-child` exception and a
|
|
467
|
+
lone row outside a list carries no dangling rule.
|
|
468
|
+
|
|
469
|
+
`box-sizing` for the same reason the switch and the group head set it: this
|
|
470
|
+
file assumes no reset of the host's, and `width: 100%` plus the padding would
|
|
471
|
+
otherwise run the row past the panel. */
|
|
472
|
+
.es-drill {
|
|
473
|
+
box-sizing: border-box;
|
|
474
|
+
width: 100%;
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
gap: 10px;
|
|
478
|
+
border: 0;
|
|
479
|
+
text-align: left;
|
|
480
|
+
font: inherit;
|
|
481
|
+
background: transparent;
|
|
482
|
+
padding: 10px 11px;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
transition: background 0.12s;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.es-drill + .es-drill { border-top: 1px solid var(--es-line); }
|
|
488
|
+
|
|
489
|
+
.es-drill:hover { background: var(--es-hover); }
|
|
490
|
+
|
|
491
|
+
/* An INSET ring, unlike the switch's and the tab's outset one: a full-width row
|
|
492
|
+
has neighbours hard against its edges, and an outset ring would be clipped by
|
|
493
|
+
the row above and below it. */
|
|
494
|
+
.es-drill:focus-visible {
|
|
495
|
+
outline: none;
|
|
496
|
+
box-shadow: inset 0 0 0 2px var(--es-accent-ring);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* The two lines, stacked, and allowed to shrink so the preview can ellipsise
|
|
500
|
+
instead of shoving the chevron off the row. */
|
|
501
|
+
.es-drill-text {
|
|
502
|
+
display: flex;
|
|
503
|
+
flex-direction: column;
|
|
504
|
+
gap: 2px;
|
|
505
|
+
min-width: 0;
|
|
506
|
+
flex: 1;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.es-drill-name {
|
|
510
|
+
font-size: 12px;
|
|
511
|
+
font-weight: 600;
|
|
512
|
+
color: var(--es-text-strong);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* The preview is one line, clipped. It never wraps: a part's copy can be a
|
|
516
|
+
paragraph, and a row that grows to fit it stops being a row. */
|
|
517
|
+
.es-drill-preview {
|
|
518
|
+
font-size: 11px;
|
|
519
|
+
color: var(--es-muted);
|
|
520
|
+
white-space: nowrap;
|
|
521
|
+
overflow: hidden;
|
|
522
|
+
text-overflow: ellipsis;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* A part with no place on the page — an identifier the eye never draws. Quiet
|
|
526
|
+
and italic, so it reads as a note about the part rather than as its copy. */
|
|
527
|
+
.es-drill-preview.is-offpage {
|
|
528
|
+
font-style: italic;
|
|
529
|
+
color: var(--es-disabled);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* The status glyph (the "hidden on the page" eye). Not `aria-hidden` here — the
|
|
533
|
+
host owns its meaning and names it; this only places it. */
|
|
534
|
+
.es-drill-status {
|
|
535
|
+
flex: none;
|
|
536
|
+
display: inline-flex;
|
|
537
|
+
align-items: center;
|
|
538
|
+
color: var(--es-muted);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* The chevron is drawn from CSS `content`, the way the group sign is: it is pure
|
|
542
|
+
direction, carries no text of its own, and stays out of the accessible name. */
|
|
543
|
+
.es-drill-chev {
|
|
544
|
+
flex: none;
|
|
545
|
+
width: 16px;
|
|
546
|
+
text-align: center;
|
|
547
|
+
color: var(--es-muted);
|
|
548
|
+
font-size: 16px;
|
|
549
|
+
line-height: 1;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.es-drill-chev::before { content: '\203A'; }
|
|
553
|
+
|
|
395
554
|
@media (prefers-reduced-motion: reduce) {
|
|
396
555
|
.es-tab,
|
|
397
556
|
/* `button.` and not `.es-group-head`: the transition it cancels is declared
|
|
398
557
|
on `button.es-group-head`, and a plainer selector would lose to it. */
|
|
399
558
|
button.es-group-head,
|
|
559
|
+
.es-drill,
|
|
400
560
|
.es-switch,
|
|
401
561
|
.es-switch::after { transition: none; }
|
|
402
562
|
}
|
package/dist/shell/index.d.ts
CHANGED
|
@@ -135,6 +135,13 @@ declare const shellTokens: {
|
|
|
135
135
|
readonly panelShadow: "0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)";
|
|
136
136
|
/** `--es-topbar-shadow`. */
|
|
137
137
|
readonly topbarShadow: "0 1px 3px rgba(0, 0, 0, 0.06)";
|
|
138
|
+
/**
|
|
139
|
+
* `--es-raised-shadow` — what a small surface sitting ON another one casts:
|
|
140
|
+
* the chip in the settings tab strip's track (card 66138). Tighter and darker
|
|
141
|
+
* than `panelShadow`, which is a whole panel floating over the app; this one
|
|
142
|
+
* has to read across 3px of track without looking like a second panel.
|
|
143
|
+
*/
|
|
144
|
+
readonly raisedShadow: "0 1px 2px rgba(16, 24, 40, 0.1)";
|
|
138
145
|
/** `--es-font` — system sans stack (also fed to `--puck-font-family`). */
|
|
139
146
|
readonly font: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif";
|
|
140
147
|
};
|
package/dist/shell/index.js
CHANGED
package/dist/shell/shell.css
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
/* — shadows — */
|
|
91
91
|
--es-panel-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06);
|
|
92
92
|
--es-topbar-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
93
|
+
--es-raised-shadow: 0 1px 2px rgba(16, 24, 40, 0.1);
|
|
93
94
|
|
|
94
95
|
/* — type — */
|
|
95
96
|
--es-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "editor-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Shared editor-chrome primitives for the EFFICIENT editors: EditorRail (a Next-16-safe left icon-rail leaf), the shell token layer, and GoldTextInput — type in place and watch the markup formatting appear as you type.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Lewis Liu",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/tokens.ts"],"names":[],"mappings":"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","file":"chunk-ML5SEFU4.js","sourcesContent":["/**\n * Editor SHELL design tokens — the ONE source both editors' chrome converge on.\n *\n * Where `rail/tokens.ts` owns the slim icon-rail, this owns the rest of the\n * editor chrome theme: accent, surfaces, fields, text, lines, chips, named\n * chrome literals, danger/warning/success, radii, gap, shadows and font. Every\n * value bar one is taken verbatim from the storefront editor's `--sf-*` block\n * (`efficient-shop/components/storefront-editor.css`), the visual reference the\n * campaign designer (`--cd-*`) and the storefront's own TS mirror (`tk`) had\n * each re-copied. After card 66024 they all resolve to these values instead.\n * The one exception is `success`, which that block has no counterpart for; see\n * its own note below.\n *\n * Exposed BOTH ways, per the same value (mirrors the rail):\n * - `shellTokens` — this JS object, for code that needs the numbers/colours\n * (e.g. the storefront's `floating-editor/tokens.ts` inline-style mirror);\n * - `shell.css` — the stylesheet, which declares the identical values as CSS\n * custom properties on `.es-editor` (names listed in `shellCssVars`).\n *\n * Numeric fields are pixel magnitudes (unitless) so they compose in code; the\n * CSS applies the `px`. Both editing surfaces are LIGHT only (no dark mode), so\n * there is a single palette here — no dark counterpart.\n */\nexport const shellTokens = {\n // ── Accent (react-os-shell blue) ──────────────────────────────────────────\n /** `--es-accent` — blue-600. */\n accent: '#2563eb',\n /** `--es-accent-tint` — blue-50. */\n accentTint: '#eff6ff',\n /** `--es-accent-tint-strong` — blue-100. */\n accentTintStrong: '#dbeafe',\n /** `--es-accent-text` — blue-700. */\n accentText: '#1d4ed8',\n /** `--es-accent-ring` — focus ring wash. */\n accentRing: 'rgba(37, 99, 235, 0.25)',\n /** `--es-accent-shadow` — accent button shadow. */\n accentShadow: 'rgba(37, 99, 235, 0.35)',\n /** `--es-on-accent` — text/icon over the solid accent. */\n onAccent: '#ffffff',\n\n // ── Surfaces (cool gray ramp) ─────────────────────────────────────────────\n /** `--es-app-bg` — gray-50, sunken canvas. */\n appBg: '#f9fafb',\n /** `--es-preview-bg` — gray-100, preview well. */\n previewBg: '#f3f4f6',\n /** `--es-panel` — raised cards / panels. */\n panel: '#ffffff',\n /** `--es-raised` — active seg / chip. */\n raised: '#ffffff',\n /** `--es-topbar-bg`. */\n topbarBg: '#ffffff',\n /** `--es-frame-bg` — device \"paper\" behind the preview iframe. */\n frameBg: '#ffffff',\n /** `--es-hover` — gray-100 row / icon hover. */\n hover: '#f3f4f6',\n /** `--es-hatch` — 45° upload-slot texture. */\n hatch: 'rgba(0, 0, 0, 0.018)',\n /** `--es-addcard-veil` — add-section card hover wash. */\n addcardVeil: 'rgba(248, 249, 250, 0.66)',\n\n // ── Fields ────────────────────────────────────────────────────────────────\n /** `--es-field-bg`. */\n fieldBg: '#ffffff',\n /** `--es-track-bg` — gray-100 segmented / search track. */\n trackBg: '#f3f4f6',\n /** `--es-input-border` — gray-300. */\n inputBorder: '#d1d5db',\n /** `--es-field-text` — gray-800. */\n fieldText: '#1f2937',\n /** `--es-field-placeholder` — gray-400. */\n fieldPlaceholder: '#9ca3af',\n /** `--es-knob` — toggle knob. */\n knob: '#ffffff',\n\n // ── Text ────────────────────────────────────────────────────────────────\n /** `--es-text` — gray-900. */\n text: '#111827',\n /** `--es-text-strong` — gray-950 active labels. */\n textStrong: '#030712',\n /** `--es-text-secondary` — gray-700. */\n textSecondary: '#374151',\n /** `--es-muted` — gray-500. */\n muted: '#6b7280',\n /** `--es-icon` — gray-500 icon buttons. */\n icon: '#6b7280',\n /** `--es-disabled` — gray-400 disabled / faint glyphs. */\n disabled: '#9ca3af',\n\n // ── Lines / chips / buttons ──────────────────────────────────────────────\n /** `--es-divider` — gray-200. */\n divider: '#e5e7eb',\n /** `--es-line` — gray-200 hairlines. */\n line: '#e5e7eb',\n /** `--es-btn-border` — gray-300. */\n btnBorder: '#d1d5db',\n /** `--es-btn-text` — gray-700. */\n btnText: '#374151',\n /** `--es-chip-bg` — gray-100. */\n chipBg: '#f3f4f6',\n /** `--es-chip-fg` — gray-500. */\n chipFg: '#6b7280',\n\n // ── Named chrome literals ─────────────────────────────────────────────────\n /** `--es-brand-square` — gray-900 wordmark square. */\n brandSquare: '#111827',\n /** `--es-device-border` — gray-200 tablet/mobile frame. */\n deviceBorder: '#e5e7eb',\n /** `--es-toast-bg` — gray-900 dark chip / seam pill. */\n toastBg: '#111827',\n /** `--es-toggle-off` — gray-300 toggle track. */\n toggleOff: '#d1d5db',\n /** `--es-scrollbar` — gray-300. */\n scrollbar: '#d1d5db',\n /** `--es-scrollbar-hover` — gray-400. */\n scrollbarHover: '#9ca3af',\n\n // ── Danger / warning / success (shell red / amber / green) ────────────────\n /** `--es-danger` — red-600. */\n danger: '#dc2626',\n /** `--es-danger-strong` — red-700. */\n dangerStrong: '#b91c1c',\n /** `--es-danger-tint` — red-50. */\n dangerTint: '#fef2f2',\n /** `--es-warning` — amber-700. */\n warning: '#b45309',\n /** `--es-preview-tint` — amber-100 \"Preview\" pill. */\n previewTint: '#fef3c7',\n /**\n * `--es-success` — green-700. THE ONE VALUE HERE WITH NO `--sf-*` BEHIND IT:\n * the storefront editor's block has a red and an amber and no green, and card\n * 8802.g needs one for a green tick. So it is picked to sit with the block\n * rather than invented next to it — the same Tailwind ramp every other colour\n * in this file comes from, at the 700 step `--es-warning` already sits on.\n * 700 and not 600 for the reason amber went there: it is the darkest step\n * still plainly the hue, and it clears WCAG AA (5.0:1) on the shell's white\n * panels, so a tick may carry a label without needing a second token.\n */\n success: '#15803d',\n\n // ── Radii / shape / gap ───────────────────────────────────────────────────\n /** `--es-panel-radius`. */\n panelRadius: 12,\n /** `--es-row-radius`. */\n rowRadius: 8,\n /** `--es-field-radius`. */\n fieldRadius: 6,\n /** `--es-panel-gap` — inter-pane gutter + outer frame padding, ONE token. */\n panelGap: 10,\n\n // ── Shadows ───────────────────────────────────────────────────────────────\n /** `--es-panel-shadow`. */\n panelShadow: '0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)',\n /** `--es-topbar-shadow`. */\n topbarShadow: '0 1px 3px rgba(0, 0, 0, 0.06)',\n\n // ── Type ──────────────────────────────────────────────────────────────────\n /** `--es-font` — system sans stack (also fed to `--puck-font-family`). */\n font: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif',\n} as const;\n\nexport type ShellTokens = typeof shellTokens;\n\n/**\n * The CSS custom-property name behind each `shellTokens` key. `shell.css` sets\n * these on `.es-editor`; override any of them on (or above) the editor root to\n * re-theme without shipping new CSS. The `shell-tokens.test.ts` parity gate\n * proves this map, `shellTokens` and `shell.css` never drift apart.\n */\nexport const shellCssVars: Record<keyof ShellTokens, string> = {\n accent: '--es-accent',\n accentTint: '--es-accent-tint',\n accentTintStrong: '--es-accent-tint-strong',\n accentText: '--es-accent-text',\n accentRing: '--es-accent-ring',\n accentShadow: '--es-accent-shadow',\n onAccent: '--es-on-accent',\n appBg: '--es-app-bg',\n previewBg: '--es-preview-bg',\n panel: '--es-panel',\n raised: '--es-raised',\n topbarBg: '--es-topbar-bg',\n frameBg: '--es-frame-bg',\n hover: '--es-hover',\n hatch: '--es-hatch',\n addcardVeil: '--es-addcard-veil',\n fieldBg: '--es-field-bg',\n trackBg: '--es-track-bg',\n inputBorder: '--es-input-border',\n fieldText: '--es-field-text',\n fieldPlaceholder: '--es-field-placeholder',\n knob: '--es-knob',\n text: '--es-text',\n textStrong: '--es-text-strong',\n textSecondary: '--es-text-secondary',\n muted: '--es-muted',\n icon: '--es-icon',\n disabled: '--es-disabled',\n divider: '--es-divider',\n line: '--es-line',\n btnBorder: '--es-btn-border',\n btnText: '--es-btn-text',\n chipBg: '--es-chip-bg',\n chipFg: '--es-chip-fg',\n brandSquare: '--es-brand-square',\n deviceBorder: '--es-device-border',\n toastBg: '--es-toast-bg',\n toggleOff: '--es-toggle-off',\n scrollbar: '--es-scrollbar',\n scrollbarHover: '--es-scrollbar-hover',\n danger: '--es-danger',\n dangerStrong: '--es-danger-strong',\n dangerTint: '--es-danger-tint',\n warning: '--es-warning',\n previewTint: '--es-preview-tint',\n success: '--es-success',\n panelRadius: '--es-panel-radius',\n rowRadius: '--es-row-radius',\n fieldRadius: '--es-field-radius',\n panelGap: '--es-panel-gap',\n panelShadow: '--es-panel-shadow',\n topbarShadow: '--es-topbar-shadow',\n font: '--es-font',\n};\n\n/**\n * Puck's own accent ramp + font, re-tinted to the shell blue. Puck reads these\n * fixed variable names (`--puck-color-azure-NN`, `--puck-font-family`) directly,\n * so they are NOT part of the `--es-*` set — but they had been copied verbatim\n * into BOTH editors' CSS, so `shell.css` now declares them once on `.es-editor`.\n * Kept here as the JS record so the parity gate can prove `shell.css` matches.\n */\nexport const puckAzureRamp: Record<string, string> = {\n '--puck-color-azure-01': '#172554',\n '--puck-color-azure-02': '#1e3a8a',\n '--puck-color-azure-03': '#1d4ed8',\n '--puck-color-azure-04': '#2563eb',\n '--puck-color-azure-05': '#3b82f6',\n '--puck-color-azure-06': '#60a5fa',\n '--puck-color-azure-07': '#93c5fd',\n '--puck-color-azure-08': '#bfdbfe',\n '--puck-color-azure-09': '#dbeafe',\n '--puck-color-azure-10': '#eff6ff',\n '--puck-color-azure-11': '#f5f9ff',\n '--puck-color-azure-12': '#fafcff',\n};\n"]}
|