bedrock-flows 0.7.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.
Files changed (85) hide show
  1. package/auth-schema.sql +8 -0
  2. package/bin/bedrock-flows.mjs +127 -0
  3. package/lib/setup.mjs +262 -0
  4. package/package.json +11 -0
  5. package/template/.storybook/main.js +46 -0
  6. package/template/.storybook/manager-head.html +963 -0
  7. package/template/.storybook/preview-head.html +35 -0
  8. package/template/.storybook/preview.js +23 -0
  9. package/template/CHANGELOG.md +236 -0
  10. package/template/README.md +26 -0
  11. package/template/apps/dashboard/index.html +15 -0
  12. package/template/apps/dashboard/package.json +22 -0
  13. package/template/apps/dashboard/src/App.module.css +1318 -0
  14. package/template/apps/dashboard/src/App.tsx +2716 -0
  15. package/template/apps/dashboard/src/auth-client.ts +17 -0
  16. package/template/apps/dashboard/src/changelog.tsx +92 -0
  17. package/template/apps/dashboard/src/index.css +86 -0
  18. package/template/apps/dashboard/src/main.tsx +15 -0
  19. package/template/apps/dashboard/src/theme.ts +31 -0
  20. package/template/apps/dashboard/src/vite-env.d.ts +4 -0
  21. package/template/apps/dashboard/vite.config.ts +48 -0
  22. package/template/apps/worker/.dev.vars.example +50 -0
  23. package/template/apps/worker/package.json +19 -0
  24. package/template/apps/worker/src/index.ts +295 -0
  25. package/template/apps/worker/tsconfig.json +11 -0
  26. package/template/apps/worker/wrangler.jsonc +29 -0
  27. package/template/bedrock.config.ts +16 -0
  28. package/template/design-system/README.md +97 -0
  29. package/template/design-system/starter-v1/components/button/component.css +42 -0
  30. package/template/design-system/starter-v1/components/button/danger.html +21 -0
  31. package/template/design-system/starter-v1/components/button/default.html +21 -0
  32. package/template/design-system/starter-v1/components/button/disabled.html +21 -0
  33. package/template/design-system/starter-v1/components/button/ghost.html +21 -0
  34. package/template/design-system/starter-v1/components/button/macro.njk +14 -0
  35. package/template/design-system/starter-v1/components/button/primary.html +21 -0
  36. package/template/design-system/starter-v1/components/button/variants.json +30 -0
  37. package/template/design-system/starter-v1/ds.json +3 -0
  38. package/template/design-system/starter-v1/global.css +52 -0
  39. package/template/design-system/starter-v1/style.css +107 -0
  40. package/template/gitignore +8 -0
  41. package/template/package.json +41 -0
  42. package/template/prototypes/F-001-hello/1-welcome.njk +30 -0
  43. package/template/prototypes/F-001-hello/2-form.njk +46 -0
  44. package/template/prototypes/F-001-hello/3-done.njk +29 -0
  45. package/template/prototypes/F-001-hello/meta.json +6 -0
  46. package/template/prototypes/_shared/_auth-gate.njk +54 -0
  47. package/template/prototypes/_shared/delivery.njk +43 -0
  48. package/template/prototypes/_shared/layout.njk +15 -0
  49. package/template/prototypes/_shared/screen.njk +1818 -0
  50. package/template/prototypes/_shared/wireflow.njk +4731 -0
  51. package/template/public/auth-gate.css +150 -0
  52. package/template/public/bedrock/color-inspector.js +284 -0
  53. package/template/public/bedrock/component-overlay.js +219 -0
  54. package/template/public/bedrock/data/bedrock-config.js +45 -0
  55. package/template/public/bedrock/font-size-overlay.js +590 -0
  56. package/template/public/bedrock/grid-overlay.js +379 -0
  57. package/template/public/bedrock/prototype-navigation.js +974 -0
  58. package/template/public/cmdk.js +146 -0
  59. package/template/public/ds-xray.css +112 -0
  60. package/template/public/ds-xray.js +271 -0
  61. package/template/public/favicon.svg +4 -0
  62. package/template/public/icons/bolt-fill.svg +3 -0
  63. package/template/public/icons/bolt.svg +3 -0
  64. package/template/public/icons/caret-down-fill.svg +3 -0
  65. package/template/public/icons/check-double.svg +4 -0
  66. package/template/public/icons/check.svg +3 -0
  67. package/template/public/icons/chevron-left.svg +3 -0
  68. package/template/public/icons/chevron-right.svg +3 -0
  69. package/template/public/icons/circle-info.svg +6 -0
  70. package/template/public/icons/grid.svg +6 -0
  71. package/template/public/icons/message-square-1.svg +3 -0
  72. package/template/public/icons/message-square.svg +3 -0
  73. package/template/public/icons/messages.svg +4 -0
  74. package/template/public/icons/options-horizontal.svg +5 -0
  75. package/template/public/icons/swatches.svg +6 -0
  76. package/template/public/icons/workflow.svg +6 -0
  77. package/template/public/lightbox.js +87 -0
  78. package/template/public/proto-chrome.css +596 -0
  79. package/template/public/screen-comments.css +723 -0
  80. package/template/public/wireflow-client.js +26 -0
  81. package/template/scripts/build-storybooks.mjs +8 -0
  82. package/template/scripts/dev-setup.mjs +15 -0
  83. package/template/scripts/generate-stories.mjs +12 -0
  84. package/template/scripts/generate-variants.mjs +22 -0
  85. package/template/tsconfig.base.json +19 -0
@@ -0,0 +1,150 @@
1
+ /* Shared auth-gate UI — THE single source for the sign-in/sign-up card.
2
+ Used by all three surfaces:
3
+ - dashboard SPA (apps/dashboard/src/App.tsx → <AuthGate/>)
4
+ - wireflow pages (prototypes/_shared/_auth-gate.njk via wireflow.njk)
5
+ - screen pages (prototypes/_shared/_auth-gate.njk via screen.njk)
6
+ Behaviour JS stays per-surface (each host has its own session plumbing);
7
+ markup + styling live here and in _auth-gate.njk. Change the gate's look
8
+ ONCE, here.
9
+
10
+ Self-contained --bfa-* tokens (light defaults, dark via the shared
11
+ data-theme override on <html> falling back to the OS preference) so the
12
+ gate renders identically over the dashboard, a wireflow canvas, or any
13
+ prototype's design system. The font is pinned for the same reason a DS
14
+ that styles bare headings would otherwise repaint the card. */
15
+ .bf-auth-gate {
16
+ --bfa-surface: #ffffff;
17
+ --bfa-surface-alt: #fafbfc;
18
+ --bfa-border: rgba(15, 23, 42, 0.15);
19
+ --bfa-text: #0f172a;
20
+ --bfa-text-muted: #64748b;
21
+ --bfa-accent: #0f172a;
22
+ --bfa-accent-fg: #ffffff;
23
+
24
+ position: fixed; inset: 0; z-index: 1000;
25
+ display: flex; align-items: center; justify-content: center;
26
+ padding: 24px;
27
+ background: rgba(15, 23, 42, 0.78);
28
+ backdrop-filter: blur(4px);
29
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
30
+ }
31
+ @media (prefers-color-scheme: dark) {
32
+ :root:not([data-theme='light']) .bf-auth-gate {
33
+ --bfa-surface: #0f172a;
34
+ --bfa-surface-alt: #111c33;
35
+ --bfa-border: rgba(255, 255, 255, 0.12);
36
+ --bfa-text: #f1f5f9;
37
+ --bfa-text-muted: #94a3b8;
38
+ --bfa-accent: #f1f5f9;
39
+ --bfa-accent-fg: #0f172a;
40
+ }
41
+ }
42
+ :root[data-theme='dark'] .bf-auth-gate {
43
+ --bfa-surface: #0f172a;
44
+ --bfa-surface-alt: #111c33;
45
+ --bfa-border: rgba(255, 255, 255, 0.12);
46
+ --bfa-text: #f1f5f9;
47
+ --bfa-text-muted: #94a3b8;
48
+ --bfa-accent: #f1f5f9;
49
+ --bfa-accent-fg: #0f172a;
50
+ }
51
+ .bf-auth-gate[hidden] { display: none !important; }
52
+ /* A DS that styles bare headings (display font, heading color) hits the
53
+ gate's heading directly — element-level rules beat inheritance. */
54
+ .bf-auth-gate h1, .bf-auth-gate h2, .bf-auth-gate h3,
55
+ .bf-auth-gate h4, .bf-auth-gate h5, .bf-auth-gate h6 {
56
+ font-family: inherit;
57
+ color: inherit;
58
+ }
59
+
60
+ .bf-auth-gate__card {
61
+ width: 100%; max-width: 360px;
62
+ background: var(--bfa-surface);
63
+ color: var(--bfa-text);
64
+ border: 1px solid var(--bfa-border);
65
+ border-radius: 12px;
66
+ box-shadow: 0 20px 60px rgba(15, 23, 42, 0.4);
67
+ padding: 24px;
68
+ }
69
+ .bf-auth-gate__title { margin: 0 0 4px; font-size: 16px; font-weight: 600; }
70
+ .bf-auth-gate__lede { margin: 0 0 16px; font-size: 13px; color: var(--bfa-text-muted); }
71
+
72
+ /* Errors are an alert banner at the top of the card (between the lede and
73
+ the form), not a small note under the submit button. */
74
+ .bf-auth-error {
75
+ margin: 0 0 12px;
76
+ padding: 10px 12px;
77
+ font-size: 13px;
78
+ line-height: 1.45;
79
+ color: #b91c1c;
80
+ background: #fef2f2;
81
+ border: 1px solid #fca5a5;
82
+ border-radius: 8px;
83
+ }
84
+
85
+ .bf-auth-form { display: flex; flex-direction: column; gap: 8px; }
86
+ .bf-auth-form[hidden],
87
+ .bf-auth-form__field[hidden] { display: none !important; }
88
+
89
+ .bf-auth-form__tabs { display: flex; gap: 4px; margin-bottom: 4px; }
90
+ .bf-auth-form__tab {
91
+ flex: 1; padding: 6px 10px;
92
+ background: transparent;
93
+ border: 1px solid var(--bfa-border);
94
+ color: var(--bfa-text-muted);
95
+ font: inherit; font-size: 12px; font-weight: 500;
96
+ border-radius: 6px; cursor: pointer;
97
+ }
98
+ .bf-auth-form__tab.is-active {
99
+ background: var(--bfa-accent);
100
+ color: var(--bfa-accent-fg);
101
+ border-color: var(--bfa-accent);
102
+ }
103
+
104
+ .bf-auth-form__google {
105
+ display: flex; align-items: center; justify-content: center; gap: 8px;
106
+ padding: 8px 12px;
107
+ background: var(--bfa-surface);
108
+ color: var(--bfa-text);
109
+ border: 1px solid var(--bfa-border);
110
+ border-radius: 6px;
111
+ font: inherit; font-size: 13px; font-weight: 500;
112
+ cursor: pointer;
113
+ }
114
+ .bf-auth-form__google:hover { background: var(--bfa-surface-alt); }
115
+ .bf-auth-form__google svg { display: block; }
116
+
117
+ .bf-auth-form__divider {
118
+ display: flex; align-items: center; gap: 8px;
119
+ color: var(--bfa-text-muted); font-size: 11px;
120
+ }
121
+ .bf-auth-form__divider::before,
122
+ .bf-auth-form__divider::after {
123
+ content: ''; flex: 1; height: 1px; background: var(--bfa-border);
124
+ }
125
+
126
+ /* Visible labels above the inputs — placeholder-only labels disappear the
127
+ moment you type. */
128
+ .bf-auth-form__field { display: flex; flex-direction: column; gap: 4px; }
129
+ .bf-auth-form__label { font-size: 12px; font-weight: 500; color: var(--bfa-text-muted); }
130
+
131
+ .bf-auth-form input {
132
+ padding: 7px 10px;
133
+ border: 1px solid var(--bfa-border);
134
+ border-radius: 6px;
135
+ font: inherit; font-size: 13px;
136
+ background: var(--bfa-surface-alt);
137
+ color: var(--bfa-text);
138
+ width: 100%;
139
+ box-sizing: border-box;
140
+ }
141
+
142
+ .bf-auth-form__submit {
143
+ padding: 8px 12px;
144
+ border: 1px solid var(--bfa-accent);
145
+ background: var(--bfa-accent);
146
+ color: var(--bfa-accent-fg);
147
+ font: inherit; font-size: 13px; font-weight: 500;
148
+ border-radius: 6px; cursor: pointer;
149
+ }
150
+ .bf-auth-form__submit:disabled { opacity: 0.5; cursor: not-allowed; }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Color Inspector Overlay
3
+ *
4
+ * Hover over any element to reveal its computed text color, background color,
5
+ * and any matching CSS custom property tokens (e.g. --color-neutral-800).
6
+ *
7
+ * Keyboard shortcut: Ctrl+Shift+C
8
+ * State persisted in localStorage.
9
+ */
10
+ (function () {
11
+ const STORAGE_KEY = 'colorOverlayEnabled';
12
+ const TOOLTIP_ID = 'color-overlay-tooltip';
13
+ const TOAST_ID = 'color-overlay-toast';
14
+ const HIGHLIGHT_CLASS = 'color-overlay-highlight';
15
+
16
+ let enabled = false;
17
+ let tooltip = null;
18
+ let tokenIndex = null;
19
+
20
+ function injectStyles() {
21
+ if (document.getElementById('color-overlay-styles')) return;
22
+ const style = document.createElement('style');
23
+ style.id = 'color-overlay-styles';
24
+ style.textContent = `
25
+ #${TOOLTIP_ID} {
26
+ position: fixed;
27
+ background: #1A1A2E;
28
+ color: white;
29
+ font-size: 11px;
30
+ font-family: monospace;
31
+ padding: 6px 8px;
32
+ border-radius: 4px;
33
+ z-index: 99999;
34
+ pointer-events: none;
35
+ white-space: nowrap;
36
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
37
+ opacity: 0;
38
+ transition: opacity 0.15s ease;
39
+ line-height: 1.5;
40
+ }
41
+ #${TOOLTIP_ID}.visible { opacity: 1; }
42
+ #${TOOLTIP_ID} .swatch {
43
+ display: inline-block;
44
+ width: 10px;
45
+ height: 10px;
46
+ border-radius: 2px;
47
+ vertical-align: -1px;
48
+ margin-right: 4px;
49
+ border: 1px solid rgba(255,255,255,0.2);
50
+ }
51
+ #${TOOLTIP_ID} .token {
52
+ color: #8ED6FB;
53
+ }
54
+ .${HIGHLIGHT_CLASS} {
55
+ outline: 1px dashed #8ED6FB !important;
56
+ outline-offset: 1px;
57
+ }
58
+ #${TOAST_ID} {
59
+ position: fixed;
60
+ bottom: 20px;
61
+ left: 50%;
62
+ transform: translateX(-50%) translateY(100px);
63
+ background: #1A1A2E;
64
+ color: white;
65
+ font-size: 13px;
66
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
67
+ padding: 10px 20px;
68
+ border-radius: 6px;
69
+ z-index: 100000;
70
+ pointer-events: none;
71
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
72
+ opacity: 0;
73
+ transition: transform 0.3s ease, opacity 0.3s ease;
74
+ }
75
+ #${TOAST_ID}.visible {
76
+ opacity: 1;
77
+ transform: translateX(-50%) translateY(0);
78
+ }
79
+ #${TOAST_ID} .toast-status--on { color: #4CAF50; font-weight: 600; }
80
+ #${TOAST_ID} .toast-status--off { color: #F44336; font-weight: 600; }
81
+ `;
82
+ document.head.appendChild(style);
83
+ }
84
+
85
+ function createTooltip() {
86
+ if (tooltip) return;
87
+ tooltip = document.createElement('div');
88
+ tooltip.id = TOOLTIP_ID;
89
+ document.body.appendChild(tooltip);
90
+ }
91
+
92
+ let toastTimeout = null;
93
+ function showToast(message, isOn) {
94
+ injectStyles();
95
+ let toast = document.getElementById(TOAST_ID);
96
+ if (!toast) {
97
+ toast = document.createElement('div');
98
+ toast.id = TOAST_ID;
99
+ document.body.appendChild(toast);
100
+ }
101
+ const cls = isOn ? 'toast-status--on' : 'toast-status--off';
102
+ const status = isOn ? 'ON' : 'OFF';
103
+ toast.innerHTML = `${message}: <span class="${cls}">${status}</span>`;
104
+ if (toastTimeout) clearTimeout(toastTimeout);
105
+ requestAnimationFrame(() => toast.classList.add('visible'));
106
+ toastTimeout = setTimeout(() => toast.classList.remove('visible'), 2000);
107
+ }
108
+
109
+ // Normalize any CSS color string to canonical rgb()/rgba() via the browser
110
+ function normalizeColor(str) {
111
+ if (!str) return null;
112
+ const tmp = document.createElement('div');
113
+ tmp.style.color = '';
114
+ tmp.style.color = str;
115
+ if (!tmp.style.color) return null;
116
+ document.body.appendChild(tmp);
117
+ const resolved = getComputedStyle(tmp).color;
118
+ document.body.removeChild(tmp);
119
+ return resolved;
120
+ }
121
+
122
+ // Build a reverse lookup map: normalized rgb string -> [token names]
123
+ function buildTokenIndex() {
124
+ const index = {};
125
+ const rootStyle = getComputedStyle(document.documentElement);
126
+ const seen = new Set();
127
+
128
+ for (const sheet of document.styleSheets) {
129
+ let rules;
130
+ try { rules = sheet.cssRules; } catch (e) { continue; }
131
+ if (!rules) continue;
132
+ for (const rule of rules) {
133
+ if (!rule.style || !rule.selectorText) continue;
134
+ if (rule.selectorText !== ':root' && rule.selectorText !== 'html') continue;
135
+ for (let i = 0; i < rule.style.length; i++) {
136
+ const name = rule.style[i];
137
+ if (!name.startsWith('--color') || seen.has(name)) continue;
138
+ seen.add(name);
139
+ const raw = rootStyle.getPropertyValue(name).trim();
140
+ if (!raw) continue;
141
+ const rgb = normalizeColor(raw);
142
+ if (!rgb) continue;
143
+ if (!index[rgb]) index[rgb] = [];
144
+ index[rgb].push(name);
145
+ }
146
+ }
147
+ }
148
+ return index;
149
+ }
150
+
151
+ function rgbToHex(rgb) {
152
+ const m = rgb.match(/\d+(\.\d+)?/g);
153
+ if (!m || m.length < 3) return null;
154
+ const [r, g, b, a] = m.map(Number);
155
+ const hex = '#' + [r, g, b].map(n => n.toString(16).padStart(2, '0')).join('').toUpperCase();
156
+ if (a !== undefined && a < 1) {
157
+ return hex + Math.round(a * 255).toString(16).padStart(2, '0').toUpperCase();
158
+ }
159
+ return hex;
160
+ }
161
+
162
+ function lookupTokens(rgb) {
163
+ if (!tokenIndex || !rgb) return [];
164
+ return tokenIndex[rgb] || [];
165
+ }
166
+
167
+ function formatEntry(label, rgb) {
168
+ if (!rgb || rgb === 'rgba(0, 0, 0, 0)' || rgb === 'transparent') return '';
169
+ const hex = rgbToHex(rgb);
170
+ const tokens = lookupTokens(rgb);
171
+ const swatch = `<span class="swatch" style="background:${rgb}"></span>`;
172
+ const tokenStr = tokens.length ? ` <span class="token">${tokens.join(', ')}</span>` : '';
173
+ return `${swatch}<strong>${label}</strong> ${hex || rgb}${tokenStr}`;
174
+ }
175
+
176
+ function handleMouseMove(e) {
177
+ if (!enabled || !tooltip) return;
178
+ const element = document.elementFromPoint(e.clientX, e.clientY);
179
+ if (!element || element === tooltip || element.closest('#protoNav')) {
180
+ tooltip.classList.remove('visible');
181
+ clearHighlight();
182
+ return;
183
+ }
184
+
185
+ clearHighlight();
186
+ element.classList.add(HIGHLIGHT_CLASS);
187
+
188
+ const cs = getComputedStyle(element);
189
+ const tag = element.tagName.toLowerCase();
190
+ const lines = [`<strong>&lt;${tag}&gt;</strong>`];
191
+ const colorLine = formatEntry('color:', cs.color);
192
+ if (colorLine) lines.push(colorLine);
193
+ const bgLine = formatEntry('bg:', cs.backgroundColor);
194
+ if (bgLine) lines.push(bgLine);
195
+
196
+ tooltip.innerHTML = lines.join('<br>');
197
+ tooltip.classList.add('visible');
198
+ positionTooltip(e.clientX, e.clientY);
199
+ }
200
+
201
+ function clearHighlight() {
202
+ document.querySelectorAll('.' + HIGHLIGHT_CLASS).forEach(el => {
203
+ el.classList.remove(HIGHLIGHT_CLASS);
204
+ });
205
+ }
206
+
207
+ function positionTooltip(x, y) {
208
+ if (!tooltip) return;
209
+ const rect = tooltip.getBoundingClientRect();
210
+ let posX = x + 15;
211
+ let posY = y + 15;
212
+ if (posX + rect.width > window.innerWidth) posX = x - rect.width - 10;
213
+ if (posY + rect.height > window.innerHeight) posY = y - rect.height - 10;
214
+ tooltip.style.left = posX + 'px';
215
+ tooltip.style.top = posY + 'px';
216
+ }
217
+
218
+ function enable() {
219
+ enabled = true;
220
+ injectStyles();
221
+ createTooltip();
222
+ if (!tokenIndex) tokenIndex = buildTokenIndex();
223
+ // Disable typography modes if active
224
+ if (window.TypographyOverlay) {
225
+ if (window.TypographyOverlay.isFontModeEnabled()) window.TypographyOverlay.disableFontMode(true);
226
+ if (window.TypographyOverlay.isDistanceModeEnabled()) window.TypographyOverlay.disableDistanceMode(true);
227
+ }
228
+ document.addEventListener('mousemove', handleMouseMove);
229
+ localStorage.setItem(STORAGE_KEY, 'true');
230
+ window.dispatchEvent(new CustomEvent('colorOverlayToggled', { detail: { enabled: true } }));
231
+ }
232
+
233
+ function disable(skipSave) {
234
+ enabled = false;
235
+ document.removeEventListener('mousemove', handleMouseMove);
236
+ if (tooltip) tooltip.classList.remove('visible');
237
+ clearHighlight();
238
+ if (!skipSave) localStorage.setItem(STORAGE_KEY, 'false');
239
+ window.dispatchEvent(new CustomEvent('colorOverlayToggled', { detail: { enabled: false } }));
240
+ }
241
+
242
+ function toggle() {
243
+ if (enabled) {
244
+ disable();
245
+ showToast('Color Inspector', false);
246
+ } else {
247
+ enable();
248
+ showToast('Color Inspector', true);
249
+ }
250
+ }
251
+
252
+ // Auto-disable if a typography mode turns on
253
+ window.addEventListener('typographyOverlayToggled', (e) => {
254
+ if (e.detail && e.detail.enabled && enabled) disable(true);
255
+ });
256
+
257
+ window.ColorOverlay = {
258
+ toggle: toggle,
259
+ enable: enable,
260
+ disable: disable,
261
+ isEnabled: function () { return enabled; }
262
+ };
263
+
264
+ document.addEventListener('keydown', function (e) {
265
+ if (e.ctrlKey && e.shiftKey && (e.key === 'C' || e.key === 'c')) {
266
+ // Avoid breaking copy
267
+ if (window.getSelection && window.getSelection().toString()) return;
268
+ e.preventDefault();
269
+ toggle();
270
+ }
271
+ });
272
+
273
+ function restore() {
274
+ if (localStorage.getItem(STORAGE_KEY) === 'true') enable();
275
+ }
276
+
277
+ if (document.readyState !== 'loading') {
278
+ restore();
279
+ } else {
280
+ document.addEventListener('DOMContentLoaded', restore);
281
+ }
282
+
283
+ console.log('[Color Inspector] Ready. Ctrl+Shift+C to toggle.');
284
+ })();
@@ -0,0 +1,219 @@
1
+ /**
2
+ * BEM Component Name Overlay Utility
3
+ *
4
+ * Displays the component name above elements with c-* classes.
5
+ * Supports two modes:
6
+ * - Top-level components (larger structural components)
7
+ * - Small components (atoms and molecules like icons, buttons, filter-chips)
8
+ * Toggle with Ctrl+Shift+B (top-level) or Ctrl+Shift+A (small components)
9
+ * State is persisted in localStorage
10
+ */
11
+ (function() {
12
+ const STORAGE_KEY_TOPLEVEL = 'bemOverlayTopLevel';
13
+ const STORAGE_KEY_ATOMS = 'bemOverlayAtoms';
14
+ const OVERLAY_CLASS = 'component-overlay-label';
15
+ const OVERLAY_CLASS_TOPLEVEL = 'component-overlay-label--toplevel';
16
+ const OVERLAY_CLASS_ATOM = 'component-overlay-label--atom';
17
+ const POSITIONED_CLASS = 'component-overlay-positioned';
18
+
19
+ // Small components - atoms and molecules (reusable UI elements)
20
+ const SMALL_COMPONENTS = [
21
+ 'icon', 'icon-list', 'progress-icon', 'progress-track', 'button', 'link', 'tag', 'tag-more', 'badge', 'chip', 'filter-chip',
22
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
23
+ 'input', 'checkbox', 'radio', 'radio-option', 'select', 'textarea',
24
+ 'label', 'required-marker', 'avatar', 'spinner', 'loader', 'divider',
25
+ 'tooltip', 'arrow', 'arrow-list', 'academy-reference', 'completion-summary'
26
+ ];
27
+
28
+ let topLevelVisible = false;
29
+ let atomsVisible = false;
30
+
31
+ // Inject styles once
32
+ function injectStyles() {
33
+ if (document.getElementById('component-overlay-styles')) return;
34
+
35
+ const style = document.createElement('style');
36
+ style.id = 'component-overlay-styles';
37
+ style.textContent = `
38
+ .${OVERLAY_CLASS} {
39
+ position: absolute;
40
+ top: 0;
41
+ left: 0;
42
+ color: white;
43
+ font-size: 10px;
44
+ font-family: monospace;
45
+ padding: 2px 6px;
46
+ z-index: 9999;
47
+ pointer-events: none;
48
+ line-height: 1.2;
49
+ white-space: nowrap;
50
+ border-radius: 0 0 3px 0;
51
+ }
52
+ .${OVERLAY_CLASS_TOPLEVEL} {
53
+ background: #E91E63;
54
+ }
55
+ .${OVERLAY_CLASS_ATOM} {
56
+ background: #9C27B0;
57
+ }
58
+ .${POSITIONED_CLASS} {
59
+ position: relative;
60
+ }
61
+ `;
62
+ document.head.appendChild(style);
63
+ }
64
+
65
+ // Check if a component is a small component (atom or molecule)
66
+ function isSmallComponent(name) {
67
+ return SMALL_COMPONENTS.includes(name.toLowerCase());
68
+ }
69
+
70
+ // Extract the first c-* class name from an element
71
+ function getComponentName(element) {
72
+ const classes = Array.from(element.classList);
73
+ for (const cls of classes) {
74
+ if (cls.startsWith('c-') && !cls.includes('__') && !cls.includes('--')) {
75
+ return cls.substring(2); // Remove 'c-' prefix
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
81
+ // Create an overlay label for a component
82
+ function createOverlay(element, name, type) {
83
+ const label = document.createElement('span');
84
+ const typeClass = type === 'atom' ? OVERLAY_CLASS_ATOM : OVERLAY_CLASS_TOPLEVEL;
85
+ label.className = `${OVERLAY_CLASS} ${typeClass}`;
86
+ label.textContent = name;
87
+ label.dataset.overlayType = type;
88
+
89
+ // Ensure parent is positioned for absolute positioning to work
90
+ const computedStyle = window.getComputedStyle(element);
91
+ if (computedStyle.position === 'static') {
92
+ element.classList.add(POSITIONED_CLASS);
93
+ }
94
+
95
+ element.appendChild(label);
96
+ }
97
+
98
+ // Refresh overlays based on current visibility states
99
+ function refreshOverlays() {
100
+ injectStyles();
101
+
102
+ // First, remove all existing overlays
103
+ const existingOverlays = document.querySelectorAll('.' + OVERLAY_CLASS);
104
+ existingOverlays.forEach(function(overlay) {
105
+ overlay.remove();
106
+ });
107
+
108
+ // Remove positioning class from all elements
109
+ const positioned = document.querySelectorAll('.' + POSITIONED_CLASS);
110
+ positioned.forEach(function(element) {
111
+ element.classList.remove(POSITIONED_CLASS);
112
+ });
113
+
114
+ // If nothing is visible, we're done
115
+ if (!topLevelVisible && !atomsVisible) return;
116
+
117
+ // Find all elements with c-* classes (component blocks, not elements or modifiers)
118
+ const allElements = document.querySelectorAll('[class*="c-"]');
119
+
120
+ allElements.forEach(function(element) {
121
+ const name = getComponentName(element);
122
+ if (name) {
123
+ const isSmall = isSmallComponent(name);
124
+ if (isSmall && atomsVisible) {
125
+ createOverlay(element, name, 'atom');
126
+ } else if (!isSmall && topLevelVisible) {
127
+ createOverlay(element, name, 'toplevel');
128
+ }
129
+ }
130
+ });
131
+ }
132
+
133
+ // Set top-level overlay state
134
+ function setTopLevelState(visible) {
135
+ topLevelVisible = visible;
136
+ localStorage.setItem(STORAGE_KEY_TOPLEVEL, visible ? 'true' : 'false');
137
+ refreshOverlays();
138
+
139
+ // Dispatch custom event for sync with prototype nav
140
+ window.dispatchEvent(new CustomEvent('bemOverlayToggled', {
141
+ detail: { topLevel: topLevelVisible, atoms: atomsVisible }
142
+ }));
143
+ }
144
+
145
+ // Set atoms overlay state
146
+ function setAtomsState(visible) {
147
+ atomsVisible = visible;
148
+ localStorage.setItem(STORAGE_KEY_ATOMS, visible ? 'true' : 'false');
149
+ refreshOverlays();
150
+
151
+ // Dispatch custom event for sync with prototype nav
152
+ window.dispatchEvent(new CustomEvent('bemOverlayToggled', {
153
+ detail: { topLevel: topLevelVisible, atoms: atomsVisible }
154
+ }));
155
+ }
156
+
157
+ // Toggle top-level overlays
158
+ function toggleTopLevel() {
159
+ setTopLevelState(!topLevelVisible);
160
+ console.log('[Component Overlay] Top-level: ' + (topLevelVisible ? 'ON' : 'OFF'));
161
+ }
162
+
163
+ // Toggle small component overlays
164
+ function toggleAtoms() {
165
+ setAtomsState(!atomsVisible);
166
+ console.log('[Component Overlay] Small components: ' + (atomsVisible ? 'ON' : 'OFF'));
167
+ }
168
+
169
+ // Expose global API for prototype navigation
170
+ window.BEMOverlay = {
171
+ toggleTopLevel: toggleTopLevel,
172
+ toggleAtoms: toggleAtoms,
173
+ showTopLevel: function() { setTopLevelState(true); },
174
+ hideTopLevel: function() { setTopLevelState(false); },
175
+ showAtoms: function() { setAtomsState(true); },
176
+ hideAtoms: function() { setAtomsState(false); },
177
+ isTopLevelVisible: function() { return topLevelVisible; },
178
+ isAtomsVisible: function() { return atomsVisible; },
179
+ // Legacy API for backwards compatibility
180
+ toggle: toggleTopLevel,
181
+ show: function() { setTopLevelState(true); },
182
+ hide: function() { setTopLevelState(false); },
183
+ isVisible: function() { return topLevelVisible || atomsVisible; }
184
+ };
185
+
186
+ // Listen for keyboard shortcuts
187
+ document.addEventListener('keydown', function(e) {
188
+ // Ctrl+Shift+B for top-level components
189
+ if (e.ctrlKey && e.shiftKey && (e.key === 'B' || e.key === 'b')) {
190
+ e.preventDefault();
191
+ toggleTopLevel();
192
+ }
193
+ // Ctrl+Shift+A for atoms
194
+ if (e.ctrlKey && e.shiftKey && (e.key === 'A' || e.key === 'a')) {
195
+ e.preventDefault();
196
+ toggleAtoms();
197
+ }
198
+ });
199
+
200
+ // Restore state from localStorage on page load
201
+ document.addEventListener('DOMContentLoaded', function() {
202
+ const savedTopLevel = localStorage.getItem(STORAGE_KEY_TOPLEVEL);
203
+ const savedAtoms = localStorage.getItem(STORAGE_KEY_ATOMS);
204
+
205
+ if (savedTopLevel === 'true') {
206
+ topLevelVisible = true;
207
+ }
208
+ if (savedAtoms === 'true') {
209
+ atomsVisible = true;
210
+ }
211
+
212
+ if (topLevelVisible || atomsVisible) {
213
+ refreshOverlays();
214
+ }
215
+ });
216
+
217
+ // Log availability on load
218
+ console.log('[Component Overlay] Ready. Ctrl+Shift+B for top-level, Ctrl+Shift+A for small components.');
219
+ })();
@@ -0,0 +1,45 @@
1
+ // Auto-generated by bedrock/generate-pages.js — do not edit manually
2
+ // Run: npm run generate
3
+ window.BEDROCK_CONFIG = {
4
+ "plugins": {
5
+ "viewport": true,
6
+ "componentOverlay": true,
7
+ "gridOverlay": true,
8
+ "typographyInspector": true,
9
+ "colorInspector": true,
10
+ "storybook": true
11
+ },
12
+ "breakpoints": [
13
+ {
14
+ "id": "full",
15
+ "label": "Full width",
16
+ "width": null
17
+ },
18
+ {
19
+ "id": "desktop",
20
+ "label": "Desktop",
21
+ "width": 1280
22
+ },
23
+ {
24
+ "id": "tablet-lg",
25
+ "label": "Large Tablet",
26
+ "width": 960
27
+ },
28
+ {
29
+ "id": "tablet",
30
+ "label": "Tablet",
31
+ "width": 720
32
+ },
33
+ {
34
+ "id": "mobile",
35
+ "label": "Mobile",
36
+ "width": 375,
37
+ "height": 812
38
+ }
39
+ ],
40
+ "storybookPorts": [
41
+ 6006,
42
+ 6007,
43
+ 6008
44
+ ]
45
+ };