@component-anatomy/storybook 0.0.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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/index.cjs +48 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +7 -0
- package/dist/manager.js +164 -0
- package/dist/manager.js.map +7 -0
- package/dist/preview.js +74 -0
- package/dist/preview.js.map +7 -0
- package/package.json +76 -0
- package/src/Panel.tsx +168 -0
- package/src/constants.ts +21 -0
- package/src/index.ts +2 -0
- package/src/manager.tsx +20 -0
- package/src/preview.ts +74 -0
- package/src/types.ts +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julien Déramond
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @component-anatomy/storybook
|
|
2
|
+
|
|
3
|
+
Storybook addon that adds an **Anatomy** panel — an interactive part list synced two-way with the story canvas.
|
|
4
|
+
|
|
5
|
+
- Hover a part in the panel → the element is highlighted in the canvas
|
|
6
|
+
- Hover a `data-part` element in the canvas → the panel entry activates
|
|
7
|
+
- Works with **Storybook 9 and 10**, any renderer (React, Vue, HTML, Web Components…)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev @component-anatomy/storybook
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// .storybook/main.ts
|
|
17
|
+
export default {
|
|
18
|
+
addons: ['@component-anatomy/storybook'],
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
Annotate your story's DOM with `data-part` and add the `anatomy` parameter:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import type { AnatomyParameters } from '@component-anatomy/storybook';
|
|
28
|
+
|
|
29
|
+
export const Anatomy: Story = {
|
|
30
|
+
parameters: {
|
|
31
|
+
anatomy: {
|
|
32
|
+
parts: [
|
|
33
|
+
{ id: 'icon', name: 'Icon', description: 'Optional leading glyph.' },
|
|
34
|
+
{ id: 'label', name: 'Label', description: 'The visible action text.' },
|
|
35
|
+
],
|
|
36
|
+
// optional:
|
|
37
|
+
preset: 'blueprint', // 'default' | 'minimal' | 'contrast' | 'blueprint'
|
|
38
|
+
theme: { accent: '#0d9488' }, // theme tokens for the canvas overlays
|
|
39
|
+
overlayLabel: true, // floating name chip
|
|
40
|
+
overlayPadding: 2, // inflate highlight boxes (px)
|
|
41
|
+
root: '.my-component', // narrow the anatomy root (CSS selector)
|
|
42
|
+
disable: false, // turn off for a story
|
|
43
|
+
} satisfies AnatomyParameters,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Omit `parts` (pass `{}`) and the panel lists parts auto-discovered from `data-part` attributes, with names derived from the ids.
|
|
49
|
+
|
|
50
|
+
Parameters follow Storybook's normal inheritance — project-wide defaults in `.storybook/preview.ts`, per-component in `meta.parameters`, per-story overrides in `story.parameters`.
|
|
51
|
+
|
|
52
|
+
## Example
|
|
53
|
+
|
|
54
|
+
A complete Storybook 10 setup with Button/Slider/Tabs stories lives in [`examples/storybook`](https://github.com/julien-deramond/component-anatomy/tree/main/examples/storybook), deployed at https://julien-deramond.github.io/component-anatomy/storybook/.
|
|
55
|
+
|
|
56
|
+
## Docs
|
|
57
|
+
|
|
58
|
+
Full documentation: https://julien-deramond.github.io/component-anatomy/docs/storybook
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
ADDON_ID: () => ADDON_ID,
|
|
24
|
+
EVENTS: () => EVENTS,
|
|
25
|
+
PANEL_ID: () => PANEL_ID,
|
|
26
|
+
PARAM_KEY: () => PARAM_KEY
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
|
|
30
|
+
// src/constants.ts
|
|
31
|
+
var ADDON_ID = "component-anatomy";
|
|
32
|
+
var PANEL_ID = `${ADDON_ID}/panel`;
|
|
33
|
+
var PARAM_KEY = "anatomy";
|
|
34
|
+
var EVENTS = {
|
|
35
|
+
/** preview → manager: a part became active in the canvas (hover/programmatic). */
|
|
36
|
+
PART_ENTER: `${ADDON_ID}/part-enter`,
|
|
37
|
+
/** preview → manager: no part is active anymore. */
|
|
38
|
+
PART_LEAVE: `${ADDON_ID}/part-leave`,
|
|
39
|
+
/** preview → manager: resolved part list for the current story. */
|
|
40
|
+
PARTS: `${ADDON_ID}/parts`,
|
|
41
|
+
/** manager → preview: the user hovers/focuses a panel entry. */
|
|
42
|
+
HOVER_ITEM: `${ADDON_ID}/hover-item`,
|
|
43
|
+
/** manager → preview: the user left a panel entry. */
|
|
44
|
+
LEAVE_ITEM: `${ADDON_ID}/leave-item`,
|
|
45
|
+
/** manager → preview: the panel mounted and wants the current part list. */
|
|
46
|
+
PARTS_REQUEST: `${ADDON_ID}/parts-request`
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/constants.ts"],
|
|
4
|
+
"sourcesContent": ["export { ADDON_ID, PANEL_ID, PARAM_KEY, EVENTS } from './constants.js';\nexport type { AnatomyParameters } from './types.js';\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/** Channel events used to sync the manager panel with the preview iframe. */\nexport const EVENTS = {\n /** preview \u2192 manager: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 manager: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 manager: resolved part list for the current story. */\n PARTS: `${ADDON_ID}/parts`,\n /** manager \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** manager \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** manager \u2192 preview: the panel mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AAGlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var ADDON_ID = "component-anatomy";
|
|
3
|
+
var PANEL_ID = `${ADDON_ID}/panel`;
|
|
4
|
+
var PARAM_KEY = "anatomy";
|
|
5
|
+
var EVENTS = {
|
|
6
|
+
/** preview → manager: a part became active in the canvas (hover/programmatic). */
|
|
7
|
+
PART_ENTER: `${ADDON_ID}/part-enter`,
|
|
8
|
+
/** preview → manager: no part is active anymore. */
|
|
9
|
+
PART_LEAVE: `${ADDON_ID}/part-leave`,
|
|
10
|
+
/** preview → manager: resolved part list for the current story. */
|
|
11
|
+
PARTS: `${ADDON_ID}/parts`,
|
|
12
|
+
/** manager → preview: the user hovers/focuses a panel entry. */
|
|
13
|
+
HOVER_ITEM: `${ADDON_ID}/hover-item`,
|
|
14
|
+
/** manager → preview: the user left a panel entry. */
|
|
15
|
+
LEAVE_ITEM: `${ADDON_ID}/leave-item`,
|
|
16
|
+
/** manager → preview: the panel mounted and wants the current part list. */
|
|
17
|
+
PARTS_REQUEST: `${ADDON_ID}/parts-request`
|
|
18
|
+
};
|
|
19
|
+
export {
|
|
20
|
+
ADDON_ID,
|
|
21
|
+
EVENTS,
|
|
22
|
+
PANEL_ID,
|
|
23
|
+
PARAM_KEY
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/constants.ts"],
|
|
4
|
+
"sourcesContent": ["export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/** Channel events used to sync the manager panel with the preview iframe. */\nexport const EVENTS = {\n /** preview \u2192 manager: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 manager: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 manager: resolved part list for the current story. */\n PARTS: `${ADDON_ID}/parts`,\n /** manager \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** manager \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** manager \u2192 preview: the panel mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n"],
|
|
5
|
+
"mappings": ";AAAO,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AAGlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/manager.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// src/manager.tsx
|
|
2
|
+
import React2 from "react";
|
|
3
|
+
import { addons, types } from "storybook/manager-api";
|
|
4
|
+
import { AddonPanel } from "storybook/internal/components";
|
|
5
|
+
|
|
6
|
+
// src/constants.ts
|
|
7
|
+
var ADDON_ID = "component-anatomy";
|
|
8
|
+
var PANEL_ID = `${ADDON_ID}/panel`;
|
|
9
|
+
var PARAM_KEY = "anatomy";
|
|
10
|
+
var EVENTS = {
|
|
11
|
+
/** preview → manager: a part became active in the canvas (hover/programmatic). */
|
|
12
|
+
PART_ENTER: `${ADDON_ID}/part-enter`,
|
|
13
|
+
/** preview → manager: no part is active anymore. */
|
|
14
|
+
PART_LEAVE: `${ADDON_ID}/part-leave`,
|
|
15
|
+
/** preview → manager: resolved part list for the current story. */
|
|
16
|
+
PARTS: `${ADDON_ID}/parts`,
|
|
17
|
+
/** manager → preview: the user hovers/focuses a panel entry. */
|
|
18
|
+
HOVER_ITEM: `${ADDON_ID}/hover-item`,
|
|
19
|
+
/** manager → preview: the user left a panel entry. */
|
|
20
|
+
LEAVE_ITEM: `${ADDON_ID}/leave-item`,
|
|
21
|
+
/** manager → preview: the panel mounted and wants the current part list. */
|
|
22
|
+
PARTS_REQUEST: `${ADDON_ID}/parts-request`
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/Panel.tsx
|
|
26
|
+
import React, { useEffect, useState } from "react";
|
|
27
|
+
import { useChannel, useParameter, useStorybookApi } from "storybook/manager-api";
|
|
28
|
+
var ACCENT_FALLBACK = "#4f46e5";
|
|
29
|
+
var styles = {
|
|
30
|
+
container: {
|
|
31
|
+
padding: "12px 16px",
|
|
32
|
+
fontFamily: '"Nunito Sans", -apple-system, ".SFNSText-Regular", "San Francisco", BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
33
|
+
fontSize: 13,
|
|
34
|
+
lineHeight: 1.5
|
|
35
|
+
},
|
|
36
|
+
empty: {
|
|
37
|
+
color: "#6b7280"
|
|
38
|
+
},
|
|
39
|
+
code: {
|
|
40
|
+
fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
|
|
41
|
+
fontSize: "0.85em",
|
|
42
|
+
background: "rgba(0,0,0,0.06)",
|
|
43
|
+
borderRadius: 3,
|
|
44
|
+
padding: "1px 5px"
|
|
45
|
+
},
|
|
46
|
+
list: {
|
|
47
|
+
display: "flex",
|
|
48
|
+
flexDirection: "column",
|
|
49
|
+
gap: 2,
|
|
50
|
+
margin: 0,
|
|
51
|
+
padding: 0,
|
|
52
|
+
listStyle: "none"
|
|
53
|
+
},
|
|
54
|
+
entry: {
|
|
55
|
+
padding: "8px 10px",
|
|
56
|
+
borderRadius: 6,
|
|
57
|
+
border: "1px solid transparent",
|
|
58
|
+
cursor: "default",
|
|
59
|
+
outline: "none",
|
|
60
|
+
transition: "background 120ms ease, border-color 120ms ease"
|
|
61
|
+
},
|
|
62
|
+
header: {
|
|
63
|
+
display: "flex",
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
gap: 8
|
|
66
|
+
},
|
|
67
|
+
indicator: {
|
|
68
|
+
width: 8,
|
|
69
|
+
height: 8,
|
|
70
|
+
borderRadius: "50%",
|
|
71
|
+
border: "2px solid #d1d5db",
|
|
72
|
+
flexShrink: 0,
|
|
73
|
+
transition: "background 120ms ease, border-color 120ms ease"
|
|
74
|
+
},
|
|
75
|
+
name: {
|
|
76
|
+
fontWeight: 700,
|
|
77
|
+
flex: 1
|
|
78
|
+
},
|
|
79
|
+
id: {
|
|
80
|
+
fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
|
|
81
|
+
fontSize: 10,
|
|
82
|
+
padding: "1px 6px",
|
|
83
|
+
borderRadius: 4,
|
|
84
|
+
background: "rgba(0,0,0,0.05)",
|
|
85
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
86
|
+
color: "#6b7280",
|
|
87
|
+
flexShrink: 0
|
|
88
|
+
},
|
|
89
|
+
description: {
|
|
90
|
+
margin: "4px 0 0",
|
|
91
|
+
paddingLeft: 16,
|
|
92
|
+
color: "#6b7280"
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var Panel = () => {
|
|
96
|
+
const params = useParameter(PARAM_KEY, void 0);
|
|
97
|
+
const api = useStorybookApi();
|
|
98
|
+
const storyId = api.getUrlState().storyId;
|
|
99
|
+
const [discovered, setDiscovered] = useState([]);
|
|
100
|
+
const [activeId, setActiveId] = useState(null);
|
|
101
|
+
const emit = useChannel({
|
|
102
|
+
[EVENTS.PARTS]: ({ parts: parts2 }) => setDiscovered(parts2),
|
|
103
|
+
[EVENTS.PART_ENTER]: ({ partId }) => setActiveId(partId),
|
|
104
|
+
[EVENTS.PART_LEAVE]: () => setActiveId(null)
|
|
105
|
+
});
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
setDiscovered([]);
|
|
108
|
+
setActiveId(null);
|
|
109
|
+
emit(EVENTS.PARTS_REQUEST);
|
|
110
|
+
}, [storyId]);
|
|
111
|
+
const parts = params?.parts ?? discovered;
|
|
112
|
+
const accent = params?.theme?.accent ?? ACCENT_FALLBACK;
|
|
113
|
+
if (!params || params.disable) {
|
|
114
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.container }, /* @__PURE__ */ React.createElement("p", { style: styles.empty }, "No anatomy configured for this story. Add", " ", /* @__PURE__ */ React.createElement("code", { style: styles.code }, "parameters.anatomy"), " and annotate elements with", " ", /* @__PURE__ */ React.createElement("code", { style: styles.code }, 'data-part="name"'), "."));
|
|
115
|
+
}
|
|
116
|
+
if (parts.length === 0) {
|
|
117
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.container }, /* @__PURE__ */ React.createElement("p", { style: styles.empty }, "No parts found. Annotate elements in your story with", " ", /* @__PURE__ */ React.createElement("code", { style: styles.code }, 'data-part="name"'), " or pass", " ", /* @__PURE__ */ React.createElement("code", { style: styles.code }, "parameters.anatomy.parts"), "."));
|
|
118
|
+
}
|
|
119
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.container }, /* @__PURE__ */ React.createElement("ul", { style: styles.list, role: "list", "aria-label": "Anatomy parts" }, parts.map((part) => {
|
|
120
|
+
const active = activeId === part.id;
|
|
121
|
+
return /* @__PURE__ */ React.createElement(
|
|
122
|
+
"li",
|
|
123
|
+
{
|
|
124
|
+
key: part.id,
|
|
125
|
+
role: "listitem",
|
|
126
|
+
tabIndex: 0,
|
|
127
|
+
"aria-label": part.name,
|
|
128
|
+
style: {
|
|
129
|
+
...styles.entry,
|
|
130
|
+
background: active ? `color-mix(in srgb, ${accent} 7%, transparent)` : void 0,
|
|
131
|
+
borderColor: active ? `color-mix(in srgb, ${accent} 20%, transparent)` : "transparent"
|
|
132
|
+
},
|
|
133
|
+
onMouseEnter: () => emit(EVENTS.HOVER_ITEM, { partId: part.id }),
|
|
134
|
+
onMouseLeave: () => emit(EVENTS.LEAVE_ITEM),
|
|
135
|
+
onFocus: () => emit(EVENTS.HOVER_ITEM, { partId: part.id }),
|
|
136
|
+
onBlur: () => emit(EVENTS.LEAVE_ITEM)
|
|
137
|
+
},
|
|
138
|
+
/* @__PURE__ */ React.createElement("div", { style: styles.header }, /* @__PURE__ */ React.createElement(
|
|
139
|
+
"span",
|
|
140
|
+
{
|
|
141
|
+
"aria-hidden": "true",
|
|
142
|
+
style: {
|
|
143
|
+
...styles.indicator,
|
|
144
|
+
background: active ? accent : void 0,
|
|
145
|
+
borderColor: active ? accent : "#d1d5db"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
), /* @__PURE__ */ React.createElement("span", { style: { ...styles.name, color: active ? accent : void 0 } }, part.name), /* @__PURE__ */ React.createElement("code", { style: styles.id }, part.id)),
|
|
149
|
+
part.description && /* @__PURE__ */ React.createElement("p", { style: styles.description }, part.description)
|
|
150
|
+
);
|
|
151
|
+
})));
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/manager.tsx
|
|
155
|
+
addons.register(ADDON_ID, () => {
|
|
156
|
+
addons.add(PANEL_ID, {
|
|
157
|
+
type: types.PANEL,
|
|
158
|
+
title: "Anatomy",
|
|
159
|
+
paramKey: PARAM_KEY,
|
|
160
|
+
match: ({ viewMode }) => viewMode === "story",
|
|
161
|
+
render: ({ active }) => /* @__PURE__ */ React2.createElement(AddonPanel, { active: !!active }, /* @__PURE__ */ React2.createElement(Panel, null))
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/manager.tsx", "../src/constants.ts", "../src/Panel.tsx"],
|
|
4
|
+
"sourcesContent": ["import React from 'react';\nimport { addons, types } from 'storybook/manager-api';\nimport { AddonPanel } from 'storybook/internal/components';\n\nimport { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants.js';\nimport { Panel } from './Panel.js';\n\naddons.register(ADDON_ID, () => {\n addons.add(PANEL_ID, {\n type: types.PANEL,\n title: 'Anatomy',\n paramKey: PARAM_KEY,\n match: ({ viewMode }) => viewMode === 'story',\n render: ({ active }) => (\n <AddonPanel active={!!active}>\n <Panel />\n </AddonPanel>\n ),\n });\n});\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/** Channel events used to sync the manager panel with the preview iframe. */\nexport const EVENTS = {\n /** preview \u2192 manager: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 manager: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 manager: resolved part list for the current story. */\n PARTS: `${ADDON_ID}/parts`,\n /** manager \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** manager \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** manager \u2192 preview: the panel mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n", "import React, { useEffect, useState } from 'react';\nimport { useChannel, useParameter, useStorybookApi } from 'storybook/manager-api';\nimport type { AnatomyPartDefinition } from '@component-anatomy/core';\n\nimport { EVENTS, PARAM_KEY } from './constants.js';\nimport type { AnatomyParameters } from './types.js';\n\nconst ACCENT_FALLBACK = '#4f46e5';\n\nconst styles: Record<string, React.CSSProperties> = {\n container: {\n padding: '12px 16px',\n fontFamily:\n '\"Nunito Sans\", -apple-system, \".SFNSText-Regular\", \"San Francisco\", BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif',\n fontSize: 13,\n lineHeight: 1.5,\n },\n empty: {\n color: '#6b7280',\n },\n code: {\n fontFamily: 'ui-monospace, \"Cascadia Code\", \"Fira Mono\", monospace',\n fontSize: '0.85em',\n background: 'rgba(0,0,0,0.06)',\n borderRadius: 3,\n padding: '1px 5px',\n },\n list: {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n margin: 0,\n padding: 0,\n listStyle: 'none',\n },\n entry: {\n padding: '8px 10px',\n borderRadius: 6,\n border: '1px solid transparent',\n cursor: 'default',\n outline: 'none',\n transition: 'background 120ms ease, border-color 120ms ease',\n },\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n },\n indicator: {\n width: 8,\n height: 8,\n borderRadius: '50%',\n border: '2px solid #d1d5db',\n flexShrink: 0,\n transition: 'background 120ms ease, border-color 120ms ease',\n },\n name: {\n fontWeight: 700,\n flex: 1,\n },\n id: {\n fontFamily: 'ui-monospace, \"Cascadia Code\", \"Fira Mono\", monospace',\n fontSize: 10,\n padding: '1px 6px',\n borderRadius: 4,\n background: 'rgba(0,0,0,0.05)',\n border: '1px solid rgba(0,0,0,0.08)',\n color: '#6b7280',\n flexShrink: 0,\n },\n description: {\n margin: '4px 0 0',\n paddingLeft: 16,\n color: '#6b7280',\n },\n};\n\nexport const Panel: React.FC = () => {\n const params = useParameter<AnatomyParameters | undefined>(PARAM_KEY, undefined);\n const api = useStorybookApi();\n const storyId = api.getUrlState().storyId;\n\n const [discovered, setDiscovered] = useState<AnatomyPartDefinition[]>([]);\n const [activeId, setActiveId] = useState<string | null>(null);\n\n const emit = useChannel({\n [EVENTS.PARTS]: ({ parts }: { parts: AnatomyPartDefinition[] }) => setDiscovered(parts),\n [EVENTS.PART_ENTER]: ({ partId }: { partId: string }) => setActiveId(partId),\n [EVENTS.PART_LEAVE]: () => setActiveId(null),\n });\n\n // Ask the preview for the current part list on mount / story change.\n useEffect(() => {\n setDiscovered([]);\n setActiveId(null);\n emit(EVENTS.PARTS_REQUEST);\n }, [storyId]);\n\n const parts = params?.parts ?? discovered;\n const accent = params?.theme?.accent ?? ACCENT_FALLBACK;\n\n if (!params || params.disable) {\n return (\n <div style={styles.container}>\n <p style={styles.empty}>\n No anatomy configured for this story. Add{' '}\n <code style={styles.code}>parameters.anatomy</code> and annotate elements with{' '}\n <code style={styles.code}>data-part=\"name\"</code>.\n </p>\n </div>\n );\n }\n\n if (parts.length === 0) {\n return (\n <div style={styles.container}>\n <p style={styles.empty}>\n No parts found. Annotate elements in your story with{' '}\n <code style={styles.code}>data-part=\"name\"</code> or pass{' '}\n <code style={styles.code}>parameters.anatomy.parts</code>.\n </p>\n </div>\n );\n }\n\n return (\n <div style={styles.container}>\n <ul style={styles.list} role=\"list\" aria-label=\"Anatomy parts\">\n {parts.map((part) => {\n const active = activeId === part.id;\n return (\n <li\n key={part.id}\n role=\"listitem\"\n tabIndex={0}\n aria-label={part.name}\n style={{\n ...styles.entry,\n background: active ? `color-mix(in srgb, ${accent} 7%, transparent)` : undefined,\n borderColor: active ? `color-mix(in srgb, ${accent} 20%, transparent)` : 'transparent',\n }}\n onMouseEnter={() => emit(EVENTS.HOVER_ITEM, { partId: part.id })}\n onMouseLeave={() => emit(EVENTS.LEAVE_ITEM)}\n onFocus={() => emit(EVENTS.HOVER_ITEM, { partId: part.id })}\n onBlur={() => emit(EVENTS.LEAVE_ITEM)}\n >\n <div style={styles.header}>\n <span\n aria-hidden=\"true\"\n style={{\n ...styles.indicator,\n background: active ? accent : undefined,\n borderColor: active ? accent : '#d1d5db',\n }}\n />\n <span style={{ ...styles.name, color: active ? accent : undefined }}>\n {part.name}\n </span>\n <code style={styles.id}>{part.id}</code>\n </div>\n {part.description && <p style={styles.description}>{part.description}</p>}\n </li>\n );\n })}\n </ul>\n </div>\n );\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["React", "parts", "React"]
|
|
7
|
+
}
|
package/dist/preview.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/preview.ts
|
|
2
|
+
import { addons, useEffect } from "storybook/preview-api";
|
|
3
|
+
import { createAnatomy } from "@component-anatomy/core";
|
|
4
|
+
|
|
5
|
+
// src/constants.ts
|
|
6
|
+
var ADDON_ID = "component-anatomy";
|
|
7
|
+
var PANEL_ID = `${ADDON_ID}/panel`;
|
|
8
|
+
var PARAM_KEY = "anatomy";
|
|
9
|
+
var EVENTS = {
|
|
10
|
+
/** preview → manager: a part became active in the canvas (hover/programmatic). */
|
|
11
|
+
PART_ENTER: `${ADDON_ID}/part-enter`,
|
|
12
|
+
/** preview → manager: no part is active anymore. */
|
|
13
|
+
PART_LEAVE: `${ADDON_ID}/part-leave`,
|
|
14
|
+
/** preview → manager: resolved part list for the current story. */
|
|
15
|
+
PARTS: `${ADDON_ID}/parts`,
|
|
16
|
+
/** manager → preview: the user hovers/focuses a panel entry. */
|
|
17
|
+
HOVER_ITEM: `${ADDON_ID}/hover-item`,
|
|
18
|
+
/** manager → preview: the user left a panel entry. */
|
|
19
|
+
LEAVE_ITEM: `${ADDON_ID}/leave-item`,
|
|
20
|
+
/** manager → preview: the panel mounted and wants the current part list. */
|
|
21
|
+
PARTS_REQUEST: `${ADDON_ID}/parts-request`
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/preview.ts
|
|
25
|
+
var withComponentAnatomy = (storyFn, context) => {
|
|
26
|
+
const params = context.parameters?.[PARAM_KEY];
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!params || params.disable) return;
|
|
29
|
+
const channel = addons.getChannel();
|
|
30
|
+
const canvas = context.canvasElement;
|
|
31
|
+
if (!canvas) return;
|
|
32
|
+
const root = params.root ? canvas.querySelector(params.root) ?? canvas : canvas;
|
|
33
|
+
const controller = createAnatomy({
|
|
34
|
+
root,
|
|
35
|
+
parts: params.parts,
|
|
36
|
+
preset: params.preset,
|
|
37
|
+
theme: params.theme,
|
|
38
|
+
overlay: {
|
|
39
|
+
label: params.overlayLabel !== false,
|
|
40
|
+
padding: params.overlayPadding
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const announceParts = () => channel.emit(EVENTS.PARTS, { storyId: context.id, parts: controller.getParts() });
|
|
44
|
+
announceParts();
|
|
45
|
+
const offEnter = controller.on(
|
|
46
|
+
"part:enter",
|
|
47
|
+
(partId) => channel.emit(EVENTS.PART_ENTER, { partId })
|
|
48
|
+
);
|
|
49
|
+
const offLeave = controller.on(
|
|
50
|
+
"part:leave",
|
|
51
|
+
() => channel.emit(EVENTS.PART_LEAVE, {})
|
|
52
|
+
);
|
|
53
|
+
const onHoverItem = ({ partId }) => controller.highlight(partId);
|
|
54
|
+
const onLeaveItem = () => controller.unhighlight();
|
|
55
|
+
channel.on(EVENTS.HOVER_ITEM, onHoverItem);
|
|
56
|
+
channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
57
|
+
channel.on(EVENTS.PARTS_REQUEST, announceParts);
|
|
58
|
+
return () => {
|
|
59
|
+
channel.off(EVENTS.HOVER_ITEM, onHoverItem);
|
|
60
|
+
channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
61
|
+
channel.off(EVENTS.PARTS_REQUEST, announceParts);
|
|
62
|
+
offEnter();
|
|
63
|
+
offLeave();
|
|
64
|
+
controller.destroy();
|
|
65
|
+
};
|
|
66
|
+
}, [context.id]);
|
|
67
|
+
return storyFn();
|
|
68
|
+
};
|
|
69
|
+
var decorators = [withComponentAnatomy];
|
|
70
|
+
export {
|
|
71
|
+
decorators,
|
|
72
|
+
withComponentAnatomy
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/preview.ts", "../src/constants.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Preview-side (iframe) entry. Registers a global decorator that mounts a\n * component-anatomy controller over the story canvas and syncs hover state\n * with the manager panel over the addon channel.\n */\nimport { addons, useEffect } from 'storybook/preview-api';\nimport type { Renderer, PartialStoryFn, StoryContext } from 'storybook/internal/types';\nimport { createAnatomy } from '@component-anatomy/core';\n\nimport { EVENTS, PARAM_KEY } from './constants.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport const withComponentAnatomy = (\n storyFn: PartialStoryFn<Renderer>,\n context: StoryContext<Renderer>\n) => {\n const params = context.parameters?.[PARAM_KEY] as AnatomyParameters | undefined;\n\n useEffect(() => {\n if (!params || params.disable) return;\n\n const channel = addons.getChannel();\n const canvas = context.canvasElement as unknown as HTMLElement;\n if (!canvas) return;\n\n const root = params.root\n ? canvas.querySelector<HTMLElement>(params.root) ?? canvas\n : canvas;\n\n const controller = createAnatomy({\n root,\n parts: params.parts,\n preset: params.preset,\n theme: params.theme,\n overlay: {\n label: params.overlayLabel !== false,\n padding: params.overlayPadding,\n },\n });\n\n const announceParts = () =>\n channel.emit(EVENTS.PARTS, { storyId: context.id, parts: controller.getParts() });\n\n announceParts();\n\n const offEnter = controller.on('part:enter', (partId) =>\n channel.emit(EVENTS.PART_ENTER, { partId })\n );\n const offLeave = controller.on('part:leave', () =>\n channel.emit(EVENTS.PART_LEAVE, {})\n );\n\n const onHoverItem = ({ partId }: { partId: string }) => controller.highlight(partId);\n const onLeaveItem = () => controller.unhighlight();\n\n channel.on(EVENTS.HOVER_ITEM, onHoverItem);\n channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);\n // The panel may mount after the story rendered \u2014 let it ask for the list.\n channel.on(EVENTS.PARTS_REQUEST, announceParts);\n\n return () => {\n channel.off(EVENTS.HOVER_ITEM, onHoverItem);\n channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);\n channel.off(EVENTS.PARTS_REQUEST, announceParts);\n offEnter();\n offLeave();\n controller.destroy();\n };\n }, [context.id]);\n\n return storyFn();\n};\n\nexport const decorators = [withComponentAnatomy];\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/** Channel events used to sync the manager panel with the preview iframe. */\nexport const EVENTS = {\n /** preview \u2192 manager: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 manager: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 manager: resolved part list for the current story. */\n PARTS: `${ADDON_ID}/parts`,\n /** manager \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** manager \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** manager \u2192 preview: the panel mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n"],
|
|
5
|
+
"mappings": ";AAKA,SAAS,QAAQ,iBAAiB;AAElC,SAAS,qBAAqB;;;ACPvB,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AAGlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;;;ADRO,IAAM,uBAAuB,CAClC,SACA,YACG;AACH,QAAM,SAAS,QAAQ,aAAa,SAAS;AAE7C,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,QAAS;AAE/B,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAQ;AAEb,UAAM,OAAO,OAAO,OAChB,OAAO,cAA2B,OAAO,IAAI,KAAK,SAClD;AAEJ,UAAM,aAAa,cAAc;AAAA,MAC/B;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,QACP,OAAO,OAAO,iBAAiB;AAAA,QAC/B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,MACpB,QAAQ,KAAK,OAAO,OAAO,EAAE,SAAS,QAAQ,IAAI,OAAO,WAAW,SAAS,EAAE,CAAC;AAElF,kBAAc;AAEd,UAAM,WAAW,WAAW;AAAA,MAAG;AAAA,MAAc,CAAC,WAC5C,QAAQ,KAAK,OAAO,YAAY,EAAE,OAAO,CAAC;AAAA,IAC5C;AACA,UAAM,WAAW,WAAW;AAAA,MAAG;AAAA,MAAc,MAC3C,QAAQ,KAAK,OAAO,YAAY,CAAC,CAAC;AAAA,IACpC;AAEA,UAAM,cAAc,CAAC,EAAE,OAAO,MAA0B,WAAW,UAAU,MAAM;AACnF,UAAM,cAAc,MAAM,WAAW,YAAY;AAEjD,YAAQ,GAAG,OAAO,YAAY,WAAW;AACzC,YAAQ,GAAG,OAAO,YAAY,WAAW;AAEzC,YAAQ,GAAG,OAAO,eAAe,aAAa;AAE9C,WAAO,MAAM;AACX,cAAQ,IAAI,OAAO,YAAY,WAAW;AAC1C,cAAQ,IAAI,OAAO,YAAY,WAAW;AAC1C,cAAQ,IAAI,OAAO,eAAe,aAAa;AAC/C,eAAS;AACT,eAAS;AACT,iBAAW,QAAQ;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,QAAQ,EAAE,CAAC;AAEf,SAAO,QAAQ;AACjB;AAEO,IAAM,aAAa,CAAC,oBAAoB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@component-anatomy/storybook",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Storybook addon — interactive component anatomy panel synced with the story canvas",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Julien Déramond",
|
|
7
|
+
"homepage": "https://github.com/julien-deramond/component-anatomy#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/julien-deramond/component-anatomy",
|
|
11
|
+
"directory": "packages/storybook"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/julien-deramond/component-anatomy/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"storybook-addon",
|
|
18
|
+
"component-anatomy",
|
|
19
|
+
"design-system",
|
|
20
|
+
"documentation",
|
|
21
|
+
"anatomy",
|
|
22
|
+
"appearance"
|
|
23
|
+
],
|
|
24
|
+
"storybook": {
|
|
25
|
+
"displayName": "Component Anatomy",
|
|
26
|
+
"supportedFrameworks": [
|
|
27
|
+
"react",
|
|
28
|
+
"vue",
|
|
29
|
+
"angular",
|
|
30
|
+
"web-components",
|
|
31
|
+
"html"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"require": "./dist/index.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./manager": "./dist/manager.js",
|
|
42
|
+
"./preview": "./dist/preview.js",
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"main": "./dist/index.cjs",
|
|
46
|
+
"module": "./dist/index.js",
|
|
47
|
+
"types": "./dist/index.d.ts",
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"src",
|
|
51
|
+
"README.md"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@component-anatomy/core": "^0.0.1"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/react": "^18.3.3",
|
|
61
|
+
"esbuild": "^0.21.0",
|
|
62
|
+
"react": "^18.3.1",
|
|
63
|
+
"storybook": "^10.0.0",
|
|
64
|
+
"typescript": "^5.4.5"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"storybook": ">=9.0.0"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=18.0.0"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "node build.mjs",
|
|
74
|
+
"typecheck": "tsc --noEmit"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/Panel.tsx
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useChannel, useParameter, useStorybookApi } from 'storybook/manager-api';
|
|
3
|
+
import type { AnatomyPartDefinition } from '@component-anatomy/core';
|
|
4
|
+
|
|
5
|
+
import { EVENTS, PARAM_KEY } from './constants.js';
|
|
6
|
+
import type { AnatomyParameters } from './types.js';
|
|
7
|
+
|
|
8
|
+
const ACCENT_FALLBACK = '#4f46e5';
|
|
9
|
+
|
|
10
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
11
|
+
container: {
|
|
12
|
+
padding: '12px 16px',
|
|
13
|
+
fontFamily:
|
|
14
|
+
'"Nunito Sans", -apple-system, ".SFNSText-Regular", "San Francisco", BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
15
|
+
fontSize: 13,
|
|
16
|
+
lineHeight: 1.5,
|
|
17
|
+
},
|
|
18
|
+
empty: {
|
|
19
|
+
color: '#6b7280',
|
|
20
|
+
},
|
|
21
|
+
code: {
|
|
22
|
+
fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
|
|
23
|
+
fontSize: '0.85em',
|
|
24
|
+
background: 'rgba(0,0,0,0.06)',
|
|
25
|
+
borderRadius: 3,
|
|
26
|
+
padding: '1px 5px',
|
|
27
|
+
},
|
|
28
|
+
list: {
|
|
29
|
+
display: 'flex',
|
|
30
|
+
flexDirection: 'column',
|
|
31
|
+
gap: 2,
|
|
32
|
+
margin: 0,
|
|
33
|
+
padding: 0,
|
|
34
|
+
listStyle: 'none',
|
|
35
|
+
},
|
|
36
|
+
entry: {
|
|
37
|
+
padding: '8px 10px',
|
|
38
|
+
borderRadius: 6,
|
|
39
|
+
border: '1px solid transparent',
|
|
40
|
+
cursor: 'default',
|
|
41
|
+
outline: 'none',
|
|
42
|
+
transition: 'background 120ms ease, border-color 120ms ease',
|
|
43
|
+
},
|
|
44
|
+
header: {
|
|
45
|
+
display: 'flex',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
gap: 8,
|
|
48
|
+
},
|
|
49
|
+
indicator: {
|
|
50
|
+
width: 8,
|
|
51
|
+
height: 8,
|
|
52
|
+
borderRadius: '50%',
|
|
53
|
+
border: '2px solid #d1d5db',
|
|
54
|
+
flexShrink: 0,
|
|
55
|
+
transition: 'background 120ms ease, border-color 120ms ease',
|
|
56
|
+
},
|
|
57
|
+
name: {
|
|
58
|
+
fontWeight: 700,
|
|
59
|
+
flex: 1,
|
|
60
|
+
},
|
|
61
|
+
id: {
|
|
62
|
+
fontFamily: 'ui-monospace, "Cascadia Code", "Fira Mono", monospace',
|
|
63
|
+
fontSize: 10,
|
|
64
|
+
padding: '1px 6px',
|
|
65
|
+
borderRadius: 4,
|
|
66
|
+
background: 'rgba(0,0,0,0.05)',
|
|
67
|
+
border: '1px solid rgba(0,0,0,0.08)',
|
|
68
|
+
color: '#6b7280',
|
|
69
|
+
flexShrink: 0,
|
|
70
|
+
},
|
|
71
|
+
description: {
|
|
72
|
+
margin: '4px 0 0',
|
|
73
|
+
paddingLeft: 16,
|
|
74
|
+
color: '#6b7280',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const Panel: React.FC = () => {
|
|
79
|
+
const params = useParameter<AnatomyParameters | undefined>(PARAM_KEY, undefined);
|
|
80
|
+
const api = useStorybookApi();
|
|
81
|
+
const storyId = api.getUrlState().storyId;
|
|
82
|
+
|
|
83
|
+
const [discovered, setDiscovered] = useState<AnatomyPartDefinition[]>([]);
|
|
84
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
85
|
+
|
|
86
|
+
const emit = useChannel({
|
|
87
|
+
[EVENTS.PARTS]: ({ parts }: { parts: AnatomyPartDefinition[] }) => setDiscovered(parts),
|
|
88
|
+
[EVENTS.PART_ENTER]: ({ partId }: { partId: string }) => setActiveId(partId),
|
|
89
|
+
[EVENTS.PART_LEAVE]: () => setActiveId(null),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Ask the preview for the current part list on mount / story change.
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
setDiscovered([]);
|
|
95
|
+
setActiveId(null);
|
|
96
|
+
emit(EVENTS.PARTS_REQUEST);
|
|
97
|
+
}, [storyId]);
|
|
98
|
+
|
|
99
|
+
const parts = params?.parts ?? discovered;
|
|
100
|
+
const accent = params?.theme?.accent ?? ACCENT_FALLBACK;
|
|
101
|
+
|
|
102
|
+
if (!params || params.disable) {
|
|
103
|
+
return (
|
|
104
|
+
<div style={styles.container}>
|
|
105
|
+
<p style={styles.empty}>
|
|
106
|
+
No anatomy configured for this story. Add{' '}
|
|
107
|
+
<code style={styles.code}>parameters.anatomy</code> and annotate elements with{' '}
|
|
108
|
+
<code style={styles.code}>data-part="name"</code>.
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (parts.length === 0) {
|
|
115
|
+
return (
|
|
116
|
+
<div style={styles.container}>
|
|
117
|
+
<p style={styles.empty}>
|
|
118
|
+
No parts found. Annotate elements in your story with{' '}
|
|
119
|
+
<code style={styles.code}>data-part="name"</code> or pass{' '}
|
|
120
|
+
<code style={styles.code}>parameters.anatomy.parts</code>.
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div style={styles.container}>
|
|
128
|
+
<ul style={styles.list} role="list" aria-label="Anatomy parts">
|
|
129
|
+
{parts.map((part) => {
|
|
130
|
+
const active = activeId === part.id;
|
|
131
|
+
return (
|
|
132
|
+
<li
|
|
133
|
+
key={part.id}
|
|
134
|
+
role="listitem"
|
|
135
|
+
tabIndex={0}
|
|
136
|
+
aria-label={part.name}
|
|
137
|
+
style={{
|
|
138
|
+
...styles.entry,
|
|
139
|
+
background: active ? `color-mix(in srgb, ${accent} 7%, transparent)` : undefined,
|
|
140
|
+
borderColor: active ? `color-mix(in srgb, ${accent} 20%, transparent)` : 'transparent',
|
|
141
|
+
}}
|
|
142
|
+
onMouseEnter={() => emit(EVENTS.HOVER_ITEM, { partId: part.id })}
|
|
143
|
+
onMouseLeave={() => emit(EVENTS.LEAVE_ITEM)}
|
|
144
|
+
onFocus={() => emit(EVENTS.HOVER_ITEM, { partId: part.id })}
|
|
145
|
+
onBlur={() => emit(EVENTS.LEAVE_ITEM)}
|
|
146
|
+
>
|
|
147
|
+
<div style={styles.header}>
|
|
148
|
+
<span
|
|
149
|
+
aria-hidden="true"
|
|
150
|
+
style={{
|
|
151
|
+
...styles.indicator,
|
|
152
|
+
background: active ? accent : undefined,
|
|
153
|
+
borderColor: active ? accent : '#d1d5db',
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
<span style={{ ...styles.name, color: active ? accent : undefined }}>
|
|
157
|
+
{part.name}
|
|
158
|
+
</span>
|
|
159
|
+
<code style={styles.id}>{part.id}</code>
|
|
160
|
+
</div>
|
|
161
|
+
{part.description && <p style={styles.description}>{part.description}</p>}
|
|
162
|
+
</li>
|
|
163
|
+
);
|
|
164
|
+
})}
|
|
165
|
+
</ul>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const ADDON_ID = 'component-anatomy';
|
|
2
|
+
export const PANEL_ID = `${ADDON_ID}/panel`;
|
|
3
|
+
|
|
4
|
+
/** Story parameter key: `parameters.anatomy = { ... }` */
|
|
5
|
+
export const PARAM_KEY = 'anatomy';
|
|
6
|
+
|
|
7
|
+
/** Channel events used to sync the manager panel with the preview iframe. */
|
|
8
|
+
export const EVENTS = {
|
|
9
|
+
/** preview → manager: a part became active in the canvas (hover/programmatic). */
|
|
10
|
+
PART_ENTER: `${ADDON_ID}/part-enter`,
|
|
11
|
+
/** preview → manager: no part is active anymore. */
|
|
12
|
+
PART_LEAVE: `${ADDON_ID}/part-leave`,
|
|
13
|
+
/** preview → manager: resolved part list for the current story. */
|
|
14
|
+
PARTS: `${ADDON_ID}/parts`,
|
|
15
|
+
/** manager → preview: the user hovers/focuses a panel entry. */
|
|
16
|
+
HOVER_ITEM: `${ADDON_ID}/hover-item`,
|
|
17
|
+
/** manager → preview: the user left a panel entry. */
|
|
18
|
+
LEAVE_ITEM: `${ADDON_ID}/leave-item`,
|
|
19
|
+
/** manager → preview: the panel mounted and wants the current part list. */
|
|
20
|
+
PARTS_REQUEST: `${ADDON_ID}/parts-request`,
|
|
21
|
+
} as const;
|
package/src/index.ts
ADDED
package/src/manager.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { addons, types } from 'storybook/manager-api';
|
|
3
|
+
import { AddonPanel } from 'storybook/internal/components';
|
|
4
|
+
|
|
5
|
+
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants.js';
|
|
6
|
+
import { Panel } from './Panel.js';
|
|
7
|
+
|
|
8
|
+
addons.register(ADDON_ID, () => {
|
|
9
|
+
addons.add(PANEL_ID, {
|
|
10
|
+
type: types.PANEL,
|
|
11
|
+
title: 'Anatomy',
|
|
12
|
+
paramKey: PARAM_KEY,
|
|
13
|
+
match: ({ viewMode }) => viewMode === 'story',
|
|
14
|
+
render: ({ active }) => (
|
|
15
|
+
<AddonPanel active={!!active}>
|
|
16
|
+
<Panel />
|
|
17
|
+
</AddonPanel>
|
|
18
|
+
),
|
|
19
|
+
});
|
|
20
|
+
});
|
package/src/preview.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview-side (iframe) entry. Registers a global decorator that mounts a
|
|
3
|
+
* component-anatomy controller over the story canvas and syncs hover state
|
|
4
|
+
* with the manager panel over the addon channel.
|
|
5
|
+
*/
|
|
6
|
+
import { addons, useEffect } from 'storybook/preview-api';
|
|
7
|
+
import type { Renderer, PartialStoryFn, StoryContext } from 'storybook/internal/types';
|
|
8
|
+
import { createAnatomy } from '@component-anatomy/core';
|
|
9
|
+
|
|
10
|
+
import { EVENTS, PARAM_KEY } from './constants.js';
|
|
11
|
+
import type { AnatomyParameters } from './types.js';
|
|
12
|
+
|
|
13
|
+
export const withComponentAnatomy = (
|
|
14
|
+
storyFn: PartialStoryFn<Renderer>,
|
|
15
|
+
context: StoryContext<Renderer>
|
|
16
|
+
) => {
|
|
17
|
+
const params = context.parameters?.[PARAM_KEY] as AnatomyParameters | undefined;
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!params || params.disable) return;
|
|
21
|
+
|
|
22
|
+
const channel = addons.getChannel();
|
|
23
|
+
const canvas = context.canvasElement as unknown as HTMLElement;
|
|
24
|
+
if (!canvas) return;
|
|
25
|
+
|
|
26
|
+
const root = params.root
|
|
27
|
+
? canvas.querySelector<HTMLElement>(params.root) ?? canvas
|
|
28
|
+
: canvas;
|
|
29
|
+
|
|
30
|
+
const controller = createAnatomy({
|
|
31
|
+
root,
|
|
32
|
+
parts: params.parts,
|
|
33
|
+
preset: params.preset,
|
|
34
|
+
theme: params.theme,
|
|
35
|
+
overlay: {
|
|
36
|
+
label: params.overlayLabel !== false,
|
|
37
|
+
padding: params.overlayPadding,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const announceParts = () =>
|
|
42
|
+
channel.emit(EVENTS.PARTS, { storyId: context.id, parts: controller.getParts() });
|
|
43
|
+
|
|
44
|
+
announceParts();
|
|
45
|
+
|
|
46
|
+
const offEnter = controller.on('part:enter', (partId) =>
|
|
47
|
+
channel.emit(EVENTS.PART_ENTER, { partId })
|
|
48
|
+
);
|
|
49
|
+
const offLeave = controller.on('part:leave', () =>
|
|
50
|
+
channel.emit(EVENTS.PART_LEAVE, {})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const onHoverItem = ({ partId }: { partId: string }) => controller.highlight(partId);
|
|
54
|
+
const onLeaveItem = () => controller.unhighlight();
|
|
55
|
+
|
|
56
|
+
channel.on(EVENTS.HOVER_ITEM, onHoverItem);
|
|
57
|
+
channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
58
|
+
// The panel may mount after the story rendered — let it ask for the list.
|
|
59
|
+
channel.on(EVENTS.PARTS_REQUEST, announceParts);
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
channel.off(EVENTS.HOVER_ITEM, onHoverItem);
|
|
63
|
+
channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
64
|
+
channel.off(EVENTS.PARTS_REQUEST, announceParts);
|
|
65
|
+
offEnter();
|
|
66
|
+
offLeave();
|
|
67
|
+
controller.destroy();
|
|
68
|
+
};
|
|
69
|
+
}, [context.id]);
|
|
70
|
+
|
|
71
|
+
return storyFn();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const decorators = [withComponentAnatomy];
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnatomyPartDefinition,
|
|
3
|
+
AnatomyPresetName,
|
|
4
|
+
AnatomyTheme,
|
|
5
|
+
} from '@component-anatomy/core';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for the anatomy addon, set through story parameters:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* export const Primary: Story = {
|
|
12
|
+
* parameters: {
|
|
13
|
+
* anatomy: {
|
|
14
|
+
* parts: [
|
|
15
|
+
* { id: 'label', name: 'Label', description: 'The visible text.' },
|
|
16
|
+
* ],
|
|
17
|
+
* },
|
|
18
|
+
* },
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export type AnatomyParameters = {
|
|
23
|
+
/**
|
|
24
|
+
* Part definitions. If omitted, parts are auto-discovered from
|
|
25
|
+
* `data-part` attributes in the rendered story.
|
|
26
|
+
*/
|
|
27
|
+
parts?: AnatomyPartDefinition[];
|
|
28
|
+
/** Named visual preset for the canvas overlays. Default: 'default'. */
|
|
29
|
+
preset?: AnatomyPresetName;
|
|
30
|
+
/** Theme token overrides (applied on top of the preset). */
|
|
31
|
+
theme?: AnatomyTheme;
|
|
32
|
+
/** Show the floating name label chip in the canvas. Default: true. */
|
|
33
|
+
overlayLabel?: boolean;
|
|
34
|
+
/** Inflate highlight boxes by N pixels. Default: 0. */
|
|
35
|
+
overlayPadding?: number;
|
|
36
|
+
/**
|
|
37
|
+
* CSS selector to narrow the anatomy root inside the story canvas.
|
|
38
|
+
* Defaults to the whole canvas element.
|
|
39
|
+
*/
|
|
40
|
+
root?: string;
|
|
41
|
+
/** Disable the addon for this story. */
|
|
42
|
+
disable?: boolean;
|
|
43
|
+
};
|