@pxsig/storybook-addon 0.3.0 → 0.3.2

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/manager.mjs CHANGED
@@ -21,6 +21,8 @@
21
21
  import React from 'react';
22
22
  import { addons, experimental_getStatusStore, types, useStorybookState } from 'storybook/manager-api';
23
23
  import { AddonPanel } from 'storybook/internal/components';
24
+ import { useTheme } from 'storybook/theming';
25
+ import { buttonStyle, dashboardUrl, panelColors, themeMode } from './panel-style.mjs';
24
26
 
25
27
  const ADDON = 'pxsig';
26
28
  const PANEL = `${ADDON}/panel`;
@@ -121,20 +123,14 @@ addons.register(ADDON, () => {
121
123
  });
122
124
 
123
125
  const h = React.createElement;
124
- const btn = (label, onClick, disabled) =>
125
- h(
126
- 'button',
127
- {
128
- type: 'button',
129
- onClick,
130
- disabled,
131
- style: { font: 'inherit', fontSize: 12, padding: '4px 10px', borderRadius: 4, border: '1px solid rgba(128,128,128,.4)', background: 'transparent', color: 'inherit', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.5 : 1 },
132
- },
133
- label
134
- );
135
126
 
136
127
  function Panel() {
137
128
  const { storyId } = useStorybookState();
129
+ // Storybook's theme: the buttons follow it, and the inspector and dashboard open in its mode.
130
+ const theme = useTheme();
131
+ const mode = themeMode(theme);
132
+ const colors = panelColors(theme);
133
+ const btn = (label, onClick, disabled) => h('button', { type: 'button', onClick, disabled, style: buttonStyle(theme, disabled) }, label);
138
134
  const [, force] = React.useReducer((n) => n + 1, 0);
139
135
  React.useEffect(() => {
140
136
  bus.addEventListener('change', force);
@@ -146,17 +142,17 @@ function Panel() {
146
142
  if (!state.up)
147
143
  return h(
148
144
  'div',
149
- { style: { padding: 16, fontSize: 13, lineHeight: 1.6 } },
145
+ { style: { padding: 16, fontSize: 13, lineHeight: 1.6, color: colors.text } },
150
146
  h('strong', null, 'The pxsig server is not running.'),
151
147
  h('div', null, 'Start it from the repo root, then this panel connects on its own:'),
152
148
  h('code', { style: { display: 'block', marginTop: 8 } }, 'npx pxsig serve --watch')
153
149
  );
154
150
  return h(
155
151
  'div',
156
- { style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 } },
152
+ { style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, color: colors.text } },
157
153
  h(
158
154
  'div',
159
- { style: { display: 'flex', gap: 8, alignItems: 'center', padding: '6px 10px', borderBottom: '1px solid rgba(128,128,128,.2)', fontSize: 12 } },
155
+ { style: { display: 'flex', gap: 8, alignItems: 'center', padding: '6px 10px', borderBottom: `1px solid ${colors.border}`, fontSize: 12 } },
160
156
  h('strong', null, st ? `pxsig: ${st.status}` : 'pxsig: no result yet'),
161
157
  st ? h('span', { style: { opacity: 0.7 } }, st.entries.map((e) => `${e.mode} ${e.state}`).join(' · ')) : null,
162
158
  ig && ig.status !== 'ok'
@@ -173,12 +169,12 @@ function Panel() {
173
169
  state.running ? h('span', { style: { opacity: 0.7 } }, 'Checking…') : null,
174
170
  btn('Check story', () => post({ story: storyId }), state.running),
175
171
  btn('Check all', () => post({ all: true }), state.running),
176
- btn('Open dashboard', () => window.open(`${SERVER}/report/?${st?.project ? `project=${encodeURIComponent(st.project)}&` : ''}story=${encodeURIComponent(storyId)}#inspect=${encodeURIComponent(st?.entries[0]?.id ?? storyId)}`, '_blank'))
172
+ btn('Open dashboard', () => window.open(dashboardUrl(SERVER, { project: st?.project ?? null, story: storyId, hash: `inspect=${encodeURIComponent(st?.entries[0]?.id ?? storyId)}` }, mode), '_blank'))
177
173
  ),
178
174
  h('iframe', {
179
- key: `${storyId}`,
175
+ key: `${storyId}:${mode}`,
180
176
  title: 'pxsig inspector',
181
- src: `${SERVER}/report/?embed=1&story=${encodeURIComponent(storyId)}`,
177
+ src: dashboardUrl(SERVER, { embed: 1, story: storyId }, mode),
182
178
  style: { flex: 1, minHeight: 0, border: 0, width: '100%' },
183
179
  })
184
180
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pxsig/storybook-addon",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "pxsig in Storybook: a visual-regression panel and sidebar statuses, fed by the pxsig server.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,9 +17,13 @@
17
17
  "files": [
18
18
  "preset.cjs",
19
19
  "manager.mjs",
20
+ "panel-style.mjs",
20
21
  "README.md"
21
22
  ],
22
23
  "peerDependencies": {
23
24
  "storybook": ">=9"
25
+ },
26
+ "scripts": {
27
+ "test": "node --test test/*.test.mjs"
24
28
  }
25
29
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The panel's look, from Storybook's own theme (`useTheme()` from storybook/theming), so the
3
+ * toolbar and its buttons follow Storybook's light or dark theme, and the embedded inspector and
4
+ * the dashboard it opens start in the same one. Plain functions: no Storybook import here.
5
+ */
6
+
7
+ /** 'dark' or 'light': the theme's base, else read from its content background. */
8
+ export function themeMode(theme) {
9
+ if (theme?.base === 'dark' || theme?.base === 'light') return theme.base;
10
+ const bg = String(theme?.background?.content ?? theme?.appContentBg ?? '');
11
+ const m = bg.match(/^#([0-9a-f]{6})$/i);
12
+ if (!m) return 'light';
13
+ const n = Number.parseInt(m[1], 16);
14
+ const lum = 0.2126 * ((n >> 16) & 255) + 0.7152 * ((n >> 8) & 255) + 0.0722 * (n & 255);
15
+ return lum < 128 ? 'dark' : 'light';
16
+ }
17
+
18
+ /** Text and border colours for the panel, with neutral fallbacks for a theme that lacks them. */
19
+ export function panelColors(theme) {
20
+ return {
21
+ text: theme?.color?.defaultText ?? theme?.textColor ?? 'inherit',
22
+ muted: theme?.barTextColor ?? theme?.textMutedColor ?? 'inherit',
23
+ border: theme?.appBorderColor ?? 'rgba(128,128,128,.4)',
24
+ background: theme?.background?.content ?? theme?.appContentBg ?? 'transparent',
25
+ };
26
+ }
27
+
28
+ /** A toolbar button styled like Storybook's inputs in the current theme. */
29
+ export function buttonStyle(theme, disabled = false) {
30
+ const c = panelColors(theme);
31
+ return {
32
+ font: 'inherit',
33
+ fontSize: 12,
34
+ padding: '4px 10px',
35
+ borderRadius: theme?.input?.borderRadius ?? theme?.appBorderRadius ?? 4,
36
+ border: `1px solid ${theme?.input?.border ?? c.border}`,
37
+ background: theme?.input?.background ?? 'transparent',
38
+ color: theme?.input?.color ?? c.text,
39
+ cursor: disabled ? 'default' : 'pointer',
40
+ opacity: disabled ? 0.5 : 1,
41
+ };
42
+ }
43
+
44
+ /** A dashboard URL on `server` with Storybook's theme mode added (`theme=dark|light`). */
45
+ export function dashboardUrl(server, params, mode) {
46
+ const q = new URLSearchParams();
47
+ for (const [k, v] of Object.entries(params)) if (v != null && k !== 'hash') q.set(k, String(v));
48
+ q.set('theme', mode);
49
+ return `${server}/report/?${q.toString()}${params.hash ? `#${params.hash}` : ''}`;
50
+ }