@unpunnyfuns/swatchbook-addon 0.1.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/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/constants-1plfdgh7.mjs +21 -0
- package/dist/constants-1plfdgh7.mjs.map +1 -0
- package/dist/hooks/index.d.mts +37 -0
- package/dist/hooks/index.mjs +33 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +16 -0
- package/dist/index.mjs.map +1 -0
- package/dist/manager.d.mts +1 -0
- package/dist/manager.mjs +903 -0
- package/dist/manager.mjs.map +1 -0
- package/dist/options-rvGQy0uV.d.mts +17 -0
- package/dist/preset.d.mts +20 -0
- package/dist/preset.mjs +163 -0
- package/dist/preset.mjs.map +1 -0
- package/dist/preview-DcMFt0cD.mjs +240 -0
- package/dist/preview-DcMFt0cD.mjs.map +1 -0
- package/dist/preview.d.mts +13 -0
- package/dist/preview.mjs +2 -0
- package/package.json +96 -0
package/dist/manager.mjs
ADDED
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import { a as GLOBAL_KEY, d as TOOL_ID, n as AXES_GLOBAL_KEY, o as INIT_EVENT, r as COLOR_FORMAT_GLOBAL_KEY, s as PANEL_ID, t as ADDON_ID } from "./constants-1plfdgh7.mjs";
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { IconButton, Placeholder, ScrollArea, WithTooltipPure } from "storybook/internal/components";
|
|
4
|
+
import { addons, types, useGlobals, useStorybookApi } from "storybook/manager-api";
|
|
5
|
+
//#region src/panel.tsx
|
|
6
|
+
/** `React.createElement` alias so the manager bundle avoids `react/jsx-runtime`. */
|
|
7
|
+
const h$1 = React.createElement;
|
|
8
|
+
function usePayload() {
|
|
9
|
+
const [payload, setPayload] = useState(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const channel = addons.getChannel();
|
|
12
|
+
const onInit = (next) => setPayload(next);
|
|
13
|
+
channel.on(INIT_EVENT, onInit);
|
|
14
|
+
return () => {
|
|
15
|
+
channel.off(INIT_EVENT, onInit);
|
|
16
|
+
};
|
|
17
|
+
}, []);
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
function makeCssVarName(path, prefix) {
|
|
21
|
+
const tail = path.replaceAll(".", "-");
|
|
22
|
+
return prefix ? `--${prefix}-${tail}` : `--${tail}`;
|
|
23
|
+
}
|
|
24
|
+
async function copy(text) {
|
|
25
|
+
try {
|
|
26
|
+
await navigator.clipboard.writeText(text);
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
/** Format a token `$value` into a short display string. */
|
|
30
|
+
function formatValue(value) {
|
|
31
|
+
if (value == null) return "";
|
|
32
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
33
|
+
if (typeof value === "object") {
|
|
34
|
+
const v = value;
|
|
35
|
+
if (typeof v["hex"] === "string") return v["hex"];
|
|
36
|
+
if ("value" in v && "unit" in v) return `${String(v["value"])}${String(v["unit"])}`;
|
|
37
|
+
return JSON.stringify(value).slice(0, 80);
|
|
38
|
+
}
|
|
39
|
+
return String(value);
|
|
40
|
+
}
|
|
41
|
+
const containerStyle = {
|
|
42
|
+
display: "flex",
|
|
43
|
+
flexDirection: "column",
|
|
44
|
+
height: "100%"
|
|
45
|
+
};
|
|
46
|
+
const headerStyle = {
|
|
47
|
+
padding: "8px 12px",
|
|
48
|
+
borderBottom: "1px solid rgba(128,128,128,0.2)",
|
|
49
|
+
display: "flex",
|
|
50
|
+
flexDirection: "column",
|
|
51
|
+
gap: 6
|
|
52
|
+
};
|
|
53
|
+
const axisIndicatorStyle = {
|
|
54
|
+
fontSize: 11,
|
|
55
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace",
|
|
56
|
+
opacity: .7
|
|
57
|
+
};
|
|
58
|
+
const treeWrapperStyle = {
|
|
59
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
60
|
+
fontSize: 12,
|
|
61
|
+
padding: 8
|
|
62
|
+
};
|
|
63
|
+
const groupRowStyle = {
|
|
64
|
+
display: "flex",
|
|
65
|
+
alignItems: "center",
|
|
66
|
+
gap: 6,
|
|
67
|
+
padding: "4px 6px",
|
|
68
|
+
borderRadius: 4,
|
|
69
|
+
cursor: "pointer",
|
|
70
|
+
userSelect: "none",
|
|
71
|
+
outline: "none"
|
|
72
|
+
};
|
|
73
|
+
const leafRowStyle = {
|
|
74
|
+
display: "flex",
|
|
75
|
+
alignItems: "center",
|
|
76
|
+
gap: 8,
|
|
77
|
+
padding: "4px 6px",
|
|
78
|
+
borderRadius: 4,
|
|
79
|
+
cursor: "pointer",
|
|
80
|
+
border: "none",
|
|
81
|
+
background: "transparent",
|
|
82
|
+
color: "inherit",
|
|
83
|
+
width: "100%",
|
|
84
|
+
textAlign: "left",
|
|
85
|
+
fontFamily: "inherit",
|
|
86
|
+
fontSize: "inherit",
|
|
87
|
+
outline: "none"
|
|
88
|
+
};
|
|
89
|
+
const caretStyle = {
|
|
90
|
+
display: "inline-block",
|
|
91
|
+
width: 12,
|
|
92
|
+
textAlign: "center",
|
|
93
|
+
opacity: .6
|
|
94
|
+
};
|
|
95
|
+
const treeUlStyle = {
|
|
96
|
+
listStyle: "none",
|
|
97
|
+
margin: 0,
|
|
98
|
+
padding: 0
|
|
99
|
+
};
|
|
100
|
+
const nestedUlStyle = {
|
|
101
|
+
listStyle: "none",
|
|
102
|
+
margin: 0,
|
|
103
|
+
paddingLeft: 18,
|
|
104
|
+
borderLeft: "1px solid rgba(128,128,128,0.2)"
|
|
105
|
+
};
|
|
106
|
+
const typePillStyle = {
|
|
107
|
+
display: "inline-block",
|
|
108
|
+
padding: "1px 6px",
|
|
109
|
+
borderRadius: 4,
|
|
110
|
+
fontSize: 10,
|
|
111
|
+
letterSpacing: .5,
|
|
112
|
+
textTransform: "uppercase",
|
|
113
|
+
background: "rgba(128,128,128,0.15)"
|
|
114
|
+
};
|
|
115
|
+
const valueStyle = {
|
|
116
|
+
marginLeft: "auto",
|
|
117
|
+
opacity: .7,
|
|
118
|
+
fontSize: 11,
|
|
119
|
+
maxWidth: "40%",
|
|
120
|
+
overflow: "hidden",
|
|
121
|
+
textOverflow: "ellipsis",
|
|
122
|
+
whiteSpace: "nowrap"
|
|
123
|
+
};
|
|
124
|
+
const countStyle = {
|
|
125
|
+
marginLeft: "auto",
|
|
126
|
+
fontSize: 11,
|
|
127
|
+
opacity: .7
|
|
128
|
+
};
|
|
129
|
+
const swatchStyle = {
|
|
130
|
+
display: "inline-block",
|
|
131
|
+
width: 14,
|
|
132
|
+
height: 14,
|
|
133
|
+
borderRadius: 3,
|
|
134
|
+
border: "1px solid rgba(128,128,128,0.3)",
|
|
135
|
+
marginLeft: 8
|
|
136
|
+
};
|
|
137
|
+
const searchInputStyle = {
|
|
138
|
+
width: "100%",
|
|
139
|
+
padding: "4px 8px",
|
|
140
|
+
fontSize: 12,
|
|
141
|
+
border: "1px solid rgba(128,128,128,0.3)",
|
|
142
|
+
borderRadius: 4,
|
|
143
|
+
background: "transparent",
|
|
144
|
+
color: "inherit"
|
|
145
|
+
};
|
|
146
|
+
function buildTree(resolved) {
|
|
147
|
+
const rootNode = {
|
|
148
|
+
kind: "group",
|
|
149
|
+
segment: "",
|
|
150
|
+
path: "",
|
|
151
|
+
children: []
|
|
152
|
+
};
|
|
153
|
+
for (const [path, token] of Object.entries(resolved)) {
|
|
154
|
+
const segments = path.split(".");
|
|
155
|
+
let node = rootNode;
|
|
156
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
157
|
+
const seg = segments[i];
|
|
158
|
+
const prefix = segments.slice(0, i + 1).join(".");
|
|
159
|
+
let child = node.children.find((c) => c.kind === "group" && c.segment === seg);
|
|
160
|
+
if (!child) {
|
|
161
|
+
child = {
|
|
162
|
+
kind: "group",
|
|
163
|
+
segment: seg,
|
|
164
|
+
path: prefix,
|
|
165
|
+
children: []
|
|
166
|
+
};
|
|
167
|
+
node.children.push(child);
|
|
168
|
+
}
|
|
169
|
+
node = child;
|
|
170
|
+
}
|
|
171
|
+
const leafSegment = segments[segments.length - 1];
|
|
172
|
+
node.children.push({
|
|
173
|
+
kind: "leaf",
|
|
174
|
+
segment: leafSegment,
|
|
175
|
+
path,
|
|
176
|
+
token
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
sortTree(rootNode);
|
|
180
|
+
return rootNode.children;
|
|
181
|
+
}
|
|
182
|
+
function sortTree(node) {
|
|
183
|
+
node.children.sort((a, b) => {
|
|
184
|
+
if (a.kind !== b.kind) return a.kind === "group" ? -1 : 1;
|
|
185
|
+
return a.segment.localeCompare(b.segment);
|
|
186
|
+
});
|
|
187
|
+
for (const c of node.children) if (c.kind === "group") sortTree(c);
|
|
188
|
+
}
|
|
189
|
+
function collectInitialExpanded(nodes, remainingDepth, out) {
|
|
190
|
+
if (remainingDepth <= 0) return;
|
|
191
|
+
for (const node of nodes) {
|
|
192
|
+
if (node.kind !== "group") continue;
|
|
193
|
+
out.add(node.path);
|
|
194
|
+
collectInitialExpanded(node.children, remainingDepth - 1, out);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function countLeaves(node) {
|
|
198
|
+
if (node.kind === "leaf") return 1;
|
|
199
|
+
let n = 0;
|
|
200
|
+
for (const c of node.children) n += countLeaves(c);
|
|
201
|
+
return n;
|
|
202
|
+
}
|
|
203
|
+
function filterTree(nodes, query) {
|
|
204
|
+
if (!query) return nodes;
|
|
205
|
+
const out = [];
|
|
206
|
+
for (const node of nodes) {
|
|
207
|
+
if (node.kind === "leaf") {
|
|
208
|
+
if (node.path.toLowerCase().includes(query)) out.push(node);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const filteredChildren = filterTree(node.children, query);
|
|
212
|
+
if (filteredChildren.length > 0) out.push({
|
|
213
|
+
...node,
|
|
214
|
+
children: filteredChildren
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
function collectAllGroupPaths(nodes, out) {
|
|
220
|
+
for (const node of nodes) if (node.kind === "group") {
|
|
221
|
+
out.add(node.path);
|
|
222
|
+
collectAllGroupPaths(node.children, out);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function DesignTokensPanel({ active }) {
|
|
226
|
+
const payload = usePayload();
|
|
227
|
+
const [globals] = useGlobals();
|
|
228
|
+
const [query, setQuery] = useState("");
|
|
229
|
+
const axes = useMemo(() => payload?.axes ?? [], [payload]);
|
|
230
|
+
const themes = useMemo(() => payload?.themes ?? [], [payload]);
|
|
231
|
+
const globalAxes = globals[AXES_GLOBAL_KEY];
|
|
232
|
+
const globalTheme = globals[GLOBAL_KEY];
|
|
233
|
+
const tuple = useMemo(() => {
|
|
234
|
+
const out = {};
|
|
235
|
+
for (const axis of axes) out[axis.name] = axis.default;
|
|
236
|
+
if (globalAxes && typeof globalAxes === "object") {
|
|
237
|
+
for (const axis of axes) {
|
|
238
|
+
const candidate = globalAxes[axis.name];
|
|
239
|
+
if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
if (globalTheme) {
|
|
244
|
+
const match = themes.find((t) => t.name === globalTheme);
|
|
245
|
+
if (match) for (const axis of axes) {
|
|
246
|
+
const candidate = match.input[axis.name];
|
|
247
|
+
if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}, [
|
|
252
|
+
axes,
|
|
253
|
+
themes,
|
|
254
|
+
globalAxes,
|
|
255
|
+
globalTheme
|
|
256
|
+
]);
|
|
257
|
+
const themeName = useMemo(() => {
|
|
258
|
+
return themes.find((t) => {
|
|
259
|
+
const input = t.input;
|
|
260
|
+
return Object.keys(input).every((k) => input[k] === tuple[k]);
|
|
261
|
+
})?.name ?? globalTheme ?? payload?.defaultTheme ?? "";
|
|
262
|
+
}, [
|
|
263
|
+
themes,
|
|
264
|
+
tuple,
|
|
265
|
+
globalTheme,
|
|
266
|
+
payload
|
|
267
|
+
]);
|
|
268
|
+
const prefix = payload?.cssVarPrefix ?? "";
|
|
269
|
+
const tokens = useMemo(() => payload?.themesResolved[themeName] ?? {}, [payload, themeName]);
|
|
270
|
+
const tokenCount = Object.keys(tokens).length;
|
|
271
|
+
const tree = useMemo(() => buildTree(tokens), [tokens]);
|
|
272
|
+
const lowerQuery = query.toLowerCase();
|
|
273
|
+
const filtered = useMemo(() => filterTree(tree, lowerQuery), [tree, lowerQuery]);
|
|
274
|
+
const initialExpanded = useMemo(() => {
|
|
275
|
+
const out = /* @__PURE__ */ new Set();
|
|
276
|
+
collectInitialExpanded(tree, 1, out);
|
|
277
|
+
return out;
|
|
278
|
+
}, [tree]);
|
|
279
|
+
const [expanded, setExpanded] = useState(initialExpanded);
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
setExpanded(initialExpanded);
|
|
282
|
+
}, [initialExpanded]);
|
|
283
|
+
const displayExpanded = useMemo(() => {
|
|
284
|
+
if (!lowerQuery) return expanded;
|
|
285
|
+
const all = /* @__PURE__ */ new Set();
|
|
286
|
+
collectAllGroupPaths(filtered, all);
|
|
287
|
+
return all;
|
|
288
|
+
}, [
|
|
289
|
+
expanded,
|
|
290
|
+
filtered,
|
|
291
|
+
lowerQuery
|
|
292
|
+
]);
|
|
293
|
+
const toggle = useCallback((path) => {
|
|
294
|
+
setExpanded((prev) => {
|
|
295
|
+
const next = new Set(prev);
|
|
296
|
+
if (next.has(path)) next.delete(path);
|
|
297
|
+
else next.add(path);
|
|
298
|
+
return next;
|
|
299
|
+
});
|
|
300
|
+
}, []);
|
|
301
|
+
const handleLeafClick = useCallback((path) => {
|
|
302
|
+
copy(`var(${makeCssVarName(path, prefix)})`);
|
|
303
|
+
}, [prefix]);
|
|
304
|
+
if (!active) return null;
|
|
305
|
+
if (!payload) return h$1(Placeholder, null, "Waiting for swatchbook preview…");
|
|
306
|
+
const showAxisIndicator = axes.length > 1 || axes.length === 1 && axes[0]?.source !== "synthetic";
|
|
307
|
+
const axisIndicatorText = axes.map((axis) => `${axis.name}: ${tuple[axis.name] ?? axis.default}`).join(" · ");
|
|
308
|
+
const disabledAxes = payload?.disabledAxes ?? [];
|
|
309
|
+
const pinnedSample = themes[0]?.input ?? {};
|
|
310
|
+
const disabledIndicatorText = disabledAxes.map((name) => `${name}: ${pinnedSample[name] ?? "?"} · pinned`).join(" · ");
|
|
311
|
+
return h$1("div", { style: containerStyle }, h$1("div", { style: headerStyle }, showAxisIndicator && h$1("div", {
|
|
312
|
+
style: axisIndicatorStyle,
|
|
313
|
+
"data-testid": "design-tokens-panel-axis-indicator"
|
|
314
|
+
}, axisIndicatorText), disabledAxes.length > 0 && h$1("div", {
|
|
315
|
+
style: {
|
|
316
|
+
...axisIndicatorStyle,
|
|
317
|
+
opacity: .5
|
|
318
|
+
},
|
|
319
|
+
"data-testid": "design-tokens-panel-disabled-axes-indicator"
|
|
320
|
+
}, disabledIndicatorText), h$1("input", {
|
|
321
|
+
style: searchInputStyle,
|
|
322
|
+
type: "search",
|
|
323
|
+
placeholder: `Search ${tokenCount} tokens in ${themeName}…`,
|
|
324
|
+
value: query,
|
|
325
|
+
onChange: (e) => setQuery(e.target.value)
|
|
326
|
+
})), h$1(DiagnosticsSection, { diagnostics: payload.diagnostics }), h$1(ScrollArea, { vertical: true }, filtered.length === 0 ? h$1(Placeholder, null, query ? "No tokens match this filter." : "No tokens in this theme.") : h$1("div", { style: treeWrapperStyle }, h$1("ul", {
|
|
327
|
+
style: treeUlStyle,
|
|
328
|
+
role: "tree"
|
|
329
|
+
}, filtered.map((node) => h$1(TreeRow, {
|
|
330
|
+
key: node.path || node.segment,
|
|
331
|
+
node,
|
|
332
|
+
expanded: displayExpanded,
|
|
333
|
+
onToggle: toggle,
|
|
334
|
+
onLeafClick: handleLeafClick,
|
|
335
|
+
prefix
|
|
336
|
+
}))))));
|
|
337
|
+
}
|
|
338
|
+
function TreeRow({ node, expanded, onToggle, onLeafClick, prefix }) {
|
|
339
|
+
if (node.kind === "leaf") return h$1(LeafRow, {
|
|
340
|
+
node,
|
|
341
|
+
onLeafClick,
|
|
342
|
+
prefix
|
|
343
|
+
});
|
|
344
|
+
const isOpen = expanded.has(node.path);
|
|
345
|
+
const onKey = (e) => {
|
|
346
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
onToggle(node.path);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
return h$1("li", {
|
|
352
|
+
role: "treeitem",
|
|
353
|
+
"aria-expanded": isOpen
|
|
354
|
+
}, h$1("div", {
|
|
355
|
+
role: "button",
|
|
356
|
+
tabIndex: 0,
|
|
357
|
+
style: groupRowStyle,
|
|
358
|
+
onClick: () => onToggle(node.path),
|
|
359
|
+
onKeyDown: onKey,
|
|
360
|
+
"data-path": node.path,
|
|
361
|
+
"data-testid": "design-tokens-panel-group"
|
|
362
|
+
}, h$1("span", {
|
|
363
|
+
style: caretStyle,
|
|
364
|
+
"aria-hidden": true
|
|
365
|
+
}, isOpen ? "▾" : "▸"), h$1("span", null, node.segment), h$1("span", { style: countStyle }, countLeaves(node))), isOpen && h$1("ul", {
|
|
366
|
+
style: nestedUlStyle,
|
|
367
|
+
role: "group"
|
|
368
|
+
}, node.children.map((c) => h$1(TreeRow, {
|
|
369
|
+
key: c.path || c.segment,
|
|
370
|
+
node: c,
|
|
371
|
+
expanded,
|
|
372
|
+
onToggle,
|
|
373
|
+
onLeafClick,
|
|
374
|
+
prefix
|
|
375
|
+
}))));
|
|
376
|
+
}
|
|
377
|
+
function LeafRow({ node, onLeafClick, prefix }) {
|
|
378
|
+
const type = node.token.$type ?? "";
|
|
379
|
+
const value = node.token.$value;
|
|
380
|
+
const displayValue = formatValue(value);
|
|
381
|
+
const colorPreview = type === "color" && typeof value === "object" && value !== null && typeof value["hex"] === "string" ? value["hex"] : null;
|
|
382
|
+
return h$1("li", { role: "treeitem" }, h$1("button", {
|
|
383
|
+
type: "button",
|
|
384
|
+
style: leafRowStyle,
|
|
385
|
+
onClick: () => onLeafClick(node.path),
|
|
386
|
+
title: `Click to copy var(${makeCssVarName(node.path, prefix)})`,
|
|
387
|
+
"data-path": node.path,
|
|
388
|
+
"data-testid": "design-tokens-panel-leaf"
|
|
389
|
+
}, h$1("span", {
|
|
390
|
+
style: caretStyle,
|
|
391
|
+
"aria-hidden": true
|
|
392
|
+
}, "•"), h$1("span", null, node.segment), type && h$1("span", { style: typePillStyle }, type), h$1("span", { style: valueStyle }, displayValue), colorPreview && h$1("span", {
|
|
393
|
+
style: {
|
|
394
|
+
...swatchStyle,
|
|
395
|
+
background: colorPreview
|
|
396
|
+
},
|
|
397
|
+
"aria-hidden": true
|
|
398
|
+
})));
|
|
399
|
+
}
|
|
400
|
+
const severityStyle = {
|
|
401
|
+
error: { color: "#d64545" },
|
|
402
|
+
warn: { color: "#b08900" },
|
|
403
|
+
info: { opacity: .6 }
|
|
404
|
+
};
|
|
405
|
+
const severityLabel = {
|
|
406
|
+
error: "ERROR",
|
|
407
|
+
warn: "WARN",
|
|
408
|
+
info: "INFO"
|
|
409
|
+
};
|
|
410
|
+
const diagnosticsSectionStyle = { borderBottom: "1px solid rgba(128,128,128,0.2)" };
|
|
411
|
+
const diagnosticsSummaryStyle = {
|
|
412
|
+
padding: "10px 12px",
|
|
413
|
+
fontSize: 12,
|
|
414
|
+
cursor: "pointer",
|
|
415
|
+
userSelect: "none",
|
|
416
|
+
listStyle: "none",
|
|
417
|
+
display: "flex",
|
|
418
|
+
alignItems: "center",
|
|
419
|
+
gap: 8
|
|
420
|
+
};
|
|
421
|
+
const diagnosticRowStyle = {
|
|
422
|
+
display: "grid",
|
|
423
|
+
gridTemplateColumns: "60px 1fr",
|
|
424
|
+
gap: 12,
|
|
425
|
+
padding: "8px 12px",
|
|
426
|
+
fontSize: 12,
|
|
427
|
+
borderTop: "1px solid rgba(128,128,128,0.12)"
|
|
428
|
+
};
|
|
429
|
+
function DiagnosticsSection({ diagnostics }) {
|
|
430
|
+
const counts = diagnostics.reduce((acc, d) => {
|
|
431
|
+
acc[d.severity] = (acc[d.severity] ?? 0) + 1;
|
|
432
|
+
return acc;
|
|
433
|
+
}, {
|
|
434
|
+
error: 0,
|
|
435
|
+
warn: 0,
|
|
436
|
+
info: 0
|
|
437
|
+
});
|
|
438
|
+
const hasErrorsOrWarnings = counts.error > 0 || counts.warn > 0;
|
|
439
|
+
const summaryText = (() => {
|
|
440
|
+
if (diagnostics.length === 0) return "✔ OK · no diagnostics";
|
|
441
|
+
const parts = [];
|
|
442
|
+
if (counts.error > 0) parts.push(`✖ ${counts.error} error${counts.error === 1 ? "" : "s"}`);
|
|
443
|
+
if (counts.warn > 0) parts.push(`⚠ ${counts.warn} warning${counts.warn === 1 ? "" : "s"}`);
|
|
444
|
+
if (counts.info > 0) parts.push(`${counts.info} info`);
|
|
445
|
+
return parts.join(" · ");
|
|
446
|
+
})();
|
|
447
|
+
const summaryColor = (() => {
|
|
448
|
+
if (diagnostics.length === 0) return "#30a46c";
|
|
449
|
+
if (counts.error > 0) return "#d64545";
|
|
450
|
+
if (counts.warn > 0) return "#b08900";
|
|
451
|
+
return "inherit";
|
|
452
|
+
})();
|
|
453
|
+
return h$1("details", {
|
|
454
|
+
style: diagnosticsSectionStyle,
|
|
455
|
+
open: hasErrorsOrWarnings,
|
|
456
|
+
"data-testid": "design-tokens-panel-diagnostics"
|
|
457
|
+
}, h$1("summary", { style: {
|
|
458
|
+
...diagnosticsSummaryStyle,
|
|
459
|
+
color: summaryColor,
|
|
460
|
+
fontWeight: 600
|
|
461
|
+
} }, h$1("span", null, "Diagnostics"), h$1("span", { style: { fontWeight: 400 } }, summaryText)), diagnostics.length === 0 ? null : h$1("div", null, diagnostics.map((d, i) => h$1("div", {
|
|
462
|
+
key: `${d.group}-${i}`,
|
|
463
|
+
style: diagnosticRowStyle
|
|
464
|
+
}, h$1("span", { style: {
|
|
465
|
+
...severityStyle[d.severity],
|
|
466
|
+
fontWeight: 600,
|
|
467
|
+
fontSize: 10
|
|
468
|
+
} }, severityLabel[d.severity]), h$1("div", null, h$1("div", null, d.message), (d.filename || d.group) && h$1("div", { style: {
|
|
469
|
+
opacity: .5,
|
|
470
|
+
fontSize: 10,
|
|
471
|
+
marginTop: 4
|
|
472
|
+
} }, [
|
|
473
|
+
d.group,
|
|
474
|
+
d.filename,
|
|
475
|
+
d.line ? `:${d.line}` : ""
|
|
476
|
+
].filter(Boolean).join(" · ")))))));
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/manager.tsx
|
|
480
|
+
/**
|
|
481
|
+
* Use explicit `React.createElement` rather than JSX so the manager bundle
|
|
482
|
+
* doesn't take a hard dependency on `react/jsx-runtime`. Storybook's manager
|
|
483
|
+
* page injects its own React as a runtime global; `react/jsx-runtime` isn't
|
|
484
|
+
* always part of that exposure, which breaks JSX with
|
|
485
|
+
* "Cannot read properties of undefined (reading 'recentlyCreatedOwnerStacks')".
|
|
486
|
+
* Mirrors the pattern `@storybook/addon-a11y` uses in its manager.
|
|
487
|
+
*/
|
|
488
|
+
const h = React.createElement;
|
|
489
|
+
const EMPTY_AXES = [];
|
|
490
|
+
const EMPTY_PRESETS = [];
|
|
491
|
+
const EMPTY_THEMES = [];
|
|
492
|
+
/**
|
|
493
|
+
* Root toolbar glyph — a split-circle ("yinyang") mark: a faint filled
|
|
494
|
+
* disc for the full-swatch silhouette, with a darker half-and-inset-disc
|
|
495
|
+
* path reading as a pair of theme variants swapped in place.
|
|
496
|
+
*/
|
|
497
|
+
function SwatchbookIcon() {
|
|
498
|
+
return h("svg", {
|
|
499
|
+
width: 14,
|
|
500
|
+
height: 14,
|
|
501
|
+
viewBox: "0 0 14 14",
|
|
502
|
+
"aria-hidden": true
|
|
503
|
+
}, h("circle", {
|
|
504
|
+
cx: 7,
|
|
505
|
+
cy: 7,
|
|
506
|
+
r: 6,
|
|
507
|
+
fill: "currentColor",
|
|
508
|
+
opacity: .15
|
|
509
|
+
}), h("path", {
|
|
510
|
+
d: "M7 1a6 6 0 0 0 0 12 3 3 0 0 0 0-6 3 3 0 0 1 0-6Z",
|
|
511
|
+
fill: "currentColor"
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
function tupleMatchesInput(tuple, input) {
|
|
515
|
+
const keys = Object.keys(input);
|
|
516
|
+
if (keys.length === 0) return false;
|
|
517
|
+
return keys.every((k) => input[k] === tuple[k]);
|
|
518
|
+
}
|
|
519
|
+
function composedNameFor(tuple, themes, fallback) {
|
|
520
|
+
return themes.find((t) => tupleMatchesInput(tuple, t.input))?.name ?? fallback;
|
|
521
|
+
}
|
|
522
|
+
function defaultTupleFor(axes) {
|
|
523
|
+
const out = {};
|
|
524
|
+
for (const axis of axes) out[axis.name] = axis.default;
|
|
525
|
+
return out;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Treat the `{ name: 'theme', source: 'synthetic' }` axis — the one core
|
|
529
|
+
* fabricates for single-theme projects with no resolver — as a special case
|
|
530
|
+
* that uses the label "Theme" instead of the axis name. Authored single-axis
|
|
531
|
+
* resolvers keep their real axis name (e.g. `mode`).
|
|
532
|
+
*/
|
|
533
|
+
function displayLabelFor(axis) {
|
|
534
|
+
if (axis.source === "synthetic" && axis.name === "theme") return "Theme";
|
|
535
|
+
return axis.name;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Compose a preset's sanitized partial tuple with the axis defaults, so
|
|
539
|
+
* applying a preset that only names some axes leaves the omitted ones at
|
|
540
|
+
* their defaults (not blank). Matches the preview decorator's own fallback
|
|
541
|
+
* logic so what the toolbar sends out is what the decorator honors.
|
|
542
|
+
*/
|
|
543
|
+
function presetTuple(preset, axes, defaults) {
|
|
544
|
+
const out = { ...defaults };
|
|
545
|
+
for (const axis of axes) {
|
|
546
|
+
const candidate = preset.axes[axis.name];
|
|
547
|
+
if (candidate !== void 0 && axis.contexts.includes(candidate)) out[axis.name] = candidate;
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
function tuplesEqual(a, b, axes) {
|
|
552
|
+
for (const axis of axes) if (a[axis.name] !== b[axis.name]) return false;
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
const SECTION_LABEL_STYLE = {
|
|
556
|
+
padding: "8px 12px 4px",
|
|
557
|
+
fontSize: 11,
|
|
558
|
+
textTransform: "uppercase",
|
|
559
|
+
letterSpacing: .5,
|
|
560
|
+
opacity: .6
|
|
561
|
+
};
|
|
562
|
+
const SECTION_BODY_STYLE = {
|
|
563
|
+
padding: "0 12px 10px",
|
|
564
|
+
display: "flex",
|
|
565
|
+
flexWrap: "wrap",
|
|
566
|
+
gap: 4
|
|
567
|
+
};
|
|
568
|
+
const AXIS_ROW_STYLE = {
|
|
569
|
+
padding: "0 12px 10px",
|
|
570
|
+
display: "grid",
|
|
571
|
+
gridTemplateColumns: "max-content 1fr",
|
|
572
|
+
columnGap: 12,
|
|
573
|
+
rowGap: 4,
|
|
574
|
+
alignItems: "center"
|
|
575
|
+
};
|
|
576
|
+
const AXIS_LABEL_STYLE = {
|
|
577
|
+
fontSize: 12,
|
|
578
|
+
fontWeight: 600,
|
|
579
|
+
opacity: .85
|
|
580
|
+
};
|
|
581
|
+
const AXIS_PILLS_STYLE = {
|
|
582
|
+
display: "flex",
|
|
583
|
+
flexWrap: "wrap",
|
|
584
|
+
gap: 4
|
|
585
|
+
};
|
|
586
|
+
const OPTION_PILL_BASE = {
|
|
587
|
+
padding: "3px 8px",
|
|
588
|
+
borderRadius: 4,
|
|
589
|
+
fontSize: 12,
|
|
590
|
+
lineHeight: "18px",
|
|
591
|
+
borderWidth: 1,
|
|
592
|
+
borderStyle: "solid",
|
|
593
|
+
borderColor: "transparent",
|
|
594
|
+
background: "transparent",
|
|
595
|
+
cursor: "pointer",
|
|
596
|
+
color: "inherit",
|
|
597
|
+
outline: "none",
|
|
598
|
+
boxShadow: "none"
|
|
599
|
+
};
|
|
600
|
+
const OPTION_PILL_ACTIVE = {
|
|
601
|
+
...OPTION_PILL_BASE,
|
|
602
|
+
fontWeight: 600,
|
|
603
|
+
background: "rgba(0, 122, 255, 0.12)",
|
|
604
|
+
borderColor: "rgba(0, 122, 255, 0.45)"
|
|
605
|
+
};
|
|
606
|
+
const PRESET_PILL_MODIFIED = {
|
|
607
|
+
display: "inline-block",
|
|
608
|
+
width: 6,
|
|
609
|
+
height: 6,
|
|
610
|
+
marginLeft: 6,
|
|
611
|
+
borderRadius: "50%",
|
|
612
|
+
background: "currentColor",
|
|
613
|
+
opacity: .6,
|
|
614
|
+
verticalAlign: "middle"
|
|
615
|
+
};
|
|
616
|
+
const DIVIDER_STYLE = {
|
|
617
|
+
height: 1,
|
|
618
|
+
background: "currentColor",
|
|
619
|
+
opacity: .1,
|
|
620
|
+
margin: "2px 8px"
|
|
621
|
+
};
|
|
622
|
+
function OptionPill({ label, active, title, onClick, trailing }) {
|
|
623
|
+
return h("button", {
|
|
624
|
+
type: "button",
|
|
625
|
+
title,
|
|
626
|
+
onClick,
|
|
627
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
628
|
+
style: active ? OPTION_PILL_ACTIVE : OPTION_PILL_BASE
|
|
629
|
+
}, label, trailing ?? null);
|
|
630
|
+
}
|
|
631
|
+
function PresetsSection({ presets, axes, defaults, activeTuple, lastApplied, onApply }) {
|
|
632
|
+
return h("div", null, h("div", { style: SECTION_LABEL_STYLE }, "Presets"), h("div", { style: SECTION_BODY_STYLE }, ...presets.map((preset) => {
|
|
633
|
+
const matches = tuplesEqual(presetTuple(preset, axes, defaults), activeTuple, axes);
|
|
634
|
+
const modified = !matches && preset.name === lastApplied;
|
|
635
|
+
const title = preset.description ? `${preset.name} — ${preset.description}` : preset.name;
|
|
636
|
+
return h(OptionPill, {
|
|
637
|
+
key: `${TOOL_ID}/preset/${preset.name}`,
|
|
638
|
+
label: preset.name,
|
|
639
|
+
active: matches,
|
|
640
|
+
title,
|
|
641
|
+
onClick: () => onApply(preset),
|
|
642
|
+
trailing: modified ? h("span", {
|
|
643
|
+
"aria-hidden": true,
|
|
644
|
+
style: PRESET_PILL_MODIFIED
|
|
645
|
+
}) : null
|
|
646
|
+
});
|
|
647
|
+
})));
|
|
648
|
+
}
|
|
649
|
+
function AxisSection({ axis, active, onSelect }) {
|
|
650
|
+
const label = displayLabelFor(axis);
|
|
651
|
+
return h("div", { style: AXIS_ROW_STYLE }, h("div", {
|
|
652
|
+
style: AXIS_LABEL_STYLE,
|
|
653
|
+
title: axis.description
|
|
654
|
+
}, label), h("div", { style: AXIS_PILLS_STYLE }, ...axis.contexts.map((ctx) => h(OptionPill, {
|
|
655
|
+
key: `${TOOL_ID}/${axis.name}/${ctx}`,
|
|
656
|
+
label: ctx,
|
|
657
|
+
active: ctx === active,
|
|
658
|
+
onClick: () => onSelect(ctx)
|
|
659
|
+
}))));
|
|
660
|
+
}
|
|
661
|
+
const COLOR_FORMAT_OPTIONS = [
|
|
662
|
+
{
|
|
663
|
+
id: "hex",
|
|
664
|
+
label: "Hex"
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
id: "rgb",
|
|
668
|
+
label: "RGB"
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: "hsl",
|
|
672
|
+
label: "HSL"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
id: "oklch",
|
|
676
|
+
label: "OKLCH"
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
id: "raw",
|
|
680
|
+
label: "Raw (JSON)"
|
|
681
|
+
}
|
|
682
|
+
];
|
|
683
|
+
function ColorFormatSection({ active, onSelect }) {
|
|
684
|
+
return h("div", null, h("div", { style: SECTION_LABEL_STYLE }, "Color format"), h("div", { style: SECTION_BODY_STYLE }, ...COLOR_FORMAT_OPTIONS.map((opt) => h(OptionPill, {
|
|
685
|
+
key: `${TOOL_ID}/color-format/${opt.id}`,
|
|
686
|
+
label: opt.label,
|
|
687
|
+
active: opt.id === active,
|
|
688
|
+
onClick: () => onSelect(opt.id)
|
|
689
|
+
}))));
|
|
690
|
+
}
|
|
691
|
+
function PopoverBody(props) {
|
|
692
|
+
const { axes, presets, defaults, activeTuple, activeColorFormat, lastApplied, onApplyPreset, onSelectAxis, onSelectColorFormat, onKeyDown } = props;
|
|
693
|
+
const sections = [];
|
|
694
|
+
if (presets.length > 0) sections.push(h(PresetsSection, {
|
|
695
|
+
key: "presets",
|
|
696
|
+
presets,
|
|
697
|
+
axes,
|
|
698
|
+
defaults,
|
|
699
|
+
activeTuple,
|
|
700
|
+
lastApplied,
|
|
701
|
+
onApply: onApplyPreset
|
|
702
|
+
}), h("div", {
|
|
703
|
+
key: "presets-divider",
|
|
704
|
+
style: DIVIDER_STYLE
|
|
705
|
+
}));
|
|
706
|
+
axes.forEach((axis, idx) => {
|
|
707
|
+
sections.push(h(AxisSection, {
|
|
708
|
+
key: `axis-${axis.name}`,
|
|
709
|
+
axis,
|
|
710
|
+
active: activeTuple[axis.name] ?? axis.default,
|
|
711
|
+
onSelect: (next) => onSelectAxis(axis.name, next)
|
|
712
|
+
}));
|
|
713
|
+
if (idx === axes.length - 1) sections.push(h("div", {
|
|
714
|
+
key: "axes-divider",
|
|
715
|
+
style: DIVIDER_STYLE
|
|
716
|
+
}));
|
|
717
|
+
});
|
|
718
|
+
sections.push(h(ColorFormatSection, {
|
|
719
|
+
key: "color-format",
|
|
720
|
+
active: activeColorFormat,
|
|
721
|
+
onSelect: onSelectColorFormat
|
|
722
|
+
}));
|
|
723
|
+
return h("div", {
|
|
724
|
+
role: "menu",
|
|
725
|
+
tabIndex: -1,
|
|
726
|
+
onKeyDown,
|
|
727
|
+
style: {
|
|
728
|
+
minWidth: 260,
|
|
729
|
+
padding: "4px 0",
|
|
730
|
+
outline: "none"
|
|
731
|
+
},
|
|
732
|
+
"data-testid": "swatchbook-toolbar-popover"
|
|
733
|
+
}, ...sections);
|
|
734
|
+
}
|
|
735
|
+
function AxesToolbar() {
|
|
736
|
+
const [globals, updateGlobals] = useGlobals();
|
|
737
|
+
const api = useStorybookApi();
|
|
738
|
+
const [payload, setPayload] = useState(null);
|
|
739
|
+
const [open, setOpen] = useState(false);
|
|
740
|
+
const bodyRef = useRef(null);
|
|
741
|
+
useEffect(() => {
|
|
742
|
+
const channel = addons.getChannel();
|
|
743
|
+
const onInit = (next) => setPayload(next);
|
|
744
|
+
channel.on(INIT_EVENT, onInit);
|
|
745
|
+
return () => {
|
|
746
|
+
channel.off(INIT_EVENT, onInit);
|
|
747
|
+
};
|
|
748
|
+
}, []);
|
|
749
|
+
const axes = payload?.axes ?? EMPTY_AXES;
|
|
750
|
+
const presets = payload?.presets ?? EMPTY_PRESETS;
|
|
751
|
+
const themes = payload?.themes ?? EMPTY_THEMES;
|
|
752
|
+
const defaults = useMemo(() => defaultTupleFor(axes), [axes]);
|
|
753
|
+
const [lastApplied, setLastApplied] = useState(null);
|
|
754
|
+
const globalTuple = globals[AXES_GLOBAL_KEY];
|
|
755
|
+
const activeColorFormat = globals["swatchbookColorFormat"] ?? "hex";
|
|
756
|
+
const activeTuple = useMemo(() => {
|
|
757
|
+
const out = { ...defaults };
|
|
758
|
+
if (globalTuple) for (const axis of axes) {
|
|
759
|
+
const candidate = globalTuple[axis.name];
|
|
760
|
+
if (candidate !== void 0 && axis.contexts.includes(candidate)) out[axis.name] = candidate;
|
|
761
|
+
}
|
|
762
|
+
return out;
|
|
763
|
+
}, [
|
|
764
|
+
axes,
|
|
765
|
+
defaults,
|
|
766
|
+
globalTuple
|
|
767
|
+
]);
|
|
768
|
+
const setAxis = useCallback((axisName, next) => {
|
|
769
|
+
const tuple = {
|
|
770
|
+
...activeTuple,
|
|
771
|
+
[axisName]: next
|
|
772
|
+
};
|
|
773
|
+
const composed = composedNameFor(tuple, themes, payload?.defaultTheme ?? themes[0]?.name ?? "");
|
|
774
|
+
updateGlobals({
|
|
775
|
+
[AXES_GLOBAL_KEY]: tuple,
|
|
776
|
+
[GLOBAL_KEY]: composed
|
|
777
|
+
});
|
|
778
|
+
}, [
|
|
779
|
+
activeTuple,
|
|
780
|
+
themes,
|
|
781
|
+
payload?.defaultTheme,
|
|
782
|
+
updateGlobals
|
|
783
|
+
]);
|
|
784
|
+
const applyPreset = useCallback((preset) => {
|
|
785
|
+
const tuple = presetTuple(preset, axes, defaults);
|
|
786
|
+
const composed = composedNameFor(tuple, themes, payload?.defaultTheme ?? themes[0]?.name ?? "");
|
|
787
|
+
updateGlobals({
|
|
788
|
+
[AXES_GLOBAL_KEY]: tuple,
|
|
789
|
+
[GLOBAL_KEY]: composed
|
|
790
|
+
});
|
|
791
|
+
setLastApplied(preset.name);
|
|
792
|
+
}, [
|
|
793
|
+
axes,
|
|
794
|
+
defaults,
|
|
795
|
+
themes,
|
|
796
|
+
payload?.defaultTheme,
|
|
797
|
+
updateGlobals
|
|
798
|
+
]);
|
|
799
|
+
useEffect(() => {
|
|
800
|
+
if (axes.length === 0) return;
|
|
801
|
+
/**
|
|
802
|
+
* alt+T cycles the primary (first) axis's contexts, keeping the rest
|
|
803
|
+
* of the tuple pinned. With multi-axis projects this makes the shortcut
|
|
804
|
+
* predictable — you always know which wheel you're spinning. For
|
|
805
|
+
* single-axis projects the behavior is identical to the pre-N-dropdown
|
|
806
|
+
* toolbar (cycle through every theme).
|
|
807
|
+
*/
|
|
808
|
+
const primary = axes[0];
|
|
809
|
+
if (!primary) return;
|
|
810
|
+
api.setAddonShortcut(ADDON_ID, {
|
|
811
|
+
label: `Cycle swatchbook ${displayLabelFor(primary)}`,
|
|
812
|
+
defaultShortcut: ["alt", "T"],
|
|
813
|
+
actionName: "cycleAxis",
|
|
814
|
+
showInMenu: true,
|
|
815
|
+
action: () => {
|
|
816
|
+
const current = activeTuple[primary.name] ?? primary.default;
|
|
817
|
+
const idx = primary.contexts.indexOf(current);
|
|
818
|
+
const next = primary.contexts[(idx + 1) % primary.contexts.length];
|
|
819
|
+
if (next !== void 0) setAxis(primary.name, next);
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
}, [
|
|
823
|
+
api,
|
|
824
|
+
axes,
|
|
825
|
+
activeTuple,
|
|
826
|
+
setAxis
|
|
827
|
+
]);
|
|
828
|
+
const handleKeyDown = useCallback((event) => {
|
|
829
|
+
if (event.key === "Escape") {
|
|
830
|
+
event.stopPropagation();
|
|
831
|
+
setOpen(false);
|
|
832
|
+
}
|
|
833
|
+
}, []);
|
|
834
|
+
/**
|
|
835
|
+
* Escape closes even when focus hasn't entered the popover yet (e.g. the
|
|
836
|
+
* user opened it via click and the mouse is still over the canvas). We
|
|
837
|
+
* attach a document-level listener when open.
|
|
838
|
+
*/
|
|
839
|
+
useEffect(() => {
|
|
840
|
+
if (!open) return;
|
|
841
|
+
const onDocKey = (e) => {
|
|
842
|
+
if (e.key === "Escape") setOpen(false);
|
|
843
|
+
};
|
|
844
|
+
document.addEventListener("keydown", onDocKey);
|
|
845
|
+
return () => document.removeEventListener("keydown", onDocKey);
|
|
846
|
+
}, [open]);
|
|
847
|
+
if (axes.length === 0) return h(IconButton, {
|
|
848
|
+
key: TOOL_ID,
|
|
849
|
+
title: "Swatchbook theme (loading…)",
|
|
850
|
+
disabled: true
|
|
851
|
+
}, h(SwatchbookIcon));
|
|
852
|
+
const button = h(IconButton, {
|
|
853
|
+
key: TOOL_ID,
|
|
854
|
+
title: `Swatchbook · ${axes.map((a) => activeTuple[a.name] ?? a.default).join(" · ")}`,
|
|
855
|
+
active: open,
|
|
856
|
+
onClick: () => setOpen((prev) => !prev)
|
|
857
|
+
}, h(SwatchbookIcon));
|
|
858
|
+
const tooltipBody = h(PopoverBody, {
|
|
859
|
+
axes,
|
|
860
|
+
presets,
|
|
861
|
+
defaults,
|
|
862
|
+
activeTuple,
|
|
863
|
+
activeColorFormat,
|
|
864
|
+
lastApplied,
|
|
865
|
+
onApplyPreset: applyPreset,
|
|
866
|
+
onSelectAxis: setAxis,
|
|
867
|
+
onSelectColorFormat: (next) => updateGlobals({ [COLOR_FORMAT_GLOBAL_KEY]: next }),
|
|
868
|
+
onKeyDown: handleKeyDown
|
|
869
|
+
});
|
|
870
|
+
return h("span", {
|
|
871
|
+
ref: bodyRef,
|
|
872
|
+
style: {
|
|
873
|
+
display: "inline-flex",
|
|
874
|
+
alignItems: "center"
|
|
875
|
+
}
|
|
876
|
+
}, h(WithTooltipPure, {
|
|
877
|
+
placement: "bottom",
|
|
878
|
+
trigger: "click",
|
|
879
|
+
visible: open,
|
|
880
|
+
onVisibleChange: (next) => setOpen(next),
|
|
881
|
+
closeOnOutsideClick: true,
|
|
882
|
+
tooltip: tooltipBody,
|
|
883
|
+
children: button
|
|
884
|
+
}));
|
|
885
|
+
}
|
|
886
|
+
addons.register(ADDON_ID, () => {
|
|
887
|
+
addons.add(TOOL_ID, {
|
|
888
|
+
type: types.TOOL,
|
|
889
|
+
title: "Swatchbook theme",
|
|
890
|
+
match: ({ viewMode, tabId }) => !tabId && (viewMode === "story" || viewMode === "docs"),
|
|
891
|
+
render: () => h(AxesToolbar)
|
|
892
|
+
});
|
|
893
|
+
addons.add(PANEL_ID, {
|
|
894
|
+
type: types.PANEL,
|
|
895
|
+
title: "Design Tokens",
|
|
896
|
+
match: ({ viewMode }) => viewMode === "story",
|
|
897
|
+
render: ({ active }) => h(DesignTokensPanel, { active: !!active })
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
//#endregion
|
|
901
|
+
export {};
|
|
902
|
+
|
|
903
|
+
//# sourceMappingURL=manager.mjs.map
|