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
@@ -43,8 +43,21 @@ const sectionHeadingClassName = 'text-lg font-semibold text-gray-900 mt-8 mb-4'
43
43
  const checkmark = (h: HtmlBuilder<UiMessage>): Html =>
44
44
  h.span([h.Class('text-white text-xs')], ['✓'])
45
45
 
46
- const nameInput = (value: string, h: HtmlBuilder<UiMessage>): Html =>
47
- Input.view(
46
+ const nameInput = (value: string, h: HtmlBuilder<UiMessage>): Html => {
47
+ const nameInputView = (attributes: Input.InputAttributes<UiMessage>): Html =>
48
+ h.div(
49
+ [h.Class(fieldClassName)],
50
+ [
51
+ h.label([...attributes.label, h.Class(labelClassName)], ['Name']),
52
+ h.input([...attributes.input, h.Class(inputClassName)]),
53
+ h.span(
54
+ [...attributes.description, h.Class(descriptionClassName)],
55
+ ['As it appears on your government-issued ID.'],
56
+ ),
57
+ ],
58
+ )
59
+
60
+ return Input.view(
48
61
  {
49
62
  id: 'fieldset-name-input',
50
63
  value,
@@ -52,24 +65,29 @@ const nameInput = (value: string, h: HtmlBuilder<UiMessage>): Html =>
52
65
  onInput: inputValue =>
53
66
  UiMessage.UpdatedFieldsetInputValue({ value: inputValue }),
54
67
  placeholder: 'Enter your full name',
55
- toView: attributes =>
56
- h.div(
57
- [h.Class(fieldClassName)],
58
- [
59
- h.label([...attributes.label, h.Class(labelClassName)], ['Name']),
60
- h.input([...attributes.input, h.Class(inputClassName)]),
61
- h.span(
62
- [...attributes.description, h.Class(descriptionClassName)],
63
- ['As it appears on your government-issued ID.'],
64
- ),
65
- ],
66
- ),
68
+ toView: nameInputView,
67
69
  },
68
70
  h,
69
71
  )
72
+ }
70
73
 
71
- const bioTextarea = (value: string, h: HtmlBuilder<UiMessage>): Html =>
72
- Textarea.view(
74
+ const bioTextarea = (value: string, h: HtmlBuilder<UiMessage>): Html => {
75
+ const bioTextareaView = (
76
+ attributes: Textarea.TextareaAttributes<UiMessage>,
77
+ ): Html =>
78
+ h.div(
79
+ [h.Class(fieldClassName)],
80
+ [
81
+ h.label([...attributes.label, h.Class(labelClassName)], ['Bio']),
82
+ h.textarea([...attributes.textarea, h.Class(textareaClassName)]),
83
+ h.span(
84
+ [...attributes.description, h.Class(descriptionClassName)],
85
+ ['A brief introduction about yourself.'],
86
+ ),
87
+ ],
88
+ )
89
+
90
+ return Textarea.view(
73
91
  {
74
92
  id: 'fieldset-bio-textarea',
75
93
  value,
@@ -78,196 +96,207 @@ const bioTextarea = (value: string, h: HtmlBuilder<UiMessage>): Html =>
78
96
  UiMessage.UpdatedFieldsetTextareaValue({ value: textareaValue }),
79
97
  placeholder: 'Tell us about yourself...',
80
98
  rows: 3,
81
- toView: attributes =>
99
+ toView: bioTextareaView,
100
+ },
101
+ h,
102
+ )
103
+ }
104
+
105
+ const termsCheckbox = (isChecked: boolean, h: HtmlBuilder<UiMessage>): Html => {
106
+ const termsCheckboxView = (
107
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
108
+ ): Html =>
109
+ h.div(
110
+ [h.Class('flex flex-col gap-1')],
111
+ [
82
112
  h.div(
83
- [h.Class(fieldClassName)],
113
+ [h.Class('flex items-center gap-2')],
84
114
  [
85
- h.label([...attributes.label, h.Class(labelClassName)], ['Bio']),
86
- h.textarea([...attributes.textarea, h.Class(textareaClassName)]),
87
- h.span(
88
- [...attributes.description, h.Class(descriptionClassName)],
89
- ['A brief introduction about yourself.'],
115
+ h.button(
116
+ [...attributes.checkbox, h.Class(checkboxClassName)],
117
+ isChecked ? [checkmark(h)] : [],
118
+ ),
119
+ h.label(
120
+ [...attributes.label, h.Class(checkboxLabelClassName)],
121
+ ['I agree to the terms and conditions'],
90
122
  ),
91
123
  ],
92
124
  ),
93
- },
94
- h,
95
- )
125
+ h.p(
126
+ [...attributes.description, h.Class(checkboxDescriptionClassName)],
127
+ ['You agree to our Terms of Service and Privacy Policy.'],
128
+ ),
129
+ ],
130
+ )
96
131
 
97
- const termsCheckbox = (isChecked: boolean, h: HtmlBuilder<UiMessage>): Html =>
98
- Checkbox.view(
132
+ return Checkbox.view(
99
133
  {
100
134
  id: FIELDSET_CHECKBOX_DEMO_ID,
101
135
  isChecked,
102
136
  hasDescription: true,
103
137
  onToggle: nextIsChecked =>
104
138
  UiMessage.ToggledFieldsetCheckboxDemo({ isChecked: nextIsChecked }),
105
- toView: attributes =>
106
- h.div(
107
- [h.Class('flex flex-col gap-1')],
108
- [
109
- h.div(
110
- [h.Class('flex items-center gap-2')],
111
- [
112
- h.button(
113
- [...attributes.checkbox, h.Class(checkboxClassName)],
114
- isChecked ? [checkmark(h)] : [],
115
- ),
116
- h.label(
117
- [...attributes.label, h.Class(checkboxLabelClassName)],
118
- ['I agree to the terms and conditions'],
119
- ),
120
- ],
121
- ),
122
- h.p(
123
- [
124
- ...attributes.description,
125
- h.Class(checkboxDescriptionClassName),
126
- ],
127
- ['You agree to our Terms of Service and Privacy Policy.'],
128
- ),
129
- ],
130
- ),
139
+ toView: termsCheckboxView,
131
140
  },
132
141
  h,
133
142
  )
143
+ }
134
144
 
135
145
  // DISABLED FIELDS
136
146
 
137
- const disabledNameInput = (h: HtmlBuilder<UiMessage>): Html =>
138
- Input.view(
147
+ const disabledNameInput = (h: HtmlBuilder<UiMessage>): Html => {
148
+ const disabledNameInputView = (
149
+ attributes: Input.InputAttributes<UiMessage>,
150
+ ): Html =>
151
+ h.div(
152
+ [h.Class(fieldClassName)],
153
+ [
154
+ h.label([...attributes.label, h.Class(labelClassName)], ['Name']),
155
+ h.input([...attributes.input, h.Class(inputClassName)]),
156
+ ],
157
+ )
158
+
159
+ return Input.view(
139
160
  {
140
161
  id: 'fieldset-disabled-name-input',
141
162
  isDisabled: true,
142
163
  value: 'Ada Lovelace',
143
- toView: attributes =>
144
- h.div(
145
- [h.Class(fieldClassName)],
146
- [
147
- h.label([...attributes.label, h.Class(labelClassName)], ['Name']),
148
- h.input([...attributes.input, h.Class(inputClassName)]),
149
- ],
150
- ),
164
+ toView: disabledNameInputView,
151
165
  },
152
166
  h,
153
167
  )
168
+ }
154
169
 
155
- const disabledBioTextarea = (h: HtmlBuilder<UiMessage>): Html =>
156
- Textarea.view(
170
+ const disabledBioTextarea = (h: HtmlBuilder<UiMessage>): Html => {
171
+ const disabledBioTextareaView = (
172
+ attributes: Textarea.TextareaAttributes<UiMessage>,
173
+ ): Html =>
174
+ h.div(
175
+ [h.Class(fieldClassName)],
176
+ [
177
+ h.label([...attributes.label, h.Class(labelClassName)], ['Bio']),
178
+ h.textarea([...attributes.textarea, h.Class(textareaClassName)]),
179
+ ],
180
+ )
181
+
182
+ return Textarea.view(
157
183
  {
158
184
  id: 'fieldset-disabled-bio-textarea',
159
185
  isDisabled: true,
160
186
  value:
161
187
  "Mathematician and writer, known for work on Charles Babbage's Analytical Engine.",
162
188
  rows: 3,
163
- toView: attributes =>
164
- h.div(
165
- [h.Class(fieldClassName)],
166
- [
167
- h.label([...attributes.label, h.Class(labelClassName)], ['Bio']),
168
- h.textarea([...attributes.textarea, h.Class(textareaClassName)]),
169
- ],
170
- ),
189
+ toView: disabledBioTextareaView,
171
190
  },
172
191
  h,
173
192
  )
193
+ }
174
194
 
175
- const disabledTermsCheckbox = (h: HtmlBuilder<UiMessage>): Html =>
176
- Checkbox.view(
195
+ const disabledTermsCheckbox = (h: HtmlBuilder<UiMessage>): Html => {
196
+ const disabledTermsCheckboxView = (
197
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
198
+ ): Html =>
199
+ h.div(
200
+ [h.Class('flex items-center gap-2')],
201
+ [
202
+ h.button(
203
+ [...attributes.checkbox, h.Class(checkboxClassName)],
204
+ [checkmark(h)],
205
+ ),
206
+ h.label(
207
+ [...attributes.label, h.Class(checkboxLabelClassName)],
208
+ ['I agree to the terms and conditions'],
209
+ ),
210
+ ],
211
+ )
212
+
213
+ return Checkbox.view(
177
214
  {
178
215
  id: FIELDSET_DISABLED_CHECKBOX_ID,
179
216
  isChecked: true,
180
217
  isDisabled: true,
181
218
  onToggle: isChecked =>
182
219
  UiMessage.ToggledFieldsetCheckboxDemo({ isChecked }),
183
- toView: attributes =>
184
- h.div(
185
- [h.Class('flex items-center gap-2')],
186
- [
187
- h.button(
188
- [...attributes.checkbox, h.Class(checkboxClassName)],
189
- [checkmark(h)],
190
- ),
191
- h.label(
192
- [...attributes.label, h.Class(checkboxLabelClassName)],
193
- ['I agree to the terms and conditions'],
194
- ),
195
- ],
196
- ),
220
+ toView: disabledTermsCheckboxView,
197
221
  },
198
222
  h,
199
223
  )
224
+ }
200
225
 
201
226
  // DEMOS
202
227
 
203
- const basicDemo = (model: UiModel, h: HtmlBuilder<UiMessage>): Html =>
204
- Fieldset.view(
205
- {
206
- id: 'fieldset-basic-demo',
207
- hasDescription: true,
208
- toView: attributes =>
209
- h.fieldset(
210
- [...attributes.fieldset, h.Class(fieldsetClassName)],
228
+ const basicDemo = (model: UiModel, h: HtmlBuilder<UiMessage>): Html => {
229
+ const basicFieldsetView = (
230
+ attributes: Fieldset.FieldsetAttributes<UiMessage>,
231
+ ): Html =>
232
+ h.fieldset(
233
+ [...attributes.fieldset, h.Class(fieldsetClassName)],
234
+ [
235
+ h.legend(
236
+ [...attributes.legend, h.Class(legendClassName)],
237
+ ['Personal Information'],
238
+ ),
239
+ h.span(
240
+ [...attributes.description, h.Class(`${descriptionClassName} mt-1`)],
241
+ ['We just need a few details.'],
242
+ ),
243
+ h.div(
244
+ [h.Class(fieldsClassName)],
211
245
  [
212
- h.legend(
213
- [...attributes.legend, h.Class(legendClassName)],
214
- ['Personal Information'],
215
- ),
216
- h.span(
217
- [
218
- ...attributes.description,
219
- h.Class(`${descriptionClassName} mt-1`),
220
- ],
221
- ['We just need a few details.'],
222
- ),
223
- h.div(
224
- [h.Class(fieldsClassName)],
225
- [
226
- nameInput(model.fieldsetInputValue, h),
227
- bioTextarea(model.fieldsetTextareaValue, h),
228
- termsCheckbox(model.isFieldsetCheckboxDemoChecked, h),
229
- ],
230
- ),
246
+ nameInput(model.fieldsetInputValue, h),
247
+ bioTextarea(model.fieldsetTextareaValue, h),
248
+ termsCheckbox(model.isFieldsetCheckboxDemoChecked, h),
231
249
  ],
232
250
  ),
251
+ ],
252
+ )
253
+
254
+ return Fieldset.view(
255
+ {
256
+ id: 'fieldset-basic-demo',
257
+ hasDescription: true,
258
+ toView: basicFieldsetView,
233
259
  },
234
260
  h,
235
261
  )
262
+ }
263
+
264
+ const disabledDemo = (h: HtmlBuilder<UiMessage>): Html => {
265
+ const disabledFieldsetView = (
266
+ attributes: Fieldset.FieldsetAttributes<UiMessage>,
267
+ ): Html =>
268
+ h.fieldset(
269
+ [...attributes.fieldset, h.Class(fieldsetClassName)],
270
+ [
271
+ h.legend(
272
+ [...attributes.legend, h.Class(legendClassName)],
273
+ ['Personal Information'],
274
+ ),
275
+ h.span(
276
+ [...attributes.description, h.Class(`${descriptionClassName} mt-1`)],
277
+ ['This fieldset is disabled.'],
278
+ ),
279
+ h.div(
280
+ [h.Class(fieldsClassName)],
281
+ [
282
+ disabledNameInput(h),
283
+ disabledBioTextarea(h),
284
+ disabledTermsCheckbox(h),
285
+ ],
286
+ ),
287
+ ],
288
+ )
236
289
 
237
- const disabledDemo = (h: HtmlBuilder<UiMessage>): Html =>
238
- Fieldset.view(
290
+ return Fieldset.view(
239
291
  {
240
292
  id: 'fieldset-disabled-demo',
241
293
  isDisabled: true,
242
294
  hasDescription: true,
243
- toView: attributes =>
244
- h.fieldset(
245
- [...attributes.fieldset, h.Class(fieldsetClassName)],
246
- [
247
- h.legend(
248
- [...attributes.legend, h.Class(legendClassName)],
249
- ['Personal Information'],
250
- ),
251
- h.span(
252
- [
253
- ...attributes.description,
254
- h.Class(`${descriptionClassName} mt-1`),
255
- ],
256
- ['This fieldset is disabled.'],
257
- ),
258
- h.div(
259
- [h.Class(fieldsClassName)],
260
- [
261
- disabledNameInput(h),
262
- disabledBioTextarea(h),
263
- disabledTermsCheckbox(h),
264
- ],
265
- ),
266
- ],
267
- ),
295
+ toView: disabledFieldsetView,
268
296
  },
269
297
  h,
270
298
  )
299
+ }
271
300
 
272
301
  // VIEW
273
302
 
@@ -42,6 +42,22 @@ const fileKey = (file: File.File): string =>
42
42
 
43
43
  export const view = Submodel.defineView<UiModel, UiMessage>(
44
44
  (model, h): Html => {
45
+ const fileDropView = (attributes: FileDrop.FileDropAttributes): Html =>
46
+ h.label(
47
+ [...attributes.root, h.Class(dropZoneClassName)],
48
+ [
49
+ h.p(
50
+ [h.Class(primaryTextClassName)],
51
+ ['Drop files or click to browse'],
52
+ ),
53
+ h.p(
54
+ [h.Class(secondaryTextClassName)],
55
+ ['Any file type. This demo just lists them.'],
56
+ ),
57
+ h.input(attributes.input),
58
+ ],
59
+ )
60
+
45
61
  return h.div(
46
62
  [],
47
63
  [
@@ -55,21 +71,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
55
71
  view: FileDrop.view,
56
72
  viewInputs: {
57
73
  multiple: true,
58
- toView: attributes =>
59
- h.label(
60
- [...attributes.root, h.Class(dropZoneClassName)],
61
- [
62
- h.p(
63
- [h.Class(primaryTextClassName)],
64
- ['Drop files or click to browse'],
65
- ),
66
- h.p(
67
- [h.Class(secondaryTextClassName)],
68
- ['Any file type. This demo just lists them.'],
69
- ),
70
- h.input(attributes.input),
71
- ],
72
- ),
74
+ toView: fileDropView,
73
75
  },
74
76
  toParentMessage: message =>
75
77
  UiMessage.GotFileDropBasicDemoMessage({ message }),
@@ -15,64 +15,72 @@ const panelClassName =
15
15
  const TRIGGER_ID = 'hover-intent-showcase-trigger'
16
16
  const PANEL_ID = 'hover-intent-showcase-panel'
17
17
 
18
- export const view = Submodel.defineView<UiModel, UiMessage>((model, h): Html =>
19
- h.div(
20
- [],
21
- [
22
- h.h2(
23
- [h.Class('text-2xl font-bold text-gray-900 mb-6')],
24
- ['Hover Intent'],
25
- ),
26
- h.p(
27
- [h.Class('mb-4 max-w-xl text-sm text-gray-600')],
18
+ export const view = Submodel.defineView<UiModel, UiMessage>(
19
+ (model, h): Html => {
20
+ const hoverIntentView = ({
21
+ trigger,
22
+ panel,
23
+ isVisible,
24
+ }: HoverIntent.RenderInfo): Html =>
25
+ h.div(
26
+ [h.Class('relative inline-block')],
28
27
  [
29
- 'Hover over or focus the “More information” trigger, then move the pointer into the card. Move the pointer or focus away from both elements, or press Escape, to close it.',
30
- ],
31
- ),
32
- h.submodel({
33
- slotId: 'hover-intent-showcase',
34
- model: model.hoverIntentDemo,
35
- view: HoverIntent.view,
36
- viewInputs: {
37
- focusTriggerSelector: `#${TRIGGER_ID}`,
38
- toView: ({ trigger, panel, isVisible }) =>
39
- h.div(
40
- [h.Class('relative inline-block')],
41
- [
42
- h.button(
28
+ h.button(
29
+ [
30
+ ...trigger,
31
+ h.Type('button'),
32
+ h.Id(TRIGGER_ID),
33
+ h.AriaControls(PANEL_ID),
34
+ h.AriaExpanded(isVisible),
35
+ h.Class(triggerClassName),
36
+ ],
37
+ ['More information'],
38
+ ),
39
+ ...(isVisible
40
+ ? [
41
+ h.div(
42
+ [...panel, h.Id(PANEL_ID), h.Class(panelClassName)],
43
43
  [
44
- ...trigger,
45
- h.Type('button'),
46
- h.Id(TRIGGER_ID),
47
- h.AriaControls(PANEL_ID),
48
- h.AriaExpanded(isVisible),
49
- h.Class(triggerClassName),
44
+ h.h3(
45
+ [h.Class('text-sm font-medium text-gray-900')],
46
+ ['Details'],
47
+ ),
48
+ h.p(
49
+ [h.Class('mt-1 text-sm leading-5 text-gray-600')],
50
+ ['A short description can provide useful context.'],
51
+ ),
50
52
  ],
51
- ['More information'],
52
53
  ),
53
- ...(isVisible
54
- ? [
55
- h.div(
56
- [...panel, h.Id(PANEL_ID), h.Class(panelClassName)],
57
- [
58
- h.h3(
59
- [h.Class('text-sm font-medium text-gray-900')],
60
- ['Details'],
61
- ),
62
- h.p(
63
- [h.Class('mt-1 text-sm leading-5 text-gray-600')],
64
- ['A short description can provide useful context.'],
65
- ),
66
- ],
67
- ),
68
- ]
69
- : []),
70
- ],
71
- ),
72
- },
73
- toParentMessage: message =>
74
- UiMessage.GotHoverIntentDemoMessage({ message }),
75
- }),
76
- ],
77
- ),
54
+ ]
55
+ : []),
56
+ ],
57
+ )
58
+
59
+ return h.div(
60
+ [],
61
+ [
62
+ h.h2(
63
+ [h.Class('text-2xl font-bold text-gray-900 mb-6')],
64
+ ['Hover Intent'],
65
+ ),
66
+ h.p(
67
+ [h.Class('mb-4 max-w-xl text-sm text-gray-600')],
68
+ [
69
+ 'Hover over or focus the “More information” trigger, then move the pointer into the card. Move the pointer or focus away from both elements, or press Escape, to close it.',
70
+ ],
71
+ ),
72
+ h.submodel({
73
+ slotId: 'hover-intent-showcase',
74
+ model: model.hoverIntentDemo,
75
+ view: HoverIntent.view,
76
+ viewInputs: {
77
+ focusTriggerSelector: `#${TRIGGER_ID}`,
78
+ toView: hoverIntentView,
79
+ },
80
+ toParentMessage: message =>
81
+ UiMessage.GotHoverIntentDemoMessage({ message }),
82
+ }),
83
+ ],
84
+ )
85
+ },
78
86
  )
@@ -15,6 +15,36 @@ const descriptionClassName = 'text-sm text-gray-500'
15
15
 
16
16
  export const view = Submodel.defineView<UiModel, UiMessage>(
17
17
  (model, h): Html => {
18
+ const basicInputView = (
19
+ attributes: Input.InputAttributes<UiMessage>,
20
+ ): Html =>
21
+ h.div(
22
+ [h.Class('flex flex-col gap-1.5 w-full')],
23
+ [
24
+ h.label([...attributes.label, h.Class(labelClassName)], ['Name']),
25
+ h.input([...attributes.input, h.Class(inputClassName)]),
26
+ h.span(
27
+ [...attributes.description, h.Class(descriptionClassName)],
28
+ ['As it appears on your government-issued ID.'],
29
+ ),
30
+ ],
31
+ )
32
+
33
+ const disabledInputView = (
34
+ attributes: Input.InputAttributes<UiMessage>,
35
+ ): Html =>
36
+ h.div(
37
+ [h.Class('flex flex-col gap-1.5 max-w-sm')],
38
+ [
39
+ h.label([...attributes.label, h.Class(labelClassName)], ['Name']),
40
+ h.input([...attributes.input, h.Class(inputClassName)]),
41
+ h.span(
42
+ [...attributes.description, h.Class(descriptionClassName)],
43
+ ['This input is disabled.'],
44
+ ),
45
+ ],
46
+ )
47
+
18
48
  return h.div(
19
49
  [],
20
50
  [
@@ -34,24 +64,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
34
64
  hasDescription: true,
35
65
  onInput: value => UiMessage.UpdatedInputDemoValue({ value }),
36
66
  placeholder: 'Enter your full name',
37
- toView: attributes =>
38
- h.div(
39
- [h.Class('flex flex-col gap-1.5 w-full')],
40
- [
41
- h.label(
42
- [...attributes.label, h.Class(labelClassName)],
43
- ['Name'],
44
- ),
45
- h.input([...attributes.input, h.Class(inputClassName)]),
46
- h.span(
47
- [
48
- ...attributes.description,
49
- h.Class(descriptionClassName),
50
- ],
51
- ['As it appears on your government-issued ID.'],
52
- ),
53
- ],
54
- ),
67
+ toView: basicInputView,
55
68
  },
56
69
  h,
57
70
  ),
@@ -68,21 +81,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
68
81
  isDisabled: true,
69
82
  hasDescription: true,
70
83
  value: 'Ada Lovelace',
71
- toView: attributes =>
72
- h.div(
73
- [h.Class('flex flex-col gap-1.5 max-w-sm')],
74
- [
75
- h.label(
76
- [...attributes.label, h.Class(labelClassName)],
77
- ['Name'],
78
- ),
79
- h.input([...attributes.input, h.Class(inputClassName)]),
80
- h.span(
81
- [...attributes.description, h.Class(descriptionClassName)],
82
- ['This input is disabled.'],
83
- ),
84
- ],
85
- ),
84
+ toView: disabledInputView,
86
85
  },
87
86
  h,
88
87
  ),