create-foldkit-app 0.32.1 → 0.33.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 (117) hide show
  1. package/dist/templates/base/.oxfmtrc.json +28 -0
  2. package/dist/templates/base/.oxlintrc.json +4 -0
  3. package/dist/templates/base/FOLDKIT.md +1 -1
  4. package/dist/templates/base/package.json +1 -1
  5. package/dist/templates/examples/api-cache/package.json +0 -2
  6. package/dist/templates/examples/api-cache/src/scene.test.ts +1 -2
  7. package/dist/templates/examples/api-cache/src/story.test.ts +3 -4
  8. package/dist/templates/examples/auth/package.json +0 -2
  9. package/dist/templates/examples/canvas-art/package.json +0 -2
  10. package/dist/templates/examples/charting/package.json +0 -2
  11. package/dist/templates/examples/charting/src/scene.test.ts +1 -2
  12. package/dist/templates/examples/charting/src/story.test.ts +3 -4
  13. package/dist/templates/examples/counter/package.json +0 -2
  14. package/dist/templates/examples/counters/package.json +0 -2
  15. package/dist/templates/examples/crash-view/package.json +0 -2
  16. package/dist/templates/examples/embedding/package.json +0 -2
  17. package/dist/templates/examples/form/package.json +0 -2
  18. package/dist/templates/examples/generative-art/package.json +0 -2
  19. package/dist/templates/examples/interrupting-commands/package.json +0 -2
  20. package/dist/templates/examples/job-application/package.json +0 -2
  21. package/dist/templates/examples/job-application/src/main.fixture.ts +69 -0
  22. package/dist/templates/examples/job-application/src/main.ts +1 -1
  23. package/dist/templates/examples/job-application/src/scene.test.ts +6 -66
  24. package/dist/templates/examples/job-application/src/step/attachments/index.ts +2 -0
  25. package/dist/templates/examples/job-application/src/step/attachments/scene.test.ts +19 -0
  26. package/dist/templates/examples/job-application/src/step/attachments/story.test.ts +46 -0
  27. package/dist/templates/examples/job-application/src/{view/attachments.ts → step/attachments/view.ts} +4 -11
  28. package/dist/templates/examples/job-application/src/{view → step/coverLetter}/coverLetter.ts +36 -7
  29. package/dist/templates/examples/job-application/src/step/coverLetter/index.ts +1 -0
  30. package/dist/templates/examples/job-application/src/step/coverLetter/scene.test.ts +24 -0
  31. package/dist/templates/examples/job-application/src/step/coverLetter/story.test.ts +19 -0
  32. package/dist/templates/examples/job-application/src/step/education/{entry.ts → entry/entry.ts} +2 -2
  33. package/dist/templates/examples/job-application/src/step/education/entry/index.ts +11 -0
  34. package/dist/templates/examples/job-application/src/step/education/entry/story.test.ts +39 -0
  35. package/dist/templates/examples/job-application/src/step/education/entry/view.ts +160 -0
  36. package/dist/templates/examples/job-application/src/step/education/index.ts +11 -1
  37. package/dist/templates/examples/job-application/src/step/education/scene.test.ts +20 -0
  38. package/dist/templates/examples/job-application/src/step/education/story.test.ts +87 -0
  39. package/dist/templates/examples/job-application/src/{view/education.ts → step/education/view.ts} +6 -10
  40. package/dist/templates/examples/job-application/src/step/personalInfo/index.ts +11 -0
  41. package/dist/templates/examples/job-application/src/step/{personalInfo.ts → personalInfo/personalInfo.ts} +2 -2
  42. package/dist/templates/examples/job-application/src/step/personalInfo/scene.test.ts +38 -0
  43. package/dist/templates/examples/job-application/src/step/personalInfo/story.test.ts +87 -0
  44. package/dist/templates/examples/job-application/src/{view/personalInfo.ts → step/personalInfo/view.ts} +24 -34
  45. package/dist/templates/examples/job-application/src/step/skills/{entry.ts → entry/entry.ts} +2 -2
  46. package/dist/templates/examples/job-application/src/step/skills/entry/index.ts +11 -0
  47. package/dist/templates/examples/job-application/src/step/skills/entry/story.test.ts +27 -0
  48. package/dist/templates/examples/job-application/src/{view/skillEntry.ts → step/skills/entry/view.ts} +9 -12
  49. package/dist/templates/examples/job-application/src/step/skills/index.ts +11 -1
  50. package/dist/templates/examples/job-application/src/step/skills/scene.test.ts +16 -0
  51. package/dist/templates/examples/job-application/src/step/skills/story.test.ts +85 -0
  52. package/dist/templates/examples/job-application/src/step/skills/view.ts +47 -0
  53. package/dist/templates/examples/job-application/src/step/workHistory/{entry.ts → entry/entry.ts} +1 -1
  54. package/dist/templates/examples/job-application/src/step/workHistory/entry/index.ts +11 -0
  55. package/dist/templates/examples/job-application/src/step/workHistory/entry/story.test.ts +42 -0
  56. package/dist/templates/examples/job-application/src/{view/workEntry.ts → step/workHistory/entry/view.ts} +27 -42
  57. package/dist/templates/examples/job-application/src/step/workHistory/index.ts +11 -1
  58. package/dist/templates/examples/job-application/src/step/workHistory/scene.test.ts +20 -0
  59. package/dist/templates/examples/job-application/src/step/workHistory/story.test.ts +87 -0
  60. package/dist/templates/examples/job-application/src/{view/workHistory.ts → step/workHistory/view.ts} +4 -8
  61. package/dist/templates/examples/job-application/src/story.test.ts +28 -419
  62. package/dist/templates/examples/job-application/src/view/datePicker.ts +4 -4
  63. package/dist/templates/examples/job-application/src/view/field.ts +3 -3
  64. package/dist/templates/examples/job-application/src/view/index.ts +3 -358
  65. package/dist/templates/examples/job-application/src/view/stepNav.ts +2 -2
  66. package/dist/templates/examples/job-application/src/view/view.ts +359 -0
  67. package/dist/templates/examples/kanban/package.json +0 -2
  68. package/dist/templates/examples/kanban/src/scene.test.ts +1 -1
  69. package/dist/templates/examples/kanban/src/story.test.ts +22 -23
  70. package/dist/templates/examples/kanban/src/view/index.ts +1 -71
  71. package/dist/templates/examples/kanban/src/view/view.ts +71 -0
  72. package/dist/templates/examples/managed-resource-layer/package.json +0 -2
  73. package/dist/templates/examples/map/package.json +0 -2
  74. package/dist/templates/examples/pixel-art/package.json +0 -2
  75. package/dist/templates/examples/pixel-art/src/scene.test.ts +16 -9
  76. package/dist/templates/examples/pixel-art/src/story.test.ts +12 -7
  77. package/dist/templates/examples/query-sync/package.json +0 -2
  78. package/dist/templates/examples/query-sync/src/story.test.ts +4 -5
  79. package/dist/templates/examples/route-transitions/package.json +0 -2
  80. package/dist/templates/examples/routing/package.json +0 -2
  81. package/dist/templates/examples/routing/src/page/people.scene.test.ts +41 -0
  82. package/dist/templates/examples/routing/src/page/people.story.test.ts +73 -0
  83. package/dist/templates/examples/routing/src/scene.test.ts +2 -17
  84. package/dist/templates/examples/routing/src/story.test.ts +4 -34
  85. package/dist/templates/examples/shopping-cart/package.json +0 -2
  86. package/dist/templates/examples/shopping-cart/src/page/products.scene.test.ts +20 -0
  87. package/dist/templates/examples/shopping-cart/src/page/products.story.test.ts +56 -0
  88. package/dist/templates/examples/shopping-cart/src/page/products.ts +1 -1
  89. package/dist/templates/examples/shopping-cart/src/scene.test.ts +1 -4
  90. package/dist/templates/examples/shopping-cart/src/story.test.ts +3 -4
  91. package/dist/templates/examples/slow-warnings/package.json +0 -2
  92. package/dist/templates/examples/snake/package.json +0 -2
  93. package/dist/templates/examples/ssg/package.json +0 -2
  94. package/dist/templates/examples/ssr/package.json +0 -2
  95. package/dist/templates/examples/state-machine/package.json +0 -2
  96. package/dist/templates/examples/state-machine/src/main.ts +79 -87
  97. package/dist/templates/examples/state-machine/src/scene.test.ts +1 -2
  98. package/dist/templates/examples/stopwatch/package.json +0 -2
  99. package/dist/templates/examples/todo/package.json +0 -2
  100. package/dist/templates/examples/ui-showcase/package.json +0 -2
  101. package/dist/templates/examples/ui-showcase/src/story.test.ts +1 -2
  102. package/dist/templates/examples/view-transitions/package.json +0 -2
  103. package/dist/templates/examples/view-transitions/src/styles.css +3 -3
  104. package/dist/templates/examples/weather/package.json +0 -2
  105. package/dist/templates/examples/web-components/package.json +0 -2
  106. package/dist/templates/examples/websocket-chat/package.json +0 -2
  107. package/dist/templates/release.json +9 -10
  108. package/dist/templates/rendering/ssg/package.json +1 -1
  109. package/dist/templates/rendering/ssr/package.json +1 -1
  110. package/dist/utils/packages.js +1 -2
  111. package/package.json +3 -3
  112. package/dist/templates/base/.prettierignore +0 -5
  113. package/dist/templates/base/.prettierrc +0 -13
  114. package/dist/templates/examples/job-application/src/step/coverLetter.ts +0 -34
  115. package/dist/templates/examples/job-application/src/view/educationEntry.ts +0 -165
  116. package/dist/templates/examples/job-application/src/view/skills.ts +0 -49
  117. /package/dist/templates/examples/job-application/src/step/{attachments.ts → attachments/attachments.ts} +0 -0
@@ -0,0 +1,359 @@
1
+ import clsx from 'clsx'
2
+ import { Array, Equal, HashSet, Match, Option, pipe } from 'effect'
3
+ import type { ChildAttribute, Document, Html, HtmlBuilder } from 'foldkit/html'
4
+
5
+ import { Button, Tabs } from '@foldkit/ui'
6
+
7
+ import { Step } from '../domain'
8
+ import { Message } from '../message'
9
+ import { type Model } from '../model'
10
+ import {
11
+ Attachments,
12
+ CoverLetter,
13
+ Education,
14
+ PersonalInfo,
15
+ Skills,
16
+ WorkHistory,
17
+ } from '../step'
18
+ import { preview } from './preview'
19
+ import { review } from './review'
20
+ import { stepMenu, stepTabButton } from './stepNav'
21
+
22
+ const StepTabs = Tabs.create<Step.Step>()
23
+
24
+ const stepHasErrors =
25
+ (model: Model) =>
26
+ (step: Step.Step): boolean =>
27
+ Match.value(step).pipe(
28
+ Match.when('PersonalInfo', () =>
29
+ PersonalInfo.hasErrors(model.personalInfo),
30
+ ),
31
+ Match.when('WorkHistory', () => WorkHistory.hasErrors(model.workHistory)),
32
+ Match.when('Education', () => Education.hasErrors(model.education)),
33
+ Match.when('Skills', () => Skills.hasErrors(model.skills)),
34
+ Match.orElse(() => false),
35
+ )
36
+
37
+ const stepIsComplete =
38
+ (model: Model) =>
39
+ (step: Step.Step): boolean =>
40
+ Match.value(step).pipe(
41
+ Match.when('PersonalInfo', () =>
42
+ PersonalInfo.isComplete(model.personalInfo),
43
+ ),
44
+ Match.when('WorkHistory', () =>
45
+ WorkHistory.isComplete(model.workHistory),
46
+ ),
47
+ Match.when('Education', () => Education.isComplete(model.education)),
48
+ Match.when('Skills', () => Skills.isComplete(model.skills)),
49
+ Match.orElse(() => true),
50
+ )
51
+
52
+ const stepNeedsAttention =
53
+ (model: Model) =>
54
+ (step: Step.Step): boolean =>
55
+ stepHasErrors(model)(step) ||
56
+ (model.isSubmitAttempted && !stepIsComplete(model)(step))
57
+
58
+ const stepsNeedingAttention = (model: Model): ReadonlyArray<Step.Step> =>
59
+ Array.filter(Step.all, stepNeedsAttention(model))
60
+
61
+ const stepContent = (
62
+ model: Model,
63
+ attentionSteps: ReadonlyArray<Step.Step>,
64
+ h: HtmlBuilder<Message>,
65
+ ): Html =>
66
+ Match.value(model.currentStep).pipe(
67
+ Match.when('PersonalInfo', () =>
68
+ h.submodel({
69
+ slotId: 'personal-info',
70
+ model: model.personalInfo,
71
+ view: PersonalInfo.view,
72
+ toParentMessage: message => Message.GotPersonalInfoMessage({ message }),
73
+ }),
74
+ ),
75
+ Match.when('WorkHistory', () =>
76
+ h.submodel({
77
+ slotId: 'work-history',
78
+ model: model.workHistory,
79
+ view: WorkHistory.view,
80
+ toParentMessage: message => Message.GotWorkHistoryMessage({ message }),
81
+ }),
82
+ ),
83
+ Match.when('Education', () =>
84
+ h.submodel({
85
+ slotId: 'education',
86
+ model: model.education,
87
+ view: Education.view,
88
+ toParentMessage: message => Message.GotEducationMessage({ message }),
89
+ }),
90
+ ),
91
+ Match.when('Skills', () =>
92
+ h.submodel({
93
+ slotId: 'skills',
94
+ model: model.skills,
95
+ view: Skills.view,
96
+ toParentMessage: message => Message.GotSkillsMessage({ message }),
97
+ }),
98
+ ),
99
+ Match.when('CoverLetter', () =>
100
+ h.submodel({
101
+ slotId: 'cover-letter',
102
+ model: model.coverLetter,
103
+ view: CoverLetter.view,
104
+ toParentMessage: message => Message.GotCoverLetterMessage({ message }),
105
+ }),
106
+ ),
107
+ Match.when('Attachments', () =>
108
+ h.submodel({
109
+ slotId: 'attachments',
110
+ model: model.attachments,
111
+ view: Attachments.view,
112
+ toParentMessage: message => Message.GotAttachmentsMessage({ message }),
113
+ }),
114
+ ),
115
+ Match.when('Review', () => review(model, attentionSteps, h)),
116
+ Match.exhaustive,
117
+ )
118
+
119
+ const isFirstStep = (model: Model): boolean =>
120
+ pipe(Step.all, Array.head, Option.exists(Equal.equals(model.currentStep)))
121
+
122
+ const isLastStep = (model: Model): boolean =>
123
+ pipe(Step.all, Array.last, Option.exists(Equal.equals(model.currentStep)))
124
+
125
+ const navigationButtons = (model: Model, h: HtmlBuilder<Message>): Html =>
126
+ h.div(
127
+ [h.Class('flex justify-between pt-6 mt-8 border-t border-gray-200')],
128
+ [
129
+ ...(isFirstStep(model)
130
+ ? [h.empty]
131
+ : [
132
+ Button.view(
133
+ {
134
+ onClick: Message.ClickedPrevious(),
135
+ toView: attributes =>
136
+ h.button(
137
+ [
138
+ ...attributes.button,
139
+ h.Class(
140
+ 'rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition cursor-pointer',
141
+ ),
142
+ ],
143
+ ['← Previous'],
144
+ ),
145
+ },
146
+ h,
147
+ ),
148
+ ]),
149
+ ...(isLastStep(model)
150
+ ? []
151
+ : [
152
+ Button.view(
153
+ {
154
+ onClick: Message.ClickedNext(),
155
+ toView: attributes =>
156
+ h.button(
157
+ [
158
+ ...attributes.button,
159
+ h.Class(
160
+ 'rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 transition cursor-pointer',
161
+ ),
162
+ ],
163
+ ['Next →'],
164
+ ),
165
+ },
166
+ h,
167
+ ),
168
+ ]),
169
+ ],
170
+ )
171
+
172
+ const pageHeader = (h: HtmlBuilder<Message>): Html =>
173
+ h.div(
174
+ [h.Class('mb-6')],
175
+ [
176
+ h.h1(
177
+ [h.Class('text-2xl font-bold text-gray-900')],
178
+ ['Apply to Work on Foldkit'],
179
+ ),
180
+ h.p(
181
+ [h.Class('text-sm text-gray-500 mt-1')],
182
+ ['Fill out the form below and watch your resume build in real time.'],
183
+ ),
184
+ ],
185
+ )
186
+
187
+ const stepContentPanel = (
188
+ model: Model,
189
+ attentionSteps: ReadonlyArray<Step.Step>,
190
+ panelAttributes: ReadonlyArray<ChildAttribute>,
191
+ h: HtmlBuilder<Message>,
192
+ ): Html =>
193
+ h.div(
194
+ [...panelAttributes, h.Class('flex-1 min-w-0')],
195
+ [
196
+ h.h2(
197
+ [h.Class('text-lg font-semibold text-gray-900 mb-6')],
198
+ [Step.show(model.currentStep)],
199
+ ),
200
+ h.div(
201
+ [h.Class('min-h-[400px]')],
202
+ [stepContent(model, attentionSteps, h)],
203
+ ),
204
+ ...(model.currentStep !== 'Review' ? [navigationButtons(model, h)] : []),
205
+ ],
206
+ )
207
+
208
+ const stepTabsLayout = (
209
+ model: Model,
210
+ attentionSteps: ReadonlyArray<Step.Step>,
211
+ attentionStepSet: HashSet.HashSet<Step.Step>,
212
+ h: HtmlBuilder<Message>,
213
+ ): Html =>
214
+ h.submodel({
215
+ slotId: model.stepTabs.id,
216
+ model: model.stepTabs,
217
+ view: StepTabs.view,
218
+ viewInputs: {
219
+ tabs: Step.all,
220
+ selectedValue: model.currentStep,
221
+ ariaLabel: 'Application steps',
222
+ orientation: 'Vertical',
223
+ toView: ({ tablist, tabs, activeIndex }) =>
224
+ h.div(
225
+ [h.Class('lg:flex lg:gap-8')],
226
+ [
227
+ h.div(
228
+ [h.Class('hidden w-60 shrink-0 lg:block')],
229
+ [
230
+ h.div(
231
+ [h.Class('sticky top-8')],
232
+ [
233
+ h.div(
234
+ [...tablist, h.Class('space-y-0.5')],
235
+ Array.map(tabs, tab =>
236
+ stepTabButton(
237
+ tab,
238
+ model.currentStep,
239
+ attentionStepSet,
240
+ h,
241
+ ),
242
+ ),
243
+ ),
244
+ ],
245
+ ),
246
+ ],
247
+ ),
248
+ ...pipe(
249
+ tabs,
250
+ Array.filter(tab => tab.index === activeIndex),
251
+ Array.map(tab =>
252
+ stepContentPanel(model, attentionSteps, tab.panel, h),
253
+ ),
254
+ ),
255
+ desktopPreviewSidebar(model, h),
256
+ mobilePreviewToggle(model, h),
257
+ ...(model.isPreviewVisible ? [mobilePreviewOverlay(model, h)] : []),
258
+ ],
259
+ ),
260
+ },
261
+ toParentMessage: message => Message.GotStepTabsMessage({ message }),
262
+ })
263
+
264
+ const desktopPreviewSidebar = (model: Model, h: HtmlBuilder<Message>): Html =>
265
+ h.div(
266
+ [h.Class('hidden w-80 shrink-0 xl:block')],
267
+ [
268
+ h.div(
269
+ [h.Class('sticky top-8')],
270
+ [
271
+ h.div(
272
+ [h.Class('mb-2 flex items-center justify-between')],
273
+ [
274
+ h.h2(
275
+ [h.Class('text-sm font-semibold text-gray-700')],
276
+ ['Live Preview'],
277
+ ),
278
+ ],
279
+ ),
280
+ h.div(
281
+ [
282
+ h.Class(
283
+ 'rounded-xl border border-gray-200 bg-white p-6 shadow-sm',
284
+ ),
285
+ ],
286
+ [preview(model)],
287
+ ),
288
+ ],
289
+ ),
290
+ ],
291
+ )
292
+
293
+ const mobilePreviewToggle = (model: Model, h: HtmlBuilder<Message>): Html =>
294
+ h.div(
295
+ [h.Class('fixed top-4 right-4 xl:hidden')],
296
+ [
297
+ Button.view(
298
+ {
299
+ onClick: Message.ToggledPreview(),
300
+ toView: attributes =>
301
+ h.button(
302
+ [
303
+ ...attributes.button,
304
+ h.Class(
305
+ clsx(
306
+ 'rounded-full px-4 py-2 text-sm font-medium shadow-lg transition cursor-pointer',
307
+ model.isPreviewVisible
308
+ ? 'bg-gray-800 text-white'
309
+ : 'bg-indigo-600 text-white',
310
+ ),
311
+ ),
312
+ ],
313
+ [model.isPreviewVisible ? 'Hide Preview' : 'Preview'],
314
+ ),
315
+ },
316
+ h,
317
+ ),
318
+ ],
319
+ )
320
+
321
+ const mobilePreviewOverlay = (model: Model, h: HtmlBuilder<Message>): Html =>
322
+ h.div(
323
+ [
324
+ h.Class(
325
+ 'fixed inset-x-4 top-16 bottom-4 overflow-y-auto rounded-xl border border-gray-200 bg-white p-6 shadow-2xl xl:hidden',
326
+ ),
327
+ ],
328
+ [preview(model)],
329
+ )
330
+
331
+ export const view = (model: Model, h: HtmlBuilder<Message>): Document => {
332
+ const attentionSteps = stepsNeedingAttention(model)
333
+ const attentionStepSet = HashSet.fromIterable(attentionSteps)
334
+ const body = h.div(
335
+ [h.Class('min-h-screen bg-gray-50')],
336
+ [
337
+ h.div(
338
+ [h.Class('mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8')],
339
+ [
340
+ pageHeader(h),
341
+ h.div(
342
+ [h.Class('mb-6 lg:hidden')],
343
+ [
344
+ stepMenu(
345
+ model,
346
+ attentionStepSet,
347
+ message => Message.GotStepMenuMessage({ message }),
348
+ h,
349
+ ),
350
+ ],
351
+ ),
352
+ stepTabsLayout(model, attentionSteps, attentionStepSet, h),
353
+ ],
354
+ ),
355
+ ],
356
+ )
357
+
358
+ return { title: 'Job Application', body }
359
+ }
@@ -20,9 +20,7 @@
20
20
  "@foldkit/devtools": "workspace:*",
21
21
  "@foldkit/vite-plugin": "workspace:*",
22
22
  "@tailwindcss/vite": "^4.3.3",
23
- "@trivago/prettier-plugin-sort-imports": "^6.0.2",
24
23
  "happy-dom": "^20.11.13",
25
- "prettier": "^3.9.6",
26
24
  "tailwindcss": "^4.3.3",
27
25
  "typescript": "^7.0.2",
28
26
  "vite": "^8.2.2",
@@ -23,7 +23,7 @@ import type { Column } from './domain/column'
23
23
  import { Message } from './message'
24
24
  import type { Model } from './model'
25
25
  import { update } from './update'
26
- import { view } from './view/index'
26
+ import { view } from './view'
27
27
 
28
28
  const card = (id: string, title: string, sortKey: string): Card => ({
29
29
  id,
@@ -4,7 +4,6 @@ import { generateKeyBetween } from 'fractional-indexing'
4
4
  import { describe, expect, test } from 'vitest'
5
5
 
6
6
  import { DragAndDrop } from '@foldkit/ui'
7
- import { Message as DragAndDropMessage } from '@foldkit/ui/dragAndDrop'
8
7
 
9
8
  import { FocusAddCardInput, GenerateCardId, SaveBoard } from './command'
10
9
  import { Column } from './domain'
@@ -156,7 +155,7 @@ describe('update', () => {
156
155
  given(emptyModel),
157
156
  message(
158
157
  Message.GotDragAndDropMessage({
159
- message: DragAndDropMessage.PressedDraggable({
158
+ message: DragAndDrop.Message.PressedDraggable({
160
159
  itemId: firstCardId,
161
160
  containerId: 'todo',
162
161
  index: 0,
@@ -167,7 +166,7 @@ describe('update', () => {
167
166
  ),
168
167
  message(
169
168
  Message.GotDragAndDropMessage({
170
- message: DragAndDropMessage.MovedPointer({
169
+ message: DragAndDrop.Message.MovedPointer({
171
170
  screenX: 100,
172
171
  screenY: 200,
173
172
  clientX: 100,
@@ -178,7 +177,7 @@ describe('update', () => {
178
177
  ),
179
178
  message(
180
179
  Message.GotDragAndDropMessage({
181
- message: DragAndDropMessage.ReleasedPointer(),
180
+ message: DragAndDrop.Message.ReleasedPointer(),
182
181
  }),
183
182
  ),
184
183
  Command.resolve(SaveBoard, Message.CompletedSaveBoard()),
@@ -197,7 +196,7 @@ describe('update', () => {
197
196
  given(emptyModel),
198
197
  message(
199
198
  Message.GotDragAndDropMessage({
200
- message: DragAndDropMessage.PressedDraggable({
199
+ message: DragAndDrop.Message.PressedDraggable({
201
200
  itemId: cardId,
202
201
  containerId: 'todo',
203
202
  index: 0,
@@ -208,7 +207,7 @@ describe('update', () => {
208
207
  ),
209
208
  message(
210
209
  Message.GotDragAndDropMessage({
211
- message: DragAndDropMessage.MovedPointer({
210
+ message: DragAndDrop.Message.MovedPointer({
212
211
  screenX: 300,
213
212
  screenY: 100,
214
213
  clientX: 300,
@@ -222,7 +221,7 @@ describe('update', () => {
222
221
  ),
223
222
  message(
224
223
  Message.GotDragAndDropMessage({
225
- message: DragAndDropMessage.ReleasedPointer(),
224
+ message: DragAndDrop.Message.ReleasedPointer(),
226
225
  }),
227
226
  ),
228
227
  Command.resolve(SaveBoard, Message.CompletedSaveBoard()),
@@ -246,7 +245,7 @@ describe('update', () => {
246
245
  given(emptyModel),
247
246
  message(
248
247
  Message.GotDragAndDropMessage({
249
- message: DragAndDropMessage.ActivatedKeyboardDrag({
248
+ message: DragAndDrop.Message.ActivatedKeyboardDrag({
250
249
  itemId: firstCardId,
251
250
  containerId: 'todo',
252
251
  index: 0,
@@ -255,7 +254,7 @@ describe('update', () => {
255
254
  ),
256
255
  message(
257
256
  Message.GotDragAndDropMessage({
258
- message: DragAndDropMessage.CompletedResolveKeyboardMove({
257
+ message: DragAndDrop.Message.CompletedResolveKeyboardMove({
259
258
  targetContainerId: 'todo',
260
259
  targetIndex: 2,
261
260
  }),
@@ -263,16 +262,16 @@ describe('update', () => {
263
262
  ),
264
263
  Command.resolve(
265
264
  DragAndDrop.FocusItem({ itemId: firstCardId }),
266
- DragAndDropMessage.CompletedFocusItem(),
265
+ DragAndDrop.Message.CompletedFocusItem(),
267
266
  ),
268
267
  message(
269
268
  Message.GotDragAndDropMessage({
270
- message: DragAndDropMessage.ConfirmedKeyboardDrop(),
269
+ message: DragAndDrop.Message.ConfirmedKeyboardDrop(),
271
270
  }),
272
271
  ),
273
272
  Command.resolve(
274
273
  DragAndDrop.FocusItem,
275
- DragAndDropMessage.CompletedFocusItem(),
274
+ DragAndDrop.Message.CompletedFocusItem(),
276
275
  ),
277
276
  Command.resolve(SaveBoard, Message.CompletedSaveBoard()),
278
277
  model(model => {
@@ -290,7 +289,7 @@ describe('update', () => {
290
289
  given(emptyModel),
291
290
  message(
292
291
  Message.GotDragAndDropMessage({
293
- message: DragAndDropMessage.ActivatedKeyboardDrag({
292
+ message: DragAndDrop.Message.ActivatedKeyboardDrag({
294
293
  itemId: cardId,
295
294
  containerId: 'todo',
296
295
  index: 0,
@@ -299,7 +298,7 @@ describe('update', () => {
299
298
  ),
300
299
  message(
301
300
  Message.GotDragAndDropMessage({
302
- message: DragAndDropMessage.CompletedResolveKeyboardMove({
301
+ message: DragAndDrop.Message.CompletedResolveKeyboardMove({
303
302
  targetContainerId: 'in-progress',
304
303
  targetIndex: 0,
305
304
  }),
@@ -307,16 +306,16 @@ describe('update', () => {
307
306
  ),
308
307
  Command.resolve(
309
308
  DragAndDrop.FocusItem({ itemId: cardId }),
310
- DragAndDropMessage.CompletedFocusItem(),
309
+ DragAndDrop.Message.CompletedFocusItem(),
311
310
  ),
312
311
  message(
313
312
  Message.GotDragAndDropMessage({
314
- message: DragAndDropMessage.ConfirmedKeyboardDrop(),
313
+ message: DragAndDrop.Message.ConfirmedKeyboardDrop(),
315
314
  }),
316
315
  ),
317
316
  Command.resolve(
318
317
  DragAndDrop.FocusItem,
319
- DragAndDropMessage.CompletedFocusItem(),
318
+ DragAndDrop.Message.CompletedFocusItem(),
320
319
  ),
321
320
  Command.resolve(SaveBoard, Message.CompletedSaveBoard()),
322
321
  model(model => {
@@ -338,7 +337,7 @@ describe('update', () => {
338
337
  given(emptyModel),
339
338
  message(
340
339
  Message.GotDragAndDropMessage({
341
- message: DragAndDropMessage.ActivatedKeyboardDrag({
340
+ message: DragAndDrop.Message.ActivatedKeyboardDrag({
342
341
  itemId: emptyModel.columns[0]!.cards[0]!.id,
343
342
  containerId: 'todo',
344
343
  index: 0,
@@ -347,12 +346,12 @@ describe('update', () => {
347
346
  ),
348
347
  message(
349
348
  Message.GotDragAndDropMessage({
350
- message: DragAndDropMessage.CancelledDrag(),
349
+ message: DragAndDrop.Message.CancelledDrag(),
351
350
  }),
352
351
  ),
353
352
  Command.resolve(
354
353
  DragAndDrop.FocusItem,
355
- DragAndDropMessage.CompletedFocusItem(),
354
+ DragAndDrop.Message.CompletedFocusItem(),
356
355
  ),
357
356
  model(model => {
358
357
  expect(model.columns).toStrictEqual(emptyModel.columns)
@@ -366,7 +365,7 @@ describe('update', () => {
366
365
  given(emptyModel),
367
366
  message(
368
367
  Message.GotDragAndDropMessage({
369
- message: DragAndDropMessage.PressedDraggable({
368
+ message: DragAndDrop.Message.PressedDraggable({
370
369
  itemId: emptyModel.columns[0]!.cards[0]!.id,
371
370
  containerId: 'todo',
372
371
  index: 0,
@@ -377,7 +376,7 @@ describe('update', () => {
377
376
  ),
378
377
  message(
379
378
  Message.GotDragAndDropMessage({
380
- message: DragAndDropMessage.MovedPointer({
379
+ message: DragAndDrop.Message.MovedPointer({
381
380
  screenX: 100,
382
381
  screenY: 200,
383
382
  clientX: 100,
@@ -388,7 +387,7 @@ describe('update', () => {
388
387
  ),
389
388
  message(
390
389
  Message.GotDragAndDropMessage({
391
- message: DragAndDropMessage.CancelledDrag(),
390
+ message: DragAndDrop.Message.CancelledDrag(),
392
391
  }),
393
392
  ),
394
393
  model(model => {
@@ -1,71 +1 @@
1
- import { Array, Option, pipe } from 'effect'
2
- import { type Document, HtmlBuilder } from 'foldkit/html'
3
-
4
- import { DragAndDrop } from '@foldkit/ui'
5
-
6
- import { Message } from '../message'
7
- import type { Model } from '../model'
8
- import { ghostCardView } from './card'
9
- import { columnView } from './column'
10
-
11
- const findDraggedCard = (model: Model) =>
12
- pipe(
13
- model.dragAndDrop,
14
- DragAndDrop.maybeDraggedItemId,
15
- Option.flatMap(cardId =>
16
- pipe(
17
- model.columns,
18
- Array.flatMap(({ cards }) => cards),
19
- Array.findFirst(({ id }) => id === cardId),
20
- ),
21
- ),
22
- )
23
-
24
- const ghostElement = (model: Model, h: HtmlBuilder<Message>) =>
25
- pipe(
26
- DragAndDrop.ghostStyle(model.dragAndDrop),
27
- Option.flatMap(ghostStyle =>
28
- Option.map(findDraggedCard(model), card => ({ ghostStyle, card })),
29
- ),
30
- Option.match({
31
- onNone: () => h.empty,
32
- onSome: ({ ghostStyle, card }) =>
33
- h.div(
34
- [h.Style(ghostStyle), h.Class('w-64'), h.AriaHidden(true)],
35
- [ghostCardView(card, h)],
36
- ),
37
- }),
38
- )
39
-
40
- export const view = (model: Model, h: HtmlBuilder<Message>): Document => ({
41
- title: 'Kanban Board',
42
- body: h.div(
43
- [h.Class('flex flex-col min-h-screen bg-gray-100')],
44
- [
45
- h.div(
46
- [h.Class('px-6 py-4 bg-white border-b border-gray-200')],
47
- [
48
- h.h1(
49
- [h.Class('text-lg font-semibold text-gray-900')],
50
- ['Kanban Board'],
51
- ),
52
- ],
53
- ),
54
- h.div(
55
- [
56
- h.Class(
57
- 'flex-1 p-6 grid grid-cols-[repeat(auto-fit,minmax(16rem,1fr))] gap-6 items-start',
58
- ),
59
- ],
60
- Array.map(model.columns, column =>
61
- columnView(model, column, message => message, h),
62
- ),
63
- ),
64
- ghostElement(model, h),
65
- h.div(
66
- [h.Class('sr-only'), h.AriaLive('assertive')],
67
- [model.announcement],
68
- ),
69
- ],
70
- ),
71
- })
1
+ export { view } from './view'