@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,1213 +0,0 @@
1
- import { Flex, Icon, Text } from '@flickfyi/core'
2
- import { Button, Input, Picker } from '@flickfyi/core/atoms'
3
- import { Field, List, Segmented, Stat, Stepper } from '@flickfyi/core/molecules'
4
- import * as stylex from '@stylexjs/stylex'
5
- import { isEqual, uniqBy } from 'lodash'
6
- import { useEffect, useMemo, useState } from 'react'
7
-
8
- import { buttonVars } from '~/atoms/button/button.stylex'
9
- import { radius, size, space, text } from '~/themes/scale.stylex'
10
- import { appearance, button, roles } from '~/themes/shared'
11
- import { colors, font } from '~/themes/tokens.stylex'
12
-
13
- import { billShares, convertUnit, formatCurrency, pickResult } from './calculations'
14
- import {
15
- calculatorDisplay,
16
- calculatorEquation,
17
- calculatorKeyLabel,
18
- calculatorKeys,
19
- initialCalculator,
20
- pressCalculatorKey,
21
- } from './calculator'
22
- import {
23
- kowalskiCatalog,
24
- type KowalskiInput,
25
- type PickerDomain,
26
- unitDefinitions,
27
- } from './catalog'
28
- import { format, parseNumberInput } from './format'
29
- import {
30
- FeatureAction,
31
- FeatureFacts,
32
- FeatureField,
33
- FeatureFrame,
34
- FeatureNumber,
35
- FeatureWell,
36
- layout,
37
- } from './frame'
38
- import { useConfiguredFeature } from './use-configured-feature'
39
-
40
- export function BillSplit(input: KowalskiInput<'billSplit'>) {
41
- const props = kowalskiCatalog.billSplit.props.parse(input)
42
- const schema = kowalskiCatalog.billSplit.storeSchema
43
- const store = useConfiguredFeature({
44
- name: props.id ?? 'KowalskiBillSplit',
45
- revision: props.revision,
46
- schema,
47
- scope: props.scope,
48
- seed: schema.parse({
49
- people: props.people,
50
- tipPercent: props.tipPercent,
51
- total: props.total,
52
- }),
53
- })
54
- const result = billShares(
55
- store.value.total,
56
- store.value.people,
57
- store.value.tipPercent,
58
- props.currency
59
- )
60
- const money = format.formatters.getNumberFormat(format.locale, {
61
- currency: props.currency,
62
- style: 'currency',
63
- })
64
- const billTotal = formatCurrency(store.value.total, props.currency)
65
- const perPerson = money.format(result.perPerson)
66
- const subtotal = billShares(store.value.total, 1, 0, props.currency).total
67
- const tipPresets = [0, 15, 20, 25]
68
- const [customTipOpen, setCustomTipOpen] = useState(false)
69
- const [peopleDraft, setPeopleDraft] = useState(
70
- format.formatNumber(store.value.people, { format: 'integer' })
71
- )
72
- const [preservePeopleDraft, setPreservePeopleDraft] = useState<{ text: string }>()
73
- useEffect(() => {
74
- if (preservePeopleDraft === undefined) {
75
- setPeopleDraft(current =>
76
- parseNumberInput(current) === store.value.people
77
- ? current
78
- : format.formatNumber(store.value.people, { format: 'integer' })
79
- )
80
- }
81
- }, [preservePeopleDraft, store.value.people])
82
- const customTipVisible = customTipOpen || !tipPresets.includes(store.value.tipPercent)
83
- const peopleValid = schema.shape.people.safeParse(parseNumberInput(peopleDraft)).success
84
- return (
85
- <FeatureFrame
86
- onRetry={store.retry}
87
- notice={store.error}
88
- component="billSplit"
89
- title={props.title}
90
- >
91
- <FeatureField
92
- component="billSplit"
93
- label="Bill total"
94
- value={billTotal}
95
- ready={store.ready}
96
- saving={store.saving}
97
- parseValue={value => parseNumberInput(value, props.currency)}
98
- validate={value =>
99
- schema.shape.total.safeParse(parseNumberInput(value, props.currency)).success
100
- }
101
- onChange={(value, onSaved) => {
102
- store.set({ total: parseNumberInput(value, props.currency) }, onSaved)
103
- }}
104
- />
105
- <Field label="Tip">
106
- <Segmented
107
- disabled={!store.ready}
108
- options={[
109
- ...tipPresets.map(tip => ({
110
- label: format.formatNumber(tip / 100, { style: 'percent' }),
111
- value: tip,
112
- })),
113
- { label: 'Custom', value: 'custom' as const },
114
- ]}
115
- value={customTipVisible ? 'custom' : store.value.tipPercent}
116
- onChange={tip => {
117
- setCustomTipOpen(tip === 'custom')
118
- if (typeof tip === 'number') {
119
- store.set({ tipPercent: tip })
120
- }
121
- }}
122
- />
123
- </Field>
124
- {customTipVisible && (
125
- <FeatureNumber
126
- component="billSplit"
127
- label="Tip percent"
128
- min={0}
129
- max={100}
130
- ready={store.ready}
131
- saving={store.saving}
132
- value={store.value.tipPercent}
133
- onChange={(tipPercent, onSaved) => {
134
- store.set({ tipPercent }, onSaved)
135
- }}
136
- />
137
- )}
138
- <Field error={!peopleValid && 'Choose 1–1,000 people.'} label="People">
139
- <Stepper
140
- decrementDisabled={!store.ready || store.value.people <= 1}
141
- decrementLabel="Fewer people"
142
- incrementDisabled={!store.ready || store.value.people >= 1000}
143
- incrementLabel="More people"
144
- onDecrement={() => {
145
- setPreservePeopleDraft(undefined)
146
- store.set(current => ({ people: Math.max(1, current.people - 1) }))
147
- }}
148
- onIncrement={() => {
149
- setPreservePeopleDraft(undefined)
150
- store.set(current => ({ people: Math.min(1000, current.people + 1) }))
151
- }}
152
- >
153
- {store.ready ? (
154
- <Input
155
- placeholder="People"
156
- value={peopleDraft}
157
- onChange={text => {
158
- setPeopleDraft(text)
159
- const edit = { text }
160
- setPreservePeopleDraft(edit)
161
- if (schema.shape.people.safeParse(parseNumberInput(text)).success) {
162
- store.set({ people: parseNumberInput(text) }, () =>
163
- setPreservePeopleDraft(current =>
164
- current === edit ? undefined : current
165
- )
166
- )
167
- }
168
- }}
169
- style={[layout.input, layout.stepperInput]}
170
- />
171
- ) : (
172
- <Text {...stylex.props(roles.text, layout.stepperInput)}>
173
- {format.formatNumber(store.value.people, { format: 'integer' })}
174
- </Text>
175
- )}
176
- </Stepper>
177
- {store.ready &&
178
- peopleValid &&
179
- preservePeopleDraft !== undefined &&
180
- !store.saving && (
181
- <FeatureAction
182
- component="billSplit"
183
- onClick={() =>
184
- store.set({ people: parseNumberInput(peopleDraft) }, () =>
185
- setPreservePeopleDraft(current =>
186
- current === preservePeopleDraft ? undefined : current
187
- )
188
- )
189
- }
190
- >
191
- Retry people
192
- </FeatureAction>
193
- )}
194
- </Field>
195
- <FeatureWell>
196
- <Stat>
197
- <Stat.Value style={billStyles.amount(perPerson.length)}>{perPerson}</Stat.Value>
198
- <Stat.Caption>Per person</Stat.Caption>
199
- </Stat>
200
- {result.remainder > 0 && (
201
- <Text {...stylex.props(roles.muted, billStyles.centered)}>
202
- {`One person pays ${money.format(result.perPerson + result.remainder)} — ${money.format(result.remainder)} extra to cover rounding.`}
203
- </Text>
204
- )}
205
- {store.value.tipPercent > 0 && (
206
- <Flex {...stylex.props(billStyles.row)}>
207
- <Text
208
- {...stylex.props(roles.muted)}
209
- >{`Tip (${format.formatNumber(store.value.tipPercent, { format: 'decimal', style: 'unit', unit: 'percent', unitDisplay: 'narrow' })})`}</Text>
210
- <Text {...stylex.props(roles.text)}>
211
- {money.format(result.total - subtotal)}
212
- </Text>
213
- </Flex>
214
- )}
215
- <Flex {...stylex.props(billStyles.row)}>
216
- <Text {...stylex.props(roles.strong)}>Total with tip</Text>
217
- <Text {...stylex.props(roles.strong)}>{money.format(result.total)}</Text>
218
- </Flex>
219
- </FeatureWell>
220
- </FeatureFrame>
221
- )
222
- }
223
-
224
- export function Expense(input: KowalskiInput<'expense'>) {
225
- const props = kowalskiCatalog.expense.props.parse(input)
226
- const schema = kowalskiCatalog.expense.storeSchema
227
- const store = useConfiguredFeature({
228
- name: props.id ?? 'KowalskiExpense',
229
- revision: props.revision,
230
- schema,
231
- scope: props.scope,
232
- seed: schema.parse({
233
- amount: props.amount,
234
- category: { value: props.category },
235
- title: { value: props.title },
236
- }),
237
- })
238
- const [draft, setDraft] = useState({ amount: '', category: '', title: '' })
239
- const [baseline, setBaseline] = useState<typeof draft | null>(null)
240
- const editing = baseline !== null
241
- const amount = parseNumberInput(draft.amount, props.currency)
242
- const validAmount = schema.shape.amount.safeParse(amount).success
243
- const validTitle =
244
- draft.title.trim().length > 0 &&
245
- kowalskiCatalog.expense.props.shape.title.safeParse(draft.title).success
246
- const validCategory = kowalskiCatalog.expense.props.shape.category.safeParse(
247
- draft.category
248
- ).success
249
- const changed =
250
- baseline !== null &&
251
- (amount !== parseNumberInput(baseline.amount, props.currency) ||
252
- draft.category !== baseline.category ||
253
- draft.title !== baseline.title)
254
- const formattedAmount = formatCurrency(store.value.amount, props.currency)
255
- return (
256
- <FeatureFrame
257
- onRetry={store.retry}
258
- notice={store.error}
259
- component="expense"
260
- title={
261
- editing
262
- ? 'Edit expense'
263
- : store.value.title.value.trim()
264
- ? store.value.title.value
265
- : 'Untitled expense'
266
- }
267
- >
268
- {editing ? (
269
- <>
270
- <Field
271
- error={!validAmount && 'Enter an amount from 0 to 1,000,000,000,000.'}
272
- label={props.currency === 'USD' ? 'Amount' : `Amount (${props.currency})`}
273
- >
274
- {store.saving ? (
275
- <Text {...stylex.props(roles.text)}>{draft.amount}</Text>
276
- ) : (
277
- <Input
278
- placeholder="Amount"
279
- value={draft.amount}
280
- onChange={value => setDraft(current => ({ ...current, amount: value }))}
281
- style={[layout.input]}
282
- />
283
- )}
284
- </Field>
285
- {(
286
- [
287
- {
288
- key: 'title',
289
- label: 'Description',
290
- valid: validTitle,
291
- validation: 'Add a description using 160 characters or fewer.',
292
- },
293
- {
294
- key: 'category',
295
- label: 'Category',
296
- valid: validCategory,
297
- validation: 'Use 80 characters or fewer.',
298
- },
299
- ] as const
300
- ).map(field => (
301
- <Field
302
- error={!field.valid && field.validation}
303
- key={field.key}
304
- label={field.label}
305
- >
306
- {store.saving ? (
307
- <Text {...stylex.props(roles.text)}>
308
- {draft[field.key] || 'Uncategorized'}
309
- </Text>
310
- ) : (
311
- <Input
312
- placeholder={field.label}
313
- value={draft[field.key]}
314
- onChange={value =>
315
- setDraft(current => ({ ...current, [field.key]: value }))
316
- }
317
- style={[layout.input]}
318
- />
319
- )}
320
- </Field>
321
- ))}
322
- <Flex {...stylex.props(layout.actions)}>
323
- <FeatureAction
324
- component="expense"
325
- disabled={store.saving}
326
- onClick={() => {
327
- if (!store.saving) {
328
- setBaseline(null)
329
- }
330
- }}
331
- >
332
- Cancel
333
- </FeatureAction>
334
- <FeatureAction
335
- component="expense"
336
- disabled={
337
- store.saving ||
338
- !store.ready ||
339
- !changed ||
340
- !validAmount ||
341
- !validTitle ||
342
- !validCategory
343
- }
344
- variant="primary"
345
- onClick={() => {
346
- if (
347
- !baseline ||
348
- !store.ready ||
349
- store.saving ||
350
- !changed ||
351
- !validAmount ||
352
- !validTitle ||
353
- !validCategory
354
- ) {
355
- return
356
- }
357
- store.set(
358
- () => ({
359
- ...(amount !== parseNumberInput(baseline.amount, props.currency) && {
360
- amount,
361
- }),
362
- ...(draft.category !== baseline.category && {
363
- category: { value: draft.category },
364
- }),
365
- ...(draft.title !== baseline.title && {
366
- title: { value: draft.title },
367
- }),
368
- }),
369
- () => setBaseline(null)
370
- )
371
- }}
372
- >
373
- {store.saving ? 'Saving…' : 'Save changes'}
374
- </FeatureAction>
375
- </Flex>
376
- {store.saving && <Text {...stylex.props(roles.muted)}>Saving changes…</Text>}
377
- </>
378
- ) : (
379
- <>
380
- <FeatureWell>
381
- <Text
382
- animated="numeric"
383
- {...stylex.props(
384
- roles.display,
385
- expenseStyles.amount(formattedAmount.length)
386
- )}
387
- >
388
- {formattedAmount}
389
- </Text>
390
- <Text {...stylex.props(roles.muted)}>
391
- {store.value.category.value.trim()
392
- ? store.value.category.value
393
- : 'Uncategorized'}
394
- </Text>
395
- </FeatureWell>
396
- {!editing && (
397
- <FeatureAction
398
- component="expense"
399
- disabled={!store.ready || store.saving}
400
- icon="pencil"
401
- onClick={() => {
402
- const values = {
403
- amount: formattedAmount,
404
- category: store.value.category.value,
405
- title: store.value.title.value,
406
- }
407
- setBaseline(values)
408
- setDraft(values)
409
- }}
410
- >
411
- Edit expense
412
- </FeatureAction>
413
- )}
414
- </>
415
- )}
416
- </FeatureFrame>
417
- )
418
- }
419
-
420
- export function UnitConverter(input: KowalskiInput<'unitConverter'>) {
421
- const props = kowalskiCatalog.unitConverter.props.parse(input)
422
- const schema = kowalskiCatalog.unitConverter.storeSchema
423
- const store = useConfiguredFeature({
424
- name: props.id ?? 'KowalskiUnitConverter',
425
- revision: props.revision,
426
- schema,
427
- scope: props.scope,
428
- seed: schema.parse({
429
- from: { value: props.from },
430
- to: { value: props.to },
431
- value: props.value,
432
- }),
433
- })
434
- const units = [
435
- { label: 'Meters', value: 'm' },
436
- { label: 'Kilometers', value: 'km' },
437
- { label: 'Miles', value: 'mi' },
438
- { label: 'Feet', value: 'ft' },
439
- { label: 'Kilograms', value: 'kg' },
440
- { label: 'Grams', value: 'g' },
441
- { label: 'Pounds', value: 'lb' },
442
- { label: 'Celsius', value: 'c' },
443
- { label: 'Fahrenheit', value: 'f' },
444
- { label: 'Kelvin', value: 'k' },
445
- ] satisfies {
446
- label: string
447
- value: keyof typeof unitDefinitions
448
- }[]
449
- const groups = [
450
- { from: 'm', label: 'Length', to: 'ft' },
451
- { from: 'kg', label: 'Weight', to: 'lb' },
452
- { from: 'c', label: 'Temperature', to: 'f' },
453
- ] satisfies {
454
- from: keyof typeof unitDefinitions
455
- label: string
456
- to: keyof typeof unitDefinitions
457
- }[]
458
- const dimension = unitDefinitions[store.value.from.value].dimension
459
- const compatibleUnits = units.filter(
460
- unit => unitDefinitions[unit.value].dimension === dimension
461
- )
462
- const fromUnit = units.find(unit => unit.value === store.value.from.value)
463
- const toUnit = units.find(unit => unit.value === store.value.to.value)
464
- const storedValue = format.formatNumber(store.value.value, { format: 'decimal' })
465
- const [draft, setDraft] = useState(storedValue)
466
- const [preserveDraft, setPreserveDraft] = useState<string>()
467
- useEffect(() => {
468
- if (store.error && store.ready) {
469
- setPreserveDraft(current => current ?? draft)
470
- return
471
- }
472
- if (preserveDraft !== undefined) {
473
- return
474
- }
475
- setDraft(current =>
476
- parseNumberInput(current) === store.value.value ? current : storedValue
477
- )
478
- }, [draft, preserveDraft, store.error, store.ready, store.value.value, storedValue])
479
- const valid = Number.isFinite(parseNumberInput(draft))
480
- const result = convertUnit(
481
- valid ? parseNumberInput(draft) : store.value.value,
482
- store.value.from.value,
483
- store.value.to.value
484
- )
485
- // Four significant digits below one, three decimals above: 6.214 mi, not 6.2137119.
486
- const formattedResult = valid
487
- ? Number.isFinite(result)
488
- ? format.formatNumber(result, {
489
- notation:
490
- Math.abs(result) >= 1e9 || (result !== 0 && Math.abs(result) < 1e-6)
491
- ? 'scientific'
492
- : 'standard',
493
- ...(Math.abs(result) < 1
494
- ? { maximumSignificantDigits: 4 }
495
- : { maximumFractionDigits: 3 }),
496
- })
497
- : 'Out of range'
498
- : '—'
499
- return (
500
- <FeatureFrame
501
- onRetry={store.retry}
502
- notice={store.error}
503
- component="unitConverter"
504
- title={props.title}
505
- >
506
- <Segmented
507
- disabled={!store.ready}
508
- options={groups.map(group => ({ label: group.label, value: group.label }))}
509
- value={
510
- groups.find(group => unitDefinitions[group.from].dimension === dimension)?.label
511
- }
512
- onChange={label => {
513
- const group = groups.find(option => option.label === label)
514
- if (!group) {
515
- return
516
- }
517
- store.set(current =>
518
- unitDefinitions[current.from.value].dimension ===
519
- unitDefinitions[group.from].dimension
520
- ? {}
521
- : { from: { value: group.from }, to: { value: group.to } }
522
- )
523
- }}
524
- />
525
- <Field error={!valid && 'Enter a valid number.'} label="From">
526
- {store.ready ? (
527
- <Input
528
- placeholder="Value"
529
- value={draft}
530
- onChange={text => {
531
- setDraft(text)
532
- setPreserveDraft(text)
533
- if (Number.isFinite(parseNumberInput(text))) {
534
- store.set(
535
- () => ({ value: parseNumberInput(text) }),
536
- () =>
537
- setPreserveDraft(current => (current === text ? undefined : current))
538
- )
539
- }
540
- }}
541
- style={[layout.input]}
542
- />
543
- ) : (
544
- <Text {...stylex.props(roles.text)}>{draft}</Text>
545
- )}
546
- {store.ready ? (
547
- <Picker
548
- label="From unit"
549
- value={fromUnit?.label}
550
- onChange={label => {
551
- store.set(current => {
552
- const unit = units.find(option => option.label === label)
553
- return unit &&
554
- unitDefinitions[unit.value].dimension ===
555
- unitDefinitions[current.to.value].dimension
556
- ? { from: { value: unit.value } }
557
- : {}
558
- })
559
- }}
560
- style={layout.input}
561
- >
562
- {compatibleUnits.map(unit => (
563
- <Text key={unit.value} value={unit.label}>
564
- {unit.label}
565
- </Text>
566
- ))}
567
- </Picker>
568
- ) : (
569
- <Text {...stylex.props(roles.text)}>{fromUnit?.label}</Text>
570
- )}
571
- {store.saving && <Text {...stylex.props(roles.muted)}>Saving changes…</Text>}
572
- {preserveDraft !== undefined &&
573
- store.ready &&
574
- valid &&
575
- parseNumberInput(draft) !== store.value.value && (
576
- <FeatureAction
577
- component="unitConverter"
578
- disabled={store.saving}
579
- onClick={() =>
580
- store.set(
581
- () => ({ value: parseNumberInput(draft) }),
582
- () =>
583
- setPreserveDraft(current => (current === draft ? undefined : current))
584
- )
585
- }
586
- >
587
- Retry saving value
588
- </FeatureAction>
589
- )}
590
- </Field>
591
- <FeatureWell>
592
- <Text {...stylex.props(roles.h1, unitStyles.result)}>{formattedResult}</Text>
593
- {store.ready ? (
594
- <Picker
595
- label="To unit"
596
- value={toUnit?.label}
597
- onChange={label => {
598
- store.set(current => {
599
- const unit = units.find(option => option.label === label)
600
- return unit &&
601
- unitDefinitions[unit.value].dimension ===
602
- unitDefinitions[current.from.value].dimension
603
- ? { to: { value: unit.value } }
604
- : {}
605
- })
606
- }}
607
- style={layout.input}
608
- >
609
- {compatibleUnits.map(unit => (
610
- <Text key={unit.value} value={unit.label}>
611
- {unit.label}
612
- </Text>
613
- ))}
614
- </Picker>
615
- ) : (
616
- <Text {...stylex.props(roles.text)}>{toUnit?.label}</Text>
617
- )}
618
- </FeatureWell>
619
- <FeatureAction
620
- component="unitConverter"
621
- icon="arrow.left.arrow.right"
622
- disabled={!store.ready}
623
- onClick={() => {
624
- store.set(current => ({ from: current.to, to: current.from }))
625
- }}
626
- >
627
- Swap units
628
- </FeatureAction>
629
- </FeatureFrame>
630
- )
631
- }
632
-
633
- export function Calculator(input: KowalskiInput<'calculator'>) {
634
- const props = kowalskiCatalog.calculator.props.parse(input)
635
- const schema = kowalskiCatalog.calculator.storeSchema
636
- const store = useConfiguredFeature({
637
- name: props.id ?? 'KowalskiCalculator',
638
- revision: props.revision,
639
- schema,
640
- scope: props.scope,
641
- seed: schema.parse({ calculation: initialCalculator(props) }),
642
- })
643
- const calculation = store.value.calculation
644
- const display = calculatorDisplay(calculation.entry)
645
- return (
646
- <FeatureFrame
647
- onRetry={store.retry}
648
- notice={store.error}
649
- component="calculator"
650
- title={props.title}
651
- >
652
- <FeatureWell>
653
- <Text {...stylex.props(roles.muted, calculatorStyles.equation)}>
654
- {calculatorEquation(calculation)}
655
- </Text>
656
- <Text {...stylex.props(roles.display, calculatorStyles.value(display.length))}>
657
- {display}
658
- </Text>
659
- {calculation.phase === 'error' && (
660
- <Text {...stylex.props(roles.muted, calculatorStyles.error)}>
661
- {calculation.operator === '/' && calculation.right === 0
662
- ? 'Cannot divide by zero'
663
- : 'Number out of range'}
664
- </Text>
665
- )}
666
- </FeatureWell>
667
- <Flex {...stylex.props(calculatorStyles.keypad)}>
668
- {calculatorKeys.map(key => {
669
- const operation = key === '+' || key === '-' || key === '*' || key === '/'
670
- const utility =
671
- key === 'backspace' || key === 'clear' || key === 'sign' || key === '%'
672
- const selected =
673
- operation && calculation.phase === 'operator' && calculation.operator === key
674
- return (
675
- <Button
676
- accessibilityLabel={
677
- key === 'backspace'
678
- ? '⌫: delete last digit'
679
- : calculatorKeyLabel(calculation, key)
680
- }
681
- key={key}
682
- disabled={!store.ready}
683
- selected={selected}
684
- onClick={() => {
685
- store.set(current => ({
686
- calculation: pressCalculatorKey(current.calculation, key),
687
- }))
688
- }}
689
- style={[
690
- button.secondary,
691
- (operation || utility) && calculatorStyles.operation,
692
- key === '=' && button.primary,
693
- calculatorStyles.key,
694
- utility && calculatorStyles.utility,
695
- operation && calculatorStyles.operator,
696
- ]}
697
- >
698
- <Text {...stylex.props(roles.h2)}>
699
- {calculatorKeyLabel(calculation, key)}
700
- </Text>
701
- </Button>
702
- )
703
- })}
704
- </Flex>
705
- </FeatureFrame>
706
- )
707
- }
708
-
709
- export function RandomPicker(input: KowalskiInput<'randomPicker'>) {
710
- const props = kowalskiCatalog.randomPicker.props.parse(input)
711
- const schema = kowalskiCatalog.randomPicker.storeSchema
712
- const name = props.id ?? 'KowalskiRandomPicker'
713
- const [poolExpanded, setPoolExpanded] = useState(false)
714
- const store = useConfiguredFeature({
715
- name,
716
- revision: props.revision,
717
- schema,
718
- scope: props.scope,
719
- seed: schema.parse({}),
720
- })
721
- const domain: PickerDomain =
722
- props.mode === 'list'
723
- ? { mode: props.mode, options: props.options }
724
- : props.mode === 'range'
725
- ? { maximum: props.maximum, minimum: props.minimum, mode: props.mode }
726
- : { mode: props.mode }
727
- // A stable key for the effect below; the saved domain itself stays structured.
728
- const domainKey = JSON.stringify(domain)
729
- const numbers = format.formatters.getNumberFormat(format.locale)
730
- const detail =
731
- props.mode === 'range'
732
- ? `${numbers.format(props.minimum)} to ${numbers.format(props.maximum)}, inclusive`
733
- : undefined
734
- const { ready, set, value } = store
735
- const result =
736
- value.result && isEqual(value.result.domain, domain) ? value.result.value : null
737
- const resultDisplay = typeof result === 'number' ? numbers.format(result) : result
738
- const resultLabel =
739
- props.mode === 'dice'
740
- ? 'You rolled'
741
- : props.mode === 'coin'
742
- ? 'It landed on'
743
- : 'Your pick'
744
- const attemptedReset = useMemo<{ key?: string }>(() => ({}), [name, props.scope])
745
- useEffect(() => {
746
- const key = JSON.stringify([domainKey, props.revision, value.result])
747
- if (
748
- ready &&
749
- attemptedReset.key !== key &&
750
- props.revision >= value.configurationRevision &&
751
- value.result &&
752
- JSON.stringify(value.result.domain) !== domainKey
753
- ) {
754
- attemptedReset.key = key
755
- set({ result: null })
756
- }
757
- }, [
758
- attemptedReset,
759
- props.revision,
760
- domainKey,
761
- ready,
762
- set,
763
- value.configurationRevision,
764
- value.result,
765
- ])
766
- return (
767
- <FeatureFrame
768
- onRetry={store.retry}
769
- notice={store.error}
770
- component="randomPicker"
771
- title={props.title}
772
- detail={detail}
773
- >
774
- {props.mode === 'list' && (
775
- <Field label="Choices">
776
- <FeatureFacts>
777
- {props.options
778
- .slice(0, poolExpanded ? props.options.length : 4)
779
- .map((option, index) => (
780
- <List.Item
781
- key={index}
782
- label={`${format.formatNumber(index + 1)}. ${option}`}
783
- trailing={
784
- result === option ? (
785
- <Icon name="checkmark" {...stylex.props(appearance.accent)} />
786
- ) : undefined
787
- }
788
- />
789
- ))}
790
- </FeatureFacts>
791
- {props.options.length > 4 && (
792
- <Button
793
- onClick={() => setPoolExpanded(current => !current)}
794
- style={[button.quiet, randomPickerStyles.disclosure]}
795
- >
796
- <Text>
797
- {poolExpanded
798
- ? 'Show fewer'
799
- : `Show all ${format.formatNumber(props.options.length)} options`}
800
- </Text>
801
- </Button>
802
- )}
803
- </Field>
804
- )}
805
- <FeatureWell style={randomPickerStyles.stage}>
806
- {!store.ready && !store.error ? (
807
- <Text {...stylex.props(roles.muted)}>Loading last result…</Text>
808
- ) : props.mode === 'coin' || props.mode === 'dice' ? (
809
- <>
810
- <Text {...stylex.props(roles.h2)}>
811
- {result === null
812
- ? props.mode === 'coin'
813
- ? 'Heads or tails?'
814
- : 'Ready to roll'
815
- : resultLabel}
816
- </Text>
817
- <Flex
818
- {...stylex.props(
819
- appearance.surface,
820
- randomPickerStyles.face,
821
- props.mode === 'coin' && randomPickerStyles.coin
822
- )}
823
- >
824
- <Text
825
- {...stylex.props(
826
- roles.display,
827
- randomPickerStyles.faceValue,
828
- props.mode === 'coin' && result !== null && randomPickerStyles.coinValue
829
- )}
830
- >
831
- {resultDisplay ?? '?'}
832
- </Text>
833
- </Flex>
834
- </>
835
- ) : resultDisplay === null ? (
836
- <Text {...stylex.props(roles.muted)}>No pick yet</Text>
837
- ) : (
838
- <>
839
- <Text {...stylex.props(roles.muted)}>{resultLabel}</Text>
840
- <Text
841
- {...stylex.props(
842
- roles.display,
843
- randomPickerStyles.result(resultDisplay.length),
844
- props.mode === 'list' && randomPickerStyles.listResult
845
- )}
846
- >
847
- {resultDisplay}
848
- </Text>
849
- </>
850
- )}
851
- </FeatureWell>
852
- {store.saving && <Text {...stylex.props(roles.muted)}>Saving pick…</Text>}
853
- <FeatureAction
854
- component="randomPicker"
855
- icon="shuffle"
856
- size="lg"
857
- variant="primary"
858
- disabled={!store.ready}
859
- onClick={() => {
860
- store.set(() => ({
861
- result: { domain, value: pickResult(domain, Math.random()) },
862
- }))
863
- }}
864
- >
865
- {props.mode === 'coin'
866
- ? result === null
867
- ? 'Flip coin'
868
- : 'Flip again'
869
- : props.mode === 'dice'
870
- ? result === null
871
- ? 'Roll die'
872
- : 'Roll again'
873
- : result === null
874
- ? 'Pick one'
875
- : 'Pick again'}
876
- </FeatureAction>
877
- </FeatureFrame>
878
- )
879
- }
880
-
881
- export function ColorPalette(input: KowalskiInput<'colorPalette'>) {
882
- const props = kowalskiCatalog.colorPalette.props.parse(input)
883
- const schema = kowalskiCatalog.colorPalette.storeSchema
884
- const store = useConfiguredFeature({
885
- name: props.id ?? 'KowalskiColorPalette',
886
- revision: props.revision,
887
- schema,
888
- scope: props.scope,
889
- seed: schema.parse({ color: { value: props.color } }),
890
- })
891
- const color = store.value.color.value
892
- const [hexDraft, setHexDraft] = useState<string | null>(null)
893
- const [paletteBase, setPaletteBase] = useState(color.toLowerCase())
894
- const editing = hexDraft !== null
895
- const draftColor = `#${hexDraft ?? ''}`
896
- const validDraft =
897
- kowalskiCatalog.colorPalette.props.shape.color.safeParse(draftColor).success
898
- const editDisabled = !store.ready || store.saving || (editing && !validDraft)
899
- const shadesDisabled = !store.ready || editing
900
- const shades = uniqBy(
901
- [
902
- { label: 'Darker', mix: -0.6 },
903
- { label: 'Dark', mix: -0.3 },
904
- { label: 'Base', mix: 0 },
905
- { label: 'Light', mix: 0.3 },
906
- { label: 'Lighter', mix: 0.6 },
907
- ].map(shade => {
908
- const value = mixColor(paletteBase, shade.mix)
909
- return { color: value, label: value === paletteBase ? 'Base' : shade.label }
910
- }),
911
- 'color'
912
- )
913
- useEffect(() => {
914
- if (
915
- !editing &&
916
- !store.saving &&
917
- ![-0.6, -0.3, 0, 0.3, 0.6].some(
918
- mix => mixColor(paletteBase, mix) === color.toLowerCase()
919
- )
920
- ) {
921
- setPaletteBase(color.toLowerCase())
922
- }
923
- }, [color, editing, paletteBase, store.saving])
924
- const channels = [1, 3, 5].map(index =>
925
- Number.parseInt(color.slice(index, index + 2), 16)
926
- )
927
- return (
928
- <FeatureFrame
929
- onRetry={store.retry}
930
- notice={store.error}
931
- component="colorPalette"
932
- title={props.title}
933
- >
934
- <Flex {...stylex.props(colorStyles.specimen)}>
935
- <Flex {...stylex.props(appearance.surface, colorStyles.preview(color))} />
936
- <Flex {...stylex.props(colorStyles.identity)}>
937
- <Flex {...stylex.props(colorStyles.value)}>
938
- <Text {...stylex.props(roles.h2)}>{color.toUpperCase()}</Text>
939
- <Text {...stylex.props(roles.muted)}>
940
- {`RGB (${channels.map(channel => format.formatNumber(channel)).join(', ')})`}
941
- </Text>
942
- </Flex>
943
- </Flex>
944
- </Flex>
945
- <Field label="Shades & tints">
946
- <Flex {...stylex.props(colorStyles.swatches(shades.length))}>
947
- {shades.map(shade => {
948
- const selected = shade.color === color.toLowerCase()
949
- return (
950
- <Button
951
- key={shade.color}
952
- selected={selected}
953
- disabled={shadesDisabled}
954
- onClick={() => {
955
- if (!shadesDisabled) {
956
- store.set(() => ({ color: { value: shade.color } }))
957
- }
958
- }}
959
- style={[
960
- button.secondary,
961
- colorStyles.swatchButton,
962
- shadesDisabled && button.disabled,
963
- ]}
964
- >
965
- <Flex {...stylex.props(colorStyles.swatchContent)}>
966
- <Flex
967
- {...stylex.props(appearance.surface, colorStyles.swatch(shade.color))}
968
- >
969
- {selected && (
970
- <Flex {...stylex.props(colorStyles.selected)}>
971
- <Icon name="checkmark" />
972
- </Flex>
973
- )}
974
- </Flex>
975
- <Text {...stylex.props(roles.label, colorStyles.centered)}>
976
- {shade.label}
977
- </Text>
978
- <Text {...stylex.props(roles.muted, colorStyles.centered)}>
979
- {shade.color.toUpperCase()}
980
- </Text>
981
- </Flex>
982
- </Button>
983
- )
984
- })}
985
- </Flex>
986
- {(store.saving || (!store.ready && !store.error) || editing) && (
987
- <Text {...stylex.props(roles.muted)}>
988
- {store.saving
989
- ? 'Saving color…'
990
- : editing
991
- ? 'Finish editing to choose a shade.'
992
- : 'Loading saved color…'}
993
- </Text>
994
- )}
995
- </Field>
996
- {editing && (
997
- <Field
998
- error={!validDraft && 'Enter six digits using 0–9 and A–F.'}
999
- label="Hex color"
1000
- >
1001
- <Flex {...stylex.props(colorStyles.inputRow)}>
1002
- <Text {...stylex.props(roles.h2, colorStyles.prefix)}>#</Text>
1003
- {store.ready && !store.saving ? (
1004
- <Input
1005
- placeholder="Hex color"
1006
- value={hexDraft}
1007
- onChange={value => setHexDraft(value.replace(/^#/, '').toUpperCase())}
1008
- style={[colorStyles.input]}
1009
- />
1010
- ) : (
1011
- <Text {...stylex.props(roles.text, colorStyles.input)}>{hexDraft}</Text>
1012
- )}
1013
- </Flex>
1014
- </Field>
1015
- )}
1016
- <Flex {...stylex.props(layout.actions)}>
1017
- {editing && (
1018
- <FeatureAction
1019
- component="colorPalette"
1020
- disabled={store.saving}
1021
- onClick={() => setHexDraft(null)}
1022
- >
1023
- Cancel
1024
- </FeatureAction>
1025
- )}
1026
- <FeatureAction
1027
- component="colorPalette"
1028
- disabled={editDisabled}
1029
- variant={editing ? 'primary' : 'secondary'}
1030
- onClick={() => {
1031
- if (editDisabled) {
1032
- return
1033
- }
1034
- if (!editing) {
1035
- setHexDraft(color.slice(1).toUpperCase())
1036
- } else if (draftColor.toLowerCase() === color.toLowerCase()) {
1037
- setHexDraft(null)
1038
- } else {
1039
- store.set(
1040
- () => ({ color: { value: draftColor } }),
1041
- () => {
1042
- setPaletteBase(draftColor.toLowerCase())
1043
- setHexDraft(null)
1044
- }
1045
- )
1046
- }
1047
- }}
1048
- >
1049
- {store.saving && editing ? 'Saving…' : editing ? 'Done' : 'Edit hex'}
1050
- </FeatureAction>
1051
- </Flex>
1052
- </FeatureFrame>
1053
- )
1054
- }
1055
-
1056
- const randomPickerStyles = stylex.create({
1057
- coin: { borderRadius: radius['full'] },
1058
- coinValue: { fontSize: text['3xl'], lineHeight: text['3xlLeading'] },
1059
- disclosure: { alignSelf: 'flex-start', paddingLeft: 0, paddingRight: 0 },
1060
- face: {
1061
- alignItems: 'center',
1062
- borderRadius: radius['3xl'],
1063
- height: space[24],
1064
- justifyContent: 'center',
1065
- width: space[24],
1066
- },
1067
- faceValue: { textAlign: 'center' },
1068
- listResult: { fontSize: text['3xl'], lineHeight: text['3xlLeading'] },
1069
- result: (length: number) => ({
1070
- fontSize: length > 14 ? text['2xl'] : length > 8 ? text['4xl'] : font.displaySize,
1071
- lineHeight:
1072
- length > 14
1073
- ? text['2xlLeading']
1074
- : length > 8
1075
- ? text['4xlLeading']
1076
- : font.displayLeading,
1077
- textAlign: 'center',
1078
- }),
1079
- stage: { alignItems: 'center', gap: space[4], justifyContent: 'center' },
1080
- })
1081
-
1082
- // Long totals step down the display scale so they never overflow the well.
1083
- const billStyles = stylex.create({
1084
- amount: (length: number) => ({
1085
- fontSize: length > 16 ? text.lg : length > 9 ? text['4xl'] : font.displaySize,
1086
- lineHeight:
1087
- length > 16
1088
- ? text.lgLeading
1089
- : length > 9
1090
- ? text['4xlLeading']
1091
- : font.displayLeading,
1092
- }),
1093
- centered: { textAlign: 'center' },
1094
- row: {
1095
- alignItems: 'center',
1096
- flexDirection: 'row',
1097
- flexWrap: 'wrap',
1098
- gap: space[2],
1099
- justifyContent: 'space-between',
1100
- },
1101
- })
1102
-
1103
- const colorStyles = stylex.create({
1104
- centered: { textAlign: 'center' },
1105
- identity: {
1106
- alignItems: 'center',
1107
- flexDirection: 'row',
1108
- flexWrap: 'wrap',
1109
- gap: space[3],
1110
- justifyContent: 'space-between',
1111
- },
1112
- input: { flexGrow: 1, minWidth: 0 },
1113
- inputRow: { alignItems: 'center', flexDirection: 'row', gap: space[2] },
1114
- prefix: { flexShrink: 0 },
1115
- preview: (color: string) => ({
1116
- backgroundColor: color,
1117
- borderRadius: radius['xl'],
1118
- height: space[24],
1119
- }),
1120
- selected: {
1121
- alignItems: 'center',
1122
- backgroundColor: colors.accent,
1123
- borderRadius: radius['xl'],
1124
- color: colors.onAccent,
1125
- height: size[6],
1126
- justifyContent: 'center',
1127
- width: size[6],
1128
- },
1129
- specimen: { gap: space[4] },
1130
- swatch: (color: string) => ({
1131
- alignItems: 'center',
1132
- backgroundColor: color,
1133
- borderRadius: radius['lg'],
1134
- height: size[16],
1135
- justifyContent: 'center',
1136
- }),
1137
- swatchButton: {
1138
- alignItems: 'stretch',
1139
- minWidth: 0,
1140
- paddingBottom: space[2],
1141
- paddingLeft: space[1],
1142
- paddingRight: space[1],
1143
- paddingTop: space[1],
1144
- },
1145
- swatchContent: { alignItems: 'stretch', gap: space[1.5] },
1146
- swatches: (count: number) => ({
1147
- display: 'grid',
1148
- gap: space[2],
1149
- gridTemplateColumns: `repeat(${Math.min(count, 3)}, minmax(0, 1fr))`,
1150
- }),
1151
- value: { gap: space[1], minWidth: 0 },
1152
- })
1153
-
1154
- // Keys sit directly in the tray at p-4, so their control radius stays concentric with it.
1155
- const calculatorStyles = stylex.create({
1156
- equation: { textAlign: 'right' },
1157
- error: { textAlign: 'right' },
1158
- key: {
1159
- alignItems: 'center',
1160
- justifyContent: 'center',
1161
- minHeight: size[12],
1162
- minWidth: 0,
1163
- paddingLeft: 0,
1164
- paddingRight: 0,
1165
- textAlign: 'center',
1166
- },
1167
- keypad: {
1168
- display: 'grid',
1169
- gap: space[2],
1170
- gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
1171
- },
1172
- operation: { [buttonVars.background]: colors.tray },
1173
- operator: { [buttonVars.text]: colors.accentInk },
1174
- utility: { [buttonVars.text]: colors.muted },
1175
- value: (length: number) => ({
1176
- fontSize: length > 16 ? text.lg : length > 9 ? text['4xl'] : font.displaySize,
1177
- lineHeight:
1178
- length > 16
1179
- ? text.lgLeading
1180
- : length > 9
1181
- ? text['4xlLeading']
1182
- : font.displayLeading,
1183
- textAlign: 'right',
1184
- }),
1185
- })
1186
-
1187
- const unitStyles = stylex.create({
1188
- result: { minWidth: 0 },
1189
- })
1190
-
1191
- // Long amounts step down the display scale so they never overflow the well.
1192
- const expenseStyles = stylex.create({
1193
- amount: (length: number) => ({
1194
- fontSize: length > 16 ? text.lg : length > 9 ? text['2xl'] : text['4xl'],
1195
- lineHeight:
1196
- length > 16 ? text.lgLeading : length > 9 ? text['2xlLeading'] : text['4xlLeading'],
1197
- width: '100%',
1198
- }),
1199
- })
1200
-
1201
- function mixColor(hex: string, amount: number) {
1202
- const target = amount < 0 ? 0 : 255
1203
- return `#${[1, 3, 5]
1204
- .map(index =>
1205
- Math.round(
1206
- Number.parseInt(hex.slice(index, index + 2), 16) * (1 - Math.abs(amount)) +
1207
- target * Math.abs(amount)
1208
- )
1209
- .toString(16)
1210
- .padStart(2, '0')
1211
- )
1212
- .join('')}`
1213
- }