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.
- package/dist/react/components/ColorField.d.ts.map +1 -1
- package/dist/react/components/ColorField.js +2 -1
- package/dist/react/components/ColorField.js.map +1 -1
- package/dist/react/components/ComboboxField.d.ts.map +1 -1
- package/dist/react/components/ComboboxField.js +2 -1
- package/dist/react/components/ComboboxField.js.map +1 -1
- package/dist/react/components/DataTable/DataTable.d.ts +39 -2
- package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/react/components/DataTable/DataTable.js +92 -22
- package/dist/react/components/DataTable/DataTable.js.map +1 -1
- package/dist/react/components/DataTable/ViewOptionsMenu.d.ts.map +1 -1
- package/dist/react/components/DataTable/ViewOptionsMenu.js +20 -7
- package/dist/react/components/DataTable/ViewOptionsMenu.js.map +1 -1
- package/dist/react/components/DataTable/types.d.ts +11 -1
- package/dist/react/components/DataTable/types.d.ts.map +1 -1
- package/dist/react/components/DataTable/types.js +6 -0
- package/dist/react/components/DataTable/types.js.map +1 -1
- package/dist/react/components/DatePicker.d.ts.map +1 -1
- package/dist/react/components/DatePicker.js +2 -1
- package/dist/react/components/DatePicker.js.map +1 -1
- package/dist/react/components/DateTimeField.d.ts.map +1 -1
- package/dist/react/components/DateTimeField.js +2 -1
- package/dist/react/components/DateTimeField.js.map +1 -1
- package/dist/react/components/ExpandableRow.d.ts +56 -0
- package/dist/react/components/ExpandableRow.d.ts.map +1 -0
- package/dist/react/components/ExpandableRow.js +54 -0
- package/dist/react/components/ExpandableRow.js.map +1 -0
- package/dist/react/components/Field.d.ts +20 -1
- package/dist/react/components/Field.d.ts.map +1 -1
- package/dist/react/components/Field.js +24 -11
- package/dist/react/components/Field.js.map +1 -1
- package/dist/react/components/FieldInput.d.ts +15 -0
- package/dist/react/components/FieldInput.d.ts.map +1 -1
- package/dist/react/components/FieldInput.js +20 -0
- package/dist/react/components/FieldInput.js.map +1 -1
- package/dist/react/components/FileDropZone.d.ts +23 -1
- package/dist/react/components/FileDropZone.d.ts.map +1 -1
- package/dist/react/components/FileDropZone.js +56 -8
- package/dist/react/components/FileDropZone.js.map +1 -1
- package/dist/react/components/FormPreviewLayout.d.ts +49 -0
- package/dist/react/components/FormPreviewLayout.d.ts.map +1 -0
- package/dist/react/components/FormPreviewLayout.js +36 -0
- package/dist/react/components/FormPreviewLayout.js.map +1 -0
- package/dist/react/components/Input.d.ts.map +1 -1
- package/dist/react/components/Input.js +3 -1
- package/dist/react/components/Input.js.map +1 -1
- package/dist/react/components/KeyBadge.d.ts +34 -0
- package/dist/react/components/KeyBadge.d.ts.map +1 -0
- package/dist/react/components/KeyBadge.js +24 -0
- package/dist/react/components/KeyBadge.js.map +1 -0
- package/dist/react/components/Modal.d.ts.map +1 -1
- package/dist/react/components/Modal.js +35 -9
- package/dist/react/components/Modal.js.map +1 -1
- package/dist/react/components/NativeSelect.d.ts.map +1 -1
- package/dist/react/components/NativeSelect.js +2 -1
- package/dist/react/components/NativeSelect.js.map +1 -1
- package/dist/react/components/RenderField.d.ts.map +1 -1
- package/dist/react/components/RenderField.js +2 -2
- package/dist/react/components/RenderField.js.map +1 -1
- package/dist/react/components/RevealAction.d.ts +33 -0
- package/dist/react/components/RevealAction.d.ts.map +1 -0
- package/dist/react/components/RevealAction.js +45 -0
- package/dist/react/components/RevealAction.js.map +1 -0
- package/dist/react/components/SecretInput.d.ts.map +1 -1
- package/dist/react/components/SecretInput.js +2 -1
- package/dist/react/components/SecretInput.js.map +1 -1
- package/dist/react/components/SelectField.d.ts.map +1 -1
- package/dist/react/components/SelectField.js +2 -1
- package/dist/react/components/SelectField.js.map +1 -1
- package/dist/react/components/SettingsStrip.d.ts +22 -0
- package/dist/react/components/SettingsStrip.d.ts.map +1 -0
- package/dist/react/components/SettingsStrip.js +18 -0
- package/dist/react/components/SettingsStrip.js.map +1 -0
- package/dist/react/components/formKit.css +555 -0
- package/dist/react/components/formKit.d.ts +12 -0
- package/dist/react/components/formKit.d.ts.map +1 -0
- package/dist/react/components/formKit.js +14 -0
- package/dist/react/components/formKit.js.map +1 -0
- package/dist/react/filter-rail/RailSection.d.ts +2 -2
- package/dist/react/filter-rail/RailSection.d.ts.map +1 -1
- package/dist/react/filter-rail/RailSection.js +6 -61
- package/dist/react/filter-rail/RailSection.js.map +1 -1
- package/dist/react/index.d.ts +8 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +8 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/field.d.ts +43 -0
- package/dist/react/lib/field.d.ts.map +1 -1
- package/dist/react/lib/field.js +68 -0
- package/dist/react/lib/field.js.map +1 -1
- package/dist/react/media/PlayerStrip.d.ts +9 -1
- package/dist/react/media/PlayerStrip.d.ts.map +1 -1
- package/dist/react/media/PlayerStrip.js +29 -2
- package/dist/react/media/PlayerStrip.js.map +1 -1
- package/dist/react/media-gallery/rememberedPreference.d.ts +2 -0
- package/dist/react/media-gallery/rememberedPreference.d.ts.map +1 -0
- package/dist/react/media-gallery/rememberedPreference.js +75 -0
- package/dist/react/media-gallery/rememberedPreference.js.map +1 -0
- package/dist/styles-areas/analytics.css +2 -2
- package/dist/styles-areas/core.css +34 -28
- package/dist/styles-areas/data-table.css +2 -2
- package/dist/styles-areas/fields.css +3 -3
- package/dist/styles-areas/layout-engine.css +2 -2
- package/dist/styles-areas/nav.css +3 -3
- package/dist/styles-areas/wizard.css +2 -2
- package/dist/styles-areas/workbench.css +2 -2
- package/dist/styles-utilities.css +111 -9
- package/package.json +14 -1
- package/scripts/styleAreas.ts +2 -0
- package/src/react/components/ColorField.tsx +3 -1
- package/src/react/components/ComboboxField.tsx +3 -1
- package/src/react/components/DataTable/DataTable.spec.tsx +18 -0
- package/src/react/components/DataTable/DataTable.tsx +136 -22
- package/src/react/components/DataTable/ViewOptionsMenu.tsx +24 -9
- package/src/react/components/DataTable/selection.spec.tsx +57 -1
- package/src/react/components/DataTable/types.ts +12 -1
- package/src/react/components/DataTable/virtualize.spec.tsx +20 -0
- package/src/react/components/DatePicker.tsx +3 -1
- package/src/react/components/DateTimeField.tsx +3 -1
- package/src/react/components/ExpandableRow.tsx +146 -0
- package/src/react/components/Field.tsx +89 -13
- package/src/react/components/Field.width.spec.tsx +162 -0
- package/src/react/components/FieldInput.tsx +30 -0
- package/src/react/components/FileDropZone.spec.tsx +65 -0
- package/src/react/components/FileDropZone.tsx +83 -5
- package/src/react/components/FormPreviewLayout.tsx +94 -0
- package/src/react/components/Input.tsx +4 -1
- package/src/react/components/KeyBadge.tsx +52 -0
- package/src/react/components/Modal.tsx +58 -14
- package/src/react/components/NativeSelect.tsx +3 -1
- package/src/react/components/RenderField.tsx +2 -1
- package/src/react/components/RevealAction.tsx +83 -0
- package/src/react/components/SecretInput.tsx +3 -1
- package/src/react/components/SelectField.tsx +3 -1
- package/src/react/components/SettingsStrip.tsx +37 -0
- package/src/react/components/formKit.css +555 -0
- package/src/react/components/formKit.spec.tsx +138 -0
- package/src/react/components/formKit.ts +19 -0
- package/src/react/filter-rail/RailSection.tsx +6 -65
- package/src/react/index.ts +20 -2
- package/src/react/lib/field.ts +93 -0
- package/src/react/media/PlayerStrip.tsx +65 -20
- package/src/react/media/playerStrip.spec.tsx +23 -0
- package/src/react/media-gallery/rememberedPreference.ts +79 -0
- package/src/styles-areas/analytics.css +2 -2
- package/src/styles-areas/core.css +34 -28
- package/src/styles-areas/data-table.css +2 -2
- package/src/styles-areas/fields.css +3 -3
- package/src/styles-areas/layout-engine.css +2 -2
- package/src/styles-areas/nav.css +3 -3
- package/src/styles-areas/wizard.css +2 -2
- package/src/styles-areas/workbench.css +2 -2
- 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
|
|
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,
|
|
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
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
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);
|