@avocadostudio-ai/blocks 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.
Files changed (88) hide show
  1. package/LICENSE +201 -0
  2. package/dist/block-error-boundary.d.ts +18 -0
  3. package/dist/block-error-boundary.js +36 -0
  4. package/dist/blocks/_base.css +108 -0
  5. package/dist/blocks/_layout.css +47 -0
  6. package/dist/blocks/_shared.d.ts +24 -0
  7. package/dist/blocks/_shared.js +153 -0
  8. package/dist/blocks/_tokens.css +127 -0
  9. package/dist/blocks/banner/renderer.d.ts +2 -0
  10. package/dist/blocks/banner/renderer.js +21 -0
  11. package/dist/blocks/banner/styles.css +72 -0
  12. package/dist/blocks/block-image.d.ts +14 -0
  13. package/dist/blocks/block-image.js +10 -0
  14. package/dist/blocks/block-types.d.ts +2 -0
  15. package/dist/blocks/block-types.js +22 -0
  16. package/dist/blocks/card/renderer.d.ts +1 -0
  17. package/dist/blocks/card/renderer.js +11 -0
  18. package/dist/blocks/card/styles.css +94 -0
  19. package/dist/blocks/card-grid/renderer.d.ts +1 -0
  20. package/dist/blocks/card-grid/renderer.js +17 -0
  21. package/dist/blocks/card-grid/styles.css +38 -0
  22. package/dist/blocks/carousel/init.d.ts +8 -0
  23. package/dist/blocks/carousel/init.js +35 -0
  24. package/dist/blocks/carousel/renderer.d.ts +2 -0
  25. package/dist/blocks/carousel/renderer.js +19 -0
  26. package/dist/blocks/carousel/styles.css +136 -0
  27. package/dist/blocks/cta/renderer.d.ts +1 -0
  28. package/dist/blocks/cta/renderer.js +7 -0
  29. package/dist/blocks/cta/styles.css +111 -0
  30. package/dist/blocks/embed/renderer.d.ts +2 -0
  31. package/dist/blocks/embed/renderer.js +16 -0
  32. package/dist/blocks/embed/styles.css +58 -0
  33. package/dist/blocks/faq-accordion/renderer.d.ts +1 -0
  34. package/dist/blocks/faq-accordion/renderer.js +11 -0
  35. package/dist/blocks/faq-accordion/styles.css +68 -0
  36. package/dist/blocks/feature-grid/renderer.d.ts +1 -0
  37. package/dist/blocks/feature-grid/renderer.js +12 -0
  38. package/dist/blocks/feature-grid/styles.css +62 -0
  39. package/dist/blocks/footer/renderer.d.ts +1 -0
  40. package/dist/blocks/footer/renderer.js +19 -0
  41. package/dist/blocks/footer/styles.css +64 -0
  42. package/dist/blocks/gallery/renderer.d.ts +2 -0
  43. package/dist/blocks/gallery/renderer.js +21 -0
  44. package/dist/blocks/gallery/styles.css +59 -0
  45. package/dist/blocks/hero/renderer.d.ts +1 -0
  46. package/dist/blocks/hero/renderer.js +20 -0
  47. package/dist/blocks/hero/styles.css +137 -0
  48. package/dist/blocks/index.d.ts +5 -0
  49. package/dist/blocks/index.js +47 -0
  50. package/dist/blocks/quote/renderer.d.ts +2 -0
  51. package/dist/blocks/quote/renderer.js +9 -0
  52. package/dist/blocks/quote/styles.css +97 -0
  53. package/dist/blocks/rich-text/renderer.d.ts +1 -0
  54. package/dist/blocks/rich-text/renderer.js +15 -0
  55. package/dist/blocks/rich-text/styles.css +64 -0
  56. package/dist/blocks/site-header/renderer.d.ts +1 -0
  57. package/dist/blocks/site-header/renderer.js +64 -0
  58. package/dist/blocks/site-header/styles.css +345 -0
  59. package/dist/blocks/stats/renderer.d.ts +1 -0
  60. package/dist/blocks/stats/renderer.js +13 -0
  61. package/dist/blocks/stats/styles.css +64 -0
  62. package/dist/blocks/styles.css +23 -0
  63. package/dist/blocks/table/renderer.d.ts +2 -0
  64. package/dist/blocks/table/renderer.js +14 -0
  65. package/dist/blocks/table/styles.css +87 -0
  66. package/dist/blocks/tabs/init.d.ts +5 -0
  67. package/dist/blocks/tabs/init.js +38 -0
  68. package/dist/blocks/tabs/renderer.d.ts +2 -0
  69. package/dist/blocks/tabs/renderer.js +30 -0
  70. package/dist/blocks/tabs/styles.css +147 -0
  71. package/dist/blocks/testimonials/renderer.d.ts +1 -0
  72. package/dist/blocks/testimonials/renderer.js +15 -0
  73. package/dist/blocks/testimonials/styles.css +92 -0
  74. package/dist/blocks/two-column/renderer.d.ts +1 -0
  75. package/dist/blocks/two-column/renderer.js +41 -0
  76. package/dist/blocks/two-column/styles.css +83 -0
  77. package/dist/blocks/video/renderer.d.ts +2 -0
  78. package/dist/blocks/video/renderer.js +52 -0
  79. package/dist/blocks/video/styles.css +84 -0
  80. package/dist/blocks-hydrator.d.ts +5 -0
  81. package/dist/blocks-hydrator.js +15 -0
  82. package/dist/catalogue.d.ts +1 -0
  83. package/dist/catalogue.js +741 -0
  84. package/dist/index.d.ts +7 -0
  85. package/dist/index.js +5 -0
  86. package/dist/renderer.d.ts +12 -0
  87. package/dist/renderer.js +35 -0
  88. package/package.json +54 -0
@@ -0,0 +1,345 @@
1
+ .site-top-nav {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ padding: 0.75rem 20px;
6
+ background: var(--bg-100, #fff);
7
+ border-bottom: 1px solid var(--border, #e4e4e7);
8
+ position: sticky;
9
+ top: 0;
10
+ z-index: 100;
11
+ container-type: inline-size;
12
+ container-name: site-nav;
13
+ }
14
+
15
+ .site-top-nav-inner {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 0.25rem;
19
+ width: 100%;
20
+ margin: 0 auto;
21
+ }
22
+
23
+ .site-brand {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 0.5rem;
27
+ text-decoration: none;
28
+ color: var(--text-100, #18181b);
29
+ font-weight: 600;
30
+ font-size: 1.125rem;
31
+ flex-shrink: 0;
32
+ margin-right: 0.75rem;
33
+ }
34
+
35
+ .site-logo {
36
+ width: 38px;
37
+ height: 38px;
38
+ border-radius: var(--radius-logo, 8px);
39
+ object-fit: contain;
40
+ }
41
+
42
+ .site-brand-text {
43
+ white-space: nowrap;
44
+ }
45
+
46
+ /* Desktop nav */
47
+ .site-nav-links-desktop {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 0.125rem;
51
+ }
52
+
53
+ .site-nav-links-desktop > a {
54
+ padding: 0.375rem 0.75rem;
55
+ border-radius: var(--radius-btn, 6px);
56
+ text-decoration: none;
57
+ color: var(--text-300, #52525b);
58
+ font-size: 0.875rem;
59
+ font-weight: 500;
60
+ transition: color 0.15s, background 0.15s;
61
+ }
62
+
63
+ .site-nav-links-desktop > a:hover {
64
+ color: var(--text-100, #18181b);
65
+ }
66
+
67
+ .site-nav-links-desktop > a.is-active {
68
+ color: var(--text-100, #18181b);
69
+ font-weight: 600;
70
+ }
71
+
72
+ /* Desktop dropdown */
73
+ .site-nav-dropdown {
74
+ position: relative;
75
+ }
76
+
77
+ .site-nav-links .site-nav-dropdown-trigger {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: 2px;
81
+ padding: 2px 0;
82
+ border: none;
83
+ border-radius: 0;
84
+ background: transparent;
85
+ color: var(--brand);
86
+ font-size: 0.9375rem;
87
+ font-weight: 700;
88
+ font-family: inherit;
89
+ cursor: pointer;
90
+ transition: color 0.15s;
91
+ }
92
+
93
+ .site-nav-links .site-nav-dropdown-trigger:hover,
94
+ .site-nav-links .site-nav-dropdown-trigger.is-active {
95
+ color: var(--brand-hover, var(--brand));
96
+ }
97
+
98
+ .dark .site-nav-links .site-nav-dropdown-trigger {
99
+ color: #e2e8f0;
100
+ }
101
+
102
+ .site-nav-chevron {
103
+ transition: transform 0.2s;
104
+ flex-shrink: 0;
105
+ }
106
+
107
+ .site-nav-dropdown:hover .site-nav-chevron,
108
+ .site-nav-dropdown:focus-within .site-nav-chevron {
109
+ transform: rotate(180deg);
110
+ }
111
+
112
+ .site-nav-dropdown-menu {
113
+ display: none;
114
+ flex-direction: column;
115
+ position: absolute;
116
+ top: 100%;
117
+ left: 0;
118
+ min-width: 180px;
119
+ padding: 8px;
120
+ padding-top: 12px;
121
+ gap: 2px;
122
+ background: var(--bg-100, #fff);
123
+ border: 1px solid var(--border, #e4e4e7);
124
+ border-radius: var(--radius-dropdown, 8px);
125
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.06);
126
+ z-index: 200;
127
+ }
128
+
129
+ .site-nav-dropdown:hover .site-nav-dropdown-menu,
130
+ .site-nav-dropdown:focus-within .site-nav-dropdown-menu {
131
+ display: flex;
132
+ }
133
+
134
+ .site-nav-dropdown-menu a {
135
+ display: block;
136
+ padding: 10px 14px;
137
+ border-radius: var(--radius-btn, 6px);
138
+ text-decoration: none !important;
139
+ color: var(--text-300, #52525b);
140
+ font-size: 0.875rem;
141
+ font-weight: 500;
142
+ white-space: nowrap;
143
+ transition: background 0.12s, color 0.12s;
144
+ }
145
+
146
+ .site-nav-dropdown-menu a:hover {
147
+ background: var(--bg-200, #f4f4f5);
148
+ color: var(--text-100, #18181b);
149
+ }
150
+
151
+ .site-nav-dropdown-menu a.is-active {
152
+ background: var(--bg-200, #f4f4f5);
153
+ font-weight: 600;
154
+ }
155
+
156
+ /* Theme toggle */
157
+ .site-theme-toggle {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ width: 2rem;
162
+ height: 2rem;
163
+ padding: 0;
164
+ border: none;
165
+ border-radius: var(--radius-btn, 6px);
166
+ background: transparent;
167
+ color: var(--text-300, #52525b);
168
+ cursor: pointer;
169
+ flex-shrink: 0;
170
+ transition: color 0.15s, background 0.15s;
171
+ }
172
+
173
+ .site-theme-toggle:hover {
174
+ color: var(--text-100, #18181b);
175
+ background: var(--bg-200, #f4f4f5);
176
+ }
177
+
178
+ .site-theme-toggle-icon {
179
+ width: 1.125rem;
180
+ height: 1.125rem;
181
+ }
182
+
183
+ /* Mobile menu */
184
+ .site-mobile-menu {
185
+ display: none;
186
+ }
187
+
188
+ .site-mobile-menu-button {
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ width: 2.25rem;
193
+ height: 2.25rem;
194
+ padding: 0;
195
+ border: none;
196
+ border-radius: var(--radius-btn, 6px);
197
+ background: transparent;
198
+ color: var(--text-300, #52525b);
199
+ cursor: pointer;
200
+ }
201
+
202
+ .site-mobile-menu-button:hover {
203
+ background: var(--bg-200, #f4f4f5);
204
+ }
205
+
206
+ .burger-icon {
207
+ display: flex;
208
+ flex-direction: column;
209
+ gap: 4px;
210
+ width: 18px;
211
+ }
212
+
213
+ .burger-icon i {
214
+ display: block;
215
+ height: 2px;
216
+ background: currentColor;
217
+ border-radius: 1px;
218
+ transition: transform 0.2s, opacity 0.2s;
219
+ }
220
+
221
+ .site-mobile-menu.is-open .burger-icon i:nth-child(1) {
222
+ transform: translateY(6px) rotate(45deg);
223
+ }
224
+
225
+ .site-mobile-menu.is-open .burger-icon i:nth-child(2) {
226
+ opacity: 0;
227
+ }
228
+
229
+ .site-mobile-menu.is-open .burger-icon i:nth-child(3) {
230
+ transform: translateY(-6px) rotate(-45deg);
231
+ }
232
+
233
+ .site-nav-links-mobile {
234
+ position: absolute;
235
+ top: 100%;
236
+ right: 1rem;
237
+ display: flex;
238
+ flex-direction: column;
239
+ align-items: stretch;
240
+ min-width: 200px;
241
+ padding: 0.5rem;
242
+ background: var(--bg-100, #fff);
243
+ border: 1px solid var(--border, #e4e4e7);
244
+ border-radius: var(--radius-dropdown, 10px);
245
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.08);
246
+ z-index: 200;
247
+ }
248
+
249
+ .site-nav-links-mobile > a {
250
+ display: block;
251
+ padding: 0.5rem 0.75rem;
252
+ border-radius: var(--radius-btn, 6px);
253
+ text-decoration: none;
254
+ color: var(--text-200, #27272a);
255
+ font-size: 0.875rem;
256
+ font-weight: 500;
257
+ text-align: left;
258
+ }
259
+
260
+ .site-nav-links-mobile > a:hover,
261
+ .site-nav-links-mobile > a.is-active {
262
+ background: var(--bg-200, #f4f4f5);
263
+ }
264
+
265
+ /* Mobile dropdown group */
266
+ .site-nav-mobile-group {
267
+ display: flex;
268
+ flex-direction: column;
269
+ }
270
+
271
+ .site-nav-mobile-group-trigger {
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: space-between;
275
+ width: 100%;
276
+ padding: 0.5rem 0.75rem;
277
+ border: none;
278
+ border-radius: var(--radius-btn, 6px);
279
+ background: transparent;
280
+ color: var(--text-200, #27272a);
281
+ font-size: 0.875rem;
282
+ font-weight: 500;
283
+ font-family: inherit;
284
+ cursor: pointer;
285
+ text-align: left;
286
+ }
287
+
288
+ .site-nav-mobile-group-trigger:hover,
289
+ .site-nav-mobile-group-trigger.is-active {
290
+ background: var(--bg-200, #f4f4f5);
291
+ }
292
+
293
+ .site-nav-chevron.is-expanded {
294
+ transform: rotate(180deg);
295
+ }
296
+
297
+ .site-nav-mobile-group-children {
298
+ display: flex;
299
+ flex-direction: column;
300
+ margin-left: 1rem;
301
+ padding-left: 0.75rem;
302
+ border-left: 2px solid var(--border, #e4e4e7);
303
+ gap: 2px;
304
+ margin-top: 4px;
305
+ margin-bottom: 4px;
306
+ }
307
+
308
+ .site-nav-mobile-group-children a {
309
+ display: block;
310
+ padding: 8px 12px;
311
+ border-radius: var(--radius-btn, 6px);
312
+ text-decoration: none;
313
+ color: var(--text-300, #52525b);
314
+ font-size: 0.875rem;
315
+ font-weight: 500;
316
+ transition: background 0.12s, color 0.12s;
317
+ }
318
+
319
+ .site-nav-mobile-group-children a:hover,
320
+ .site-nav-mobile-group-children a.is-active {
321
+ background: var(--bg-200, #f4f4f5);
322
+ color: var(--text-100, #18181b);
323
+ }
324
+
325
+ .site-mobile-backdrop {
326
+ position: fixed;
327
+ inset: 0;
328
+ z-index: 99;
329
+ }
330
+
331
+ /* Responsive: hide desktop nav, show mobile menu when the header itself is
332
+ narrow. Uses a container query so the breakpoint tracks the header's own
333
+ width (e.g. inside the editor iframe) rather than the viewport. */
334
+ @container site-nav (max-width: 768px) {
335
+ .site-nav-links-desktop {
336
+ display: none;
337
+ }
338
+ .site-mobile-menu {
339
+ display: block;
340
+ margin-left: auto;
341
+ }
342
+ .site-theme-toggle {
343
+ margin-left: 0;
344
+ }
345
+ }
@@ -0,0 +1 @@
1
+ export declare function Stats(props: Record<string, unknown>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveHeadingTag } from "@avocadostudio-ai/shared";
3
+ import { renderBlockIcon } from "../_shared.js";
4
+ export function Stats(props) {
5
+ const title = String(props.title ?? "");
6
+ const items = Array.isArray(props.stats) ? props.stats : [];
7
+ const HeadingTag = resolveHeadingTag("Stats", props);
8
+ return (_jsx("section", { className: "stats-section", children: _jsxs("div", { className: "section__inner", children: [title.length > 0 && (_jsx(HeadingTag, { "data-editable-target": "title", "data-editable-target-label": "title", "data-editable-label": "title", children: title })), _jsx("div", { className: "stats-grid", children: items.map((item, idx) => {
9
+ const row = (item ?? {});
10
+ const description = typeof row.description === "string" ? row.description.trim() : "";
11
+ return (_jsxs("div", { className: "stat-item", children: [renderBlockIcon(row.icon, { className: "stat-item__icon", size: 32 }), _jsx("span", { className: "stat-item__value", "data-editable-target": `stats[${idx}].value`, "data-editable-target-label": `stats[${idx}].value`, "data-editable-label": `stats[${idx}].value`, children: String(row.value ?? "") }), _jsx("span", { className: "stat-item__label", "data-editable-target": `stats[${idx}].label`, "data-editable-target-label": `stats[${idx}].label`, "data-editable-label": `stats[${idx}].label`, children: String(row.label ?? "") }), description.length > 0 && (_jsx("span", { className: "stat-item__description", "data-editable-target": `stats[${idx}].description`, "data-editable-target-label": `stats[${idx}].description`, "data-editable-label": `stats[${idx}].description`, children: description }))] }, idx));
12
+ }) })] }) }));
13
+ }
@@ -0,0 +1,64 @@
1
+ .stats-section {
2
+ text-align: center;
3
+ padding: 40px 20px;
4
+ }
5
+
6
+ .stats-section .section__inner > h2 {
7
+ margin-bottom: 24px;
8
+ }
9
+
10
+ .stats-grid {
11
+ display: grid;
12
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
13
+ gap: 24px;
14
+ }
15
+
16
+ .stat-item {
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ gap: 4px;
21
+ }
22
+
23
+ .stat-item__icon {
24
+ display: block;
25
+ font-size: 1.75rem;
26
+ line-height: 1;
27
+ margin-bottom: 4px;
28
+ width: 32px;
29
+ height: 32px;
30
+ object-fit: contain;
31
+ }
32
+
33
+ img.stat-item__icon {
34
+ border-radius: 4px;
35
+ }
36
+
37
+ .stat-item__description {
38
+ font-size: 0.875rem;
39
+ color: var(--body-secondary);
40
+ max-width: 24ch;
41
+ text-align: center;
42
+ line-height: 1.45;
43
+ margin-top: 2px;
44
+ }
45
+
46
+ .stat-item__value {
47
+ font-size: clamp(2rem, 4vw, 3rem);
48
+ font-weight: 800;
49
+ line-height: 1.1;
50
+ letter-spacing: -0.02em;
51
+ color: var(--brand);
52
+ }
53
+
54
+ .stat-item__label {
55
+ font-size: 0.9375rem;
56
+ color: var(--caption);
57
+ font-weight: 500;
58
+ }
59
+
60
+ @media (max-width: 900px) {
61
+ .stats-grid {
62
+ grid-template-columns: 1fr;
63
+ }
64
+ }
@@ -0,0 +1,23 @@
1
+ @import "./_tokens.css";
2
+ @import "./_base.css";
3
+ @import "./_layout.css";
4
+ @import "./hero/styles.css";
5
+ @import "./feature-grid/styles.css";
6
+ @import "./testimonials/styles.css";
7
+ @import "./faq-accordion/styles.css";
8
+ @import "./cta/styles.css";
9
+ @import "./card/styles.css";
10
+ @import "./card-grid/styles.css";
11
+ @import "./rich-text/styles.css";
12
+ @import "./stats/styles.css";
13
+ @import "./two-column/styles.css";
14
+ @import "./site-header/styles.css";
15
+ @import "./footer/styles.css";
16
+ @import "./embed/styles.css";
17
+ @import "./banner/styles.css";
18
+ @import "./carousel/styles.css";
19
+ @import "./gallery/styles.css";
20
+ @import "./tabs/styles.css";
21
+ @import "./table/styles.css";
22
+ @import "./quote/styles.css";
23
+ @import "./video/styles.css";
@@ -0,0 +1,2 @@
1
+ import type { JSX } from "react";
2
+ export declare function Table(props: Record<string, unknown>): JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveHeadingTag } from "@avocadostudio-ai/shared";
3
+ import { renderInline } from "../_shared.js";
4
+ export function Table(props) {
5
+ const HeadingTag = resolveHeadingTag("Table", props);
6
+ const title = String(props.title ?? "");
7
+ const headers = Array.isArray(props.headers) ? props.headers.map(h => String(h ?? "")) : [];
8
+ const rows = Array.isArray(props.rows) ? props.rows : [];
9
+ const striped = String(props.striped) === "true";
10
+ return (_jsx("section", { className: "table-block", children: _jsxs("div", { className: "section__inner", children: [title.length > 0 && (_jsx(HeadingTag, { "data-editable-target": "title", "data-editable-target-label": "title", "data-editable-label": "title", children: renderInline(title) })), _jsx("div", { className: "table-block__scroll", children: _jsxs("table", { className: `table-block__table${striped ? " table-block__table--striped" : ""}`, children: [headers.length > 0 && (_jsx("thead", { children: _jsx("tr", { children: headers.map((header, idx) => (_jsx("th", { "data-editable-target": `headers[${idx}]`, children: renderInline(header) }, idx))) }) })), _jsx("tbody", { children: rows.map((rawRow, rowIdx) => {
11
+ const row = Array.isArray(rawRow) ? rawRow : [];
12
+ return (_jsx("tr", { children: row.map((rawCell, cellIdx) => (_jsx("td", { "data-editable-target": `rows[${rowIdx}][${cellIdx}]`, children: renderInline(String(rawCell ?? "")) }, cellIdx))) }, rowIdx));
13
+ }) })] }) })] }) }));
14
+ }
@@ -0,0 +1,87 @@
1
+ .table-block {
2
+ padding: 40px 20px;
3
+ background: var(--section-bg);
4
+ }
5
+
6
+ .table-block__scroll {
7
+ overflow-x: auto;
8
+ -webkit-overflow-scrolling: touch;
9
+ border-radius: var(--radius-card, 12px);
10
+ border: 1px solid var(--border);
11
+ }
12
+
13
+ .table-block__table {
14
+ width: 100%;
15
+ border-collapse: collapse;
16
+ font-size: 0.95rem;
17
+ line-height: 1.5;
18
+ }
19
+
20
+ .table-block__table th,
21
+ .table-block__table td {
22
+ padding: 12px 16px;
23
+ text-align: left;
24
+ border-bottom: 1px solid var(--border);
25
+ white-space: nowrap;
26
+ }
27
+
28
+ .table-block__table th {
29
+ font-weight: 600;
30
+ color: var(--heading);
31
+ background: var(--table-header-bg, color-mix(in srgb, var(--heading) 8%, var(--section-bg)));
32
+ font-size: 0.85rem;
33
+ text-transform: uppercase;
34
+ letter-spacing: 0.04em;
35
+ }
36
+
37
+ .table-block__table td {
38
+ color: var(--text-100);
39
+ }
40
+
41
+ .table-block__table tbody tr:last-child td {
42
+ border-bottom: none;
43
+ }
44
+
45
+ /* Striped variant */
46
+ .table-block__table--striped tbody tr:nth-child(odd) {
47
+ background: var(--section-bg);
48
+ }
49
+
50
+ .table-block__table--striped tbody tr:nth-child(even) {
51
+ background: var(--stripe-bg, color-mix(in srgb, var(--heading) 4%, var(--section-bg)));
52
+ }
53
+
54
+ /* First column — sticky + emphasis */
55
+ .table-block__table th:first-child {
56
+ position: sticky;
57
+ left: 0;
58
+ z-index: 1;
59
+ background: var(--table-header-bg, color-mix(in srgb, var(--heading) 8%, var(--section-bg)));
60
+ }
61
+
62
+ .table-block__table td:first-child {
63
+ position: sticky;
64
+ left: 0;
65
+ z-index: 1;
66
+ font-weight: 500;
67
+ color: var(--heading);
68
+ background: inherit;
69
+ }
70
+
71
+ @media (max-width: 600px) {
72
+ .table-block {
73
+ padding: 24px 12px;
74
+ }
75
+
76
+ .table-block__scroll {
77
+ margin-right: -12px;
78
+ border-right: none;
79
+ border-radius: var(--radius-card, 12px) 0 0 var(--radius-card, 12px);
80
+ }
81
+
82
+ .table-block__table th,
83
+ .table-block__table td {
84
+ padding: 10px 12px;
85
+ font-size: 0.9rem;
86
+ }
87
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Wire up tab switching, and overflow fade gradient for the tab bar.
3
+ * Safe to call multiple times — uses a data-ready guard.
4
+ */
5
+ export declare function initTabs(root?: Document | HTMLElement): void;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Wire up tab switching, and overflow fade gradient for the tab bar.
3
+ * Safe to call multiple times — uses a data-ready guard.
4
+ */
5
+ export function initTabs(root = document) {
6
+ root.querySelectorAll(".tabs-block:not([data-ready])").forEach((el) => {
7
+ el.setAttribute("data-ready", "1");
8
+ const tabButtons = el.querySelectorAll(".tabs-block__tab");
9
+ const panels = el.querySelectorAll(".tabs-block__panel");
10
+ const barWrap = el.querySelector(".tabs-block__bar-wrap");
11
+ const bar = el.querySelector(".tabs-block__bar");
12
+ // Tab switching
13
+ tabButtons.forEach((btn, i) => {
14
+ btn.onclick = () => {
15
+ tabButtons.forEach((b, j) => {
16
+ b.classList.toggle("tabs-block__tab--active", i === j);
17
+ b.setAttribute("aria-selected", i === j ? "true" : "false");
18
+ });
19
+ panels.forEach((p, j) => {
20
+ p.style.display = i === j ? "" : "none";
21
+ });
22
+ };
23
+ });
24
+ // Overflow fade gradient
25
+ if (barWrap && bar) {
26
+ const syncOverflow = () => {
27
+ const overflows = bar.scrollWidth > bar.clientWidth;
28
+ barWrap.classList.toggle("tabs-block__bar-wrap--overflow", overflows);
29
+ const atEnd = bar.scrollLeft + bar.clientWidth >= bar.scrollWidth - 2;
30
+ barWrap.classList.toggle("tabs-block__bar-wrap--at-end", atEnd);
31
+ };
32
+ syncOverflow();
33
+ bar.addEventListener("scroll", syncOverflow, { passive: true });
34
+ // Re-check on resize (e.g. viewport toggle in catalogue)
35
+ new ResizeObserver(syncOverflow).observe(bar);
36
+ }
37
+ });
38
+ }
@@ -0,0 +1,2 @@
1
+ import { type JSX } from "react";
2
+ export declare function Tabs(props: Record<string, unknown>): JSX.Element;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveHeadingTag } from "@avocadostudio-ai/shared";
3
+ import { renderRichTextContent } from "../_shared.js";
4
+ // Deterministic, SSR-stable instance id for the tab/panel aria wiring. We can't
5
+ // use useId() here: under the live-preview store the client re-renders blocks
6
+ // from a client-authoritative store, which shifts React's useId tree counter and
7
+ // trips a hydration mismatch (Tabs is the only block that needs per-instance
8
+ // ids). A pure hash of the tab content is identical on server and client, so the
9
+ // ids line up. Distinct Tabs blocks hash differently; the only collision is two
10
+ // blocks with identical titles + labels, which at worst duplicates aria ids.
11
+ function stableTabsUid(seed) {
12
+ let h = 5381;
13
+ for (let i = 0; i < seed.length; i += 1)
14
+ h = ((h << 5) + h + seed.charCodeAt(i)) | 0;
15
+ return (h >>> 0).toString(36);
16
+ }
17
+ export function Tabs(props) {
18
+ const title = String(props.title ?? "");
19
+ const tabs = Array.isArray(props.tabs) ? props.tabs : [];
20
+ const uid = `-${stableTabsUid(`${title}|${tabs.map((t) => String(t?.label ?? "")).join("|")}`)}`;
21
+ const HeadingTag = resolveHeadingTag("Tabs", props);
22
+ return (_jsx("section", { className: "tabs-block", children: _jsxs("div", { className: "section__inner", children: [title.length > 0 && (_jsx(HeadingTag, { className: "tabs-block__title", "data-editable-target": "title", "data-editable-target-label": "title", "data-editable-label": "title", children: title })), _jsx("div", { className: "tabs-block__bar-wrap", children: _jsx("div", { className: "tabs-block__bar", role: "tablist", children: tabs.map((raw, idx) => {
23
+ const tab = (raw ?? {});
24
+ return (_jsx("button", { id: `tabs-tab${uid}-${idx}`, className: `tabs-block__tab${idx === 0 ? " tabs-block__tab--active" : ""}`, role: "tab", type: "button", "aria-selected": idx === 0 ? "true" : "false", "aria-controls": `tabs-panel${uid}-${idx}`, "data-editable-target": `tabs[${idx}].label`, "data-editable-target-label": `tabs[${idx}].label`, "data-editable-label": `tabs[${idx}].label`, children: String(tab.label ?? "") }, idx));
25
+ }) }) }), tabs.map((raw, idx) => {
26
+ const tab = (raw ?? {});
27
+ const content = String(tab.content ?? "");
28
+ return (_jsx("div", { className: "tabs-block__panel", id: `tabs-panel${uid}-${idx}`, role: "tabpanel", "aria-labelledby": `tabs-tab${uid}-${idx}`, tabIndex: 0, style: idx === 0 ? undefined : { display: "none" }, "data-editable-target": `tabs[${idx}].content`, "data-editable-target-label": `tabs[${idx}].content`, "data-editable-label": `tabs[${idx}].content`, children: content.length > 0 && (_jsx("div", { className: "tabs-block__content", children: renderRichTextContent(content) })) }, idx));
29
+ })] }) }));
30
+ }