@foldstryx/kitchen-sink 0.1.0 → 0.3.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/index.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import { Option, Schema as S } from 'effect';
2
2
  import { Command, Runtime, Submodel } from 'foldkit';
3
- import { html } from 'foldkit/html';
4
- import { Alert, Attention, Avatar, Badge, Button, Card, Checkbox, Details, Dialog, DropdownMenu, EmptyState, Field, Grid, Input, ListRow, LoadingPanel, NativeSelect, Page, Pagination, Row, Separator, Stack, Stat, Switch, Table, Tabs, Text, Toast, Tooltip, elAttrs, sxAttrs, } from '@foldstryx/foldkit';
3
+ import { Alert, Attention, Avatar, Badge, Button, Card, Checkbox, Details, Dialog, DropdownMenu, EmptyState, Field, Grid, GridFocus, Input, ListRow, LoadingPanel, Page, Pagination, Row, Selector, Separator, Stack, Stat, Switch, Table, TableSelection, Tabs, Text, Toast, ToggleButton, Tooltip, elAttrs, sxAttrs, } from '@foldstryx/foldkit';
5
4
  import { layoutStyles, tooltipStyles } from '@foldstryx/styles';
6
5
  const DemoTabs = Tabs.create();
7
6
  const DemoMenu = DropdownMenu.create();
8
7
  const DemoToast = Toast.create();
8
+ const KindSelector = Selector.create();
9
+ const KIND_OPTIONS = [
10
+ { value: 'all', label: 'All kinds' },
11
+ { value: 'active', label: 'Active' },
12
+ ];
9
13
  export const Model = S.Struct({
10
14
  clicks: S.Finite,
11
15
  detailsOpen: S.Boolean,
@@ -13,16 +17,27 @@ export const Model = S.Struct({
13
17
  email: S.String,
14
18
  filter: S.String,
15
19
  includeInactive: S.Boolean,
16
- kind: S.String,
20
+ kind: S.Literals(['all', 'active']),
21
+ kindSelector: Selector.Model,
17
22
  menu: DropdownMenu.Model,
18
23
  notifications: S.Boolean,
19
24
  page: S.Finite,
25
+ selectedTab: S.Literals(['overview', 'details', 'settings']),
20
26
  tabs: Tabs.Model,
21
27
  toast: DemoToast.Model,
22
28
  tooltip: Tooltip.Model,
29
+ toggleBold: S.Boolean,
30
+ toggleView: S.NullOr(S.String),
31
+ toggleMulti: S.Array(S.String),
32
+ tableSelected: S.Array(S.String),
33
+ matrixPressed: S.Array(S.Boolean),
23
34
  });
24
35
  export const Clicked = () => ({ _tag: 'Clicked' });
25
- export const FieldChanged = (field, value) => ({ _tag: 'FieldChanged', field, value });
36
+ export const FieldChanged = (field, value) => ({
37
+ _tag: 'FieldChanged',
38
+ field,
39
+ value,
40
+ });
26
41
  export const IncludeInactiveChanged = (checked) => ({
27
42
  _tag: 'IncludeInactiveChanged',
28
43
  checked,
@@ -51,6 +66,10 @@ export const GotTabsMessage = (message) => ({
51
66
  _tag: 'GotTabsMessage',
52
67
  message,
53
68
  });
69
+ export const GotKindSelectorMessage = (message) => ({
70
+ _tag: 'GotKindSelectorMessage',
71
+ message,
72
+ });
54
73
  export const GotMenuMessage = (message) => ({
55
74
  _tag: 'GotMenuMessage',
56
75
  message,
@@ -63,6 +82,34 @@ export const ShowToast = (variant) => ({
63
82
  _tag: 'ShowToast',
64
83
  variant,
65
84
  });
85
+ export const ToggleBoldChanged = (pressed) => ({
86
+ _tag: 'ToggleBoldChanged',
87
+ pressed,
88
+ });
89
+ export const ToggleViewChanged = (value) => ({
90
+ _tag: 'ToggleViewChanged',
91
+ value,
92
+ });
93
+ export const ToggleMultiChanged = (value) => ({
94
+ _tag: 'ToggleMultiChanged',
95
+ value,
96
+ });
97
+ export const TableRowSelectionChanged = (id, checked) => ({
98
+ _tag: 'TableRowSelectionChanged',
99
+ id,
100
+ checked,
101
+ });
102
+ export const TableSelectAllChanged = (checked) => ({
103
+ _tag: 'TableSelectAllChanged',
104
+ checked,
105
+ });
106
+ export const MatrixCellPressed = (index, next) => ({
107
+ _tag: 'MatrixCellPressed',
108
+ index,
109
+ next,
110
+ });
111
+ const mapCompletedGridFocus = (message) => message;
112
+ const mapSyncCheckbox = (message) => message;
66
113
  export const init = () => [
67
114
  {
68
115
  clicks: 0,
@@ -72,12 +119,29 @@ export const init = () => [
72
119
  filter: '',
73
120
  includeInactive: false,
74
121
  kind: 'all',
122
+ kindSelector: Selector.init({ id: 'catalog-kind' }),
75
123
  menu: DropdownMenu.init({ id: 'catalog-menu' }),
76
124
  notifications: false,
77
125
  page: 1,
126
+ selectedTab: 'overview',
78
127
  tabs: Tabs.init({ id: 'catalog-tabs' }),
79
128
  toast: DemoToast.init({ id: 'catalog-toast' }),
80
129
  tooltip: Tooltip.init('catalog-tooltip'),
130
+ toggleBold: false,
131
+ toggleView: 'grid',
132
+ toggleMulti: ['filters'],
133
+ tableSelected: ['foldstryx'],
134
+ matrixPressed: [
135
+ true,
136
+ false,
137
+ false,
138
+ false,
139
+ false,
140
+ false,
141
+ false,
142
+ false,
143
+ false,
144
+ ],
81
145
  },
82
146
  [],
83
147
  ];
@@ -98,12 +162,24 @@ export const update = (model, message) => {
98
162
  ];
99
163
  }
100
164
  case 'GotTabsMessage': {
101
- const [tabs, commands] = DemoTabs.update(model.tabs, message.message);
165
+ const [tabs, commands, maybeOut] = DemoTabs.update(model.tabs, message.message);
166
+ const selectedTab = maybeOut._tag === 'Some' ? maybeOut.value.value : model.selectedTab;
102
167
  return [
103
- { ...model, tabs },
168
+ { ...model, tabs, selectedTab },
104
169
  Command.mapMessages(commands, m => GotTabsMessage(m)),
105
170
  ];
106
171
  }
172
+ case 'GotKindSelectorMessage': {
173
+ const [kindSelector, commands, maybeOut] = KindSelector.update(model.kindSelector, message.message);
174
+ const kind = Option.match(maybeOut, {
175
+ onNone: () => model.kind,
176
+ onSome: out => out.value,
177
+ });
178
+ return [
179
+ { ...model, kindSelector, kind },
180
+ Command.mapMessages(commands, m => GotKindSelectorMessage(m)),
181
+ ];
182
+ }
107
183
  case 'GotMenuMessage': {
108
184
  const [menu, commands] = DemoMenu.update(model.menu, message.message);
109
185
  return [
@@ -149,10 +225,49 @@ export const update = (model, message) => {
149
225
  },
150
226
  [],
151
227
  ];
228
+ case 'ToggleBoldChanged':
229
+ return [{ ...model, toggleBold: message.pressed }, []];
230
+ case 'ToggleViewChanged':
231
+ return [{ ...model, toggleView: message.value }, []];
232
+ case 'ToggleMultiChanged':
233
+ return [{ ...model, toggleMulti: message.value }, []];
234
+ case 'TableRowSelectionChanged': {
235
+ const selected = new Set(model.tableSelected);
236
+ if (message.checked) {
237
+ selected.add(message.id);
238
+ }
239
+ else {
240
+ selected.delete(message.id);
241
+ }
242
+ return [{ ...model, tableSelected: [...selected] }, []];
243
+ }
244
+ case 'TableSelectAllChanged': {
245
+ const rows = [
246
+ { id: 'foldstryx', isEnabled: true },
247
+ { id: 'sidebar', isEnabled: true },
248
+ { id: 'docs', isEnabled: false },
249
+ ];
250
+ const selectedSet = new Set(model.tableSelected);
251
+ const next = message.checked
252
+ ? TableSelection.selectAll(rows, selectedSet)
253
+ : TableSelection.deselectAll(rows, selectedSet);
254
+ return [{ ...model, tableSelected: [...next] }, []];
255
+ }
256
+ case 'MatrixCellPressed':
257
+ return [
258
+ {
259
+ ...model,
260
+ matrixPressed: model.matrixPressed.map((pressed, index) => index === message.index ? message.next : pressed),
261
+ },
262
+ [],
263
+ ];
264
+ case 'CompletedGridFocus':
265
+ return [model, []];
266
+ case 'CompletedSyncCheckboxIndeterminate':
267
+ return [model, []];
152
268
  }
153
269
  };
154
- export const view = Submodel.defineView(model => {
155
- const h = html();
270
+ export const view = Submodel.defineView((model, h) => {
156
271
  return h.div(elAttrs(sxAttrs(h, layoutStyles.catalogShell)), [
157
272
  Stack.view({
158
273
  gap: 'lg',
@@ -161,25 +276,25 @@ export const view = Submodel.defineView(model => {
161
276
  variant: 'title',
162
277
  as: 'h1',
163
278
  children: 'Foldstryx catalog',
164
- }),
279
+ }, h),
165
280
  Text.view({
166
281
  variant: 'muted',
167
282
  children: 'First primitives: layout, type, controls, and chrome.',
168
- }),
283
+ }, h),
169
284
  Card.section({
170
285
  title: 'Typography',
171
286
  description: 'Astryx-faithful type roles.',
172
287
  padded: true,
173
288
  children: [
174
- Text.view({ children: 'Body text for a readable interface.' }),
175
- Text.view({ variant: 'body', children: 'Label text' }),
176
- Text.view({ variant: 'muted', children: 'Supporting text' }),
289
+ Text.view({ children: 'Body text for a readable interface.' }, h),
290
+ Text.view({ variant: 'body', children: 'Label text' }, h),
291
+ Text.view({ variant: 'muted', children: 'Supporting text' }, h),
177
292
  Text.view({
178
293
  variant: 'mono',
179
294
  children: 'const font = "Maple Mono NL NF"',
180
- }),
295
+ }, h),
181
296
  ],
182
- }),
297
+ }, h),
183
298
  Card.section({
184
299
  title: 'Stack and Row',
185
300
  description: 'Token-based spacing and alignment.',
@@ -188,19 +303,19 @@ export const view = Submodel.defineView(model => {
188
303
  Row.view({
189
304
  align: 'wrap',
190
305
  children: [
191
- Text.view({ children: 'Row item' }),
192
- Text.view({ children: 'Another item' }),
306
+ Text.view({ children: 'Row item' }, h),
307
+ Text.view({ children: 'Another item' }, h),
193
308
  ],
194
- }),
309
+ }, h),
195
310
  Stack.view({
196
311
  gap: 'sm',
197
312
  children: [
198
- Text.view({ children: 'Stack item' }),
199
- Text.view({ children: 'Another item' }),
313
+ Text.view({ children: 'Stack item' }, h),
314
+ Text.view({ children: 'Another item' }, h),
200
315
  ],
201
- }),
316
+ }, h),
202
317
  ],
203
- }),
318
+ }, h),
204
319
  Card.section({
205
320
  title: 'Page',
206
321
  description: 'Header, content, and footer regions compose a shell.',
@@ -216,35 +331,45 @@ export const view = Submodel.defineView(model => {
216
331
  size: 'sm',
217
332
  variant: 'secondary',
218
333
  onClick: Clicked(),
219
- }),
334
+ }, h),
220
335
  ],
221
- }),
336
+ }, h),
222
337
  content: [
223
338
  Text.view({
224
339
  children: 'Page content sits between the header and footer.',
225
- }),
340
+ }, h),
226
341
  ],
227
342
  footer: Page.footer([
228
- Text.view({ variant: 'mutedSm', children: 'Footer region' }),
229
- ]),
230
- }),
343
+ Text.view({ variant: 'mutedSm', children: 'Footer region' }, h),
344
+ ], h),
345
+ }, h),
231
346
  ],
232
- }),
347
+ }, h),
233
348
  Card.section({
234
349
  title: 'Grid',
235
- description: 'Responsive columns with a token gap scale.',
350
+ description: 'Fixed, preset, and responsive column APIs.',
236
351
  padded: true,
237
352
  children: [
238
- Grid.view({
239
- columns: 3,
353
+ Stack.view({
354
+ gap: 'md',
240
355
  children: [
241
- Text.view({ children: 'Cell one' }),
242
- Text.view({ children: 'Cell two' }),
243
- Text.view({ children: 'Cell three' }),
356
+ Grid.view({
357
+ columns: 6,
358
+ gap: 'sm',
359
+ children: Array.from({ length: 6 }, (_, index) => Text.view({ children: `Col ${index + 1}` }, h)),
360
+ }, h),
361
+ Grid.view({
362
+ columns: { minWidth: 280, max: 4 },
363
+ children: [
364
+ Text.view({ children: 'Responsive one' }, h),
365
+ Text.view({ children: 'Responsive two' }, h),
366
+ Text.view({ children: 'Responsive three' }, h),
367
+ ],
368
+ }, h),
244
369
  ],
245
- }),
370
+ }, h),
246
371
  ],
247
- }),
372
+ }, h),
248
373
  Card.section({
249
374
  title: 'Avatar',
250
375
  description: 'Sizes, shapes, image, and fallback labeling.',
@@ -253,19 +378,19 @@ export const view = Submodel.defineView(model => {
253
378
  Row.view({
254
379
  align: 'wrap',
255
380
  children: [
256
- Avatar.view({ fallback: 'JD', label: 'Jane Doe' }),
257
- Avatar.view({ fallback: 'AB', size: 'sm' }),
258
- Avatar.view({ fallback: 'CD', size: 'lg' }),
259
- Avatar.view({ fallback: 'EF', shape: 'rounded' }),
381
+ Avatar.view({ fallback: 'JD', label: 'Jane Doe' }, h),
382
+ Avatar.view({ fallback: 'AB', size: 'sm' }, h),
383
+ Avatar.view({ fallback: 'CD', size: 'lg' }, h),
384
+ Avatar.view({ fallback: 'EF', shape: 'rounded' }, h),
260
385
  Avatar.view({
261
386
  fallback: 'GH',
262
387
  imageSrc: 'data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2232%22 height=%2232%22><rect width=%2232%22 height=%2232%22 fill=%22%230064E0%22/></svg>',
263
388
  label: 'With image',
264
- }),
389
+ }, h),
265
390
  ],
266
- }),
391
+ }, h),
267
392
  ],
268
- }),
393
+ }, h),
269
394
  Card.section({
270
395
  title: 'Buttons',
271
396
  description: `Click count: ${model.clicks}`,
@@ -274,16 +399,70 @@ export const view = Submodel.defineView(model => {
274
399
  Row.view({
275
400
  align: 'wrap',
276
401
  children: [
277
- Button.view({ label: 'Primary', onClick: Clicked() }),
278
- Button.view({ label: 'Secondary', variant: 'secondary' }),
279
- Button.view({ label: 'Ghost', variant: 'ghost' }),
280
- Button.view({ label: 'Danger', variant: 'danger' }),
281
- Button.view({ label: 'Small', size: 'sm' }),
282
- Button.view({ label: 'Disabled', isDisabled: true }),
402
+ Button.view({ label: 'Primary', onClick: Clicked() }, h),
403
+ Button.view({ label: 'Secondary', variant: 'secondary' }, h),
404
+ Button.view({ label: 'Ghost', variant: 'ghost' }, h),
405
+ Button.view({ label: 'Danger', variant: 'danger' }, h),
406
+ Button.view({ label: 'Small', size: 'sm' }, h),
407
+ Button.view({ label: 'Disabled', isDisabled: true }, h),
283
408
  ],
284
- }),
409
+ }, h),
285
410
  ],
286
- }),
411
+ }, h),
412
+ Card.section({
413
+ title: 'ToggleButton',
414
+ description: 'Pressed ghost buttons and selection groups.',
415
+ padded: true,
416
+ children: [
417
+ Row.view({
418
+ align: 'wrap',
419
+ children: [
420
+ ToggleButton.view({
421
+ label: 'Bold',
422
+ isPressed: model.toggleBold,
423
+ onPressedChange: pressed => ToggleBoldChanged(pressed),
424
+ }, h),
425
+ ToggleButton.groupView({
426
+ label: 'View mode',
427
+ value: model.toggleView,
428
+ onChange: value => ToggleViewChanged(value),
429
+ items: [
430
+ { value: 'list', label: 'List' },
431
+ { value: 'grid', label: 'Grid' },
432
+ ],
433
+ }, h),
434
+ ToggleButton.groupView({
435
+ label: 'Filters',
436
+ type: 'multiple',
437
+ value: model.toggleMulti,
438
+ onChange: value => ToggleMultiChanged(value),
439
+ items: [
440
+ { value: 'filters', label: 'Filters' },
441
+ { value: 'sort', label: 'Sort' },
442
+ ],
443
+ }, h),
444
+ ],
445
+ }, h),
446
+ ],
447
+ }, h),
448
+ Card.section({
449
+ title: 'Grid matrix',
450
+ description: 'role=grid keyboard navigation with ToggleButtons.',
451
+ padded: true,
452
+ children: [
453
+ Grid.matrix({
454
+ columns: 3,
455
+ ariaLabel: 'Selection matrix',
456
+ children: model.matrixPressed.map((isPressed, index) => Grid.gridcell([
457
+ ToggleButton.view({
458
+ label: `Cell ${index}`,
459
+ isPressed,
460
+ onPressedChange: next => MatrixCellPressed(index, next),
461
+ }, h),
462
+ ], h)),
463
+ }, h, mapCompletedGridFocus),
464
+ ],
465
+ }, h),
287
466
  Card.section({
288
467
  title: 'Card',
289
468
  description: 'Root, header, and body slots compose surface chrome.',
@@ -291,9 +470,9 @@ export const view = Submodel.defineView(model => {
291
470
  children: [
292
471
  Text.view({
293
472
  children: 'Cards provide a neutral surface with border, radius, and elevation.',
294
- }),
473
+ }, h),
295
474
  ],
296
- }),
475
+ }, h),
297
476
  Card.section({
298
477
  title: 'Form',
299
478
  description: 'Labeled controls with shared Astryx form styling.',
@@ -306,14 +485,14 @@ export const view = Submodel.defineView(model => {
306
485
  onInput: value => FieldChanged('email', value),
307
486
  placeholder: 'name@example.com',
308
487
  description: 'We will never share your email.',
309
- }),
488
+ }, h),
310
489
  Input.view({
311
490
  id: 'catalog-disabled',
312
491
  label: 'Disabled',
313
492
  value: 'Read only',
314
493
  isDisabled: true,
315
- }),
316
- Field.group({
494
+ }, h),
495
+ Field.group(h, {
317
496
  orientation: 'horizontal',
318
497
  children: [
319
498
  Checkbox.control({
@@ -321,30 +500,22 @@ export const view = Submodel.defineView(model => {
321
500
  checked: model.includeInactive,
322
501
  label: 'Accept terms',
323
502
  onChange: checked => IncludeInactiveChanged(checked),
324
- }),
503
+ }, h, mapSyncCheckbox),
325
504
  ],
326
505
  }),
327
- h.submodel({
328
- slotId: 'catalog-notifications',
329
- model: {
330
- id: 'catalog-notifications',
331
- isChecked: model.notifications,
332
- },
333
- view: Switch.view,
334
- viewInputs: Switch.styledViewInputs({ id: 'catalog-notifications', isChecked: model.notifications }, {
335
- label: 'Notifications',
336
- description: 'Enable notifications.',
337
- }),
338
- toParentMessage: message => message._tag === 'Toggled'
339
- ? NotificationsChanged(!model.notifications)
340
- : NotificationsChanged(message.isChecked),
341
- }),
342
- Separator.view(),
506
+ Switch.view({
507
+ id: 'catalog-notifications',
508
+ isChecked: model.notifications,
509
+ onToggle: checked => NotificationsChanged(checked),
510
+ label: 'Notifications',
511
+ description: 'Enable notifications.',
512
+ }, h),
513
+ Separator.view({}, h),
343
514
  ],
344
- }),
515
+ }, h),
345
516
  Card.section({
346
517
  title: 'Dense controls',
347
- description: 'Compact inputs and native select for filters.',
518
+ description: 'Compact inputs and selector for filters.',
348
519
  padded: true,
349
520
  children: [
350
521
  Row.view({
@@ -359,24 +530,31 @@ export const view = Submodel.defineView(model => {
359
530
  value: model.filter,
360
531
  onInput: value => FieldChanged('filter', value),
361
532
  label: 'Filter',
362
- }),
363
- NativeSelect.view({
533
+ }, h),
534
+ Selector.labeledField({
364
535
  id: 'catalog-kind',
365
- ariaLabel: 'Kind',
366
- density: 'compact',
367
- width: 'sm',
368
- value: model.kind,
369
- options: [
370
- { value: 'all', label: 'All kinds' },
371
- { value: 'active', label: 'Active' },
372
- ],
373
- onChange: value => FieldChanged('kind', value),
374
536
  label: 'Kind',
375
- }),
537
+ children: [
538
+ h.submodel({
539
+ slotId: 'catalog-kind',
540
+ model: model.kindSelector,
541
+ view: KindSelector.view,
542
+ viewInputs: Selector.styledViewInputs({
543
+ options: KIND_OPTIONS,
544
+ selectedValue: model.kind,
545
+ density: 'compact',
546
+ width: 'sm',
547
+ ariaLabel: 'Kind',
548
+ isOpen: model.kindSelector.isOpen,
549
+ }, h),
550
+ toParentMessage: message => GotKindSelectorMessage(message),
551
+ }),
552
+ ],
553
+ }, h),
376
554
  ],
377
- }),
555
+ }, h),
378
556
  ],
379
- }),
557
+ }, h),
380
558
  Card.section({
381
559
  title: 'Badges',
382
560
  description: 'Status and metadata chips across sentiment variants.',
@@ -385,18 +563,18 @@ export const view = Submodel.defineView(model => {
385
563
  Row.view({
386
564
  align: 'wrap',
387
565
  children: [
388
- Badge.view({ label: 'Default' }),
389
- Badge.view({ label: 'Secondary', variant: 'secondary' }),
390
- Badge.view({ label: 'Destructive', variant: 'destructive' }),
391
- Badge.view({ label: 'Outline', variant: 'outline' }),
392
- Badge.view({ label: 'Success', variant: 'success' }),
393
- Badge.view({ label: 'Warning', variant: 'warning' }),
394
- Badge.view({ label: 'Info', variant: 'info' }),
395
- Badge.view({ label: 'Large', size: 'lg' }),
566
+ Badge.view({ label: 'Default' }, h),
567
+ Badge.view({ label: 'Secondary', variant: 'secondary' }, h),
568
+ Badge.view({ label: 'Destructive', variant: 'destructive' }, h),
569
+ Badge.view({ label: 'Outline', variant: 'outline' }, h),
570
+ Badge.view({ label: 'Success', variant: 'success' }, h),
571
+ Badge.view({ label: 'Warning', variant: 'warning' }, h),
572
+ Badge.view({ label: 'Info', variant: 'info' }, h),
573
+ Badge.view({ label: 'Large', size: 'lg' }, h),
396
574
  ],
397
- }),
575
+ }, h),
398
576
  ],
399
- }),
577
+ }, h),
400
578
  Card.section({
401
579
  title: 'Alerts',
402
580
  description: 'Role=alert banners with optional action slots.',
@@ -408,7 +586,7 @@ export const view = Submodel.defineView(model => {
408
586
  Alert.view({
409
587
  title: 'Default',
410
588
  body: 'A neutral informational banner.',
411
- }),
589
+ }, h),
412
590
  Alert.view({
413
591
  variant: 'destructive',
414
592
  title: 'Error',
@@ -418,21 +596,21 @@ export const view = Submodel.defineView(model => {
418
596
  variant: 'ghost',
419
597
  size: 'sm',
420
598
  onClick: Clicked(),
421
- }),
422
- }),
599
+ }, h),
600
+ }, h),
423
601
  Alert.view({
424
602
  variant: 'warning',
425
603
  body: 'Review required before continuing.',
426
604
  compact: true,
427
- }),
605
+ }, h),
428
606
  Alert.view({
429
607
  variant: 'success',
430
608
  body: 'Import complete.',
431
- }),
609
+ }, h),
432
610
  ],
433
- }),
611
+ }, h),
434
612
  ],
435
- }),
613
+ }, h),
436
614
  Card.section({
437
615
  title: 'Feedback',
438
616
  description: 'Loading, empty, and attention surfaces.',
@@ -444,7 +622,7 @@ export const view = Submodel.defineView(model => {
444
622
  LoadingPanel.view({
445
623
  message: 'Loading panel…',
446
624
  card: false,
447
- }),
625
+ }, h),
448
626
  EmptyState.view({
449
627
  title: 'Nothing here',
450
628
  message: 'Empty state with an optional action.',
@@ -453,17 +631,17 @@ export const view = Submodel.defineView(model => {
453
631
  size: 'sm',
454
632
  variant: 'ghost',
455
633
  onClick: Clicked(),
456
- }),
634
+ }, h),
457
635
  card: false,
458
- }),
636
+ }, h),
459
637
  Attention.view({
460
638
  title: 'Attention',
461
639
  body: 'Soft callout for inline notices (not role=alert).',
462
- }),
640
+ }, h),
463
641
  ],
464
- }),
642
+ }, h),
465
643
  ],
466
- }),
644
+ }, h),
467
645
  Card.section({
468
646
  title: 'Tooltip',
469
647
  description: 'Hover or focus the trigger to reveal the panel.',
@@ -480,13 +658,15 @@ export const view = Submodel.defineView(model => {
480
658
  h.button(elAttrs(trigger), [
481
659
  'Hover or focus me',
482
660
  ]),
483
- h.div(elAttrs(sxAttrs(h, tooltipStyles.content, isVisible ? undefined : tooltipStyles.contentHidden), panel), ['Tooltip content']),
661
+ h.div(elAttrs(sxAttrs(h, tooltipStyles.content, isVisible
662
+ ? undefined
663
+ : tooltipStyles.contentHidden), panel), ['Tooltip content']),
484
664
  ]),
485
665
  },
486
666
  toParentMessage: message => GotTooltipMessage(message),
487
667
  }),
488
668
  ],
489
- }),
669
+ }, h),
490
670
  Card.section({
491
671
  title: 'Data display',
492
672
  description: 'Tables, stats, list rows, pagination, and disclosures.',
@@ -500,63 +680,126 @@ export const view = Submodel.defineView(model => {
500
680
  Table.thead([
501
681
  Table.tr({
502
682
  children: [
503
- Table.th('Project'),
504
- Table.th({ align: 'right', children: 'Value' }),
505
- Table.th({ align: 'right', children: 'Status' }),
683
+ Table.selectionHeader({
684
+ checked: TableSelection.getIsAllSelected([
685
+ {
686
+ id: 'foldstryx',
687
+ isEnabled: true,
688
+ },
689
+ {
690
+ id: 'sidebar',
691
+ isEnabled: true,
692
+ },
693
+ {
694
+ id: 'docs',
695
+ isEnabled: false,
696
+ },
697
+ ], new Set(model.tableSelected)),
698
+ isIndeterminate: TableSelection.getIsIndeterminate([
699
+ {
700
+ id: 'foldstryx',
701
+ isEnabled: true,
702
+ },
703
+ {
704
+ id: 'sidebar',
705
+ isEnabled: true,
706
+ },
707
+ {
708
+ id: 'docs',
709
+ isEnabled: false,
710
+ },
711
+ ], new Set(model.tableSelected)),
712
+ onChange: checked => TableSelectAllChanged(checked),
713
+ }, h, mapSyncCheckbox),
714
+ Table.th('Project', h),
715
+ Table.th({ align: 'right', children: 'Value' }, h),
716
+ Table.th({
717
+ align: 'right',
718
+ children: 'Status',
719
+ }, h),
506
720
  ],
507
- }),
508
- ]),
721
+ }, h),
722
+ ], h),
509
723
  Table.tbody([
510
724
  Table.tr({
725
+ isSelected: model.tableSelected.includes('foldstryx'),
511
726
  children: [
512
- Table.td('Foldstryx'),
513
- Table.td({ align: 'right', children: '1,240' }),
727
+ Table.selectionCell({
728
+ rowId: 'foldstryx',
729
+ rowLabel: 'Foldstryx',
730
+ checked: model.tableSelected.includes('foldstryx'),
731
+ onChange: checked => TableRowSelectionChanged('foldstryx', checked),
732
+ }, h, mapSyncCheckbox),
733
+ Table.td('Foldstryx', h),
734
+ Table.td({
735
+ align: 'right',
736
+ children: '1,240',
737
+ }, h),
514
738
  Table.td({
515
739
  align: 'right',
516
740
  tone: 'success',
517
741
  children: 'Active',
518
- }),
742
+ }, h),
519
743
  ],
520
- }),
744
+ }, h),
521
745
  Table.tr({
746
+ isSelected: model.tableSelected.includes('sidebar'),
522
747
  children: [
523
- Table.td('Sidebar'),
524
- Table.td({ align: 'right', children: '860' }),
748
+ Table.selectionCell({
749
+ rowId: 'sidebar',
750
+ rowLabel: 'Sidebar',
751
+ checked: model.tableSelected.includes('sidebar'),
752
+ onChange: checked => TableRowSelectionChanged('sidebar', checked),
753
+ }, h, mapSyncCheckbox),
754
+ Table.td('Sidebar', h),
755
+ Table.td({ align: 'right', children: '860' }, h),
525
756
  Table.td({
526
757
  align: 'right',
527
758
  tone: 'warning',
528
759
  children: 'Review',
529
- }),
760
+ }, h),
530
761
  ],
531
- }),
762
+ }, h),
532
763
  Table.tr({
533
- presentation: 'summary',
764
+ isSelected: model.tableSelected.includes('docs'),
534
765
  children: [
535
- Table.td('Total'),
536
- Table.td({ align: 'right', children: '2,100' }),
537
- Table.td({ align: 'right', children: '' }),
766
+ Table.selectionCell({
767
+ rowId: 'docs',
768
+ rowLabel: 'Docs',
769
+ checked: model.tableSelected.includes('docs'),
770
+ onChange: checked => TableRowSelectionChanged('docs', checked),
771
+ isDisabled: true,
772
+ }, h, mapSyncCheckbox),
773
+ Table.td('Docs', h),
774
+ Table.td({ align: 'right', children: '420' }, h),
775
+ Table.td({
776
+ align: 'right',
777
+ children: 'Paused',
778
+ }, h),
538
779
  ],
539
- }),
540
- ]),
541
- ]),
542
- ]),
780
+ }, h),
781
+ ], h),
782
+ ], h),
783
+ ], h),
543
784
  Row.view({
544
785
  align: 'wrap',
545
786
  children: [
546
787
  Stat.card({
547
788
  label: 'Active users',
548
789
  state: new Stat.Ready({ value: '1,240' }),
549
- }),
790
+ }, h),
550
791
  Stat.card({
551
792
  label: 'Error rate',
552
- state: new Stat.Failed({ message: 'Unavailable' }),
553
- }),
793
+ state: new Stat.Failed({
794
+ message: 'Unavailable',
795
+ }),
796
+ }, h),
554
797
  Stat.card({
555
798
  label: 'Requests',
556
799
  state: new Stat.Loading(),
557
- }),
800
+ }, h),
558
801
  ],
559
- }),
802
+ }, h),
560
803
  ListRow.view({
561
804
  title: 'Recent activity',
562
805
  meta: ['Updated 2 min ago'],
@@ -566,9 +809,9 @@ export const view = Submodel.defineView(model => {
566
809
  size: 'sm',
567
810
  variant: 'ghost',
568
811
  onClick: Clicked(),
569
- }),
812
+ }, h),
570
813
  ],
571
- }),
814
+ }, h),
572
815
  Pagination.view({
573
816
  status: `Page ${model.page} of 5`,
574
817
  previous: Button.view({
@@ -577,30 +820,30 @@ export const view = Submodel.defineView(model => {
577
820
  size: 'sm',
578
821
  onClick: PageChanged(-1),
579
822
  isDisabled: model.page <= 1,
580
- }),
823
+ }, h),
581
824
  next: Button.view({
582
825
  label: 'Next',
583
826
  variant: 'secondary',
584
827
  size: 'sm',
585
828
  onClick: PageChanged(1),
586
829
  isDisabled: model.page >= 5,
587
- }),
588
- }),
830
+ }, h),
831
+ }, h),
589
832
  Details.view({
590
833
  summary: 'More about this data',
591
834
  children: [
592
835
  Text.view({
593
836
  variant: 'muted',
594
837
  children: 'Disclosure body with supporting detail.',
595
- }),
838
+ }, h),
596
839
  ],
597
840
  open: model.detailsOpen,
598
841
  onToggle: isOpen => DetailsToggled(isOpen),
599
- }),
842
+ }, h),
600
843
  ],
601
- }),
844
+ }, h),
602
845
  ],
603
- }),
846
+ }, h),
604
847
  Card.section({
605
848
  title: 'Dialog',
606
849
  description: 'Modal surface with accessible labeling and dismissal.',
@@ -609,7 +852,7 @@ export const view = Submodel.defineView(model => {
609
852
  Button.view({
610
853
  label: 'Open dialog',
611
854
  onClick: GotDialogMessage(Dialog.RequestedOpen()),
612
- }),
855
+ }, h),
613
856
  h.submodel({
614
857
  slotId: 'catalog-dialog',
615
858
  model: model.dialog,
@@ -620,23 +863,25 @@ export const view = Submodel.defineView(model => {
620
863
  description: 'Controlled dialog with accessible labeling.',
621
864
  showClose: true,
622
865
  onRequestClose: message => GotDialogMessage(message),
623
- body: [Text.view({ children: 'Dialog body content.' })],
866
+ body: [
867
+ Text.view({ children: 'Dialog body content.' }, h),
868
+ ],
624
869
  footer: [
625
870
  Button.view({
626
871
  label: 'Cancel',
627
872
  variant: 'secondary',
628
873
  onClick: GotDialogMessage(Dialog.RequestedClose()),
629
- }),
874
+ }, h),
630
875
  Button.view({
631
876
  label: 'Confirm',
632
877
  onClick: GotDialogMessage(Dialog.RequestedClose()),
633
- }),
878
+ }, h),
634
879
  ],
635
- }),
880
+ }, h),
636
881
  toParentMessage: message => GotDialogMessage(message),
637
882
  }),
638
883
  ],
639
- }),
884
+ }, h),
640
885
  Card.section({
641
886
  title: 'Tabs',
642
887
  description: 'Accessible tablist with controlled selection.',
@@ -647,17 +892,18 @@ export const view = Submodel.defineView(model => {
647
892
  model: model.tabs,
648
893
  view: DemoTabs.view,
649
894
  viewInputs: DemoTabs.styledViewInputs({
895
+ selectedValue: model.selectedTab,
650
896
  tabs: ['overview', 'details', 'settings'],
651
897
  ariaLabel: 'Catalog tabs',
652
898
  renderPanel: value => Text.view({
653
899
  variant: 'muted',
654
900
  children: `Panel for ${value}.`,
655
- }),
656
- }),
901
+ }, h),
902
+ }, h),
657
903
  toParentMessage: message => GotTabsMessage(message),
658
904
  }),
659
905
  ],
660
- }),
906
+ }, h),
661
907
  Card.section({
662
908
  title: 'Dropdown menu',
663
909
  description: 'Menu trigger with accessible items and disabled behavior.',
@@ -674,11 +920,11 @@ export const view = Submodel.defineView(model => {
674
920
  ? { label: 'Delete', variant: 'destructive' }
675
921
  : { label: item[0].toUpperCase() + item.slice(1) },
676
922
  isItemDisabled: item => item === 'duplicate',
677
- }),
923
+ }, h),
678
924
  toParentMessage: message => GotMenuMessage(message),
679
925
  }),
680
926
  ],
681
- }),
927
+ }, h),
682
928
  Card.section({
683
929
  title: 'Toast',
684
930
  description: 'Status notifications with lifecycle and dismissal.',
@@ -691,24 +937,24 @@ export const view = Submodel.defineView(model => {
691
937
  label: 'Info',
692
938
  variant: 'secondary',
693
939
  onClick: ShowToast('Info'),
694
- }),
940
+ }, h),
695
941
  Button.view({
696
942
  label: 'Success',
697
943
  variant: 'secondary',
698
944
  onClick: ShowToast('Success'),
699
- }),
945
+ }, h),
700
946
  Button.view({
701
947
  label: 'Warning',
702
948
  variant: 'secondary',
703
949
  onClick: ShowToast('Warning'),
704
- }),
950
+ }, h),
705
951
  Button.view({
706
952
  label: 'Error',
707
953
  variant: 'secondary',
708
954
  onClick: ShowToast('Error'),
709
- }),
955
+ }, h),
710
956
  ],
711
- }),
957
+ }, h),
712
958
  h.submodel({
713
959
  slotId: 'catalog-toast',
714
960
  model: model.toast,
@@ -717,9 +963,9 @@ export const view = Submodel.defineView(model => {
717
963
  toParentMessage: message => GotToastMessage(message),
718
964
  }),
719
965
  ],
720
- }),
966
+ }, h),
721
967
  ],
722
- }),
968
+ }, h),
723
969
  ]);
724
970
  });
725
971
  export const Mount = { Model, init, update, view };