cursedbelt 5.28.0 → 5.30.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 (153) hide show
  1. package/dist/react/components/ColorField.d.ts.map +1 -1
  2. package/dist/react/components/ColorField.js +2 -1
  3. package/dist/react/components/ColorField.js.map +1 -1
  4. package/dist/react/components/ComboboxField.d.ts.map +1 -1
  5. package/dist/react/components/ComboboxField.js +2 -1
  6. package/dist/react/components/ComboboxField.js.map +1 -1
  7. package/dist/react/components/DataTable/DataTable.d.ts +39 -2
  8. package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
  9. package/dist/react/components/DataTable/DataTable.js +92 -22
  10. package/dist/react/components/DataTable/DataTable.js.map +1 -1
  11. package/dist/react/components/DataTable/ViewOptionsMenu.d.ts.map +1 -1
  12. package/dist/react/components/DataTable/ViewOptionsMenu.js +20 -7
  13. package/dist/react/components/DataTable/ViewOptionsMenu.js.map +1 -1
  14. package/dist/react/components/DataTable/types.d.ts +11 -1
  15. package/dist/react/components/DataTable/types.d.ts.map +1 -1
  16. package/dist/react/components/DataTable/types.js +6 -0
  17. package/dist/react/components/DataTable/types.js.map +1 -1
  18. package/dist/react/components/DatePicker.d.ts.map +1 -1
  19. package/dist/react/components/DatePicker.js +2 -1
  20. package/dist/react/components/DatePicker.js.map +1 -1
  21. package/dist/react/components/DateTimeField.d.ts.map +1 -1
  22. package/dist/react/components/DateTimeField.js +2 -1
  23. package/dist/react/components/DateTimeField.js.map +1 -1
  24. package/dist/react/components/ExpandableRow.d.ts +56 -0
  25. package/dist/react/components/ExpandableRow.d.ts.map +1 -0
  26. package/dist/react/components/ExpandableRow.js +54 -0
  27. package/dist/react/components/ExpandableRow.js.map +1 -0
  28. package/dist/react/components/Field.d.ts +20 -1
  29. package/dist/react/components/Field.d.ts.map +1 -1
  30. package/dist/react/components/Field.js +24 -11
  31. package/dist/react/components/Field.js.map +1 -1
  32. package/dist/react/components/FieldInput.d.ts +15 -0
  33. package/dist/react/components/FieldInput.d.ts.map +1 -1
  34. package/dist/react/components/FieldInput.js +20 -0
  35. package/dist/react/components/FieldInput.js.map +1 -1
  36. package/dist/react/components/FileDropZone.d.ts +23 -1
  37. package/dist/react/components/FileDropZone.d.ts.map +1 -1
  38. package/dist/react/components/FileDropZone.js +56 -8
  39. package/dist/react/components/FileDropZone.js.map +1 -1
  40. package/dist/react/components/FormPreviewLayout.d.ts +49 -0
  41. package/dist/react/components/FormPreviewLayout.d.ts.map +1 -0
  42. package/dist/react/components/FormPreviewLayout.js +36 -0
  43. package/dist/react/components/FormPreviewLayout.js.map +1 -0
  44. package/dist/react/components/Input.d.ts.map +1 -1
  45. package/dist/react/components/Input.js +3 -1
  46. package/dist/react/components/Input.js.map +1 -1
  47. package/dist/react/components/KeyBadge.d.ts +34 -0
  48. package/dist/react/components/KeyBadge.d.ts.map +1 -0
  49. package/dist/react/components/KeyBadge.js +24 -0
  50. package/dist/react/components/KeyBadge.js.map +1 -0
  51. package/dist/react/components/Modal.d.ts.map +1 -1
  52. package/dist/react/components/Modal.js +35 -9
  53. package/dist/react/components/Modal.js.map +1 -1
  54. package/dist/react/components/NativeSelect.d.ts.map +1 -1
  55. package/dist/react/components/NativeSelect.js +2 -1
  56. package/dist/react/components/NativeSelect.js.map +1 -1
  57. package/dist/react/components/RenderField.d.ts.map +1 -1
  58. package/dist/react/components/RenderField.js +2 -2
  59. package/dist/react/components/RenderField.js.map +1 -1
  60. package/dist/react/components/RevealAction.d.ts +33 -0
  61. package/dist/react/components/RevealAction.d.ts.map +1 -0
  62. package/dist/react/components/RevealAction.js +45 -0
  63. package/dist/react/components/RevealAction.js.map +1 -0
  64. package/dist/react/components/SecretInput.d.ts.map +1 -1
  65. package/dist/react/components/SecretInput.js +2 -1
  66. package/dist/react/components/SecretInput.js.map +1 -1
  67. package/dist/react/components/SelectField.d.ts.map +1 -1
  68. package/dist/react/components/SelectField.js +2 -1
  69. package/dist/react/components/SelectField.js.map +1 -1
  70. package/dist/react/components/SettingsStrip.d.ts +22 -0
  71. package/dist/react/components/SettingsStrip.d.ts.map +1 -0
  72. package/dist/react/components/SettingsStrip.js +18 -0
  73. package/dist/react/components/SettingsStrip.js.map +1 -0
  74. package/dist/react/components/formKit.css +555 -0
  75. package/dist/react/components/formKit.d.ts +12 -0
  76. package/dist/react/components/formKit.d.ts.map +1 -0
  77. package/dist/react/components/formKit.js +14 -0
  78. package/dist/react/components/formKit.js.map +1 -0
  79. package/dist/react/filter-rail/RailSection.d.ts +2 -2
  80. package/dist/react/filter-rail/RailSection.d.ts.map +1 -1
  81. package/dist/react/filter-rail/RailSection.js +6 -61
  82. package/dist/react/filter-rail/RailSection.js.map +1 -1
  83. package/dist/react/index.d.ts +8 -3
  84. package/dist/react/index.d.ts.map +1 -1
  85. package/dist/react/index.js +8 -3
  86. package/dist/react/index.js.map +1 -1
  87. package/dist/react/lib/field.d.ts +43 -0
  88. package/dist/react/lib/field.d.ts.map +1 -1
  89. package/dist/react/lib/field.js +68 -0
  90. package/dist/react/lib/field.js.map +1 -1
  91. package/dist/react/media/PlayerStrip.d.ts +9 -1
  92. package/dist/react/media/PlayerStrip.d.ts.map +1 -1
  93. package/dist/react/media/PlayerStrip.js +29 -2
  94. package/dist/react/media/PlayerStrip.js.map +1 -1
  95. package/dist/react/media-gallery/rememberedPreference.d.ts +2 -0
  96. package/dist/react/media-gallery/rememberedPreference.d.ts.map +1 -0
  97. package/dist/react/media-gallery/rememberedPreference.js +75 -0
  98. package/dist/react/media-gallery/rememberedPreference.js.map +1 -0
  99. package/dist/styles-areas/analytics.css +2 -2
  100. package/dist/styles-areas/core.css +34 -28
  101. package/dist/styles-areas/data-table.css +2 -2
  102. package/dist/styles-areas/fields.css +3 -3
  103. package/dist/styles-areas/layout-engine.css +2 -2
  104. package/dist/styles-areas/nav.css +3 -3
  105. package/dist/styles-areas/wizard.css +2 -2
  106. package/dist/styles-areas/workbench.css +2 -2
  107. package/dist/styles-utilities.css +111 -9
  108. package/package.json +14 -1
  109. package/scripts/styleAreas.ts +2 -0
  110. package/src/react/components/ColorField.tsx +3 -1
  111. package/src/react/components/ComboboxField.tsx +3 -1
  112. package/src/react/components/DataTable/DataTable.spec.tsx +18 -0
  113. package/src/react/components/DataTable/DataTable.tsx +136 -22
  114. package/src/react/components/DataTable/ViewOptionsMenu.tsx +24 -9
  115. package/src/react/components/DataTable/selection.spec.tsx +57 -1
  116. package/src/react/components/DataTable/types.ts +12 -1
  117. package/src/react/components/DataTable/virtualize.spec.tsx +20 -0
  118. package/src/react/components/DatePicker.tsx +3 -1
  119. package/src/react/components/DateTimeField.tsx +3 -1
  120. package/src/react/components/ExpandableRow.tsx +146 -0
  121. package/src/react/components/Field.tsx +89 -13
  122. package/src/react/components/Field.width.spec.tsx +162 -0
  123. package/src/react/components/FieldInput.tsx +30 -0
  124. package/src/react/components/FileDropZone.spec.tsx +65 -0
  125. package/src/react/components/FileDropZone.tsx +83 -5
  126. package/src/react/components/FormPreviewLayout.tsx +94 -0
  127. package/src/react/components/Input.tsx +4 -1
  128. package/src/react/components/KeyBadge.tsx +52 -0
  129. package/src/react/components/Modal.tsx +58 -14
  130. package/src/react/components/NativeSelect.tsx +3 -1
  131. package/src/react/components/RenderField.tsx +2 -1
  132. package/src/react/components/RevealAction.tsx +83 -0
  133. package/src/react/components/SecretInput.tsx +3 -1
  134. package/src/react/components/SelectField.tsx +3 -1
  135. package/src/react/components/SettingsStrip.tsx +37 -0
  136. package/src/react/components/formKit.css +555 -0
  137. package/src/react/components/formKit.spec.tsx +138 -0
  138. package/src/react/components/formKit.ts +19 -0
  139. package/src/react/filter-rail/RailSection.tsx +6 -65
  140. package/src/react/index.ts +20 -2
  141. package/src/react/lib/field.ts +93 -0
  142. package/src/react/media/PlayerStrip.tsx +65 -20
  143. package/src/react/media/playerStrip.spec.tsx +23 -0
  144. package/src/react/media-gallery/rememberedPreference.ts +79 -0
  145. package/src/styles-areas/analytics.css +2 -2
  146. package/src/styles-areas/core.css +34 -28
  147. package/src/styles-areas/data-table.css +2 -2
  148. package/src/styles-areas/fields.css +3 -3
  149. package/src/styles-areas/layout-engine.css +2 -2
  150. package/src/styles-areas/nav.css +3 -3
  151. package/src/styles-areas/wizard.css +2 -2
  152. package/src/styles-areas/workbench.css +2 -2
  153. package/src/styles-utilities.css +111 -9
@@ -0,0 +1,555 @@
1
+ /*
2
+ * The form kit's own look, as plain CSS — `ExpandableRow`, `SettingsStrip`, `RevealAction`,
3
+ * `KeyBadge` and `FormPreviewLayout`. Prefix `cbfk-`. Owner's cb-form review, 2026-09-30.
4
+ *
5
+ * 🔴 Plain CSS inside `@layer base`, for the reason `searchInput.css` gives at length: desk has
6
+ * no Tailwind and draws these from this file alone, while a Tailwind app's utilities sit in
7
+ * `@layer utilities` ABOVE `base`, so a `className` passed to any of these still wins there. An
8
+ * unlayered rule here would out-rank every utility in every app and could not be overridden.
9
+ *
10
+ * Tokens fall back to the plain names an app is most likely to bridge (`--border`,
11
+ * `--foreground`, `--muted-foreground`, `--muted`, `--card`, `--accent`, `--ring`,
12
+ * `--radius-sm`) — desk answers those on `.cs-root`, and `test/ui/belt-tokens.test.ts` there
13
+ * fails if a name this file reads is left unanswered.
14
+ *
15
+ * 🔴 Layout switches on the CONTAINER, not the viewport. A builder row lives in a 960px dialog
16
+ * on a desktop, in a 380px side panel on the same desktop, and in a phone sheet; only the width
17
+ * it was actually given says whether its controls fit on one line.
18
+ */
19
+
20
+ @layer base {
21
+ /* ── ExpandableRow ─────────────────────────────────────────────────────── */
22
+
23
+ .cbfk-row {
24
+ container-type: inline-size;
25
+ box-sizing: border-box;
26
+ min-width: 0;
27
+ border: 1px solid var(--border);
28
+ border-radius: var(--radius-md, var(--radius-sm, 0.5rem));
29
+ background-color: var(--card, var(--background));
30
+ color: var(--foreground);
31
+ transition: border-color 120ms ease;
32
+ }
33
+
34
+ .cbfk-row[data-expanded="true"] {
35
+ border-color: var(--border-strong, var(--border));
36
+ }
37
+
38
+ .cbfk-row-head {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 0.5rem;
42
+ min-height: 2.75rem;
43
+ padding: 0.375rem 0.5rem 0.375rem 0.5rem;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .cbfk-row-lead {
48
+ display: inline-flex;
49
+ flex: none;
50
+ align-items: center;
51
+ color: var(--muted-foreground);
52
+ }
53
+
54
+ .cbfk-row-title {
55
+ display: flex;
56
+ flex: 1 1 auto;
57
+ align-items: center;
58
+ min-width: 0;
59
+ font-size: 0.875rem;
60
+ font-weight: 500;
61
+ line-height: 1.25rem;
62
+ }
63
+
64
+ /* A title that IS an input (the builder's name field) fills its slot. */
65
+ .cbfk-row-title > input,
66
+ .cbfk-row-title > textarea {
67
+ flex: 1 1 auto;
68
+ min-width: 0;
69
+ width: 100%;
70
+ }
71
+
72
+ .cbfk-row-summary {
73
+ display: inline-flex;
74
+ flex: none;
75
+ flex-wrap: wrap;
76
+ align-items: center;
77
+ gap: 0.25rem;
78
+ color: var(--muted-foreground);
79
+ font-size: 0.75rem;
80
+ }
81
+
82
+ /* The type indicator is secondary metadata confirming what the row is, never a heavy
83
+ box competing with the name — so a control put here loses its border weight until it
84
+ is hovered or focused, and reads in the muted tone. */
85
+ .cbfk-row-meta {
86
+ display: inline-flex;
87
+ flex: none;
88
+ align-items: center;
89
+ min-width: 0;
90
+ color: var(--muted-foreground);
91
+ font-size: 0.8125rem;
92
+ }
93
+
94
+ .cbfk-row-meta > select,
95
+ .cbfk-row-meta > input {
96
+ border-color: transparent;
97
+ background-color: transparent;
98
+ color: inherit;
99
+ transition:
100
+ border-color 120ms ease,
101
+ background-color 120ms ease;
102
+ }
103
+
104
+ .cbfk-row-meta > select:hover,
105
+ .cbfk-row-meta > select:focus-visible,
106
+ .cbfk-row-meta > input:hover,
107
+ .cbfk-row-meta > input:focus-visible {
108
+ border-color: var(--border-strong, var(--border));
109
+ background-color: var(--background);
110
+ color: var(--foreground);
111
+ }
112
+
113
+ .cbfk-row-actions {
114
+ display: inline-flex;
115
+ flex: none;
116
+ align-items: center;
117
+ gap: 0.125rem;
118
+ }
119
+
120
+ .cbfk-row-toggle {
121
+ display: inline-flex;
122
+ flex: none;
123
+ align-items: center;
124
+ justify-content: center;
125
+ box-sizing: border-box;
126
+ width: 2rem;
127
+ height: 2rem;
128
+ margin: 0;
129
+ padding: 0;
130
+ border: 0;
131
+ border-radius: var(--radius-sm, 0.375rem);
132
+ background: transparent;
133
+ color: var(--muted-foreground);
134
+ cursor: pointer;
135
+ }
136
+
137
+ .cbfk-row-toggle:hover {
138
+ background-color: var(--muted, transparent);
139
+ color: var(--foreground);
140
+ }
141
+
142
+ .cbfk-row-toggle:focus-visible {
143
+ outline: 2px solid var(--ring);
144
+ outline-offset: 1px;
145
+ }
146
+
147
+ .cbfk-row-toggle svg {
148
+ width: 1rem;
149
+ height: 1rem;
150
+ transition: transform 160ms ease;
151
+ }
152
+
153
+ .cbfk-row[data-expanded="true"] .cbfk-row-toggle svg {
154
+ transform: rotate(180deg);
155
+ }
156
+
157
+ /* The settings panel — indented so it sits physically UNDER the title it belongs to,
158
+ one step quieter than the row, and separated by a hairline rather than a second box. */
159
+ .cbfk-row-panel {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 0.75rem;
163
+ padding: 0.75rem 0.75rem 0.875rem var(--cbfk-indent, 0.75rem);
164
+ border-top: 1px solid var(--border-subtle, var(--border));
165
+ animation: cbfk-reveal 160ms ease-out;
166
+ }
167
+
168
+ .cbfk-row[data-has-lead="true"] {
169
+ --cbfk-indent: 2.25rem;
170
+ }
171
+
172
+ @keyframes cbfk-reveal {
173
+ from {
174
+ opacity: 0;
175
+ transform: translateY(-4px);
176
+ }
177
+ to {
178
+ opacity: 1;
179
+ transform: none;
180
+ }
181
+ }
182
+
183
+ /* Thumb targets on a touch screen (DESIGN.md §5): the toggle's reach grows to 44px. */
184
+ @media (pointer: coarse) {
185
+ .cbfk-row-toggle {
186
+ width: 2.75rem;
187
+ height: 2.75rem;
188
+ }
189
+ }
190
+
191
+ /*
192
+ * Narrow: one line cannot hold a handle, a name, a type picker and three actions, so the
193
+ * row becomes a two-line block — handle, name and chevron on line one; the type under the
194
+ * name with the utilities at its right, where a thumb reaches them. Two lines, not four:
195
+ * a collapsed list on a phone still has to show several rows at once.
196
+ */
197
+ @container (max-width: 30rem) {
198
+ .cbfk-row-head {
199
+ display: grid;
200
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
201
+ grid-template-areas:
202
+ "lead title title toggle"
203
+ ". meta summary actions";
204
+ row-gap: 0.25rem;
205
+ align-items: center;
206
+ }
207
+
208
+ .cbfk-row-lead {
209
+ grid-area: lead;
210
+ }
211
+
212
+ .cbfk-row-title {
213
+ grid-area: title;
214
+ }
215
+
216
+ .cbfk-row-toggle {
217
+ grid-area: toggle;
218
+ }
219
+
220
+ .cbfk-row-meta {
221
+ grid-area: meta;
222
+ }
223
+
224
+ .cbfk-row-summary {
225
+ grid-area: summary;
226
+ }
227
+
228
+ .cbfk-row-actions {
229
+ grid-area: actions;
230
+ justify-content: flex-end;
231
+ }
232
+
233
+ .cbfk-row-panel {
234
+ padding-left: 0.75rem;
235
+ }
236
+ }
237
+
238
+ /* ── SettingsStrip ─────────────────────────────────────────────────────── */
239
+
240
+ /* Secondary configuration — a required tick, an example value — clustered on one slightly
241
+ darker strip so it reads as belonging together and apart from the row's identity. */
242
+ .cbfk-strip {
243
+ display: flex;
244
+ flex-wrap: wrap;
245
+ align-items: center;
246
+ gap: 0.5rem 1rem;
247
+ box-sizing: border-box;
248
+ min-inline-size: 0;
249
+ margin: 0;
250
+ padding: 0.5rem 0.75rem;
251
+ border: 0;
252
+ border-radius: var(--radius-sm, 0.375rem);
253
+ background-color: var(--surface-sunken, var(--muted));
254
+ color: var(--muted-foreground);
255
+ font-size: 0.8125rem;
256
+ }
257
+
258
+ .cbfk-strip-label {
259
+ flex-basis: 100%;
260
+ margin: 0;
261
+ color: var(--muted-foreground);
262
+ font-size: 0.75rem;
263
+ font-weight: 500;
264
+ }
265
+
266
+ /* An input on the strip is sized to a typical short value, never edge to edge. */
267
+ .cbfk-strip > input:not([type="checkbox"]):not([type="radio"]),
268
+ .cbfk-strip > select {
269
+ /* border-box, or a host with no preflight caps the CONTENT at 28rem and the box overshoots. */
270
+ box-sizing: border-box;
271
+ flex: 1 1 14rem;
272
+ max-width: 28rem;
273
+ min-width: 0;
274
+ }
275
+
276
+ .cbfk-strip > label {
277
+ display: inline-flex;
278
+ align-items: center;
279
+ gap: 0.375rem;
280
+ cursor: pointer;
281
+ }
282
+
283
+ /* ── RevealAction ──────────────────────────────────────────────────────── */
284
+
285
+ /* The door into something rarely used: a micro-button line, low contrast at rest, with a
286
+ clear hover state — never an empty input sitting open on every row. */
287
+ .cbfk-reveal {
288
+ display: inline-flex;
289
+ align-items: center;
290
+ align-self: flex-start;
291
+ gap: 0.25rem;
292
+ box-sizing: border-box;
293
+ min-height: 1.75rem;
294
+ margin: 0;
295
+ padding: 0 0.5rem 0 0.375rem;
296
+ border: 1px dashed transparent;
297
+ border-radius: var(--radius-sm, 0.375rem);
298
+ background: transparent;
299
+ color: var(--muted-foreground);
300
+ font: inherit;
301
+ font-size: 0.75rem;
302
+ font-weight: 500;
303
+ cursor: pointer;
304
+ transition:
305
+ color 120ms ease,
306
+ border-color 120ms ease,
307
+ background-color 120ms ease;
308
+ }
309
+
310
+ .cbfk-reveal:hover {
311
+ border-color: var(--border);
312
+ background-color: var(--muted, transparent);
313
+ color: var(--accent, var(--foreground));
314
+ }
315
+
316
+ .cbfk-reveal:focus-visible {
317
+ outline: 2px solid var(--ring);
318
+ outline-offset: 1px;
319
+ }
320
+
321
+ .cbfk-reveal svg {
322
+ width: 0.875rem;
323
+ height: 0.875rem;
324
+ }
325
+
326
+ @media (pointer: coarse) {
327
+ .cbfk-reveal {
328
+ min-height: 2.75rem;
329
+ }
330
+ }
331
+
332
+ /* ── KeyBadge ──────────────────────────────────────────────────────────── */
333
+
334
+ /* A technical key as metadata: mono, muted, a low-contrast pill. 12px, not the 10px a
335
+ mock-up might ask for — DESIGN.md §4 floors secondary text at 12px. */
336
+ .cbfk-key {
337
+ display: inline-flex;
338
+ flex: none;
339
+ align-items: center;
340
+ box-sizing: border-box;
341
+ max-width: 100%;
342
+ padding: 0.0625rem 0.375rem;
343
+ border-radius: var(--radius-sm, 0.25rem);
344
+ background-color: var(--muted, var(--surface-sunken));
345
+ color: var(--muted-foreground);
346
+ font-family: var(
347
+ --font-mono,
348
+ ui-monospace,
349
+ SFMono-Regular,
350
+ Menlo,
351
+ Consolas,
352
+ monospace
353
+ );
354
+ font-size: 0.75rem;
355
+ font-weight: 400;
356
+ line-height: 1.125rem;
357
+ letter-spacing: 0.02em;
358
+ white-space: nowrap;
359
+ overflow: hidden;
360
+ text-overflow: ellipsis;
361
+ }
362
+
363
+ .cbfk-key[data-uppercase="true"] {
364
+ text-transform: uppercase;
365
+ letter-spacing: 0.06em;
366
+ }
367
+
368
+ .cbfk-keyline {
369
+ display: flex;
370
+ align-items: baseline;
371
+ gap: 0.5rem;
372
+ min-width: 0;
373
+ margin: 0;
374
+ color: var(--muted-foreground);
375
+ font-size: 0.8125rem;
376
+ font-weight: 400;
377
+ line-height: 1.25rem;
378
+ }
379
+
380
+ /* ── FormPreviewLayout ─────────────────────────────────────────────────── */
381
+
382
+ .cbfk-split {
383
+ container-type: inline-size;
384
+ box-sizing: border-box;
385
+ min-width: 0;
386
+ }
387
+
388
+ .cbfk-split-grid {
389
+ display: flex;
390
+ flex-direction: column;
391
+ gap: 1rem;
392
+ }
393
+
394
+ .cbfk-split-form {
395
+ display: flex;
396
+ flex-direction: column;
397
+ gap: 1rem;
398
+ min-width: 0;
399
+ }
400
+
401
+ /* Narrow: the preview comes FIRST and pins to the top of the scroll, so the card can be
402
+ watched while the fields below it are edited. Opaque, so what scrolls under it does not
403
+ show through. */
404
+ .cbfk-split-preview {
405
+ position: sticky;
406
+ /* Sticky stops at the scroller's PADDING edge, so inside a padded scroller (every dialog
407
+ body) the bar would pin 16px down with the form showing above it. The host sets this
408
+ to minus its body's top padding: belt `Modal` and desk's `.cs-modal` both do. */
409
+ top: var(--cbfk-sticky-top, 0);
410
+ z-index: var(--z-sticky, 100);
411
+ order: -1;
412
+ min-width: 0;
413
+ margin: 0 -0.25rem;
414
+ padding: 0.25rem;
415
+ background-color: var(--cbfk-sticky-bg, var(--popover, var(--card, var(--background))));
416
+ }
417
+
418
+ .cbfk-split-bar {
419
+ display: flex;
420
+ align-items: center;
421
+ gap: 0.5rem;
422
+ box-sizing: border-box;
423
+ width: 100%;
424
+ min-height: 2.75rem;
425
+ margin: 0;
426
+ padding: 0.25rem 0.5rem;
427
+ border: 1px solid var(--border);
428
+ border-radius: var(--radius-md, var(--radius-sm, 0.5rem));
429
+ background-color: var(--card, var(--background));
430
+ color: var(--foreground);
431
+ font: inherit;
432
+ font-size: 0.8125rem;
433
+ text-align: left;
434
+ cursor: pointer;
435
+ }
436
+
437
+ .cbfk-split-bar:focus-visible {
438
+ outline: 2px solid var(--ring);
439
+ outline-offset: 1px;
440
+ }
441
+
442
+ .cbfk-split-bar-summary {
443
+ display: flex;
444
+ flex: 1 1 auto;
445
+ align-items: center;
446
+ gap: 0.5rem;
447
+ min-width: 0;
448
+ overflow: hidden;
449
+ }
450
+
451
+ .cbfk-split-bar-label {
452
+ flex: none;
453
+ color: var(--muted-foreground);
454
+ font-size: 0.75rem;
455
+ font-weight: 500;
456
+ }
457
+
458
+ .cbfk-split-bar svg {
459
+ flex: none;
460
+ width: 1rem;
461
+ height: 1rem;
462
+ color: var(--muted-foreground);
463
+ transition: transform 160ms ease;
464
+ }
465
+
466
+ .cbfk-split[data-preview-open="true"] .cbfk-split-bar svg {
467
+ transform: rotate(180deg);
468
+ }
469
+
470
+ .cbfk-split-body {
471
+ max-height: 45dvh;
472
+ margin-top: 0.5rem;
473
+ /* A card taller than 45dvh scrolls inside the pinned bar rather than covering the form.
474
+ guardrails-ignore no-raw-overflow: plain CSS for a no-Tailwind host — ScrollArea is a
475
+ React component and this is one capped box inside a sticky bar, with no keyboard to clear. */
476
+ overflow: auto;
477
+ overscroll-behavior: contain;
478
+ }
479
+
480
+ .cbfk-split[data-preview-open="false"] .cbfk-split-body {
481
+ display: none;
482
+ }
483
+
484
+ .cbfk-split-head {
485
+ display: none;
486
+ margin: 0 0 0.5rem;
487
+ color: var(--muted-foreground);
488
+ font-size: 0.75rem;
489
+ font-weight: 500;
490
+ }
491
+
492
+ .cbfk-split-below {
493
+ min-width: 0;
494
+ margin-top: 0.5rem;
495
+ }
496
+
497
+ /*
498
+ * Wide: twelve columns, the form on seven and the live preview on five, sticky at the top
499
+ * of its column. The bar is gone (the preview is simply there) and the body is never
500
+ * collapsed, whatever the narrow toggle last said.
501
+ */
502
+ @container (min-width: 52rem) {
503
+ .cbfk-split-grid {
504
+ display: grid;
505
+ grid-template-columns: repeat(12, minmax(0, 1fr));
506
+ align-items: start;
507
+ gap: 1.5rem;
508
+ }
509
+
510
+ .cbfk-split-form {
511
+ grid-column: span 7 / span 7;
512
+ }
513
+
514
+ .cbfk-split-preview {
515
+ order: 0;
516
+ grid-column: span 5 / span 5;
517
+ margin: 0;
518
+ padding: 0;
519
+ background: transparent;
520
+ }
521
+
522
+ .cbfk-split-bar {
523
+ display: none;
524
+ }
525
+
526
+ .cbfk-split-head {
527
+ display: block;
528
+ }
529
+
530
+ /* The builder runs under both columns. A grid item's sticky box is held in its own
531
+ area, so the preview beside the short fields stays in that first row. */
532
+ .cbfk-split-below {
533
+ grid-column: 1 / -1;
534
+ }
535
+
536
+ .cbfk-split-body,
537
+ .cbfk-split[data-preview-open="false"] .cbfk-split-body {
538
+ display: block;
539
+ max-height: none;
540
+ margin-top: 0;
541
+ overflow: visible;
542
+ }
543
+ }
544
+
545
+ @media (prefers-reduced-motion: reduce) {
546
+ .cbfk-row-panel {
547
+ animation: none;
548
+ }
549
+
550
+ .cbfk-row-toggle svg,
551
+ .cbfk-split-bar svg {
552
+ transition: none;
553
+ }
554
+ }
555
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `cursedbelt/react/form-kit` — the builder-form pieces that draw from their own plain
3
+ * stylesheet (`formKit.css`), so an app with no Tailwind and no `./react` barrel (desk) can
4
+ * mount them. A Tailwind app gets the same five from `cursedbelt/react`.
5
+ */
6
+ export { ExpandableRow, type ExpandableRowProps } from './ExpandableRow.js';
7
+ export { FormPreviewLayout, type FormPreviewLayoutProps } from './FormPreviewLayout.js';
8
+ export { KeyBadge, type KeyBadgeProps } from './KeyBadge.js';
9
+ export { RevealAction, type RevealActionProps } from './RevealAction.js';
10
+ export { SettingsStrip, type SettingsStripProps } from './SettingsStrip.js';
11
+ export { DESCRIPTION_FOLD_CHARS, declareFieldWidth, type FieldWidth, inputTypeWidth, naturalFieldWidth, } from '../lib/field.js';
12
+ //# sourceMappingURL=formKit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formKit.d.ts","sourceRoot":"","sources":["../../../src/react/components/formKit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,UAAU,EACf,cAAc,EACd,iBAAiB,GAClB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `cursedbelt/react/form-kit` — the builder-form pieces that draw from their own plain
3
+ * stylesheet (`formKit.css`), so an app with no Tailwind and no `./react` barrel (desk) can
4
+ * mount them. A Tailwind app gets the same five from `cursedbelt/react`.
5
+ */
6
+ export { ExpandableRow } from './ExpandableRow.js';
7
+ export { FormPreviewLayout } from './FormPreviewLayout.js';
8
+ export { KeyBadge } from './KeyBadge.js';
9
+ export { RevealAction } from './RevealAction.js';
10
+ export { SettingsStrip } from './SettingsStrip.js';
11
+ // The width rule `Field` applies, for an app whose field row is its own (desk's): one answer to
12
+ // "how wide may this control grow", not a copy of it per app.
13
+ export { DESCRIPTION_FOLD_CHARS, declareFieldWidth, inputTypeWidth, naturalFieldWidth, } from '../lib/field.js';
14
+ //# sourceMappingURL=formKit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formKit.js","sourceRoot":"","sources":["../../../src/react/components/formKit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,aAAa,EAA2B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAA+B,MAAM,wBAAwB,CAAC;AACxF,OAAO,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,aAAa,EAA2B,MAAM,oBAAoB,CAAC;AAC5E,gGAAgG;AAChG,8DAA8D;AAC9D,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EAEjB,cAAc,EACd,iBAAiB,GAClB,MAAM,iBAAiB,CAAC"}
@@ -48,8 +48,8 @@ export interface RailSectionProps {
48
48
  }
49
49
  /**
50
50
  * The open state of a remembered section: `localStorage` under `key`, re-read when the app seeds
51
- * it, written and ANNOUNCED on every toggle. `key === undefined` is a plain `useState`-free
52
- * default — the hook is still called unconditionally, so a section may gain a key later.
51
+ * it, written and ANNOUNCED on every toggle (`useRememberedPreference`). `key === undefined` is a
52
+ * plain default — the hook is still called unconditionally, so a section may gain a key later.
53
53
  */
54
54
  export declare function useRailSectionOpen(key: string | undefined, defaultOpen?: boolean): [open: boolean, setOpen: (open: boolean) => void];
55
55
  export declare function RailSection({ label, children, action, open: controlledOpen, onOpenChange, persistKey, defaultOpen, collapsible, className, headerClassName, contentClassName, }: RailSectionProps): ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"RailSection.d.ts","sourceRoot":"","sources":["../../../src/react/filter-rail/RailSection.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAsD,MAAM,OAAO,CAAC;AAK3F;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,oFAAoF;IACpF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6FAA6F;IAC7F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAiBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,WAAW,UAAO,GACjB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,CAgDnD;AAOD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,IAAI,EAAE,cAAc,EACpB,YAAY,EACZ,UAAU,EACV,WAAkB,EAClB,WAAkB,EAClB,SAAS,EACT,eAAe,EACf,gBAAgB,GACjB,EAAE,gBAAgB,GAAG,SAAS,CAuE9B"}
1
+ {"version":3,"file":"RailSection.d.ts","sourceRoot":"","sources":["../../../src/react/filter-rail/RailSection.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAgC,MAAM,OAAO,CAAC;AAKrE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,oFAAoF;IACpF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6FAA6F;IAC7F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,WAAW,UAAO,GACjB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,CAKnD;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,IAAI,EAAE,cAAc,EACpB,YAAY,EACZ,UAAU,EACV,WAAkB,EAClB,WAAkB,EAClB,SAAS,EACT,eAAe,EACf,gBAAgB,GACjB,EAAE,gBAAgB,GAAG,SAAS,CAuE9B"}
@@ -1,78 +1,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronRight } from 'lucide-react';
3
- import { useCallback, useId, useState, useSyncExternalStore } from 'react';
4
- import { announcePreference, onPreferenceSeed } from '../media-gallery/preferenceBus.js';
3
+ import { useCallback, useId, useState } from 'react';
4
+ import { useRememberedPreference } from '../media-gallery/rememberedPreference.js';
5
5
  import { cn } from '../lib/cn.js';
6
6
  import { TAP_TARGET_HEIGHT } from '../lib/tapTarget.js';
7
7
  /** The stored value for "shut". Anything else — absent included — is the section's default. */
8
8
  const SHUT = 'closed';
9
9
  const OPEN = 'open';
10
- /** Page memory first (a value `localStorage` refused to keep), then `localStorage`. */
11
- function readStored(key) {
12
- const kept = memory.get(key);
13
- if (kept !== undefined)
14
- return kept;
15
- try {
16
- return globalThis.localStorage?.getItem(key) ?? null;
17
- }
18
- catch {
19
- return null;
20
- }
21
- }
22
10
  /**
23
11
  * The open state of a remembered section: `localStorage` under `key`, re-read when the app seeds
24
- * it, written and ANNOUNCED on every toggle. `key === undefined` is a plain `useState`-free
25
- * default — the hook is still called unconditionally, so a section may gain a key later.
12
+ * it, written and ANNOUNCED on every toggle (`useRememberedPreference`). `key === undefined` is a
13
+ * plain default — the hook is still called unconditionally, so a section may gain a key later.
26
14
  */
27
15
  export function useRailSectionOpen(key, defaultOpen = true) {
28
- const subscribe = useCallback((changed) => {
29
- if (!key)
30
- return () => { };
31
- const offSeed = onPreferenceSeed((keys) => {
32
- if (!keys.includes(key))
33
- return;
34
- // The seed is the authority now; a value parked in page memory must not shadow it.
35
- memory.delete(key);
36
- changed();
37
- });
38
- // Another tab on the same origin toggled it.
39
- const onStorage = (event) => {
40
- if (event.key === key)
41
- changed();
42
- };
43
- globalThis.addEventListener?.('storage', onStorage);
44
- local.add(changed);
45
- return () => {
46
- offSeed();
47
- globalThis.removeEventListener?.('storage', onStorage);
48
- local.delete(changed);
49
- };
50
- }, [key]);
51
- const stored = useSyncExternalStore(subscribe, () => (key ? readStored(key) : null), () => null);
16
+ const [stored, setStored] = useRememberedPreference(key);
52
17
  const open = stored === SHUT ? false : stored === OPEN ? true : defaultOpen;
53
- const setOpen = useCallback((next) => {
54
- if (!key)
55
- return;
56
- const value = next ? OPEN : SHUT;
57
- try {
58
- globalThis.localStorage?.setItem(key, value);
59
- memory.delete(key);
60
- }
61
- catch {
62
- // Unremembered across reloads, but it must still MOVE: keep it in page memory, which
63
- // `readStored` answers from first.
64
- memory.set(key, value);
65
- }
66
- for (const changed of [...local])
67
- changed();
68
- announcePreference(key, value);
69
- }, [key]);
18
+ const setOpen = useCallback((next) => setStored(next ? OPEN : SHUT), [setStored]);
70
19
  return [open, setOpen];
71
20
  }
72
- /** Every mounted remembered section, so a toggle re-renders the others sharing its key. */
73
- const local = new Set();
74
- /** Where a value goes when `localStorage` throws (Safari private mode, a sandboxed iframe). */
75
- const memory = new Map();
76
21
  export function RailSection({ label, children, action, open: controlledOpen, onOpenChange, persistKey, defaultOpen = true, collapsible = true, className, headerClassName, contentClassName, }) {
77
22
  const [remembered, setRemembered] = useRailSectionOpen(persistKey, defaultOpen);
78
23
  const [plain, setPlain] = useState(defaultOpen);