@flickfyi/flkr 0.0.182 → 0.0.185

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 (243) hide show
  1. package/dist/{api-client-CAg1b8Bh.js → api-client-nn4U_Vfn.js} +1 -1
  2. package/dist/{app-DpM5nhb9.js → app-COXGJhrt.js} +3 -3
  3. package/dist/{bundler-DNrDcqyk.js → bundler-CW4YC73e.js} +4 -2
  4. package/dist/cli.js +11 -11
  5. package/dist/{delegate-DcVye0KE.js → delegate-aUVluBjB.js} +2 -2
  6. package/dist/{generate-B1l1z_FZ.js → generate-BeL1QPDi.js} +1 -1
  7. package/dist/{preview-Dqn_Tq6K.js → preview-DTVM8Csb.js} +2 -2
  8. package/dist/{publish-BmDqO1xU.js → publish-BJG-s-w5.js} +4 -4
  9. package/dist/{pull-CO80DMn8.js → pull-B_Yfjpi2.js} +1 -1
  10. package/dist/{save-8crfOeo1.js → save-UrfgmK2-.js} +2 -2
  11. package/dist/{sdk-app-BXWrf_pf.js → sdk-app-BAxznAf9.js} +3 -3
  12. package/dist/{sdk-deploy-CaEonlvx.js → sdk-deploy-D1wgQxse.js} +3 -3
  13. package/dist/{sdk-generation-CF8ATbrL.js → sdk-generation-CLQ-QrVM.js} +1 -1
  14. package/dist/{sdk-project-CKV3U3u9.js → sdk-project-BaO0fqhW.js} +1 -1
  15. package/dist/{use-auth-B6nwU2rp.js → use-auth-CtM4_BAY.js} +1 -1
  16. package/node_modules/@flickfyi/biscuit/.turbo/turbo-build.log +4 -4
  17. package/node_modules/@flickfyi/core/README.md +19 -9
  18. package/node_modules/@flickfyi/core/dist/chunks/{default-CuxDsQoM.js → default-BTmKidHV.js} +1 -1
  19. package/node_modules/@flickfyi/core/dist/chunks/{default-BB6YexEH.cjs → default-JhTq5I_y.cjs} +1 -1
  20. package/node_modules/@flickfyi/core/dist/chunks/{define-CUJRqVKY.cjs → define-BKCRhpuY.cjs} +1 -1
  21. package/node_modules/@flickfyi/core/dist/chunks/{define-e_VqSs4M.js → define-DZXgUsy7.js} +5 -5
  22. package/node_modules/@flickfyi/core/dist/kowalski/catalog.cjs +1 -1
  23. package/node_modules/@flickfyi/core/dist/kowalski/catalog.js +728 -549
  24. package/node_modules/@flickfyi/core/dist/kowalski/index.cjs +5 -5
  25. package/node_modules/@flickfyi/core/dist/kowalski/index.js +4297 -3700
  26. package/node_modules/@flickfyi/core/dist/kowalski/schemas.cjs +1 -0
  27. package/node_modules/@flickfyi/core/dist/kowalski/schemas.js +637 -0
  28. package/node_modules/@flickfyi/core/dist/kowalski-catalog.css +516 -0
  29. package/node_modules/@flickfyi/core/dist/kowalski.css +10 -0
  30. package/node_modules/@flickfyi/core/dist/themes/athletic/athletic.cjs +1 -1
  31. package/node_modules/@flickfyi/core/dist/themes/athletic/athletic.js +1 -1
  32. package/node_modules/@flickfyi/core/dist/themes/compact/compact.cjs +1 -1
  33. package/node_modules/@flickfyi/core/dist/themes/compact/compact.js +1 -1
  34. package/node_modules/@flickfyi/core/dist/themes/default/default.cjs +1 -1
  35. package/node_modules/@flickfyi/core/dist/themes/default/default.js +1 -1
  36. package/node_modules/@flickfyi/core/dist/themes/editorial/editorial.cjs +1 -1
  37. package/node_modules/@flickfyi/core/dist/themes/editorial/editorial.js +1 -1
  38. package/node_modules/@flickfyi/core/dist/themes/handwritten/handwritten.cjs +1 -1
  39. package/node_modules/@flickfyi/core/dist/themes/handwritten/handwritten.js +1 -1
  40. package/node_modules/@flickfyi/core/dist/themes/index.cjs +1 -1
  41. package/node_modules/@flickfyi/core/dist/themes/index.js +1 -1
  42. package/node_modules/@flickfyi/core/dist/themes/kids/kids.cjs +1 -1
  43. package/node_modules/@flickfyi/core/dist/themes/kids/kids.js +1 -1
  44. package/node_modules/@flickfyi/core/dist/themes/rounded/rounded.cjs +1 -1
  45. package/node_modules/@flickfyi/core/dist/themes/rounded/rounded.js +1 -1
  46. package/node_modules/@flickfyi/core/dist/themes/technical/technical.cjs +1 -1
  47. package/node_modules/@flickfyi/core/dist/themes/technical/technical.js +1 -1
  48. package/node_modules/@flickfyi/core/dist-types/hooks/use-feature-store/index.d.ts +43 -0
  49. package/node_modules/@flickfyi/core/dist-types/kowalski/calculations.d.ts +57 -3
  50. package/node_modules/@flickfyi/core/dist-types/kowalski/calculator.d.ts +7 -6
  51. package/node_modules/@flickfyi/core/dist-types/kowalski/catalog.d.cts +524 -809
  52. package/node_modules/@flickfyi/core/dist-types/kowalski/catalog.d.ts +524 -809
  53. package/node_modules/@flickfyi/core/dist-types/kowalski/components/bill-split.d.ts +38 -0
  54. package/node_modules/@flickfyi/core/dist-types/kowalski/components/bookmark.d.ts +48 -0
  55. package/node_modules/@flickfyi/core/dist-types/kowalski/components/budget.d.ts +39 -0
  56. package/node_modules/@flickfyi/core/dist-types/kowalski/components/calculator.d.ts +62 -0
  57. package/node_modules/@flickfyi/core/dist-types/kowalski/components/checklist.d.ts +35 -0
  58. package/node_modules/@flickfyi/core/dist-types/kowalski/components/color-palette.d.ts +32 -0
  59. package/node_modules/@flickfyi/core/dist-types/kowalski/components/contact.d.ts +41 -0
  60. package/node_modules/@flickfyi/core/dist-types/kowalski/components/countdown.d.ts +34 -0
  61. package/node_modules/@flickfyi/core/dist-types/kowalski/components/event.d.ts +43 -0
  62. package/node_modules/@flickfyi/core/dist-types/kowalski/components/expense.d.ts +40 -0
  63. package/node_modules/@flickfyi/core/dist-types/kowalski/components/goal.d.ts +36 -0
  64. package/node_modules/@flickfyi/core/dist-types/kowalski/components/habit.d.ts +57 -0
  65. package/node_modules/@flickfyi/core/dist-types/kowalski/components/ledger.d.ts +43 -0
  66. package/node_modules/@flickfyi/core/dist-types/kowalski/components/note.d.ts +35 -0
  67. package/node_modules/@flickfyi/core/dist-types/kowalski/components/poll.d.ts +41 -0
  68. package/node_modules/@flickfyi/core/dist-types/kowalski/components/random-picker.d.ts +69 -0
  69. package/node_modules/@flickfyi/core/dist-types/kowalski/components/rating.d.ts +33 -0
  70. package/node_modules/@flickfyi/core/dist-types/kowalski/components/reminder.d.ts +48 -0
  71. package/node_modules/@flickfyi/core/dist-types/kowalski/components/rotation.d.ts +39 -0
  72. package/node_modules/@flickfyi/core/dist-types/kowalski/components/scoreboard.d.ts +37 -0
  73. package/node_modules/@flickfyi/core/dist-types/kowalski/components/shopping-list.d.ts +41 -0
  74. package/node_modules/@flickfyi/core/dist-types/kowalski/components/task-board.d.ts +39 -0
  75. package/node_modules/@flickfyi/core/dist-types/kowalski/components/time-zone.d.ts +34 -0
  76. package/node_modules/@flickfyi/core/dist-types/kowalski/components/timer.d.ts +43 -0
  77. package/node_modules/@flickfyi/core/dist-types/kowalski/components/trip.d.ts +61 -0
  78. package/node_modules/@flickfyi/core/dist-types/kowalski/components/unit-converter.d.ts +106 -0
  79. package/node_modules/@flickfyi/core/dist-types/kowalski/create-schemas.d.ts +6 -0
  80. package/node_modules/@flickfyi/core/dist-types/kowalski/date-time.d.ts +1 -1
  81. package/node_modules/@flickfyi/core/dist-types/kowalski/everyday.d.ts +28 -8
  82. package/node_modules/@flickfyi/core/dist-types/kowalski/feature-schema.d.ts +65 -0
  83. package/node_modules/@flickfyi/core/dist-types/kowalski/format.d.ts +4 -0
  84. package/node_modules/@flickfyi/core/dist-types/kowalski/fortune.d.ts +21 -0
  85. package/node_modules/@flickfyi/core/dist-types/kowalski/frame.d.ts +4 -4
  86. package/node_modules/@flickfyi/core/dist-types/kowalski/index.d.cts +31 -7
  87. package/node_modules/@flickfyi/core/dist-types/kowalski/index.d.ts +31 -7
  88. package/node_modules/@flickfyi/core/dist-types/kowalski/lesson.d.ts +89 -0
  89. package/node_modules/@flickfyi/core/dist-types/kowalski/recipe.d.ts +129 -8
  90. package/node_modules/@flickfyi/core/dist-types/kowalski/schemas.d.cts +4 -0
  91. package/node_modules/@flickfyi/core/dist-types/kowalski/schemas.d.ts +4 -0
  92. package/node_modules/@flickfyi/core/dist-types/kowalski/time-since.d.ts +34 -0
  93. package/node_modules/@flickfyi/core/examples/example-metadata.ts +19 -2
  94. package/node_modules/@flickfyi/core/examples/kowalski/bill-split/bill-split.stories.tsx +25 -6
  95. package/node_modules/@flickfyi/core/examples/kowalski/bill-split/bill-split.tsx +6 -12
  96. package/node_modules/@flickfyi/core/examples/kowalski/bookmark/bookmark.stories.tsx +22 -5
  97. package/node_modules/@flickfyi/core/examples/kowalski/bookmark/bookmark.tsx +6 -18
  98. package/node_modules/@flickfyi/core/examples/kowalski/budget/budget.stories.tsx +26 -5
  99. package/node_modules/@flickfyi/core/examples/kowalski/budget/budget.tsx +6 -21
  100. package/node_modules/@flickfyi/core/examples/kowalski/calculator/calculator.stories.tsx +18 -5
  101. package/node_modules/@flickfyi/core/examples/kowalski/calculator/calculator.tsx +6 -19
  102. package/node_modules/@flickfyi/core/examples/kowalski/checklist/checklist.stories.tsx +25 -5
  103. package/node_modules/@flickfyi/core/examples/kowalski/checklist/checklist.tsx +6 -21
  104. package/node_modules/@flickfyi/core/examples/kowalski/color-palette/color-palette.stories.tsx +19 -6
  105. package/node_modules/@flickfyi/core/examples/kowalski/color-palette/color-palette.tsx +6 -17
  106. package/node_modules/@flickfyi/core/examples/kowalski/contact/contact.stories.tsx +30 -5
  107. package/node_modules/@flickfyi/core/examples/kowalski/contact/contact.tsx +6 -18
  108. package/node_modules/@flickfyi/core/examples/kowalski/countdown/countdown.stories.tsx +22 -5
  109. package/node_modules/@flickfyi/core/examples/kowalski/countdown/countdown.tsx +6 -18
  110. package/node_modules/@flickfyi/core/examples/kowalski/event/event.stories.tsx +23 -5
  111. package/node_modules/@flickfyi/core/examples/kowalski/event/event.tsx +6 -19
  112. package/node_modules/@flickfyi/core/examples/kowalski/expense/expense.stories.tsx +23 -5
  113. package/node_modules/@flickfyi/core/examples/kowalski/expense/expense.tsx +6 -18
  114. package/node_modules/@flickfyi/core/examples/kowalski/fortune/fortune.stories.tsx +61 -0
  115. package/node_modules/@flickfyi/core/examples/kowalski/fortune/fortune.tsx +11 -0
  116. package/node_modules/@flickfyi/core/examples/kowalski/goal/goal.stories.tsx +18 -5
  117. package/node_modules/@flickfyi/core/examples/kowalski/goal/goal.tsx +3 -19
  118. package/node_modules/@flickfyi/core/examples/kowalski/habit/habit.stories.tsx +22 -5
  119. package/node_modules/@flickfyi/core/examples/kowalski/habit/habit.tsx +6 -18
  120. package/node_modules/@flickfyi/core/examples/kowalski/ledger/ledger.stories.tsx +25 -5
  121. package/node_modules/@flickfyi/core/examples/kowalski/ledger/ledger.tsx +6 -20
  122. package/node_modules/@flickfyi/core/examples/kowalski/lesson/lesson.stories.tsx +99 -0
  123. package/node_modules/@flickfyi/core/examples/kowalski/lesson/lesson.tsx +11 -0
  124. package/node_modules/@flickfyi/core/examples/kowalski/note/note.stories.tsx +21 -5
  125. package/node_modules/@flickfyi/core/examples/kowalski/note/note.tsx +3 -17
  126. package/node_modules/@flickfyi/core/examples/kowalski/poll/poll.stories.tsx +26 -5
  127. package/node_modules/@flickfyi/core/examples/kowalski/poll/poll.tsx +3 -21
  128. package/node_modules/@flickfyi/core/examples/kowalski/random-picker/random-picker.stories.tsx +37 -6
  129. package/node_modules/@flickfyi/core/examples/kowalski/random-picker/random-picker.tsx +6 -18
  130. package/node_modules/@flickfyi/core/examples/kowalski/rating/rating.stories.tsx +18 -5
  131. package/node_modules/@flickfyi/core/examples/kowalski/rating/rating.tsx +6 -14
  132. package/node_modules/@flickfyi/core/examples/kowalski/recipe/recipe.stories.tsx +117 -5
  133. package/node_modules/@flickfyi/core/examples/kowalski/recipe/recipe.tsx +6 -115
  134. package/node_modules/@flickfyi/core/examples/kowalski/reminder/reminder.stories.tsx +22 -5
  135. package/node_modules/@flickfyi/core/examples/kowalski/reminder/reminder.tsx +6 -18
  136. package/node_modules/@flickfyi/core/examples/kowalski/rotation/rotation.stories.tsx +26 -5
  137. package/node_modules/@flickfyi/core/examples/kowalski/rotation/rotation.tsx +6 -22
  138. package/node_modules/@flickfyi/core/examples/kowalski/scoreboard/scoreboard.stories.tsx +26 -5
  139. package/node_modules/@flickfyi/core/examples/kowalski/scoreboard/scoreboard.tsx +6 -21
  140. package/node_modules/@flickfyi/core/examples/kowalski/shopping-list/shopping-list.stories.tsx +28 -6
  141. package/node_modules/@flickfyi/core/examples/kowalski/shopping-list/shopping-list.tsx +6 -22
  142. package/node_modules/@flickfyi/core/examples/kowalski/task-board/task-board.stories.tsx +27 -6
  143. package/node_modules/@flickfyi/core/examples/kowalski/task-board/task-board.tsx +6 -22
  144. package/node_modules/@flickfyi/core/examples/kowalski/time-since/time-since.stories.tsx +33 -0
  145. package/node_modules/@flickfyi/core/examples/kowalski/time-since/time-since.tsx +12 -0
  146. package/node_modules/@flickfyi/core/examples/kowalski/time-zone/time-zone.stories.tsx +22 -6
  147. package/node_modules/@flickfyi/core/examples/kowalski/time-zone/time-zone.tsx +6 -17
  148. package/node_modules/@flickfyi/core/examples/kowalski/timer/timer.stories.tsx +18 -5
  149. package/node_modules/@flickfyi/core/examples/kowalski/timer/timer.tsx +6 -18
  150. package/node_modules/@flickfyi/core/examples/kowalski/trip/trip.stories.tsx +25 -5
  151. package/node_modules/@flickfyi/core/examples/kowalski/trip/trip.tsx +3 -21
  152. package/node_modules/@flickfyi/core/examples/kowalski/unit-converter/unit-converter.stories.tsx +19 -6
  153. package/node_modules/@flickfyi/core/examples/kowalski/unit-converter/unit-converter.tsx +6 -19
  154. package/node_modules/@flickfyi/core/examples/parrott/calculator/calculator.stories.tsx +10 -3
  155. package/node_modules/@flickfyi/core/examples/parrott/calculator/calculator.tsx +2 -13
  156. package/node_modules/@flickfyi/core/examples/parrott/checklist/checklist.stories.tsx +10 -3
  157. package/node_modules/@flickfyi/core/examples/parrott/checklist/checklist.tsx +0 -8
  158. package/node_modules/@flickfyi/core/examples/parrott/countdown/countdown.stories.tsx +10 -3
  159. package/node_modules/@flickfyi/core/examples/parrott/countdown/countdown.tsx +0 -8
  160. package/node_modules/@flickfyi/core/examples/parrott/counter/counter.stories.tsx +10 -3
  161. package/node_modules/@flickfyi/core/examples/parrott/counter/counter.tsx +0 -8
  162. package/node_modules/@flickfyi/core/examples/parrott/fortune/fortune.stories.tsx +10 -3
  163. package/node_modules/@flickfyi/core/examples/parrott/fortune/fortune.tsx +0 -8
  164. package/node_modules/@flickfyi/core/examples/parrott/grid/grid.stories.tsx +10 -3
  165. package/node_modules/@flickfyi/core/examples/parrott/grid/grid.tsx +4 -9
  166. package/node_modules/@flickfyi/core/examples/parrott/kale-smoothie/kale-smoothie.stories.tsx +10 -3
  167. package/node_modules/@flickfyi/core/examples/parrott/kale-smoothie/kale-smoothie.tsx +0 -8
  168. package/node_modules/@flickfyi/core/examples/parrott/list/list.stories.tsx +10 -3
  169. package/node_modules/@flickfyi/core/examples/parrott/list/list.tsx +0 -8
  170. package/node_modules/@flickfyi/core/examples/parrott/log/log.stories.tsx +10 -3
  171. package/node_modules/@flickfyi/core/examples/parrott/log/log.tsx +0 -8
  172. package/node_modules/@flickfyi/core/examples/parrott/timed-task/timed-task.stories.tsx +10 -3
  173. package/node_modules/@flickfyi/core/examples/parrott/timed-task/timed-task.tsx +0 -8
  174. package/node_modules/@flickfyi/core/examples/parrott/timer/timer.stories.tsx +10 -3
  175. package/node_modules/@flickfyi/core/examples/parrott/timer/timer.tsx +0 -8
  176. package/node_modules/@flickfyi/core/package.json +16 -3
  177. package/node_modules/@flickfyi/core/src/{kowalski/use-configured-feature.ts → hooks/use-configured-feature/index.ts} +1 -1
  178. package/node_modules/@flickfyi/core/src/{kowalski/use-feature-store.ts → hooks/use-feature-store/index.ts} +37 -12
  179. package/node_modules/@flickfyi/core/src/kowalski/README.md +13 -0
  180. package/node_modules/@flickfyi/core/src/kowalski/calculations.ts +36 -8
  181. package/node_modules/@flickfyi/core/src/kowalski/calculator.ts +15 -7
  182. package/node_modules/@flickfyi/core/src/kowalski/catalog.ts +110 -704
  183. package/node_modules/@flickfyi/core/src/kowalski/components/bill-split.tsx +284 -0
  184. package/node_modules/@flickfyi/core/src/kowalski/components/bookmark.tsx +347 -0
  185. package/node_modules/@flickfyi/core/src/kowalski/components/budget.tsx +169 -0
  186. package/node_modules/@flickfyi/core/src/kowalski/components/calculator.tsx +202 -0
  187. package/node_modules/@flickfyi/core/src/kowalski/components/checklist.tsx +119 -0
  188. package/node_modules/@flickfyi/core/src/kowalski/components/color-palette.tsx +302 -0
  189. package/node_modules/@flickfyi/core/src/kowalski/components/contact.tsx +247 -0
  190. package/node_modules/@flickfyi/core/src/kowalski/components/countdown.tsx +173 -0
  191. package/node_modules/@flickfyi/core/src/kowalski/components/event.tsx +260 -0
  192. package/node_modules/@flickfyi/core/src/kowalski/components/expense.tsx +257 -0
  193. package/node_modules/@flickfyi/core/src/kowalski/components/goal.tsx +182 -0
  194. package/node_modules/@flickfyi/core/src/kowalski/components/habit.tsx +272 -0
  195. package/node_modules/@flickfyi/core/src/kowalski/components/ledger.tsx +211 -0
  196. package/node_modules/@flickfyi/core/src/kowalski/components/note.tsx +188 -0
  197. package/node_modules/@flickfyi/core/src/kowalski/components/poll.tsx +242 -0
  198. package/node_modules/@flickfyi/core/src/kowalski/components/random-picker.tsx +308 -0
  199. package/node_modules/@flickfyi/core/src/kowalski/components/rating.tsx +112 -0
  200. package/node_modules/@flickfyi/core/src/kowalski/components/reminder.tsx +298 -0
  201. package/node_modules/@flickfyi/core/src/kowalski/components/rotation.tsx +146 -0
  202. package/node_modules/@flickfyi/core/src/kowalski/components/scoreboard.tsx +139 -0
  203. package/node_modules/@flickfyi/core/src/kowalski/components/shopping-list.tsx +144 -0
  204. package/node_modules/@flickfyi/core/src/kowalski/components/task-board.tsx +146 -0
  205. package/node_modules/@flickfyi/core/src/kowalski/components/time-zone.tsx +240 -0
  206. package/node_modules/@flickfyi/core/src/kowalski/components/timer.tsx +225 -0
  207. package/node_modules/@flickfyi/core/src/kowalski/components/trip.tsx +338 -0
  208. package/node_modules/@flickfyi/core/src/kowalski/components/unit-converter.tsx +285 -0
  209. package/node_modules/@flickfyi/core/src/kowalski/create-schemas.ts +8 -0
  210. package/node_modules/@flickfyi/core/src/kowalski/date-time.tsx +1 -1
  211. package/node_modules/@flickfyi/core/src/kowalski/everyday.tsx +1 -640
  212. package/node_modules/@flickfyi/core/src/kowalski/feature-schema.ts +84 -0
  213. package/node_modules/@flickfyi/core/src/kowalski/format.ts +29 -3
  214. package/node_modules/@flickfyi/core/src/kowalski/fortune.tsx +67 -0
  215. package/node_modules/@flickfyi/core/src/kowalski/frame.tsx +5 -6
  216. package/node_modules/@flickfyi/core/src/kowalski/index.ts +31 -22
  217. package/node_modules/@flickfyi/core/src/kowalski/lesson.tsx +564 -0
  218. package/node_modules/@flickfyi/core/src/kowalski/recipe.tsx +78 -37
  219. package/node_modules/@flickfyi/core/src/kowalski/schemas.ts +67 -0
  220. package/node_modules/@flickfyi/core/src/kowalski/stories/composition.tsx +22 -20
  221. package/node_modules/@flickfyi/core/src/kowalski/time-since.tsx +145 -0
  222. package/node_modules/@flickfyi/core/src/molecules/progress/progress.tsx +4 -1
  223. package/node_modules/@flickfyi/core/src/oklch.ts +1 -6
  224. package/node_modules/@flickfyi/core/src/parrott/theme.ts +3 -1
  225. package/node_modules/@flickfyi/core/src/themes/editorial/editorial.ts +6 -1
  226. package/node_modules/@flickfyi/core/src/themes/palette.ts +5 -5
  227. package/node_modules/@flickfyi/core/src/themes/stories/composition.tsx +11 -9
  228. package/node_modules/@flickfyi/core/src/themes/stories/gallery.tsx +7 -11
  229. package/node_modules/@flickfyi/core/tsgo.bun.lock +7 -7
  230. package/node_modules/@flickfyi/core/tsgo.package.json +1 -1
  231. package/package.json +2 -2
  232. package/node_modules/@flickfyi/core/dist-types/kowalski/clocks.d.ts +0 -4
  233. package/node_modules/@flickfyi/core/dist-types/kowalski/details.d.ts +0 -7
  234. package/node_modules/@flickfyi/core/dist-types/kowalski/productivity.d.ts +0 -5
  235. package/node_modules/@flickfyi/core/dist-types/kowalski/tools.d.ts +0 -7
  236. package/node_modules/@flickfyi/core/dist-types/kowalski/use-feature-store.d.ts +0 -15
  237. package/node_modules/@flickfyi/core/src/kowalski/clocks.tsx +0 -464
  238. package/node_modules/@flickfyi/core/src/kowalski/details.tsx +0 -1387
  239. package/node_modules/@flickfyi/core/src/kowalski/productivity.tsx +0 -575
  240. package/node_modules/@flickfyi/core/src/kowalski/tools.tsx +0 -1213
  241. /package/node_modules/@flickfyi/core/dist-types/{kowalski/use-clock.d.ts → hooks/use-clock/index.d.ts} +0 -0
  242. /package/node_modules/@flickfyi/core/dist-types/{kowalski/use-configured-feature.d.ts → hooks/use-configured-feature/index.d.ts} +0 -0
  243. /package/node_modules/@flickfyi/core/src/{kowalski/use-clock.ts → hooks/use-clock/index.ts} +0 -0
@@ -1,1387 +0,0 @@
1
- import { Flex, Icon, Text } from '@flickfyi/core'
2
- import { Button as NativeButton, Input } from '@flickfyi/core/atoms'
3
- import { List } from '@flickfyi/core/molecules'
4
- import * as stylex from '@stylexjs/stylex'
5
- import { toArray } from 'lodash'
6
- import { useMemo, useState } from 'react'
7
-
8
- import { size, space, text } from '~/themes/scale.stylex'
9
- import { appearance, button, roles } from '~/themes/shared'
10
-
11
- import { nextReminder } from './calculations'
12
- import { kowalskiCatalog, type KowalskiInput } from './catalog'
13
- import { FeatureDate, FeatureDateTime } from './date-time'
14
- import { format } from './format'
15
- import {
16
- FeatureAction,
17
- FeatureEdit,
18
- FeatureFacts,
19
- FeatureFrame,
20
- FeatureWell,
21
- layout,
22
- } from './frame'
23
- import { useClock } from './use-clock'
24
- import { useConfiguredFeature } from './use-configured-feature'
25
-
26
- export function Event(input: KowalskiInput<'event'>) {
27
- const props = kowalskiCatalog.event.props.parse(input)
28
- const schema = kowalskiCatalog.event.storeSchema
29
- const store = useConfiguredFeature({
30
- name: props.id ?? 'KowalskiEvent',
31
- revision: props.revision,
32
- schema,
33
- scope: props.scope,
34
- seed: schema.parse({
35
- location: { value: props.location },
36
- startsAt: { value: props.startsAt },
37
- title: { value: props.title },
38
- }),
39
- })
40
- const [draft, setDraft] = useState({
41
- location: '',
42
- startsAt: props.startsAt,
43
- title: '',
44
- })
45
- const [baseline, setBaseline] = useState<typeof draft | null>(null)
46
- const [editingDate, setEditingDate] = useState(false)
47
- const editing = baseline !== null
48
- const title = store.value.title.value.trim()
49
- ? store.value.title.value
50
- : 'Untitled event'
51
- const instant = store.value.startsAt.value
52
- ? Date.parse(store.value.startsAt.value)
53
- : null
54
- const timeParts =
55
- instant === null
56
- ? []
57
- : format.formatters
58
- .getDateTimeFormat(format.locale, {
59
- fractionalSecondDigits: instant % 1000 === 0 ? undefined : 3,
60
- hour: 'numeric',
61
- hour12: true,
62
- minute: '2-digit',
63
- second: instant % 60_000 === 0 ? undefined : '2-digit',
64
- timeZone: props.timeZone,
65
- timeZoneName: 'long',
66
- })
67
- .formatToParts(instant)
68
- const validTitle =
69
- draft.title.trim().length > 0 &&
70
- kowalskiCatalog.event.props.shape.title.safeParse(draft.title).success
71
- const validLocation = kowalskiCatalog.event.props.shape.location.safeParse(
72
- draft.location
73
- ).success
74
- const changed =
75
- baseline &&
76
- (draft.title !== baseline.title ||
77
- draft.location !== baseline.location ||
78
- draft.startsAt !== baseline.startsAt)
79
- return (
80
- <FeatureFrame
81
- onRetry={store.retry}
82
- notice={store.error}
83
- component="event"
84
- title={title}
85
- detail={
86
- instant === null
87
- ? 'Date to be decided'
88
- : format.formatters
89
- .getDateTimeFormat(format.locale, {
90
- day: 'numeric',
91
- month: 'long',
92
- timeZone: props.timeZone,
93
- weekday: 'long',
94
- })
95
- .format(instant)
96
- }
97
- >
98
- {editing && !store.saving ? (
99
- <Flex {...stylex.props(eventStyles.editor)}>
100
- <Flex {...stylex.props(layout.field)}>
101
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>Title</Text>
102
- <Input
103
- placeholder="Title"
104
- value={draft.title}
105
- onChange={title => setDraft(current => ({ ...current, title }))}
106
- style={[layout.input]}
107
- />
108
- {!validTitle && (
109
- <Text {...stylex.props(roles.muted)}>
110
- {draft.title.trim()
111
- ? 'Use 160 characters or fewer.'
112
- : 'Add an event title.'}
113
- </Text>
114
- )}
115
- </Flex>
116
- <FeatureDateTime
117
- inline
118
- clearable
119
- component="event"
120
- label="Start date & time"
121
- onChange={startsAt => setDraft(current => ({ ...current, startsAt }))}
122
- onEditingChange={setEditingDate}
123
- ready={store.ready}
124
- timeZone={props.timeZone}
125
- value={draft.startsAt}
126
- />
127
- <Flex {...stylex.props(layout.field)}>
128
- <Flex {...stylex.props(eventStyles.fieldLabel)}>
129
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>Location</Text>
130
- <Text {...stylex.props(roles.muted, eventStyles.optional)}>Optional</Text>
131
- </Flex>
132
- <Input
133
- placeholder="Location"
134
- value={draft.location}
135
- onChange={location => setDraft(current => ({ ...current, location }))}
136
- style={[layout.input]}
137
- />
138
- {!validLocation && (
139
- <Text {...stylex.props(roles.muted)}>Use 500 characters or fewer.</Text>
140
- )}
141
- </Flex>
142
- <Flex {...stylex.props(layout.actions)}>
143
- <FeatureAction component="event" onClick={() => setBaseline(null)}>
144
- Cancel
145
- </FeatureAction>
146
- <FeatureAction
147
- component="event"
148
- disabled={
149
- editingDate ||
150
- store.saving ||
151
- !store.ready ||
152
- !changed ||
153
- !validTitle ||
154
- !validLocation
155
- }
156
- onClick={() => {
157
- if (
158
- editingDate ||
159
- !baseline ||
160
- !changed ||
161
- !validTitle ||
162
- !validLocation
163
- ) {
164
- return
165
- }
166
- store.set(
167
- current => ({
168
- ...(draft.title !== baseline.title &&
169
- draft.title !== current.title.value && {
170
- title: { value: draft.title },
171
- }),
172
- ...(draft.startsAt !== baseline.startsAt &&
173
- draft.startsAt !== current.startsAt.value && {
174
- startsAt: { value: draft.startsAt },
175
- }),
176
- ...(draft.location !== baseline.location &&
177
- draft.location !== current.location.value && {
178
- location: { value: draft.location },
179
- }),
180
- }),
181
- () => setBaseline(null)
182
- )
183
- }}
184
- variant="primary"
185
- >
186
- Save changes
187
- </FeatureAction>
188
- </Flex>
189
- </Flex>
190
- ) : (
191
- <>
192
- {store.saving ? (
193
- <Text {...stylex.props(roles.muted)}>Saving changes…</Text>
194
- ) : !store.ready && !store.error ? (
195
- <Text {...stylex.props(roles.muted)}>Loading event…</Text>
196
- ) : null}
197
- <FeatureFacts>
198
- <List.Item
199
- label="Time"
200
- trailing={
201
- instant === null
202
- ? 'Not set'
203
- : timeParts
204
- .filter(part => part.type !== 'timeZoneName')
205
- .map(part => part.value)
206
- .join('')
207
- .trim()
208
- }
209
- />
210
- <List.Item
211
- label="Where"
212
- trailing={
213
- store.value.location.value.trim()
214
- ? store.value.location.value
215
- : 'To be decided'
216
- }
217
- />
218
- </FeatureFacts>
219
- {!editing && (
220
- <FeatureAction
221
- component="event"
222
- disabled={!store.ready || store.saving}
223
- icon="pencil"
224
- onClick={() => {
225
- const values = {
226
- location: store.value.location.value,
227
- startsAt: store.value.startsAt.value,
228
- title: store.value.title.value,
229
- }
230
- setDraft(values)
231
- setBaseline(values)
232
- }}
233
- >
234
- Edit event
235
- </FeatureAction>
236
- )}
237
- </>
238
- )}
239
- </FeatureFrame>
240
- )
241
- }
242
-
243
- export function Reminder(input: KowalskiInput<'reminder'>) {
244
- const props = kowalskiCatalog.reminder.props.parse(input)
245
- const timeZone = props.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone
246
- const schema = kowalskiCatalog.reminder.storeSchema
247
- const store = useConfiguredFeature({
248
- name: props.id ?? 'KowalskiReminder',
249
- revision: props.revision,
250
- schema,
251
- scope: props.scope,
252
- seed: schema.parse({ dueAt: { value: props.dueAt }, title: { value: props.title } }),
253
- })
254
- const [draft, setDraft] = useState({ dueAt: props.dueAt, title: props.title })
255
- const [baseline, setBaseline] = useState<typeof draft | null>(null)
256
- const [editingDate, setEditingDate] = useState(false)
257
- const editing = baseline !== null
258
- const done = store.value.completedAt !== null
259
- const due = store.value.dueAt.value ? Date.parse(store.value.dueAt.value) : null
260
- const now = useClock(due !== null)
261
- const title = store.value.title.value.trim()
262
- ? store.value.title.value
263
- : 'Untitled reminder'
264
- const dateFormat = format.formatters.getDateTimeFormat(format.locale, {
265
- day: 'numeric',
266
- month: 'long',
267
- timeZone,
268
- weekday: 'short',
269
- year: 'numeric',
270
- })
271
- const timeFormat = format.formatters.getDateTimeFormat(format.locale, {
272
- fractionalSecondDigits: due !== null && due % 1000 !== 0 ? 3 : undefined,
273
- hour: 'numeric',
274
- hour12: true,
275
- minute: '2-digit',
276
- second: due !== null && due % 60_000 !== 0 ? '2-digit' : undefined,
277
- timeZone,
278
- })
279
- const dueStatus =
280
- due === null
281
- ? 'No due date'
282
- : done
283
- ? 'Due date'
284
- : due < now
285
- ? 'Overdue'
286
- : dateFormat.format(due) === dateFormat.format(now)
287
- ? 'Due today'
288
- : 'Upcoming'
289
- const next = done ? nextReminder(store.value.dueAt.value, props.recurrence, now) : null
290
- const validTitle =
291
- draft.title.trim().length > 0 &&
292
- kowalskiCatalog.reminder.props.shape.title.safeParse(draft.title).success
293
- const changed =
294
- baseline && (draft.title !== baseline.title || draft.dueAt !== baseline.dueAt)
295
- const edit = () => {
296
- const values = { dueAt: store.value.dueAt.value, title: store.value.title.value }
297
- setDraft(values)
298
- setBaseline(values)
299
- }
300
- return (
301
- <FeatureFrame
302
- onRetry={store.retry}
303
- notice={store.error}
304
- component="reminder"
305
- title={title}
306
- detail={
307
- store.value.completedAt === null
308
- ? undefined
309
- : `Completed ${format.formatters
310
- .getDateTimeFormat(format.locale, {
311
- dateStyle: 'medium',
312
- hour12: true,
313
- timeStyle: 'short',
314
- timeZone,
315
- })
316
- .format(store.value.completedAt)}`
317
- }
318
- trailing={
319
- !editing && (
320
- <FeatureEdit disabled={!store.ready} label="Edit reminder" onClick={edit} />
321
- )
322
- }
323
- >
324
- {editing && store.saving ? (
325
- <Text {...stylex.props(roles.muted)}>Saving changes…</Text>
326
- ) : editing ? (
327
- <Flex {...stylex.props(reminderStyles.editor)}>
328
- <Flex {...stylex.props(layout.field)}>
329
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>Reminder</Text>
330
- <Input
331
- placeholder="Reminder"
332
- value={draft.title}
333
- onChange={title => setDraft(current => ({ ...current, title }))}
334
- style={[layout.input]}
335
- />
336
- {!validTitle && (
337
- <Text {...stylex.props(roles.muted)}>
338
- {draft.title.trim()
339
- ? 'Use 500 characters or fewer.'
340
- : 'Add a reminder title.'}
341
- </Text>
342
- )}
343
- </Flex>
344
- <FeatureDateTime
345
- inline
346
- clearable
347
- component="reminder"
348
- label="Due date & time"
349
- onChange={dueAt => setDraft(current => ({ ...current, dueAt }))}
350
- onEditingChange={setEditingDate}
351
- ready={store.ready}
352
- timeZone={timeZone}
353
- value={draft.dueAt}
354
- />
355
- <Flex {...stylex.props(layout.actions)}>
356
- <FeatureAction component="reminder" onClick={() => setBaseline(null)}>
357
- Cancel
358
- </FeatureAction>
359
- <FeatureAction
360
- component="reminder"
361
- disabled={
362
- editingDate || store.saving || !store.ready || !changed || !validTitle
363
- }
364
- onClick={() => {
365
- if (editingDate || !baseline || !changed || !validTitle) {
366
- return
367
- }
368
- store.set(
369
- current => ({
370
- ...(draft.title !== baseline.title &&
371
- draft.title !== current.title.value && {
372
- title: { value: draft.title },
373
- }),
374
- ...(draft.dueAt !== baseline.dueAt &&
375
- draft.dueAt !== current.dueAt.value && {
376
- dueAt: { value: draft.dueAt },
377
- }),
378
- }),
379
- () => setBaseline(null)
380
- )
381
- }}
382
- variant="primary"
383
- >
384
- {store.saving ? 'Saving…' : 'Save changes'}
385
- </FeatureAction>
386
- </Flex>
387
- </Flex>
388
- ) : (
389
- <>
390
- <FeatureFacts>
391
- <List.Item
392
- label={dueStatus}
393
- trailing={due === null ? 'Not set' : dateFormat.format(due)}
394
- />
395
- {due !== null && <List.Item label="Time" trailing={timeFormat.format(due)} />}
396
- {props.recurrence !== 'none' && (
397
- <List.Item
398
- description={
399
- due === null ? 'Add a due date to start repeating.' : undefined
400
- }
401
- label="Repeats"
402
- trailing={
403
- props.recurrence === 'daily' ? 'Every 24 hours' : 'Every 7 days'
404
- }
405
- />
406
- )}
407
- {next && (
408
- <List.Item
409
- label="Next occurrence"
410
- trailing={`${dateFormat.format(Date.parse(next))} · ${timeFormat.format(Date.parse(next))}`}
411
- />
412
- )}
413
- </FeatureFacts>
414
- <Flex {...stylex.props(layout.actions)}>
415
- {next && (
416
- <FeatureAction
417
- component="reminder"
418
- disabled={!store.ready}
419
- icon="arrow.right"
420
- onClick={() =>
421
- store.set(current => {
422
- const dueAt = nextReminder(
423
- current.dueAt.value,
424
- props.recurrence,
425
- Date.now()
426
- )
427
- return current.completedAt !== null && dueAt
428
- ? { completedAt: null, dueAt: { value: dueAt } }
429
- : {}
430
- })
431
- }
432
- variant="primary"
433
- >
434
- Start next reminder
435
- </FeatureAction>
436
- )}
437
- <FeatureAction
438
- component="reminder"
439
- disabled={!store.ready}
440
- icon={done ? 'arrow.uturn.backward' : 'checkmark'}
441
- onClick={() =>
442
- store.set(current => ({
443
- completedAt: current.completedAt === null ? Date.now() : null,
444
- }))
445
- }
446
- variant={next ? 'secondary' : 'primary'}
447
- >
448
- {done ? 'Reopen' : 'Complete'}
449
- </FeatureAction>
450
- </Flex>
451
- </>
452
- )}
453
- </FeatureFrame>
454
- )
455
- }
456
-
457
- export function Trip(input: KowalskiInput<'trip'>) {
458
- const props = kowalskiCatalog.trip.props.parse(input)
459
- const schema = kowalskiCatalog.trip.storeSchema
460
- const store = useConfiguredFeature({
461
- name: props.id ?? 'KowalskiTrip',
462
- revision: props.revision,
463
- schema,
464
- scope: props.scope,
465
- seed: schema.parse({
466
- destination: { value: props.destination },
467
- endDate: { value: props.endDate },
468
- origin: { value: props.origin },
469
- startDate: { value: props.startDate },
470
- }),
471
- })
472
- const [draft, setDraft] = useState({
473
- destination: props.destination,
474
- endDate: props.endDate,
475
- origin: props.origin,
476
- startDate: props.startDate,
477
- })
478
- const [baseline, setBaseline] = useState<typeof draft | null>(null)
479
- const [editingEndDate, setEditingEndDate] = useState(false)
480
- const [editingStartDate, setEditingStartDate] = useState(false)
481
- const [saveError, setSaveError] = useState(false)
482
- const editing = baseline !== null
483
- const transport = {
484
- car: { icon: 'car.fill', label: 'By car' },
485
- other: { icon: 'suitcase.fill', label: 'Travel plans' },
486
- plane: { icon: 'airplane', label: 'By plane' },
487
- train: { icon: 'tram.fill', label: 'By train' },
488
- walk: { icon: 'figure.walk', label: 'On foot' },
489
- }[props.transport]
490
- const start = store.value.startDate.value
491
- const end = store.value.endDate.value
492
- const dateFormat = format.formatters.getDateTimeFormat(format.locale, {
493
- day: 'numeric',
494
- era: start?.startsWith('0000-') || end?.startsWith('0000-') ? 'short' : undefined,
495
- month: 'short',
496
- timeZone: 'UTC',
497
- year: 'numeric',
498
- })
499
- const startInstant = start ? Date.parse(`${start}T12:00:00Z`) : null
500
- const endInstant = end ? Date.parse(`${end}T12:00:00Z`) : null
501
- const nights =
502
- startInstant !== null && endInstant !== null
503
- ? Math.round((endInstant - startInstant) / 86_400_000)
504
- : null
505
- const dates =
506
- startInstant !== null && endInstant !== null
507
- ? start === end
508
- ? dateFormat.format(startInstant)
509
- : `${dateFormat.format(startInstant)} – ${dateFormat.format(endInstant)}`
510
- : startInstant === null
511
- ? endInstant === null
512
- ? 'Dates open'
513
- : `Until ${dateFormat.format(endInstant)}`
514
- : `From ${dateFormat.format(startInstant)}`
515
- const duration =
516
- nights === null
517
- ? start
518
- ? 'End date to be decided'
519
- : end
520
- ? 'Start date to be decided'
521
- : 'Set dates when you’re ready'
522
- : nights === 0
523
- ? 'Day trip'
524
- : format.formatMessage(
525
- {
526
- defaultMessage:
527
- '{days, number} days · {nights, plural, one {# night} other {# nights}}',
528
- id: 'kowalski.trip.duration',
529
- },
530
- { days: nights + 1, nights }
531
- )
532
- const changed =
533
- baseline &&
534
- (draft.destination !== baseline.destination ||
535
- draft.endDate !== baseline.endDate ||
536
- draft.origin !== baseline.origin ||
537
- draft.startDate !== baseline.startDate)
538
- const validOrigin = kowalskiCatalog.trip.props.shape.origin.safeParse(
539
- draft.origin
540
- ).success
541
- const validDestination = kowalskiCatalog.trip.props.shape.destination.safeParse(
542
- draft.destination
543
- ).success
544
- const nextStart =
545
- baseline && draft.startDate !== baseline.startDate ? draft.startDate : start
546
- const nextEnd = baseline && draft.endDate !== baseline.endDate ? draft.endDate : end
547
- const validDates = !nextStart || !nextEnd || nextStart <= nextEnd
548
- return (
549
- <FeatureFrame
550
- onRetry={store.retry}
551
- notice={store.error}
552
- component="trip"
553
- title={props.title}
554
- detail={transport.label}
555
- >
556
- {editing && store.saving ? (
557
- <Text {...stylex.props(roles.muted)}>Saving changes…</Text>
558
- ) : editing ? (
559
- <Flex {...stylex.props(tripStyles.editor)}>
560
- <Flex {...stylex.props(layout.field)}>
561
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>From</Text>
562
- <Input
563
- placeholder="From"
564
- value={draft.origin}
565
- onChange={origin => setDraft(current => ({ ...current, origin }))}
566
- style={[layout.input]}
567
- />
568
- {!validOrigin && (
569
- <Text {...stylex.props(roles.muted)}>Use 160 characters or fewer.</Text>
570
- )}
571
- </Flex>
572
- <Flex {...stylex.props(layout.field)}>
573
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>To</Text>
574
- <Input
575
- placeholder="To"
576
- value={draft.destination}
577
- onChange={destination => setDraft(current => ({ ...current, destination }))}
578
- style={[layout.input]}
579
- />
580
- {!validDestination && (
581
- <Text {...stylex.props(roles.muted)}>Use 160 characters or fewer.</Text>
582
- )}
583
- </Flex>
584
- <FeatureDate
585
- inline
586
- clearable
587
- component="trip"
588
- label="Start date"
589
- onChange={startDate => {
590
- setDraft(current => ({ ...current, startDate }))
591
- setSaveError(false)
592
- }}
593
- onEditingChange={setEditingStartDate}
594
- ready={store.ready}
595
- value={draft.startDate}
596
- />
597
- <FeatureDate
598
- inline
599
- clearable
600
- component="trip"
601
- label="End date"
602
- onChange={endDate => {
603
- setDraft(current => ({ ...current, endDate }))
604
- setSaveError(false)
605
- }}
606
- onEditingChange={setEditingEndDate}
607
- ready={store.ready}
608
- value={draft.endDate}
609
- />
610
- {(!validDates || saveError) && (
611
- <Text {...stylex.props(roles.muted)}>
612
- The trip cannot end before it starts. Adjust either date before saving.
613
- </Text>
614
- )}
615
- <Flex {...stylex.props(layout.actions)}>
616
- <FeatureAction
617
- component="trip"
618
- onClick={() => {
619
- setBaseline(null)
620
- setEditingEndDate(false)
621
- setEditingStartDate(false)
622
- }}
623
- >
624
- Cancel
625
- </FeatureAction>
626
- <FeatureAction
627
- component="trip"
628
- disabled={
629
- store.saving ||
630
- !store.ready ||
631
- editingEndDate ||
632
- editingStartDate ||
633
- !changed ||
634
- !validOrigin ||
635
- !validDestination ||
636
- !validDates
637
- }
638
- onClick={() => {
639
- if (
640
- store.saving ||
641
- !store.ready ||
642
- editingEndDate ||
643
- editingStartDate ||
644
- !baseline ||
645
- !changed ||
646
- !validOrigin ||
647
- !validDestination ||
648
- !validDates
649
- ) {
650
- return
651
- }
652
- try {
653
- store.set(
654
- current => ({
655
- ...(draft.destination !== baseline.destination &&
656
- draft.destination !== current.destination.value && {
657
- destination: { value: draft.destination },
658
- }),
659
- ...(draft.endDate !== baseline.endDate &&
660
- draft.endDate !== current.endDate.value && {
661
- endDate: { value: draft.endDate },
662
- }),
663
- ...(draft.origin !== baseline.origin &&
664
- draft.origin !== current.origin.value && {
665
- origin: { value: draft.origin },
666
- }),
667
- ...(draft.startDate !== baseline.startDate &&
668
- draft.startDate !== current.startDate.value && {
669
- startDate: { value: draft.startDate },
670
- }),
671
- }),
672
- () => setBaseline(null)
673
- )
674
- } catch {
675
- setSaveError(true)
676
- }
677
- }}
678
- variant="primary"
679
- >
680
- {store.saving ? 'Saving…' : 'Save changes'}
681
- </FeatureAction>
682
- </Flex>
683
- </Flex>
684
- ) : (
685
- <>
686
- <FeatureFacts>
687
- <List.Item
688
- label="From"
689
- trailing={
690
- store.value.origin.value.trim()
691
- ? store.value.origin.value
692
- : 'To be decided'
693
- }
694
- />
695
- <List.Item
696
- label="To"
697
- trailing={
698
- store.value.destination.value.trim()
699
- ? store.value.destination.value
700
- : 'To be decided'
701
- }
702
- />
703
- <List.Item description={duration} label="Dates" trailing={dates} />
704
- </FeatureFacts>
705
- {!editing && (
706
- <FeatureAction
707
- component="trip"
708
- disabled={!store.ready || store.saving}
709
- icon="pencil"
710
- onClick={() => {
711
- const values = {
712
- destination: store.value.destination.value,
713
- endDate: store.value.endDate.value,
714
- origin: store.value.origin.value,
715
- startDate: store.value.startDate.value,
716
- }
717
- setDraft(values)
718
- setBaseline(values)
719
- setSaveError(false)
720
- }}
721
- >
722
- Edit trip
723
- </FeatureAction>
724
- )}
725
- </>
726
- )}
727
- </FeatureFrame>
728
- )
729
- }
730
-
731
- export function Contact(input: KowalskiInput<'contact'>) {
732
- const props = kowalskiCatalog.contact.props.parse(input)
733
- const schema = kowalskiCatalog.contact.storeSchema
734
- const store = useConfiguredFeature({
735
- name: props.id ?? 'KowalskiContact',
736
- revision: props.revision,
737
- schema,
738
- scope: props.scope,
739
- seed: schema.parse({
740
- email: { value: props.email ?? null },
741
- name: { value: props.name },
742
- phone: { value: props.phone ?? null },
743
- }),
744
- })
745
- const [draft, setDraft] = useState({ email: '', name: '', phone: '' })
746
- const [baseline, setBaseline] = useState<typeof draft | null>(null)
747
- const editing = baseline !== null
748
- const name = store.value.name.value.trim() ? store.value.name.value : 'Unnamed contact'
749
- const fields = [
750
- {
751
- key: 'name',
752
- label: 'Name',
753
- valid:
754
- draft.name.trim().length > 0 &&
755
- kowalskiCatalog.contact.props.shape.name.safeParse(draft.name).success,
756
- validation: draft.name.trim() ? 'Use 160 characters or fewer.' : 'Add a name.',
757
- },
758
- {
759
- key: 'phone',
760
- label: 'Phone',
761
- valid:
762
- draft.phone === '' ||
763
- kowalskiCatalog.contact.props.shape.phone.safeParse(draft.phone).success,
764
- validation: 'Use 80 characters or fewer.',
765
- },
766
- {
767
- key: 'email',
768
- label: 'Email',
769
- valid:
770
- draft.email === '' ||
771
- kowalskiCatalog.contact.props.shape.email.safeParse(draft.email).success,
772
- validation: 'Enter a valid email address.',
773
- },
774
- ] as const
775
- const changed =
776
- baseline &&
777
- (draft.email !== baseline.email ||
778
- draft.name !== baseline.name ||
779
- draft.phone !== baseline.phone)
780
- return (
781
- <FeatureFrame
782
- onRetry={store.retry}
783
- notice={store.error}
784
- component="contact"
785
- title={name}
786
- trailing={
787
- !editing && (
788
- <FeatureEdit
789
- disabled={!store.ready || store.saving}
790
- label="Edit contact"
791
- onClick={() => {
792
- const values = {
793
- email: store.value.email.value ?? '',
794
- name: store.value.name.value,
795
- phone: store.value.phone.value ?? '',
796
- }
797
- setBaseline(values)
798
- setDraft(values)
799
- }}
800
- />
801
- )
802
- }
803
- >
804
- {editing && !store.saving ? (
805
- <Flex {...stylex.props(contactStyles.editor)}>
806
- {fields.map(field => (
807
- <Flex key={field.key} {...stylex.props(layout.field)}>
808
- <Flex {...stylex.props(contactStyles.fieldLabel)}>
809
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>
810
- {field.label}
811
- </Text>
812
- {field.key !== 'name' && (
813
- <Text {...stylex.props(roles.muted, contactStyles.optional)}>
814
- Optional
815
- </Text>
816
- )}
817
- </Flex>
818
- <Input
819
- placeholder={field.label}
820
- value={draft[field.key]}
821
- onChange={value =>
822
- setDraft(current => ({ ...current, [field.key]: value }))
823
- }
824
- style={[layout.input]}
825
- />
826
- {!field.valid && (
827
- <Text {...stylex.props(roles.muted)}>{field.validation}</Text>
828
- )}
829
- </Flex>
830
- ))}
831
- <Flex {...stylex.props(layout.actions)}>
832
- <FeatureAction component="contact" onClick={() => setBaseline(null)}>
833
- Cancel
834
- </FeatureAction>
835
- <FeatureAction
836
- component="contact"
837
- disabled={
838
- store.saving ||
839
- !store.ready ||
840
- !changed ||
841
- fields.some(field => !field.valid)
842
- }
843
- variant="primary"
844
- onClick={() => {
845
- if (!baseline || !changed || fields.some(field => !field.valid)) {
846
- return
847
- }
848
- store.set(
849
- current => ({
850
- ...(draft.email !== baseline.email &&
851
- draft.email !== (current.email.value ?? '') && {
852
- email: { value: draft.email || null },
853
- }),
854
- ...(draft.name !== baseline.name &&
855
- draft.name !== current.name.value && {
856
- name: { value: draft.name },
857
- }),
858
- ...(draft.phone !== baseline.phone &&
859
- draft.phone !== (current.phone.value ?? '') && {
860
- phone: { value: draft.phone || null },
861
- }),
862
- }),
863
- () => setBaseline(null)
864
- )
865
- }}
866
- >
867
- Save changes
868
- </FeatureAction>
869
- </Flex>
870
- </Flex>
871
- ) : (
872
- <>
873
- {store.saving ? (
874
- <Text {...stylex.props(roles.muted)}>Saving changes…</Text>
875
- ) : !store.ready && !store.error ? (
876
- <Text {...stylex.props(roles.muted)}>Loading contact…</Text>
877
- ) : null}
878
- <FeatureFacts>
879
- <List.Item
880
- label="Phone"
881
- trailing={store.value.phone.value ?? 'No phone number'}
882
- />
883
- <List.Item
884
- label="Email"
885
- trailing={store.value.email.value ?? 'No email address'}
886
- />
887
- </FeatureFacts>
888
- <Flex {...stylex.props(layout.actions)}>
889
- {(
890
- [
891
- { address: store.value.phone.value, icon: 'phone', verb: 'Call' },
892
- { address: store.value.email.value, icon: 'envelope', verb: 'Email' },
893
- ] as const
894
- ).map(
895
- field =>
896
- field.address !== null && (
897
- <NativeButton
898
- accessibilityLabel={`${field.verb} ${name}`}
899
- disabled={!store.ready || store.saving}
900
- href={
901
- field.verb === 'Call'
902
- ? `tel:${encodeURIComponent(field.address.replaceAll(/[()\s.-]/g, ''))}`
903
- : `mailto:${encodeURIComponent(field.address)}`
904
- }
905
- key={field.verb}
906
- style={[button.secondary, button.lg, layout.action]}
907
- >
908
- <Flex {...stylex.props(layout.actionContent)}>
909
- <Icon name={field.icon} />
910
- <Text>{field.verb}</Text>
911
- </Flex>
912
- </NativeButton>
913
- )
914
- )}
915
- </Flex>
916
- </>
917
- )}
918
- </FeatureFrame>
919
- )
920
- }
921
-
922
- export function Bookmark(input: KowalskiInput<'bookmark'>) {
923
- const props = kowalskiCatalog.bookmark.props.parse(input)
924
- const schema = kowalskiCatalog.bookmark.storeSchema
925
- const store = useConfiguredFeature({
926
- name: props.id ?? 'KowalskiBookmark',
927
- revision: props.revision,
928
- schema,
929
- scope: props.scope,
930
- seed: schema.parse({
931
- note: { value: props.note },
932
- title: { value: props.title },
933
- url: { value: props.url },
934
- }),
935
- })
936
- const [draft, setDraft] = useState({ note: '', title: '', url: '' })
937
- const [baseline, setBaseline] = useState<typeof draft | null>(null)
938
- const [showAddress, setShowAddress] = useState(false)
939
- const [showFullNote, setShowFullNote] = useState(false)
940
- const editing = baseline !== null
941
- const domain = (store.value.url.value ?? '')
942
- .replace(/^https?:\/\//i, '')
943
- .split(/[/?#]/)[0]
944
- ?.split('@')
945
- .pop()
946
- ?.replace(/^www\./i, '')
947
- const title = store.value.title.value.trim()
948
- ? store.value.title.value
949
- : domain || 'Untitled bookmark'
950
- const note = store.value.note.value
951
- const noteCharacters = toArray(note)
952
- const longNote = noteCharacters.length > 360
953
- const valid = {
954
- note: kowalskiCatalog.bookmark.props.shape.note.safeParse(draft.note).success,
955
- title: kowalskiCatalog.bookmark.props.shape.title.safeParse(draft.title).success,
956
- url: kowalskiCatalog.bookmark.props.shape.url.safeParse(draft.url).success,
957
- }
958
- const changed =
959
- baseline &&
960
- (draft.note !== baseline.note ||
961
- draft.title !== baseline.title ||
962
- draft.url !== baseline.url)
963
- function editBookmark() {
964
- if (!store.ready || store.saving) {
965
- return
966
- }
967
- const values = {
968
- note: store.value.note.value,
969
- title: store.value.title.value,
970
- url: store.value.url.value ?? '',
971
- }
972
- setDraft(values)
973
- setBaseline(values)
974
- }
975
- return (
976
- <FeatureFrame
977
- onRetry={store.retry}
978
- notice={store.error}
979
- component="bookmark"
980
- title={title}
981
- detail={domain || 'No web address'}
982
- trailing={
983
- !editing && (
984
- <FeatureEdit
985
- disabled={!store.ready || store.saving}
986
- label="Edit bookmark"
987
- onClick={editBookmark}
988
- />
989
- )
990
- }
991
- >
992
- {editing ? (
993
- <Flex {...stylex.props(bookmarkStyles.editor)}>
994
- {store.saving && <Text {...stylex.props(roles.muted)}>Saving changes…</Text>}
995
- {(
996
- [
997
- { key: 'title', label: 'Title', message: 'Use 160 characters or fewer.' },
998
- {
999
- key: 'url',
1000
- label: 'Web address',
1001
- message: 'Use a valid address starting with https:// or http://.',
1002
- },
1003
- { key: 'note', label: 'Note', message: 'Use 10,000 characters or fewer.' },
1004
- ] as const
1005
- ).map(field => (
1006
- <Flex key={field.key} {...stylex.props(layout.field)}>
1007
- <Flex {...stylex.props(bookmarkStyles.fieldLabel)}>
1008
- <Text {...stylex.props(roles.label, layout.fieldLabel)}>
1009
- {field.label}
1010
- </Text>
1011
- {field.key !== 'url' && (
1012
- <Text {...stylex.props(roles.muted, bookmarkStyles.caption)}>
1013
- Optional
1014
- </Text>
1015
- )}
1016
- </Flex>
1017
- {store.saving ? (
1018
- <Text
1019
- {...stylex.props(
1020
- roles.text,
1021
- appearance.surface,
1022
- layout.input,
1023
- bookmarkStyles.submittedValue
1024
- )}
1025
- >
1026
- {draft[field.key] || (field.key === 'note' ? 'No note' : 'No title')}
1027
- </Text>
1028
- ) : (
1029
- <Input
1030
- multiline={field.key === 'note'}
1031
- placeholder={field.label}
1032
- value={draft[field.key]}
1033
- onChange={value =>
1034
- setDraft(current => ({ ...current, [field.key]: value }))
1035
- }
1036
- style={[layout.input, field.key === 'note' && layout.multiline]}
1037
- />
1038
- )}
1039
- {!valid[field.key] && (
1040
- <Text {...stylex.props(roles.muted)}>{field.message}</Text>
1041
- )}
1042
- </Flex>
1043
- ))}
1044
- <Flex {...stylex.props(layout.actions)}>
1045
- <FeatureAction
1046
- component="bookmark"
1047
- disabled={store.saving}
1048
- onClick={() => setBaseline(null)}
1049
- >
1050
- Cancel
1051
- </FeatureAction>
1052
- <FeatureAction
1053
- component="bookmark"
1054
- disabled={
1055
- store.saving ||
1056
- !store.ready ||
1057
- !changed ||
1058
- !valid.title ||
1059
- !valid.url ||
1060
- !valid.note
1061
- }
1062
- onClick={() => {
1063
- if (
1064
- !store.ready ||
1065
- store.saving ||
1066
- !baseline ||
1067
- !changed ||
1068
- !valid.title ||
1069
- !valid.url ||
1070
- !valid.note
1071
- ) {
1072
- return
1073
- }
1074
- store.set(
1075
- current => ({
1076
- ...(draft.title !== baseline.title &&
1077
- draft.title !== current.title.value && {
1078
- title: { value: draft.title },
1079
- }),
1080
- ...(draft.url !== baseline.url &&
1081
- draft.url !== (current.url.value ?? '') && {
1082
- url: { value: draft.url || null },
1083
- }),
1084
- ...(draft.note !== baseline.note &&
1085
- draft.note !== current.note.value && {
1086
- note: { value: draft.note },
1087
- }),
1088
- }),
1089
- () => setBaseline(null)
1090
- )
1091
- }}
1092
- variant="primary"
1093
- >
1094
- {store.saving ? 'Saving…' : 'Save changes'}
1095
- </FeatureAction>
1096
- </Flex>
1097
- </Flex>
1098
- ) : (
1099
- <Flex {...stylex.props(bookmarkStyles.details)}>
1100
- {store.saving ? (
1101
- <Text {...stylex.props(roles.muted)}>Saving changes…</Text>
1102
- ) : !store.ready && !store.error ? (
1103
- <Text {...stylex.props(roles.muted)}>Loading bookmark…</Text>
1104
- ) : null}
1105
- <Flex {...stylex.props(bookmarkStyles.note)}>
1106
- {note && (
1107
- <Text {...stylex.props(roles.text, bookmarkStyles.noteText)}>
1108
- {longNote && !showFullNote
1109
- ? `${noteCharacters.slice(0, 360).join('')}…`
1110
- : note}
1111
- </Text>
1112
- )}
1113
- {longNote && (
1114
- <FeatureAction
1115
- component="bookmark"
1116
- onClick={() => setShowFullNote(current => !current)}
1117
- variant="quiet"
1118
- >
1119
- {showFullNote ? 'Show less' : 'Read full note'}
1120
- </FeatureAction>
1121
- )}
1122
- {!note && (
1123
- <FeatureAction
1124
- component="bookmark"
1125
- disabled={!store.ready || store.saving}
1126
- icon="plus"
1127
- onClick={editBookmark}
1128
- variant="quiet"
1129
- >
1130
- Add a note
1131
- </FeatureAction>
1132
- )}
1133
- </Flex>
1134
- {store.value.url.value && (
1135
- <Flex {...stylex.props(bookmarkStyles.address)}>
1136
- <NativeButton
1137
- onClick={() => setShowAddress(current => !current)}
1138
- style={[button.quiet, bookmarkStyles.addressButton]}
1139
- >
1140
- <Flex {...stylex.props(bookmarkStyles.addressRow)}>
1141
- <Text {...stylex.props(roles.strong, bookmarkStyles.caption)}>
1142
- {showAddress ? 'Hide web address' : 'Show web address'}
1143
- </Text>
1144
- <Icon
1145
- name={showAddress ? 'chevron.up' : 'chevron.down'}
1146
- {...stylex.props(roles.muted, bookmarkStyles.chevron)}
1147
- />
1148
- </Flex>
1149
- </NativeButton>
1150
- {showAddress && (
1151
- <Flex {...stylex.props(appearance.surface, bookmarkStyles.addressValue)}>
1152
- <Text {...stylex.props(roles.muted, bookmarkStyles.caption)}>
1153
- {store.value.url.value}
1154
- </Text>
1155
- </Flex>
1156
- )}
1157
- </Flex>
1158
- )}
1159
- {store.value.url.value && (
1160
- <NativeButton
1161
- accessibilityLabel={`Open ${title}`}
1162
- disabled={!store.ready || store.saving}
1163
- href={store.value.url.value}
1164
- style={[button.primary, button.lg, layout.action]}
1165
- >
1166
- <Flex {...stylex.props(layout.actionContent)}>
1167
- <Icon name="arrow.up.right" />
1168
- <Text>Open link</Text>
1169
- </Flex>
1170
- </NativeButton>
1171
- )}
1172
- </Flex>
1173
- )}
1174
- </FeatureFrame>
1175
- )
1176
- }
1177
-
1178
- export function Note(input: KowalskiInput<'note'>) {
1179
- const props = kowalskiCatalog.note.props.parse(input)
1180
- const schema = kowalskiCatalog.note.storeSchema
1181
- const store = useConfiguredFeature({
1182
- name: props.id ?? 'KowalskiNote',
1183
- revision: props.revision,
1184
- schema,
1185
- scope: props.scope,
1186
- seed: schema.parse({ body: { value: props.body }, title: { value: props.title } }),
1187
- })
1188
- const [bodyDraft, setBodyDraft] = useState<{ value: string } | null>(null)
1189
- const [titleDraft, setTitleDraft] = useState<{ value: string } | null>(null)
1190
- const body = bodyDraft?.value ?? store.value.body.value
1191
- const title = titleDraft?.value ?? store.value.title.value
1192
- const validBody = body.length <= 20_000
1193
- const validTitle = title.length <= 160
1194
- const text = store.ready ? body : store.value.body.value
1195
- const wordCount = useMemo(
1196
- () =>
1197
- [...new Intl.Segmenter('en', { granularity: 'word' }).segment(text)].filter(
1198
- // Older JavaScriptCore versions mark numeric words as non-word-like.
1199
- word => word.isWordLike || /\p{N}/u.test(word.segment)
1200
- ).length,
1201
- [text]
1202
- )
1203
- const hasDraft = bodyDraft !== null || titleDraft !== null
1204
- const status = store.ready
1205
- ? !validBody || !validTitle
1206
- ? 'Not saved'
1207
- : store.saving
1208
- ? 'Saving…'
1209
- : hasDraft || store.error
1210
- ? 'Not saved'
1211
- : 'Saved'
1212
- : store.error
1213
- ? 'Read-only'
1214
- : 'Loading…'
1215
- return (
1216
- <FeatureFrame
1217
- onRetry={store.retry}
1218
- notice={store.error}
1219
- component="note"
1220
- title={store.value.title.value || 'Untitled note'}
1221
- header={false}
1222
- >
1223
- <FeatureWell>
1224
- {store.ready ? (
1225
- <Input
1226
- multiline
1227
- placeholder="Untitled note"
1228
- value={title}
1229
- onChange={next => {
1230
- const draft = { value: next }
1231
- setTitleDraft(draft)
1232
- if (next.length <= 160) {
1233
- store.set(
1234
- () => ({ title: draft }),
1235
- () => setTitleDraft(current => (current === draft ? null : current))
1236
- )
1237
- }
1238
- }}
1239
- style={[roles.h2, noteStyles.page]}
1240
- />
1241
- ) : (
1242
- <Text {...stylex.props(roles.h2)}>
1243
- {store.value.title.value || 'Untitled note'}
1244
- </Text>
1245
- )}
1246
- {store.ready ? (
1247
- <Input
1248
- multiline
1249
- placeholder="Start writing…"
1250
- value={body}
1251
- onChange={next => {
1252
- const draft = { value: next }
1253
- setBodyDraft(draft)
1254
- if (next.length <= 20_000) {
1255
- store.set(
1256
- () => ({ body: draft }),
1257
- () => setBodyDraft(current => (current === draft ? null : current))
1258
- )
1259
- }
1260
- }}
1261
- style={[roles.text, noteStyles.page, noteStyles.body]}
1262
- />
1263
- ) : (
1264
- <Text {...stylex.props(roles.text, noteStyles.body)}>
1265
- {text || (store.error ? 'This note is empty.' : 'Loading note…')}
1266
- </Text>
1267
- )}
1268
- </FeatureWell>
1269
- {!validTitle && (
1270
- <Text {...stylex.props(roles.muted, layout.notice)}>
1271
- Title is too long. Use 160 characters or fewer.
1272
- </Text>
1273
- )}
1274
- <Flex {...stylex.props(noteStyles.writing)}>
1275
- {!validBody && (
1276
- <Text {...stylex.props(roles.muted, layout.notice)}>
1277
- Note is too long. Use 20,000 characters or fewer to save.
1278
- </Text>
1279
- )}
1280
- <Flex {...stylex.props(noteStyles.footer)}>
1281
- <Text {...stylex.props(roles.muted)}>
1282
- {format.formatMessage(
1283
- {
1284
- defaultMessage: '{count, plural, one {# word} other {# words}}',
1285
- id: 'kowalski.note.words',
1286
- },
1287
- { count: wordCount }
1288
- )}
1289
- </Text>
1290
- <Text {...stylex.props(roles.muted)}>{status}</Text>
1291
- </Flex>
1292
- {store.ready && !store.saving && hasDraft && validBody && validTitle && (
1293
- <FeatureAction
1294
- component="note"
1295
- onClick={() => {
1296
- store.set(
1297
- () => ({
1298
- ...(bodyDraft && { body: bodyDraft }),
1299
- ...(titleDraft && { title: titleDraft }),
1300
- }),
1301
- () => {
1302
- setBodyDraft(current => (current === bodyDraft ? null : current))
1303
- setTitleDraft(current => (current === titleDraft ? null : current))
1304
- }
1305
- )
1306
- }}
1307
- >
1308
- Retry save
1309
- </FeatureAction>
1310
- )}
1311
- </Flex>
1312
- </FeatureFrame>
1313
- )
1314
- }
1315
-
1316
- const eventStyles = stylex.create({
1317
- editor: { gap: space[5] },
1318
- fieldLabel: {
1319
- alignItems: 'center',
1320
- flexDirection: 'row',
1321
- justifyContent: 'space-between',
1322
- },
1323
- optional: { fontSize: text['xs'] },
1324
- })
1325
-
1326
- const contactStyles = stylex.create({
1327
- editor: { gap: space[5] },
1328
- fieldLabel: {
1329
- alignItems: 'center',
1330
- flexDirection: 'row',
1331
- justifyContent: 'space-between',
1332
- },
1333
- optional: { fontSize: text['xs'] },
1334
- })
1335
-
1336
- const reminderStyles = stylex.create({
1337
- editor: { gap: space[5] },
1338
- })
1339
-
1340
- const tripStyles = stylex.create({
1341
- editor: { gap: space[5] },
1342
- })
1343
-
1344
- const bookmarkStyles = stylex.create({
1345
- address: { gap: space[1], minWidth: size[0.5] },
1346
- addressButton: { padding: `${space[2]} 0`, textAlign: 'left' },
1347
- addressRow: {
1348
- alignItems: 'center',
1349
- flexDirection: 'row',
1350
- gap: space[3],
1351
- justifyContent: 'space-between',
1352
- },
1353
- addressValue: { minWidth: size[0.5], padding: `${space[3]} ${space[3.5]}` },
1354
- caption: { fontSize: text['sm'] },
1355
- chevron: { fontSize: text['xs'], fontWeight: '600' },
1356
- details: { gap: space[4] },
1357
- editor: { gap: space[5] },
1358
- fieldLabel: {
1359
- alignItems: 'center',
1360
- flexDirection: 'row',
1361
- justifyContent: 'space-between',
1362
- },
1363
- note: { gap: space[3] },
1364
- noteText: { lineHeight: '1.6' },
1365
- submittedValue: { minWidth: size[0.5] },
1366
- })
1367
-
1368
- // The note is a page: borderless text on one surface, word count and status below it.
1369
- const noteStyles = stylex.create({
1370
- body: { minHeight: space[24] },
1371
- footer: {
1372
- alignItems: 'center',
1373
- flexDirection: 'row',
1374
- flexWrap: 'wrap',
1375
- gap: space[2],
1376
- justifyContent: 'space-between',
1377
- },
1378
- page: {
1379
- backgroundColor: 'transparent',
1380
- borderWidth: 0,
1381
- paddingLeft: 0,
1382
- paddingRight: 0,
1383
- textAlign: 'left',
1384
- width: '100%',
1385
- },
1386
- writing: { gap: space[2] },
1387
- })