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
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@effect/platform-browser": "4.0.0",
15
15
  "@effect/platform-node": "4.0.0",
16
- "@effect/platform-node-shared": "4.0.0",
16
+ "@foldkit/node": "workspace:*",
17
17
  "@foldkit/ui": "workspace:*",
18
18
  "effect": "4.0.0",
19
19
  "foldkit": "workspace:*"
@@ -4,6 +4,8 @@ import { Server } from 'foldkit/experimental'
4
4
  import { readCountCookie } from './cookie'
5
5
  import { Flags, init, view } from './main'
6
6
 
7
+ export const renderDocument = Server.renderDocument
8
+
7
9
  const flagsForRequest = (cookieHeader: string): Flags => ({
8
10
  initialCount: readCountCookie(cookieHeader),
9
11
  renderedAt: new Date().toISOString(),
@@ -1,6 +1,7 @@
1
1
  import { Runtime } from 'foldkit'
2
2
 
3
3
  import { Flags, Message, Model, init, update, view } from './main'
4
+ import './styles.css'
4
5
 
5
6
  const application = Runtime.makeApplication({
6
7
  Model,
@@ -123,6 +123,7 @@ export const subscriptions = Subscription.make<Model, Message>()(entry => ({
123
123
  dependenciesToStream: ({ isRunning }) =>
124
124
  Stream.when(
125
125
  Stream.tick(Duration.millis(TICK_INTERVAL_MS)).pipe(
126
+ Stream.drop(1),
126
127
  Stream.map(Message.Ticked),
127
128
  ),
128
129
  Effect.sync(() => isRunning),
@@ -385,8 +385,8 @@ const mobileNavLinkClassName = (isActive: boolean): string =>
385
385
  : 'text-gray-700 hover:bg-gray-200',
386
386
  )
387
387
 
388
- const sidebarView = (currentRoute: AppRoute, h: HtmlBuilder<Message>): Html =>
389
- componentNav(currentRoute, ({ nav, items }) =>
388
+ const sidebarView = (currentRoute: AppRoute, h: HtmlBuilder<Message>): Html => {
389
+ const sidebarNavView = ({ nav, items }: Nav.RenderInfo): Html =>
390
390
  h.nav(
391
391
  [
392
392
  ...nav,
@@ -412,15 +412,28 @@ const sidebarView = (currentRoute: AppRoute, h: HtmlBuilder<Message>): Html =>
412
412
  ),
413
413
  navListView(items, navLinkClassName, h),
414
414
  ],
415
- ),
416
- )
415
+ )
416
+
417
+ return componentNav(currentRoute, sidebarNavView)
418
+ }
417
419
 
418
420
  const mobileMenuContent = (
419
421
  currentRoute: AppRoute,
420
422
  closeButton: Dialog.RenderInfo['closeButton'],
421
423
  h: HtmlBuilder<UiMessage>,
422
- ): Html =>
423
- h.div(
424
+ ): Html => {
425
+ const mobileNavView = ({ nav, items }: Nav.RenderInfo): Html =>
426
+ h.nav(
427
+ [
428
+ ...nav,
429
+ h.Class('flex-1 overflow-y-auto min-h-0 p-4'),
430
+ h.Tabindex(-1),
431
+ h.Autofocus(true),
432
+ ],
433
+ [navListView(items, mobileNavLinkClassName, h)],
434
+ )
435
+
436
+ return h.div(
424
437
  [h.Class('flex flex-col h-full')],
425
438
  [
426
439
  h.div(
@@ -460,19 +473,10 @@ const mobileMenuContent = (
460
473
  ),
461
474
  ],
462
475
  ),
463
- componentNav(currentRoute, ({ nav, items }) =>
464
- h.nav(
465
- [
466
- ...nav,
467
- h.Class('flex-1 overflow-y-auto min-h-0 p-4'),
468
- h.Tabindex(-1),
469
- h.Autofocus(true),
470
- ],
471
- [navListView(items, mobileNavLinkClassName, h)],
472
- ),
473
- ),
476
+ componentNav(currentRoute, mobileNavView),
474
477
  ],
475
478
  )
479
+ }
476
480
 
477
481
  const mobileHeaderView = (model: Model, h: HtmlBuilder<Message>): Html =>
478
482
  h.header(
@@ -522,33 +526,41 @@ const mobileMenuDialogView = Submodel.defineView<
522
526
  UiModel,
523
527
  UiMessage,
524
528
  MobileMenuViewInputs
525
- >((model, { currentRoute }, h): Html =>
526
- h.submodel({
529
+ >((model, { currentRoute }, h): Html => {
530
+ const mobileMenuDialogContent = ({
531
+ dialog,
532
+ backdrop,
533
+ panel,
534
+ closeButton,
535
+ isVisible,
536
+ }: Dialog.RenderInfo): Html =>
537
+ h.dialog(
538
+ [...dialog, h.Class('md:hidden')],
539
+ isVisible
540
+ ? [
541
+ h.div([...backdrop, h.Class('fixed inset-0 z-[59]')]),
542
+ h.div(
543
+ [
544
+ ...panel,
545
+ h.Class('fixed inset-0 z-[60] bg-white flex flex-col'),
546
+ ],
547
+ [mobileMenuContent(currentRoute, closeButton, h)],
548
+ ),
549
+ ]
550
+ : [],
551
+ )
552
+
553
+ return h.submodel({
527
554
  slotId: model.mobileMenuDialog.id,
528
555
  model: model.mobileMenuDialog,
529
556
  view: Dialog.view,
530
557
  viewInputs: {
531
- toView: ({ dialog, backdrop, panel, closeButton, isVisible }) =>
532
- h.dialog(
533
- [...dialog, h.Class('md:hidden')],
534
- isVisible
535
- ? [
536
- h.div([...backdrop, h.Class('fixed inset-0 z-[59]')]),
537
- h.div(
538
- [
539
- ...panel,
540
- h.Class('fixed inset-0 z-[60] bg-white flex flex-col'),
541
- ],
542
- [mobileMenuContent(currentRoute, closeButton, h)],
543
- ),
544
- ]
545
- : [],
546
- ),
558
+ toView: mobileMenuDialogContent,
547
559
  },
548
560
  toParentMessage: message =>
549
561
  UiMessage.GotMobileMenuDialogMessage({ message }),
550
- }),
551
- )
562
+ })
563
+ })
552
564
 
553
565
  const mobileMenuView = (model: Model, h: HtmlBuilder<Message>): Html =>
554
566
  h.submodel({
@@ -1,7 +1,7 @@
1
1
  import { Option } from 'effect'
2
2
  import { Subscription } from 'foldkit'
3
3
 
4
- import { DragAndDrop, Slider, VirtualList } from '@foldkit/ui'
4
+ import { DragAndDrop, Slider } from '@foldkit/ui'
5
5
 
6
6
  import { Message as UiMessage } from './message'
7
7
  import type { UiModel } from './model'
@@ -33,21 +33,6 @@ const sliderVolumeSubscriptions = Subscription.lift({
33
33
  toParentMessage: message => UiMessage.GotSliderVolumeDemoMessage({ message }),
34
34
  })
35
35
 
36
- const virtualListDemoSubscriptions = Subscription.lift({
37
- virtualListContainerEvents: VirtualList.subscriptions.containerEvents,
38
- })<UiModel, UiMessage>({
39
- read: model => Option.some(model.virtualListDemo),
40
- toParentMessage: message => UiMessage.GotVirtualListDemoMessage({ message }),
41
- })
42
-
43
- const virtualListVariableDemoSubscriptions = Subscription.lift({
44
- virtualListVariableContainerEvents: VirtualList.subscriptions.containerEvents,
45
- })<UiModel, UiMessage>({
46
- read: model => Option.some(model.virtualListVariableDemo),
47
- toParentMessage: message =>
48
- UiMessage.GotVirtualListVariableDemoMessage({ message }),
49
- })
50
-
51
36
  const toastDemoSubscriptions = Subscription.lift(Toast.subscriptions)<
52
37
  UiModel,
53
38
  UiMessage
@@ -60,7 +45,5 @@ export const subscriptions = Subscription.aggregate(
60
45
  dragAndDropSubscriptions,
61
46
  sliderRatingSubscriptions,
62
47
  sliderVolumeSubscriptions,
63
- virtualListDemoSubscriptions,
64
- virtualListVariableDemoSubscriptions,
65
48
  toastDemoSubscriptions,
66
49
  )
@@ -35,11 +35,7 @@ import { CityCombobox, CityMultiCombobox } from './view/combobox'
35
35
  import { CharacterListbox, ItemListbox, ItemMultiListbox } from './view/listbox'
36
36
  import { PlanRadioGroup } from './view/radioGroup'
37
37
  import { DemoTabs } from './view/tabs'
38
- import {
39
- ROW_COUNT as VIRTUAL_LIST_ROW_COUNT,
40
- variableActivities,
41
- variableRowHeightPx,
42
- } from './view/virtualList'
38
+ import { ROW_COUNT as VIRTUAL_LIST_ROW_COUNT } from './view/virtualList'
43
39
 
44
40
  const reorderColumns = (
45
41
  columns: ReadonlyArray<DemoColumn>,
@@ -906,13 +902,7 @@ const foldVirtualListVariableDemo = Update.foldChild({
906
902
  })
907
903
 
908
904
  const foldVirtualListVariableDemoScrollToIndex = Update.foldChild({
909
- update: (virtualList: VirtualList.Model, index: number) =>
910
- VirtualList.scrollToIndexVariable(
911
- virtualList,
912
- variableActivities,
913
- variableRowHeightPx,
914
- index,
915
- ),
905
+ update: VirtualList.scrollToIndex,
916
906
  read: (model: UiModel) => Option.some(model.virtualListVariableDemo),
917
907
  write: (model, nextVirtualListVariableDemo) =>
918
908
  modifyFields(model, {
@@ -175,6 +175,14 @@ const yearsView = (
175
175
 
176
176
  export const view = Submodel.defineView<UiModel, UiMessage>(
177
177
  (model, h): Html => {
178
+ const calendarView = Match.type<Calendar.CalendarAttributes>().pipe(
179
+ Match.tagsExhaustive({
180
+ Days: days => daysView(days, h),
181
+ Months: months => monthsView(months, h),
182
+ Years: years => yearsView(years, h),
183
+ }),
184
+ )
185
+
178
186
  return h.div(
179
187
  [],
180
188
  [
@@ -185,13 +193,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
185
193
  view: Calendar.view,
186
194
  viewInputs: {
187
195
  maybeSelectedDate: model.maybeCalendarBasicDemoSelectedDate,
188
- toView: Match.type<Calendar.CalendarAttributes>().pipe(
189
- Match.tagsExhaustive({
190
- Days: days => daysView(days, h),
191
- Months: months => monthsView(months, h),
192
- Years: years => yearsView(years, h),
193
- }),
194
- ),
196
+ toView: calendarView,
195
197
  },
196
198
  toParentMessage: message =>
197
199
  UiMessage.GotCalendarBasicDemoMessage({ message }),
@@ -24,6 +24,31 @@ const descriptionClassName = 'text-sm text-gray-500'
24
24
  export const view = Submodel.defineView<UiModel, UiMessage>(
25
25
  (model, h): Html => {
26
26
  const checkmark = h.span([h.Class('text-white text-xs')], ['✓'])
27
+ const basicCheckboxView = (
28
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
29
+ ): Html =>
30
+ h.div(
31
+ [h.Class('flex flex-col gap-1')],
32
+ [
33
+ h.div(
34
+ [h.Class(topRowClassName)],
35
+ [
36
+ h.button(
37
+ [...attributes.checkbox, h.Class(checkboxClassName)],
38
+ model.isCheckboxBasicDemoChecked ? [checkmark] : [],
39
+ ),
40
+ h.label(
41
+ [...attributes.label, h.Class(labelClassName)],
42
+ ['Accept terms and conditions'],
43
+ ),
44
+ ],
45
+ ),
46
+ h.p(
47
+ [...attributes.description, h.Class(descriptionClassName)],
48
+ ['You agree to our Terms of Service and Privacy Policy.'],
49
+ ),
50
+ ],
51
+ )
27
52
 
28
53
  return h.div(
29
54
  [],
@@ -41,29 +66,7 @@ export const view = Submodel.defineView<UiModel, UiMessage>(
41
66
  hasDescription: true,
42
67
  onToggle: isChecked =>
43
68
  UiMessage.ToggledCheckboxBasicDemo({ isChecked }),
44
- toView: attributes =>
45
- h.div(
46
- [h.Class('flex flex-col gap-1')],
47
- [
48
- h.div(
49
- [h.Class(topRowClassName)],
50
- [
51
- h.button(
52
- [...attributes.checkbox, h.Class(checkboxClassName)],
53
- model.isCheckboxBasicDemoChecked ? [checkmark] : [],
54
- ),
55
- h.label(
56
- [...attributes.label, h.Class(labelClassName)],
57
- ['Accept terms and conditions'],
58
- ),
59
- ],
60
- ),
61
- h.p(
62
- [...attributes.description, h.Class(descriptionClassName)],
63
- ['You agree to our Terms of Service and Privacy Policy.'],
64
- ),
65
- ],
66
- ),
69
+ toView: basicCheckboxView,
67
70
  },
68
71
  h,
69
72
  ),
@@ -101,6 +104,57 @@ const indeterminateDemo = (
101
104
  }
102
105
  }
103
106
 
107
+ const selectAllView = (
108
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
109
+ ): Html =>
110
+ h.div(
111
+ [h.Class(topRowClassName)],
112
+ [
113
+ h.button(
114
+ [...attributes.checkbox, h.Class(checkboxClassName)],
115
+ resolveSelectAllMark(),
116
+ ),
117
+ h.label(
118
+ [...attributes.label, h.Class(labelClassName)],
119
+ ['All notifications'],
120
+ ),
121
+ ],
122
+ )
123
+
124
+ const emailNotificationsView = (
125
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
126
+ ): Html =>
127
+ notificationOptionView(
128
+ attributes,
129
+ model.isCheckboxOptionADemoChecked,
130
+ 'Email notifications',
131
+ )
132
+
133
+ const pushNotificationsView = (
134
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
135
+ ): Html =>
136
+ notificationOptionView(
137
+ attributes,
138
+ model.isCheckboxOptionBDemoChecked,
139
+ 'Push notifications',
140
+ )
141
+
142
+ const notificationOptionView = (
143
+ attributes: Checkbox.CheckboxAttributes<UiMessage>,
144
+ isChecked: boolean,
145
+ label: string,
146
+ ): Html =>
147
+ h.div(
148
+ [h.Class(topRowClassName)],
149
+ [
150
+ h.button(
151
+ [...attributes.checkbox, h.Class(checkboxClassName)],
152
+ isChecked ? [checkmark] : [],
153
+ ),
154
+ h.label([...attributes.label, h.Class(labelClassName)], [label]),
155
+ ],
156
+ )
157
+
104
158
  return [
105
159
  h.div(
106
160
  [h.Class('flex flex-col gap-3')],
@@ -112,20 +166,7 @@ const indeterminateDemo = (
112
166
  isIndeterminate,
113
167
  onToggle: isChecked =>
114
168
  UiMessage.ToggledCheckboxAllDemo({ isChecked }),
115
- toView: attributes =>
116
- h.div(
117
- [h.Class(topRowClassName)],
118
- [
119
- h.button(
120
- [...attributes.checkbox, h.Class(checkboxClassName)],
121
- resolveSelectAllMark(),
122
- ),
123
- h.label(
124
- [...attributes.label, h.Class(labelClassName)],
125
- ['All notifications'],
126
- ),
127
- ],
128
- ),
169
+ toView: selectAllView,
129
170
  },
130
171
  h,
131
172
  ),
@@ -138,20 +179,7 @@ const indeterminateDemo = (
138
179
  isChecked: model.isCheckboxOptionADemoChecked,
139
180
  onToggle: isChecked =>
140
181
  UiMessage.ToggledCheckboxOptionADemo({ isChecked }),
141
- toView: attributes =>
142
- h.div(
143
- [h.Class(topRowClassName)],
144
- [
145
- h.button(
146
- [...attributes.checkbox, h.Class(checkboxClassName)],
147
- model.isCheckboxOptionADemoChecked ? [checkmark] : [],
148
- ),
149
- h.label(
150
- [...attributes.label, h.Class(labelClassName)],
151
- ['Email notifications'],
152
- ),
153
- ],
154
- ),
182
+ toView: emailNotificationsView,
155
183
  },
156
184
  h,
157
185
  ),
@@ -161,20 +189,7 @@ const indeterminateDemo = (
161
189
  isChecked: model.isCheckboxOptionBDemoChecked,
162
190
  onToggle: isChecked =>
163
191
  UiMessage.ToggledCheckboxOptionBDemo({ isChecked }),
164
- toView: attributes =>
165
- h.div(
166
- [h.Class(topRowClassName)],
167
- [
168
- h.button(
169
- [...attributes.checkbox, h.Class(checkboxClassName)],
170
- model.isCheckboxOptionBDemoChecked ? [checkmark] : [],
171
- ),
172
- h.label(
173
- [...attributes.label, h.Class(labelClassName)],
174
- ['Push notifications'],
175
- ),
176
- ],
177
- ),
192
+ toView: pushNotificationsView,
178
193
  },
179
194
  h,
180
195
  ),