create-foldkit-app 0.38.0 → 0.39.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 (58) hide show
  1. package/dist/templates/base/FOLDKIT.md +3 -1
  2. package/dist/templates/examples/canvas-art/src/main.ts +1 -1
  3. package/dist/templates/examples/charting/src/chartHost.ts +3 -4
  4. package/dist/templates/examples/charting/src/entry.ts +0 -2
  5. package/dist/templates/examples/charting/src/view/chart.ts +101 -36
  6. package/dist/templates/examples/embedding/src/main.ts +3 -3
  7. package/dist/templates/examples/generative-art/src/subscription.ts +1 -1
  8. package/dist/templates/examples/job-application/src/step/education/education.ts +14 -16
  9. package/dist/templates/examples/job-application/src/step/skills/skills.ts +14 -16
  10. package/dist/templates/examples/job-application/src/step/workHistory/workHistory.ts +14 -16
  11. package/dist/templates/examples/map/src/entry.ts +1 -2
  12. package/dist/templates/examples/map/src/main.ts +123 -145
  13. package/dist/templates/examples/map/src/mapHost.ts +10 -12
  14. package/dist/templates/examples/map/src/mount.test.ts +93 -68
  15. package/dist/templates/examples/pixel-art/src/subscription.ts +5 -5
  16. package/dist/templates/examples/routing/src/main.ts +3 -3
  17. package/dist/templates/examples/slow-warnings/src/main.ts +4 -4
  18. package/dist/templates/examples/snake/src/main.ts +4 -3
  19. package/dist/templates/examples/ssg/src/entry.server.ts +2 -0
  20. package/dist/templates/examples/ssg/src/entry.ts +1 -0
  21. package/dist/templates/examples/ssr/package.json +1 -1
  22. package/dist/templates/examples/ssr/src/entry.server.ts +2 -0
  23. package/dist/templates/examples/ssr/src/entry.ts +1 -0
  24. package/dist/templates/examples/stopwatch/src/main.ts +1 -0
  25. package/dist/templates/examples/ui-showcase/src/main.ts +49 -37
  26. package/dist/templates/examples/ui-showcase/src/ui/subscriptions.ts +1 -18
  27. package/dist/templates/examples/ui-showcase/src/ui/update.ts +2 -12
  28. package/dist/templates/examples/ui-showcase/src/ui/view/calendar.ts +9 -7
  29. package/dist/templates/examples/ui-showcase/src/ui/view/checkbox.ts +80 -65
  30. package/dist/templates/examples/ui-showcase/src/ui/view/dialog.ts +102 -123
  31. package/dist/templates/examples/ui-showcase/src/ui/view/disclosure.ts +128 -120
  32. package/dist/templates/examples/ui-showcase/src/ui/view/fieldset.ts +173 -144
  33. package/dist/templates/examples/ui-showcase/src/ui/view/fileDrop.ts +17 -15
  34. package/dist/templates/examples/ui-showcase/src/ui/view/hoverIntent.ts +64 -56
  35. package/dist/templates/examples/ui-showcase/src/ui/view/input.ts +32 -33
  36. package/dist/templates/examples/ui-showcase/src/ui/view/meter.ts +82 -73
  37. package/dist/templates/examples/ui-showcase/src/ui/view/popover.ts +100 -81
  38. package/dist/templates/examples/ui-showcase/src/ui/view/progress.ts +83 -68
  39. package/dist/templates/examples/ui-showcase/src/ui/view/radioGroup.ts +79 -85
  40. package/dist/templates/examples/ui-showcase/src/ui/view/select.ts +63 -67
  41. package/dist/templates/examples/ui-showcase/src/ui/view/slider.ts +72 -72
  42. package/dist/templates/examples/ui-showcase/src/ui/view/switch.ts +25 -26
  43. package/dist/templates/examples/ui-showcase/src/ui/view/tabs.ts +58 -46
  44. package/dist/templates/examples/ui-showcase/src/ui/view/textarea.ts +32 -39
  45. package/dist/templates/examples/ui-showcase/src/ui/view/tooltip.ts +48 -36
  46. package/dist/templates/examples/websocket-chat/src/main.ts +44 -18
  47. package/dist/templates/examples/websocket-chat/src/story.test.ts +33 -6
  48. package/dist/templates/release.json +12 -11
  49. package/dist/templates/rendering/ssg/src/entry.server.ts +2 -0
  50. package/dist/templates/rendering/ssg/src/entry.ts +1 -0
  51. package/dist/templates/rendering/ssg/vite.config.ts +1 -0
  52. package/dist/templates/rendering/ssr/scripts/serve.ts +4 -145
  53. package/dist/templates/rendering/ssr/src/entry.server.ts +2 -0
  54. package/dist/templates/rendering/ssr/src/entry.ts +1 -0
  55. package/dist/templates/rendering/ssr/vite.config.ts +1 -0
  56. package/dist/utils/files.js +2 -0
  57. package/package.json +1 -1
  58. package/dist/templates/examples/charting/src/subscription.ts +0 -70
@@ -59,6 +59,83 @@ const checkPlaceholder = (h: HtmlBuilder<UiMessage>): Html => {
59
59
 
60
60
  export const view = Submodel.defineView<UiModel, UiMessage>(
61
61
  (model, h): Html => {
62
+ const verticalPlanOptionsView = ({
63
+ group,
64
+ options,
65
+ }: RadioGroup.RenderInfo<Plan>): Html =>
66
+ h.div(
67
+ [...group, h.Class(verticalGroupClassName)],
68
+ options.map(option => {
69
+ const plan = option.value
70
+ return h.div(
71
+ [...option.option, h.Class(verticalOptionClassName)],
72
+ [
73
+ h.div(
74
+ [h.Class('flex w-full items-center justify-between')],
75
+ [
76
+ h.div(
77
+ [],
78
+ [
79
+ h.span(
80
+ [...option.label, h.Class(labelClassName)],
81
+ [plan],
82
+ ),
83
+ h.p(
84
+ [...option.description, h.Class(descriptionClassName)],
85
+ [planDescriptions[plan]],
86
+ ),
87
+ ],
88
+ ),
89
+ h.div(
90
+ [h.Class('flex items-center gap-3')],
91
+ [
92
+ h.span([h.Class(priceClassName)], [planPrices[plan]]),
93
+ option.isSelected ? checkIcon(h) : checkPlaceholder(h),
94
+ ],
95
+ ),
96
+ ],
97
+ ),
98
+ ],
99
+ )
100
+ }),
101
+ )
102
+
103
+ const horizontalPlanOptionsView = ({
104
+ group,
105
+ options,
106
+ }: RadioGroup.RenderInfo<Plan>): Html =>
107
+ h.div(
108
+ [...group, h.Class(horizontalGroupClassName)],
109
+ options.map(option => {
110
+ const plan = option.value
111
+ return h.div(
112
+ [...option.option, h.Class(horizontalOptionClassName)],
113
+ [
114
+ h.div(
115
+ [h.Class('flex w-full items-center justify-between')],
116
+ [
117
+ h.div(
118
+ [],
119
+ [
120
+ h.span(
121
+ [...option.label, h.Class(labelClassName)],
122
+ [plan],
123
+ ),
124
+ h.p(
125
+ [...option.description, h.Class(descriptionClassName)],
126
+ [planDescriptions[plan]],
127
+ ),
128
+ ],
129
+ ),
130
+ option.isSelected ? checkIcon(h) : checkPlaceholder(h),
131
+ ],
132
+ ),
133
+ h.span([h.Class(priceClassName + ' mt-2')], [planPrices[plan]]),
134
+ ],
135
+ )
136
+ }),
137
+ )
138
+
62
139
  return h.div(
63
140
  [],
64
141
  [
@@ -80,51 +157,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
80
157
  options: plans,
81
158
  ariaLabel: 'Server plan',
82
159
  hasOptionDescription: () => true,
83
- toView: ({ group, options }) =>
84
- h.div(
85
- [...group, h.Class(verticalGroupClassName)],
86
- options.map(option => {
87
- const plan = option.value
88
- return h.div(
89
- [...option.option, h.Class(verticalOptionClassName)],
90
- [
91
- h.div(
92
- [h.Class('flex w-full items-center justify-between')],
93
- [
94
- h.div(
95
- [],
96
- [
97
- h.span(
98
- [...option.label, h.Class(labelClassName)],
99
- [plan],
100
- ),
101
- h.p(
102
- [
103
- ...option.description,
104
- h.Class(descriptionClassName),
105
- ],
106
- [planDescriptions[plan]],
107
- ),
108
- ],
109
- ),
110
- h.div(
111
- [h.Class('flex items-center gap-3')],
112
- [
113
- h.span(
114
- [h.Class(priceClassName)],
115
- [planPrices[plan]],
116
- ),
117
- option.isSelected
118
- ? checkIcon(h)
119
- : checkPlaceholder(h),
120
- ],
121
- ),
122
- ],
123
- ),
124
- ],
125
- )
126
- }),
127
- ),
160
+ toView: verticalPlanOptionsView,
128
161
  },
129
162
  toParentMessage: message =>
130
163
  UiMessage.GotVerticalRadioGroupDemoMessage({ message }),
@@ -144,46 +177,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
144
177
  ariaLabel: 'Server plan',
145
178
  orientation: 'Horizontal',
146
179
  hasOptionDescription: () => true,
147
- toView: ({ group, options }) =>
148
- h.div(
149
- [...group, h.Class(horizontalGroupClassName)],
150
- options.map(option => {
151
- const plan = option.value
152
- return h.div(
153
- [...option.option, h.Class(horizontalOptionClassName)],
154
- [
155
- h.div(
156
- [h.Class('flex w-full items-center justify-between')],
157
- [
158
- h.div(
159
- [],
160
- [
161
- h.span(
162
- [...option.label, h.Class(labelClassName)],
163
- [plan],
164
- ),
165
- h.p(
166
- [
167
- ...option.description,
168
- h.Class(descriptionClassName),
169
- ],
170
- [planDescriptions[plan]],
171
- ),
172
- ],
173
- ),
174
- option.isSelected
175
- ? checkIcon(h)
176
- : checkPlaceholder(h),
177
- ],
178
- ),
179
- h.span(
180
- [h.Class(priceClassName + ' mt-2')],
181
- [planPrices[plan]],
182
- ),
183
- ],
184
- )
185
- }),
186
- ),
180
+ toView: horizontalPlanOptionsView,
187
181
  },
188
182
  toParentMessage: message =>
189
183
  UiMessage.GotHorizontalRadioGroupDemoMessage({ message }),
@@ -21,6 +21,67 @@ const descriptionClassName = 'text-sm text-gray-500'
21
21
 
22
22
  export const view = Submodel.defineView<UiModel, UiMessage>(
23
23
  (model, h): Html => {
24
+ const countryOptions = (): ReadonlyArray<Html> => [
25
+ h.option([h.Value('us')], ['United States']),
26
+ h.option([h.Value('ca')], ['Canada']),
27
+ h.option([h.Value('gb')], ['United Kingdom']),
28
+ h.option([h.Value('au')], ['Australia']),
29
+ ]
30
+
31
+ const basicSelectView = (
32
+ attributes: Select.SelectAttributes<UiMessage>,
33
+ ): Html =>
34
+ h.div(
35
+ [h.Class('flex flex-col gap-1.5 w-full')],
36
+ [
37
+ h.label([...attributes.label, h.Class(labelClassName)], ['Country']),
38
+ h.div(
39
+ [h.Class(selectWrapperClassName)],
40
+ [
41
+ h.select(
42
+ [...attributes.select, h.Class(selectClassName)],
43
+ countryOptions(),
44
+ ),
45
+ h.span(
46
+ [h.Class(chevronClassName)],
47
+ [Icon.chevronDown('w-4 h-4')],
48
+ ),
49
+ ],
50
+ ),
51
+ h.span(
52
+ [...attributes.description, h.Class(descriptionClassName)],
53
+ ['Where you currently reside.'],
54
+ ),
55
+ ],
56
+ )
57
+
58
+ const disabledSelectView = (
59
+ attributes: Select.SelectAttributes<UiMessage>,
60
+ ): Html =>
61
+ h.div(
62
+ [h.Class('flex flex-col gap-1.5 max-w-sm')],
63
+ [
64
+ h.label([...attributes.label, h.Class(labelClassName)], ['Country']),
65
+ h.div(
66
+ [h.Class(selectWrapperClassName)],
67
+ [
68
+ h.select(
69
+ [...attributes.select, h.Class(selectClassName)],
70
+ countryOptions(),
71
+ ),
72
+ h.span(
73
+ [h.Class(chevronClassName)],
74
+ [Icon.chevronDown('w-4 h-4')],
75
+ ),
76
+ ],
77
+ ),
78
+ h.span(
79
+ [...attributes.description, h.Class(descriptionClassName)],
80
+ ['This select is disabled.'],
81
+ ),
82
+ ],
83
+ )
84
+
24
85
  return h.div(
25
86
  [],
26
87
  [
@@ -39,41 +100,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
39
100
  value: model.selectDemoValue,
40
101
  hasDescription: true,
41
102
  onChange: value => UiMessage.UpdatedSelectDemoValue({ value }),
42
- toView: attributes =>
43
- h.div(
44
- [h.Class('flex flex-col gap-1.5 w-full')],
45
- [
46
- h.label(
47
- [...attributes.label, h.Class(labelClassName)],
48
- ['Country'],
49
- ),
50
- h.div(
51
- [h.Class(selectWrapperClassName)],
52
- [
53
- h.select(
54
- [...attributes.select, h.Class(selectClassName)],
55
- [
56
- h.option([h.Value('us')], ['United States']),
57
- h.option([h.Value('ca')], ['Canada']),
58
- h.option([h.Value('gb')], ['United Kingdom']),
59
- h.option([h.Value('au')], ['Australia']),
60
- ],
61
- ),
62
- h.span(
63
- [h.Class(chevronClassName)],
64
- [Icon.chevronDown('w-4 h-4')],
65
- ),
66
- ],
67
- ),
68
- h.span(
69
- [
70
- ...attributes.description,
71
- h.Class(descriptionClassName),
72
- ],
73
- ['Where you currently reside.'],
74
- ),
75
- ],
76
- ),
103
+ toView: basicSelectView,
77
104
  },
78
105
  h,
79
106
  ),
@@ -90,38 +117,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
90
117
  isDisabled: true,
91
118
  hasDescription: true,
92
119
  value: 'us',
93
- toView: attributes =>
94
- h.div(
95
- [h.Class('flex flex-col gap-1.5 max-w-sm')],
96
- [
97
- h.label(
98
- [...attributes.label, h.Class(labelClassName)],
99
- ['Country'],
100
- ),
101
- h.div(
102
- [h.Class(selectWrapperClassName)],
103
- [
104
- h.select(
105
- [...attributes.select, h.Class(selectClassName)],
106
- [
107
- h.option([h.Value('us')], ['United States']),
108
- h.option([h.Value('ca')], ['Canada']),
109
- h.option([h.Value('gb')], ['United Kingdom']),
110
- h.option([h.Value('au')], ['Australia']),
111
- ],
112
- ),
113
- h.span(
114
- [h.Class(chevronClassName)],
115
- [Icon.chevronDown('w-4 h-4')],
116
- ),
117
- ],
118
- ),
119
- h.span(
120
- [...attributes.description, h.Class(descriptionClassName)],
121
- ['This select is disabled.'],
122
- ),
123
- ],
124
- ),
120
+ toView: disabledSelectView,
125
121
  },
126
122
  h,
127
123
  ),
@@ -42,11 +42,80 @@ const volumeFormatted = (value: number): string => `${Math.round(value * 100)}%`
42
42
 
43
43
  export const view = Submodel.defineView<UiModel, UiMessage>(
44
44
  (model, h): Html => {
45
+ const horizontalSliderView = (attributes: Slider.SliderAttributes): Html =>
46
+ h.div(
47
+ [h.Class(horizontalRowClassName)],
48
+ [
49
+ h.div(
50
+ [h.Class(horizontalHeaderClassName)],
51
+ [
52
+ h.label(
53
+ [...attributes.label, h.Class(labelClassName)],
54
+ ['Rating'],
55
+ ),
56
+ h.span(
57
+ [h.Class(valueClassName)],
58
+ [ratingFormatted(model.sliderRatingValue)],
59
+ ),
60
+ ],
61
+ ),
62
+ h.div(
63
+ [...attributes.root, h.Class(horizontalRootClassName)],
64
+ [
65
+ h.div(
66
+ [...attributes.track, h.Class(horizontalTrackClassName)],
67
+ [
68
+ h.div([
69
+ ...attributes.filledTrack,
70
+ h.Class(horizontalFilledTrackClassName),
71
+ ]),
72
+ ],
73
+ ),
74
+ h.div([...attributes.thumb, h.Class(thumbClassName)]),
75
+ ],
76
+ ),
77
+ ],
78
+ )
79
+
80
+ const verticalSliderView = (attributes: Slider.SliderAttributes): Html =>
81
+ h.div(
82
+ [h.Class(verticalRowClassName)],
83
+ [
84
+ h.div(
85
+ [h.Class(verticalHeaderClassName)],
86
+ [
87
+ h.label(
88
+ [...attributes.label, h.Class(labelClassName)],
89
+ ['Volume'],
90
+ ),
91
+ h.span(
92
+ [h.Class(valueClassName)],
93
+ [volumeFormatted(model.sliderVolumeValue)],
94
+ ),
95
+ ],
96
+ ),
97
+ h.div(
98
+ [...attributes.root, h.Class(verticalRootClassName)],
99
+ [
100
+ h.div(
101
+ [...attributes.track, h.Class(verticalTrackClassName)],
102
+ [
103
+ h.div([
104
+ ...attributes.filledTrack,
105
+ h.Class(verticalFilledTrackClassName),
106
+ ]),
107
+ ],
108
+ ),
109
+ h.div([...attributes.thumb, h.Class(thumbClassName)]),
110
+ ],
111
+ ),
112
+ ],
113
+ )
114
+
45
115
  return h.div(
46
116
  [],
47
117
  [
48
118
  h.h2([h.Class('text-2xl font-bold text-gray-900 mb-6')], ['Slider']),
49
-
50
119
  h.h3(
51
120
  [h.Class('text-lg font-semibold text-gray-900 mt-8 mb-4')],
52
121
  ['Horizontal'],
@@ -58,43 +127,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
58
127
  viewInputs: {
59
128
  value: model.sliderRatingValue,
60
129
  formatValue: value => `${value} of 10`,
61
- toView: attributes =>
62
- h.div(
63
- [h.Class(horizontalRowClassName)],
64
- [
65
- h.div(
66
- [h.Class(horizontalHeaderClassName)],
67
- [
68
- h.label(
69
- [...attributes.label, h.Class(labelClassName)],
70
- ['Rating'],
71
- ),
72
- h.span(
73
- [h.Class(valueClassName)],
74
- [ratingFormatted(model.sliderRatingValue)],
75
- ),
76
- ],
77
- ),
78
- h.div(
79
- [...attributes.root, h.Class(horizontalRootClassName)],
80
- [
81
- h.div(
82
- [
83
- ...attributes.track,
84
- h.Class(horizontalTrackClassName),
85
- ],
86
- [
87
- h.div([
88
- ...attributes.filledTrack,
89
- h.Class(horizontalFilledTrackClassName),
90
- ]),
91
- ],
92
- ),
93
- h.div([...attributes.thumb, h.Class(thumbClassName)]),
94
- ],
95
- ),
96
- ],
97
- ),
130
+ toView: horizontalSliderView,
98
131
  },
99
132
  toParentMessage: message =>
100
133
  UiMessage.GotSliderRatingDemoMessage({ message }),
@@ -114,40 +147,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
114
147
  thumbAlignment: 'Edge',
115
148
  thumbSize: '1.25rem',
116
149
  formatValue: value => `${Math.round(value * 100)} percent`,
117
- toView: attributes =>
118
- h.div(
119
- [h.Class(verticalRowClassName)],
120
- [
121
- h.div(
122
- [h.Class(verticalHeaderClassName)],
123
- [
124
- h.label(
125
- [...attributes.label, h.Class(labelClassName)],
126
- ['Volume'],
127
- ),
128
- h.span(
129
- [h.Class(valueClassName)],
130
- [volumeFormatted(model.sliderVolumeValue)],
131
- ),
132
- ],
133
- ),
134
- h.div(
135
- [...attributes.root, h.Class(verticalRootClassName)],
136
- [
137
- h.div(
138
- [...attributes.track, h.Class(verticalTrackClassName)],
139
- [
140
- h.div([
141
- ...attributes.filledTrack,
142
- h.Class(verticalFilledTrackClassName),
143
- ]),
144
- ],
145
- ),
146
- h.div([...attributes.thumb, h.Class(thumbClassName)]),
147
- ],
148
- ),
149
- ],
150
- ),
150
+ toView: verticalSliderView,
151
151
  },
152
152
  toParentMessage: message =>
153
153
  UiMessage.GotSliderVolumeDemoMessage({ message }),
@@ -32,6 +32,30 @@ const knob = (isChecked: boolean, h: HtmlBuilder<UiMessage>): Html => {
32
32
 
33
33
  export const view = Submodel.defineView<UiModel, UiMessage>(
34
34
  (model, h): Html => {
35
+ const switchView = (attributes: Switch.SwitchAttributes<UiMessage>): Html =>
36
+ h.div(
37
+ [h.Class(wrapperClassName)],
38
+ [
39
+ h.button(
40
+ [...attributes.button, h.Class(buttonClassName)],
41
+ [knob(model.isSwitchDemoChecked, h)],
42
+ ),
43
+ h.div(
44
+ [],
45
+ [
46
+ h.label(
47
+ [...attributes.label, h.Class(labelClassName)],
48
+ ['Enable notifications'],
49
+ ),
50
+ h.p(
51
+ [...attributes.description, h.Class(descriptionClassName)],
52
+ ['Get notified when something important happens.'],
53
+ ),
54
+ ],
55
+ ),
56
+ ],
57
+ )
58
+
35
59
  return h.div(
36
60
  [],
37
61
  [
@@ -46,32 +70,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
46
70
  hasDescription: true,
47
71
  onToggle: isChecked =>
48
72
  UiMessage.ToggledSwitchDemo({ isChecked }),
49
- toView: attributes =>
50
- h.div(
51
- [h.Class(wrapperClassName)],
52
- [
53
- h.button(
54
- [...attributes.button, h.Class(buttonClassName)],
55
- [knob(model.isSwitchDemoChecked, h)],
56
- ),
57
- h.div(
58
- [],
59
- [
60
- h.label(
61
- [...attributes.label, h.Class(labelClassName)],
62
- ['Enable notifications'],
63
- ),
64
- h.p(
65
- [
66
- ...attributes.description,
67
- h.Class(descriptionClassName),
68
- ],
69
- ['Get notified when something important happens.'],
70
- ),
71
- ],
72
- ),
73
- ],
74
- ),
73
+ toView: switchView,
75
74
  },
76
75
  h,
77
76
  ),
@@ -96,6 +96,62 @@ const panelFor = (tab: DemoTab, h: HtmlBuilder<UiMessage>): Html =>
96
96
 
97
97
  export const view = Submodel.defineView<UiModel, UiMessage>(
98
98
  (model, h): Html => {
99
+ const horizontalTabsView = ({
100
+ tablist,
101
+ tabs,
102
+ activeIndex,
103
+ }: Tabs.RenderInfo<DemoTab>): Html =>
104
+ h.div(
105
+ [],
106
+ [
107
+ h.div(
108
+ [...tablist, h.Class('flex')],
109
+ tabs.map(tab =>
110
+ h.button(
111
+ [...tab.tab, h.Class(horizontalButtonClassName)],
112
+ [h.span([], [tab.value])],
113
+ ),
114
+ ),
115
+ ),
116
+ ...Array.map(
117
+ Array.filter(tabs, tab => tab.index === activeIndex),
118
+ tab =>
119
+ h.div(
120
+ [...tab.panel, h.Class(horizontalPanelClassName)],
121
+ [panelFor(tab.value, h)],
122
+ ),
123
+ ),
124
+ ],
125
+ )
126
+
127
+ const verticalTabsView = ({
128
+ tablist,
129
+ tabs,
130
+ activeIndex,
131
+ }: Tabs.RenderInfo<DemoTab>): Html =>
132
+ h.div(
133
+ [h.Class('flex')],
134
+ [
135
+ h.div(
136
+ [...tablist, h.Class('flex flex-col')],
137
+ tabs.map(tab =>
138
+ h.button(
139
+ [...tab.tab, h.Class(verticalButtonClassName)],
140
+ [h.span([], [tab.value])],
141
+ ),
142
+ ),
143
+ ),
144
+ ...Array.map(
145
+ Array.filter(tabs, tab => tab.index === activeIndex),
146
+ tab =>
147
+ h.div(
148
+ [...tab.panel, h.Class(verticalPanelClassName)],
149
+ [panelFor(tab.value, h)],
150
+ ),
151
+ ),
152
+ ],
153
+ )
154
+
99
155
  return h.div(
100
156
  [],
101
157
  [
@@ -113,29 +169,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
113
169
  tabs: demoTabs,
114
170
  selectedValue: model.horizontalTabsDemoTab,
115
171
  ariaLabel: 'Framework comparison tabs',
116
- toView: ({ tablist, tabs, activeIndex }) =>
117
- h.div(
118
- [],
119
- [
120
- h.div(
121
- [...tablist, h.Class('flex')],
122
- tabs.map(tab =>
123
- h.button(
124
- [...tab.tab, h.Class(horizontalButtonClassName)],
125
- [h.span([], [tab.value])],
126
- ),
127
- ),
128
- ),
129
- ...Array.map(
130
- Array.filter(tabs, tab => tab.index === activeIndex),
131
- tab =>
132
- h.div(
133
- [...tab.panel, h.Class(horizontalPanelClassName)],
134
- [panelFor(tab.value, h)],
135
- ),
136
- ),
137
- ],
138
- ),
172
+ toView: horizontalTabsView,
139
173
  },
140
174
  toParentMessage: message =>
141
175
  UiMessage.GotHorizontalTabsDemoMessage({ message }),
@@ -154,29 +188,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
154
188
  selectedValue: model.verticalTabsDemoTab,
155
189
  ariaLabel: 'Framework comparison tabs',
156
190
  orientation: 'Vertical',
157
- toView: ({ tablist, tabs, activeIndex }) =>
158
- h.div(
159
- [h.Class('flex')],
160
- [
161
- h.div(
162
- [...tablist, h.Class('flex flex-col')],
163
- tabs.map(tab =>
164
- h.button(
165
- [...tab.tab, h.Class(verticalButtonClassName)],
166
- [h.span([], [tab.value])],
167
- ),
168
- ),
169
- ),
170
- ...Array.map(
171
- Array.filter(tabs, tab => tab.index === activeIndex),
172
- tab =>
173
- h.div(
174
- [...tab.panel, h.Class(verticalPanelClassName)],
175
- [panelFor(tab.value, h)],
176
- ),
177
- ),
178
- ],
179
- ),
191
+ toView: verticalTabsView,
180
192
  },
181
193
  toParentMessage: message =>
182
194
  UiMessage.GotVerticalTabsDemoMessage({ message }),