editor-shell 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * The settings-panel primitives — the shared grammar BOTH editors' inspector
6
+ * panels are built from (owner-approved design, 2026-08-20).
7
+ *
8
+ * Every one is CONTROLLED and hook-free: the host owns which tab is active and
9
+ * which groups are open, exactly as the rail's host owns selection. That is what
10
+ * keeps this a directive-free leaf a Next-16 server component can import.
11
+ */
12
+ /** One tab in the strip. `badge` shows a count (e.g. unsaved overrides). */
13
+ interface SettingsTabItem {
14
+ id: string;
15
+ label: string;
16
+ badge?: number;
17
+ }
18
+ interface SettingsTabsProps {
19
+ items: SettingsTabItem[];
20
+ /** The `id` of the tab currently shown. */
21
+ activeId: string;
22
+ onSelect: (id: string) => void;
23
+ ariaLabel?: string;
24
+ className?: string;
25
+ }
26
+ interface SettingsGroupProps {
27
+ /** The group's name — "Items", "Spacing", "Typeface". */
28
+ title: string;
29
+ /** Open state lives with the host, so it can persist it. */
30
+ open: boolean;
31
+ onToggle: (open: boolean) => void;
32
+ /**
33
+ * How many settings in this group differ from their default. Shown as a chip
34
+ * on the pill so a FOLDED group still admits it holds changes.
35
+ */
36
+ count?: number;
37
+ /** Ties the pill to its body for assistive tech (`aria-controls`). */
38
+ id?: string;
39
+ children: ReactNode;
40
+ className?: string;
41
+ }
42
+ interface SettingsFieldProps {
43
+ label: string;
44
+ /** An 18px glyph from `editor-shell/icons` — one meaning, one SVG. */
45
+ icon?: ReactNode;
46
+ /** The current value, right-aligned on the label row ("34 px", "style"). */
47
+ value?: ReactNode;
48
+ /** One quiet line under the control. Use it instead of a clause-long label. */
49
+ hint?: ReactNode;
50
+ /** Marks the row as changed from its default: the label takes the accent. */
51
+ changed?: boolean;
52
+ /** Set when the control is a single form element, so the label points at it. */
53
+ htmlFor?: string;
54
+ /** A trailing control on the label row — a reset dot, a badge. */
55
+ action?: ReactNode;
56
+ /** The control itself. */
57
+ children: ReactNode;
58
+ className?: string;
59
+ }
60
+
61
+ /**
62
+ * The tab strip under the panel head.
63
+ *
64
+ * It replaces the old stack of fold headers a merchant had to open and hunt
65
+ * through: the buckets a section actually has become tabs, in one fixed order,
66
+ * so the panel keeps the same shape on every section. A section shows only the
67
+ * tabs it has — pass three items and three tabs render.
68
+ *
69
+ * Controlled: the host decides `activeId`. No hooks, no browser globals.
70
+ */
71
+ declare function SettingsTabs({ items, activeId, onSelect, ariaLabel, className, }: SettingsTabsProps): react.JSX.Element;
72
+
73
+ /**
74
+ * A group of settings under a soft pill row that folds with − / +.
75
+ *
76
+ * The sign is drawn by `panel.css` from `aria-expanded`, so the open state has
77
+ * exactly one home (the attribute assistive tech already reads) instead of a
78
+ * second copy in the markup.
79
+ *
80
+ * `count` is the reason a fold here is safe: a folded group that holds changes
81
+ * still says so on its pill, so nothing a merchant changed can hide.
82
+ */
83
+ declare function SettingsGroup({ title, open, onToggle, count, id, children, className, }: SettingsGroupProps): react.JSX.Element;
84
+
85
+ /**
86
+ * One setting: a label row, then its control.
87
+ *
88
+ * The row is stacked rather than side-by-side so the control gets the panel's
89
+ * full width — a 308px panel cannot afford a label column AND a usable slider.
90
+ * The label reads small and quiet; the VALUE sits at the end of the same line in
91
+ * the text colour, which is what a merchant scans for. That contrast is the
92
+ * point: before this, label and value looked alike and the panel read as noise.
93
+ *
94
+ * `hint` is where a clause-long explanation goes. A label is a name, never a
95
+ * sentence.
96
+ */
97
+ declare function SettingsField({ label, icon, value, hint, changed, htmlFor, action, children, className, }: SettingsFieldProps): react.JSX.Element;
98
+
99
+ export { SettingsField, type SettingsFieldProps, SettingsGroup, type SettingsGroupProps, type SettingsTabItem, SettingsTabs, type SettingsTabsProps };
@@ -0,0 +1,100 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+
3
+ // src/panel/SettingsTabs.tsx
4
+ function SettingsTabs({
5
+ items,
6
+ activeId,
7
+ onSelect,
8
+ ariaLabel = "Settings",
9
+ className
10
+ }) {
11
+ return /* @__PURE__ */ jsx(
12
+ "div",
13
+ {
14
+ role: "tablist",
15
+ "aria-label": ariaLabel,
16
+ "aria-orientation": "horizontal",
17
+ className: className ? `es-tabs ${className}` : "es-tabs",
18
+ children: items.map((item) => {
19
+ const active = item.id === activeId;
20
+ return /* @__PURE__ */ jsxs(
21
+ "button",
22
+ {
23
+ type: "button",
24
+ role: "tab",
25
+ "aria-selected": active,
26
+ className: active ? "es-tab is-active" : "es-tab",
27
+ onClick: () => onSelect(item.id),
28
+ children: [
29
+ item.label,
30
+ typeof item.badge === "number" && item.badge > 0 ? /* @__PURE__ */ jsx("span", { className: "es-tab-badge", children: item.badge }) : null
31
+ ]
32
+ },
33
+ item.id
34
+ );
35
+ })
36
+ }
37
+ );
38
+ }
39
+ function SettingsGroup({
40
+ title,
41
+ open,
42
+ onToggle,
43
+ count,
44
+ id,
45
+ children,
46
+ className
47
+ }) {
48
+ const bodyId = id ? `${id}-body` : void 0;
49
+ return /* @__PURE__ */ jsxs("div", { className: className ? `es-group ${className}` : "es-group", children: [
50
+ /* @__PURE__ */ jsxs(
51
+ "button",
52
+ {
53
+ type: "button",
54
+ id,
55
+ "aria-expanded": open,
56
+ "aria-controls": bodyId,
57
+ className: "es-group-head",
58
+ onClick: () => onToggle(!open),
59
+ children: [
60
+ title,
61
+ typeof count === "number" && count > 0 ? /* @__PURE__ */ jsx("span", { className: "es-group-count", children: count }) : null,
62
+ /* @__PURE__ */ jsx("span", { className: "es-group-sign", "aria-hidden": "true" })
63
+ ]
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx("div", { id: bodyId, className: "es-group-body", hidden: !open, children })
67
+ ] });
68
+ }
69
+ function SettingsField({
70
+ label,
71
+ icon,
72
+ value,
73
+ hint,
74
+ changed,
75
+ htmlFor,
76
+ action,
77
+ children,
78
+ className
79
+ }) {
80
+ const classes = ["es-field"];
81
+ if (changed) classes.push("is-changed");
82
+ if (className) classes.push(className);
83
+ const labelBody = /* @__PURE__ */ jsxs(Fragment, { children: [
84
+ icon ? /* @__PURE__ */ jsx("span", { className: "es-field-icon", "aria-hidden": "true", children: icon }) : null,
85
+ /* @__PURE__ */ jsx("span", { className: "es-field-label", children: label })
86
+ ] });
87
+ return /* @__PURE__ */ jsxs("div", { className: classes.join(" "), children: [
88
+ /* @__PURE__ */ jsxs("div", { className: "es-field-head", children: [
89
+ htmlFor ? /* @__PURE__ */ jsx("label", { className: "es-field-name", htmlFor, children: labelBody }) : /* @__PURE__ */ jsx("span", { className: "es-field-name", children: labelBody }),
90
+ value !== void 0 && value !== null ? /* @__PURE__ */ jsx("span", { className: "es-field-value", children: value }) : null,
91
+ action
92
+ ] }),
93
+ /* @__PURE__ */ jsx("div", { className: "es-field-ctl", children }),
94
+ hint ? /* @__PURE__ */ jsx("p", { className: "es-field-hint", children: hint }) : null
95
+ ] });
96
+ }
97
+
98
+ export { SettingsField, SettingsGroup, SettingsTabs };
99
+ //# sourceMappingURL=index.js.map
100
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/panel/SettingsTabs.tsx","../../src/panel/SettingsGroup.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 */\nexport function SettingsTabs({\n items,\n activeId,\n onSelect,\n ariaLabel = 'Settings',\n className,\n}: SettingsTabsProps) {\n return (\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 return (\n <button\n key={item.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n className={active ? 'es-tab is-active' : 'es-tab'}\n onClick={() => onSelect(item.id)}\n >\n {item.label}\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 );\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 { SettingsFieldProps } from './types';\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 */\nexport function SettingsField({\n label,\n icon,\n value,\n hint,\n changed,\n htmlFor,\n action,\n children,\n className,\n}: SettingsFieldProps) {\n const classes = ['es-field'];\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\">{label}</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 {action}\n </div>\n <div className=\"es-field-ctl\">{children}</div>\n {hint ? <p className=\"es-field-hint\">{hint}</p> : null}\n </div>\n );\n}\n"]}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * editor-shell/panel.css — the settings-panel grammar.
3
+ *
4
+ * Values come from the shell token layer (`editor-shell/shell.css`), never from
5
+ * literals: this file is the storefront editor's approved panel design expressed
6
+ * once, so both editors read the same rules. Light only — neither editor has a
7
+ * dark mode.
8
+ *
9
+ * Two details are deliberate and were decided by measurement, not taste:
10
+ * · rows sit 20px apart while a label sits 7px from its own control, so a
11
+ * setting reads as ONE thing with air around it;
12
+ * · the label is small and grey, the value is larger and dark. Before that
13
+ * contrast existed, a merchant could not tell a name from a number.
14
+ */
15
+
16
+ /* ── the tab strip ─────────────────────────────────────────────────────────── */
17
+
18
+ .es-tabs {
19
+ display: flex;
20
+ gap: 2px;
21
+ padding: 0 14px;
22
+ border-bottom: 1px solid var(--es-divider);
23
+ background: var(--es-panel);
24
+ flex: none;
25
+ }
26
+
27
+ .es-tab {
28
+ border: 0;
29
+ background: transparent;
30
+ font: inherit;
31
+ font-size: 12px;
32
+ font-weight: 600;
33
+ color: var(--es-muted);
34
+ padding: 9px 10px 8px;
35
+ cursor: pointer;
36
+ /* The active rule sits ON the strip's own border, not under it. */
37
+ border-bottom: 2px solid transparent;
38
+ margin-bottom: -1px;
39
+ display: inline-flex;
40
+ align-items: center;
41
+ gap: 6px;
42
+ transition: color 0.12s;
43
+ }
44
+
45
+ .es-tab:hover { color: var(--es-text); }
46
+
47
+ .es-tab.is-active {
48
+ color: var(--es-accent-text);
49
+ border-bottom-color: var(--es-accent);
50
+ }
51
+
52
+ .es-tab-badge {
53
+ min-width: 16px;
54
+ height: 16px;
55
+ padding: 0 5px;
56
+ border-radius: 999px;
57
+ background: var(--es-accent);
58
+ color: var(--es-on-accent);
59
+ font-size: 10px;
60
+ font-weight: 700;
61
+ display: inline-flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ }
65
+
66
+ /* ── a group: the soft pill row that folds ─────────────────────────────────── */
67
+
68
+ .es-group + .es-group { margin-top: 6px; }
69
+
70
+ .es-group-head {
71
+ width: 100%;
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ border: 0;
76
+ cursor: pointer;
77
+ text-align: left;
78
+ font: inherit;
79
+ font-size: 12px;
80
+ font-weight: 600;
81
+ color: var(--es-text);
82
+ background: var(--es-hover);
83
+ border-radius: var(--es-row-radius);
84
+ padding: 8px 11px;
85
+ margin: 20px 0 0;
86
+ transition: background 0.12s;
87
+ }
88
+
89
+ .es-group-head:hover { background: var(--es-chip-bg); }
90
+
91
+ .es-group-count {
92
+ min-width: 16px;
93
+ height: 16px;
94
+ padding: 0 5px;
95
+ border-radius: 999px;
96
+ background: var(--es-accent);
97
+ color: var(--es-on-accent);
98
+ font-size: 10px;
99
+ font-weight: 700;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ }
104
+
105
+ /* The sign is drawn from aria-expanded so the open state has ONE home. */
106
+ .es-group-sign {
107
+ margin-left: auto;
108
+ width: 16px;
109
+ text-align: center;
110
+ color: var(--es-muted);
111
+ font-size: 14px;
112
+ line-height: 1;
113
+ flex: none;
114
+ }
115
+
116
+ .es-group-head[aria-expanded='true'] .es-group-sign::before { content: '−'; }
117
+ .es-group-head[aria-expanded='false'] .es-group-sign::before { content: '+'; }
118
+
119
+ /* `hidden` on the body is the fold; this only stops a flex/grid parent from
120
+ overriding the attribute's own display:none. */
121
+ .es-group-body[hidden] { display: none; }
122
+ .es-group-body > .es-field:first-child { margin-top: 14px; }
123
+
124
+ /* ── one setting ───────────────────────────────────────────────────────────── */
125
+
126
+ .es-field { margin-top: 20px; }
127
+
128
+ .es-field-head {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 8px;
132
+ margin-bottom: 7px;
133
+ min-height: 16px;
134
+ }
135
+
136
+ .es-field-name {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ gap: 8px;
140
+ min-width: 0;
141
+ }
142
+
143
+ .es-field-icon {
144
+ width: 16px;
145
+ height: 16px;
146
+ flex: none;
147
+ color: var(--es-icon);
148
+ display: inline-flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ }
152
+
153
+ .es-field-icon svg { width: 16px; height: 16px; }
154
+
155
+ .es-field-label {
156
+ font-size: 11px;
157
+ font-weight: 600;
158
+ color: var(--es-muted);
159
+ }
160
+
161
+ .es-field.is-changed .es-field-label { color: var(--es-accent-text); }
162
+
163
+ .es-field-value {
164
+ margin-left: auto;
165
+ font-size: 12px;
166
+ color: var(--es-text);
167
+ font-variant-numeric: tabular-nums;
168
+ }
169
+
170
+ .es-field-ctl {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 8px;
174
+ min-width: 0;
175
+ }
176
+
177
+ .es-field-hint {
178
+ margin: 7px 0 0;
179
+ padding-left: 24px;
180
+ font-size: 11px;
181
+ line-height: 1.45;
182
+ color: var(--es-muted);
183
+ }
184
+
185
+ @media (prefers-reduced-motion: reduce) {
186
+ .es-tab,
187
+ .es-group-head { transition: none; }
188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editor-shell",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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",
@@ -22,25 +22,36 @@
22
22
  "exports": {
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
- "import": "./dist/index.js"
25
+ "import": "./dist/index.js",
26
+ "default": "./dist/index.js"
26
27
  },
27
28
  "./rail": {
28
29
  "types": "./dist/rail/index.d.ts",
29
- "import": "./dist/rail/index.js"
30
+ "import": "./dist/rail/index.js",
31
+ "default": "./dist/rail/index.js"
30
32
  },
31
33
  "./icons": {
32
34
  "types": "./dist/icons/index.d.ts",
33
- "import": "./dist/icons/index.js"
35
+ "import": "./dist/icons/index.js",
36
+ "default": "./dist/icons/index.js"
34
37
  },
38
+ "./panel": {
39
+ "types": "./dist/panel/index.d.ts",
40
+ "import": "./dist/panel/index.js",
41
+ "default": "./dist/panel/index.js"
42
+ },
43
+ "./panel.css": "./dist/panel/panel.css",
35
44
  "./rail.css": "./dist/rail/rail.css",
36
45
  "./shell": {
37
46
  "types": "./dist/shell/index.d.ts",
38
- "import": "./dist/shell/index.js"
47
+ "import": "./dist/shell/index.js",
48
+ "default": "./dist/shell/index.js"
39
49
  },
40
50
  "./shell.css": "./dist/shell/shell.css",
41
51
  "./gold": {
42
52
  "types": "./dist/gold/index.d.ts",
43
- "import": "./dist/gold/index.js"
53
+ "import": "./dist/gold/index.js",
54
+ "default": "./dist/gold/index.js"
44
55
  },
45
56
  "./gold.css": "./dist/gold/gold.css"
46
57
  },
@@ -72,10 +83,11 @@
72
83
  "typescript": "^5.3.0"
73
84
  },
74
85
  "scripts": {
75
- "build": "tsup && cp src/rail/rail.css dist/rail/rail.css && cp src/shell/shell.css dist/shell/shell.css && cp src/gold/gold.css dist/gold/gold.css",
86
+ "build": "tsup && cp src/rail/rail.css dist/rail/rail.css && cp src/shell/shell.css dist/shell/shell.css && cp src/gold/gold.css dist/gold/gold.css && cp src/panel/panel.css dist/panel/panel.css",
76
87
  "dev": "tsup --watch",
77
88
  "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
78
89
  "test": "node scripts/test.mjs",
90
+ "verify:exports": "node scripts/verify-exports-resolve.mjs",
79
91
  "clean": "rm -rf dist"
80
92
  },
81
93
  "keywords": [