kasy-cli 1.21.9 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/lib/commands/add.js +93 -80
  2. package/lib/commands/configure.js +100 -32
  3. package/lib/commands/doctor.js +28 -2
  4. package/lib/commands/new.js +80 -37
  5. package/lib/commands/notifications.js +1 -1
  6. package/lib/commands/remove.js +43 -15
  7. package/lib/commands/run.js +2 -2
  8. package/lib/commands/update.js +2 -2
  9. package/lib/scaffold/CHANGELOG.json +14 -0
  10. package/lib/scaffold/backends/api/generator.js +14 -14
  11. package/lib/scaffold/backends/api/patch/README.md +83 -0
  12. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  13. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
  14. package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
  15. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
  16. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
  17. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
  18. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
  19. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
  20. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
  21. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
  22. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  23. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  24. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
  25. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  26. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
  27. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  28. package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
  29. package/lib/scaffold/backends/firebase/deploy.js +87 -13
  30. package/lib/scaffold/backends/firebase/generator.js +5 -5
  31. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  32. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  33. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  34. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  35. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  37. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  40. package/lib/scaffold/backends/supabase/generator.js +17 -17
  41. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  42. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  43. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  44. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  45. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  46. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  48. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  49. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  50. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  51. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  52. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  53. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  55. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  56. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  57. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  59. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  60. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  61. package/lib/scaffold/catalog.js +9 -11
  62. package/lib/scaffold/generate.js +45 -31
  63. package/lib/scaffold/shared/generator-utils.js +188 -81
  64. package/lib/scaffold/shared/sort-imports.js +191 -0
  65. package/lib/scaffold/shared/template-strings.js +3 -3
  66. package/lib/utils/checks.js +2 -2
  67. package/lib/utils/i18n/messages-en.js +50 -35
  68. package/lib/utils/i18n/messages-es.js +50 -35
  69. package/lib/utils/i18n/messages-pt.js +52 -37
  70. package/lib/utils/updates.js +15 -15
  71. package/package.json +1 -1
  72. package/templates/firebase/.env.example +2 -2
  73. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  74. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  75. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  78. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  79. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  80. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  81. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  82. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  83. package/templates/firebase/firestore.rules +24 -5
  84. package/templates/firebase/functions/package-lock.json +22 -1
  85. package/templates/firebase/functions/package.json +2 -1
  86. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  87. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  88. package/templates/firebase/functions/src/index.ts +8 -2
  89. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  90. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  91. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  92. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  93. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  94. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  95. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  96. package/templates/firebase/lib/components/components.dart +4 -1
  97. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  98. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  99. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  100. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  101. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  102. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
  103. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  104. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  105. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  106. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  108. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  109. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  110. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
  111. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  112. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  113. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
  114. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  115. package/templates/firebase/lib/core/config/features.dart +5 -4
  116. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  117. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  118. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  119. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  120. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  121. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  122. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  123. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  124. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  125. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  126. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  127. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  128. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  129. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  130. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  131. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  132. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  133. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  134. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  135. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  136. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  137. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  138. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  139. package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
  140. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  141. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  142. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  143. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  144. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  145. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  146. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  147. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  148. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  149. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  150. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  151. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  152. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  153. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  154. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  155. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  156. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  157. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  158. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  159. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
  160. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  161. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  162. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  163. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  164. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  165. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  166. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  167. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  168. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  169. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  170. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  171. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  172. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  173. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  174. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  175. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  176. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  177. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  178. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  182. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  183. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  184. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  192. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  193. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  194. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  198. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  199. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  200. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  201. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  202. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  203. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  204. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  205. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  206. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  208. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  209. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  210. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  211. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  212. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  213. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  214. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  215. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  216. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  226. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  227. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  228. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  229. package/templates/firebase/lib/main.dart +6 -3
  230. package/templates/firebase/lib/router.dart +15 -23
  231. package/templates/firebase/pubspec.yaml +4 -5
  232. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  233. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  234. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  235. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  236. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  238. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  239. package/templates/firebase/test/test_utils.dart +6 -6
  240. package/templates/firebase/web/index.html +5 -2
  241. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  242. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  243. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  244. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  245. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  246. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  247. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  249. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  250. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  251. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  252. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  253. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  254. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  255. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  256. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  257. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  258. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -1,3 +1,5 @@
1
+ import 'dart:ui' show ImageFilter;
2
+
1
3
  import 'package:flutter/material.dart';
2
4
  import 'package:kasy_kit/components/kasy_app_bar.dart';
3
5
  import 'package:kasy_kit/core/theme/theme.dart';
@@ -33,6 +35,10 @@ class DesignSystemPage extends StatelessWidget {
33
35
  SizedBox(height: KasySpacing.md),
34
36
  _TypographySection(),
35
37
  SizedBox(height: KasySpacing.xl),
38
+ _SectionHeader('Effect Styles'),
39
+ SizedBox(height: KasySpacing.md),
40
+ _EffectsSection(),
41
+ SizedBox(height: KasySpacing.xl),
36
42
  _SectionHeader('Spacing'),
37
43
  SizedBox(height: KasySpacing.md),
38
44
  _SpacingSection(),
@@ -90,127 +96,513 @@ class _TokenDivider extends StatelessWidget {
90
96
  // Colors
91
97
  // ---------------------------------------------------------------------------
92
98
 
99
+ /// Accessor that reads one token from a [KasyColors] theme (light or dark).
100
+ typedef _Pick = Color Function(KasyColors c);
101
+
102
+ class _Variant {
103
+ final String name;
104
+ final _Pick pick;
105
+
106
+ /// Force a hairline border on the swatch (for near-surface / transparent tones).
107
+ final bool border;
108
+ const _Variant(this.name, this.pick, {this.border = false});
109
+ }
110
+
93
111
  class _ColorsSection extends StatelessWidget {
94
112
  const _ColorsSection();
95
113
 
96
114
  @override
97
115
  Widget build(BuildContext context) {
98
- final c = context.colors;
116
+ return const Column(
117
+ crossAxisAlignment: CrossAxisAlignment.stretch,
118
+ children: [
119
+ _ColorsIntro(),
120
+ SizedBox(height: KasySpacing.lg),
99
121
 
100
- final List<_ColorToken> brandTokens = [
101
- _ColorToken('primary', c.primary),
102
- _ColorToken('onPrimary', c.onPrimary, border: true),
103
- ];
104
- final List<_ColorToken> semanticTokens = [
105
- _ColorToken('success', c.success),
106
- _ColorToken('onSuccess', c.onSuccess, border: true),
107
- _ColorToken('warning', c.warning),
108
- _ColorToken('onWarning', c.onWarning, border: true),
109
- _ColorToken('error', c.error),
110
- _ColorToken('onError', c.onError, border: true),
111
- ];
112
- final List<_ColorToken> backgroundTokens = [
113
- _ColorToken('background', c.background, border: true),
114
- _ColorToken('surface', c.surface, border: true),
115
- _ColorToken('surfacePrimarySoft', c.surfacePrimarySoft),
116
- _ColorToken('surfaceNeutralSoft', c.surfaceNeutralSoft),
117
- _ColorToken('surfaceErrorSoft', c.surfaceErrorSoft),
118
- ];
119
- final List<_ColorToken> contentTokens = [
120
- _ColorToken('onBackground', c.onBackground),
121
- _ColorToken('onSurface', c.onSurface),
122
- _ColorToken('muted', c.muted),
123
- _ColorToken('outline', c.outline, border: true),
124
- _ColorToken('outlineButton', c.outlineButton, border: true),
125
- ];
126
- final List<_ColorToken> greyTokens = [
127
- _ColorToken('grey1', c.grey1),
128
- _ColorToken('grey2', c.grey2),
129
- _ColorToken('grey3', c.grey3),
130
- ];
131
- final List<_ColorToken> fieldTokens = [
132
- _ColorToken('onSurface (label)', c.onSurface),
133
- _ColorToken('onSurface 54% (helper)', c.onSurface.withValues(alpha: 0.54)),
134
- _ColorToken('onSurface 45% (hint)', c.onSurface.withValues(alpha: 0.45)),
135
- _ColorToken('onSurface 62% (icon)', c.onSurface.withValues(alpha: 0.62)),
136
- ];
137
- final List<_ColorToken> avatarTokens = [
138
- _ColorToken('avatarFallbackFill', c.avatarFallbackFill),
139
- ];
140
- final List<_ColorToken> premiumTokens = [
141
- _ColorToken('premiumOverlayMid', c.premiumOverlayMid),
142
- _ColorToken('premiumOverlayDark', c.premiumOverlayDark),
143
- _ColorToken('premiumBannerText', c.premiumBannerText),
144
- ];
122
+ // --- Semantic roles (solid + soft preview) ---
123
+ _SemanticCategory(
124
+ name: 'Accent',
125
+ description:
126
+ 'The primary brand identity. Used sparingly for key actions, '
127
+ 'highlights and moments of emphasis.',
128
+ base: _pickAccent,
129
+ onColor: _pickAccentForeground,
130
+ soft: _pickAccentSoft,
131
+ softForeground: _pickAccentSoftForeground,
132
+ variants: [
133
+ _Variant('accent', _pickAccent),
134
+ _Variant('accent-hover', _pickAccentHover),
135
+ _Variant('accent-foreground', _pickAccentForeground, border: true),
136
+ _Variant('accent-soft', _pickAccentSoft, border: true),
137
+ _Variant('accent-soft-hover', _pickAccentSoftHover, border: true),
138
+ _Variant('accent-soft-foreground', _pickAccentSoftForeground),
139
+ ],
140
+ ),
141
+ SizedBox(height: KasySpacing.xl),
142
+ _SemanticCategory(
143
+ name: 'Default',
144
+ description:
145
+ 'The neutral backbone of the system. Used for most '
146
+ 'non-emphasized UI elements.',
147
+ base: _pickNeutral,
148
+ onColor: _pickNeutralForeground,
149
+ variants: [
150
+ _Variant('default', _pickNeutral, border: true),
151
+ _Variant('default-hover', _pickNeutralHover, border: true),
152
+ _Variant('default-foreground', _pickNeutralForeground),
153
+ ],
154
+ ),
155
+ SizedBox(height: KasySpacing.xl),
156
+ _SemanticCategory(
157
+ name: 'Success',
158
+ description:
159
+ 'Communicates positive outcomes, confirmations and completed '
160
+ 'states.',
161
+ base: _pickSuccess,
162
+ onColor: _pickSuccessForeground,
163
+ soft: _pickSuccessSoft,
164
+ softForeground: _pickSuccessSoftForeground,
165
+ variants: [
166
+ _Variant('success', _pickSuccess),
167
+ _Variant('success-hover', _pickSuccessHover),
168
+ _Variant('success-foreground', _pickSuccessForeground, border: true),
169
+ _Variant('success-soft', _pickSuccessSoft, border: true),
170
+ _Variant('success-soft-hover', _pickSuccessSoftHover, border: true),
171
+ _Variant('success-soft-foreground', _pickSuccessSoftForeground),
172
+ ],
173
+ ),
174
+ SizedBox(height: KasySpacing.xl),
175
+ _SemanticCategory(
176
+ name: 'Warning',
177
+ description:
178
+ 'Indicates caution or risk that requires attention but is not '
179
+ 'destructive.',
180
+ base: _pickWarning,
181
+ onColor: _pickWarningForeground,
182
+ soft: _pickWarningSoft,
183
+ softForeground: _pickWarningSoftForeground,
184
+ variants: [
185
+ _Variant('warning', _pickWarning),
186
+ _Variant('warning-hover', _pickWarningHover),
187
+ _Variant('warning-foreground', _pickWarningForeground, border: true),
188
+ _Variant('warning-soft', _pickWarningSoft, border: true),
189
+ _Variant('warning-soft-hover', _pickWarningSoftHover, border: true),
190
+ _Variant('warning-soft-foreground', _pickWarningSoftForeground),
191
+ ],
192
+ ),
193
+ SizedBox(height: KasySpacing.xl),
194
+ _SemanticCategory(
195
+ name: 'Danger',
196
+ description:
197
+ 'Represents destructive, irreversible or critical actions and '
198
+ 'states.',
199
+ base: _pickDanger,
200
+ onColor: _pickDangerForeground,
201
+ soft: _pickDangerSoft,
202
+ softForeground: _pickDangerSoftForeground,
203
+ variants: [
204
+ _Variant('danger', _pickDanger),
205
+ _Variant('danger-hover', _pickDangerHover),
206
+ _Variant('danger-foreground', _pickDangerForeground, border: true),
207
+ _Variant('danger-soft', _pickDangerSoft, border: true),
208
+ _Variant('danger-soft-hover', _pickDangerSoftHover, border: true),
209
+ _Variant('danger-soft-foreground', _pickDangerSoftForeground),
210
+ ],
211
+ ),
212
+ SizedBox(height: KasySpacing.xl),
145
213
 
146
- return _TokenCard(
147
- children: [
148
- const _ColorGroupLabel('Brand'),
149
- ..._buildColorRows(context, brandTokens),
150
- const _TokenDivider(),
151
- const _ColorGroupLabel('Semantic'),
152
- ..._buildColorRows(context, semanticTokens),
153
- const _TokenDivider(),
154
- const _ColorGroupLabel('Background & Surface'),
155
- ..._buildColorRows(context, backgroundTokens),
156
- const _TokenDivider(),
157
- const _ColorGroupLabel('Content'),
158
- ..._buildColorRows(context, contentTokens),
159
- const _TokenDivider(),
160
- const _ColorGroupLabel('Grey Scale'),
161
- ..._buildColorRows(context, greyTokens),
162
- const _TokenDivider(),
163
- const _ColorGroupLabel('Field'),
164
- ..._buildColorRows(context, fieldTokens),
165
- const _TokenDivider(),
166
- const _ColorGroupLabel('Avatar'),
167
- ..._buildColorRows(context, avatarTokens),
168
- const _TokenDivider(),
169
- const _ColorGroupLabel('Premium'),
170
- ..._buildColorRows(context, premiumTokens),
214
+ // --- Neutral swatch roles ---
215
+ _SwatchCategory(
216
+ name: 'Foreground',
217
+ description:
218
+ 'Primary content such as text and icons. Adapts automatically to '
219
+ 'background and surface contexts.',
220
+ variants: [
221
+ _Variant('foreground', _pickForeground),
222
+ _Variant('muted', _pickForegroundMuted),
223
+ _Variant('segment', _pickForegroundSegment),
224
+ _Variant('overlay', _pickForegroundOverlay),
225
+ _Variant('link', _pickForegroundLink),
226
+ _Variant('inverse', _pickForegroundInverse, border: true),
227
+ ],
228
+ ),
229
+ SizedBox(height: KasySpacing.xl),
230
+ _SwatchCategory(
231
+ name: 'Background',
232
+ description:
233
+ 'The base canvas of the interface. Establishes overall contrast '
234
+ 'and mood while staying visually quiet.',
235
+ variants: [
236
+ _Variant('background', _pickBackground, border: true),
237
+ _Variant('background-secondary', _pickBackgroundSecondary, border: true),
238
+ _Variant('background-tertiary', _pickBackgroundTertiary, border: true),
239
+ _Variant('background-inverse', _pickBackgroundInverse),
240
+ ],
241
+ ),
242
+ SizedBox(height: KasySpacing.xl),
243
+ _SwatchCategory(
244
+ name: 'Surface',
245
+ description:
246
+ 'Containers such as cards, panels, modals and dropdowns. Create '
247
+ 'separation through elevation and layering.',
248
+ variants: [
249
+ _Variant('surface', _pickSurface, border: true),
250
+ _Variant('surface-secondary', _pickSurfaceSecondary, border: true),
251
+ _Variant('surface-tertiary', _pickSurfaceTertiary, border: true),
252
+ _Variant('surface-transparent', _pickSurfaceTransparent, border: true),
253
+ ],
254
+ ),
255
+ SizedBox(height: KasySpacing.xl),
256
+ _SwatchCategory(
257
+ name: 'Form field',
258
+ description:
259
+ 'Specialized tokens for inputs and controls, accounting for '
260
+ 'default, hover and focus states.',
261
+ variants: [
262
+ _Variant('field-background', _pickFieldBackground, border: true),
263
+ _Variant('field-background-hover', _pickFieldBackgroundHover, border: true),
264
+ _Variant('field-background-focus', _pickFieldBackgroundFocus, border: true),
265
+ _Variant('field-placeholder', _pickFieldPlaceholder),
266
+ _Variant('field-foreground', _pickFieldForeground),
267
+ _Variant('field-border', _pickFieldBorder, border: true),
268
+ _Variant('field-border-hover', _pickFieldBorderHover, border: true),
269
+ ],
270
+ ),
271
+ SizedBox(height: KasySpacing.xl),
272
+ _SwatchCategory(
273
+ name: 'Separator',
274
+ description:
275
+ 'Dividers, outlines and subtle boundaries. Low contrast and '
276
+ 'unobtrusive by design.',
277
+ variants: [
278
+ _Variant('separator', _pickSeparator, border: true),
279
+ _Variant('separator-secondary', _pickSeparatorSecondary, border: true),
280
+ _Variant('separator-tertiary', _pickSeparatorTertiary, border: true),
281
+ ],
282
+ ),
283
+ SizedBox(height: KasySpacing.xl),
284
+ _SwatchCategory(
285
+ name: 'Other',
286
+ description:
287
+ 'Shared utility tokens used for borders, overlays, segmented '
288
+ 'controls and backdrops.',
289
+ variants: [
290
+ _Variant('border', _pickBorder, border: true),
291
+ _Variant('overlay', _pickOverlay, border: true),
292
+ _Variant('segment', _pickSegment, border: true),
293
+ _Variant('backdrop', _pickBackdrop, border: true),
294
+ ],
295
+ ),
171
296
  ],
172
297
  );
173
298
  }
299
+ }
300
+
301
+ // --- Token accessors (kept top-level so the section tree stays const) ---
302
+ Color _pickAccent(KasyColors c) => c.accent;
303
+ Color _pickAccentHover(KasyColors c) => c.accentHover;
304
+ Color _pickAccentForeground(KasyColors c) => c.accentForeground;
305
+ Color _pickAccentSoft(KasyColors c) => c.accentSoft;
306
+ Color _pickAccentSoftHover(KasyColors c) => c.accentSoftHover;
307
+ Color _pickAccentSoftForeground(KasyColors c) => c.accentSoftForeground;
308
+ Color _pickNeutral(KasyColors c) => c.neutral;
309
+ Color _pickNeutralHover(KasyColors c) => c.neutralHover;
310
+ Color _pickNeutralForeground(KasyColors c) => c.neutralForeground;
311
+ Color _pickSuccess(KasyColors c) => c.success;
312
+ Color _pickSuccessHover(KasyColors c) => c.successHover;
313
+ Color _pickSuccessForeground(KasyColors c) => c.successForeground;
314
+ Color _pickSuccessSoft(KasyColors c) => c.successSoft;
315
+ Color _pickSuccessSoftHover(KasyColors c) => c.successSoftHover;
316
+ Color _pickSuccessSoftForeground(KasyColors c) => c.successSoftForeground;
317
+ Color _pickWarning(KasyColors c) => c.warning;
318
+ Color _pickWarningHover(KasyColors c) => c.warningHover;
319
+ Color _pickWarningForeground(KasyColors c) => c.warningForeground;
320
+ Color _pickWarningSoft(KasyColors c) => c.warningSoft;
321
+ Color _pickWarningSoftHover(KasyColors c) => c.warningSoftHover;
322
+ Color _pickWarningSoftForeground(KasyColors c) => c.warningSoftForeground;
323
+ Color _pickDanger(KasyColors c) => c.danger;
324
+ Color _pickDangerHover(KasyColors c) => c.dangerHover;
325
+ Color _pickDangerForeground(KasyColors c) => c.dangerForeground;
326
+ Color _pickDangerSoft(KasyColors c) => c.dangerSoft;
327
+ Color _pickDangerSoftHover(KasyColors c) => c.dangerSoftHover;
328
+ Color _pickDangerSoftForeground(KasyColors c) => c.dangerSoftForeground;
329
+ Color _pickForeground(KasyColors c) => c.foreground;
330
+ Color _pickForegroundMuted(KasyColors c) => c.foregroundMuted;
331
+ Color _pickForegroundSegment(KasyColors c) => c.foregroundSegment;
332
+ Color _pickForegroundOverlay(KasyColors c) => c.foregroundOverlay;
333
+ Color _pickForegroundLink(KasyColors c) => c.foregroundLink;
334
+ Color _pickForegroundInverse(KasyColors c) => c.foregroundInverse;
335
+ Color _pickBackground(KasyColors c) => c.background;
336
+ Color _pickBackgroundSecondary(KasyColors c) => c.backgroundSecondary;
337
+ Color _pickBackgroundTertiary(KasyColors c) => c.backgroundTertiary;
338
+ Color _pickBackgroundInverse(KasyColors c) => c.backgroundInverse;
339
+ Color _pickSurface(KasyColors c) => c.surface;
340
+ Color _pickSurfaceSecondary(KasyColors c) => c.surfaceSecondary;
341
+ Color _pickSurfaceTertiary(KasyColors c) => c.surfaceTertiary;
342
+ Color _pickSurfaceTransparent(KasyColors c) => c.surfaceTransparent;
343
+ Color _pickFieldBackground(KasyColors c) => c.fieldBackground;
344
+ Color _pickFieldBackgroundHover(KasyColors c) => c.fieldBackgroundHover;
345
+ Color _pickFieldBackgroundFocus(KasyColors c) => c.fieldBackgroundFocus;
346
+ Color _pickFieldPlaceholder(KasyColors c) => c.fieldPlaceholder;
347
+ Color _pickFieldForeground(KasyColors c) => c.fieldForeground;
348
+ Color _pickFieldBorder(KasyColors c) => c.fieldBorder;
349
+ Color _pickFieldBorderHover(KasyColors c) => c.fieldBorderHover;
350
+ Color _pickSeparator(KasyColors c) => c.separator;
351
+ Color _pickSeparatorSecondary(KasyColors c) => c.separatorSecondary;
352
+ Color _pickSeparatorTertiary(KasyColors c) => c.separatorTertiary;
353
+ Color _pickBorder(KasyColors c) => c.border;
354
+ Color _pickOverlay(KasyColors c) => c.overlay;
355
+ Color _pickSegment(KasyColors c) => c.segment;
356
+ Color _pickBackdrop(KasyColors c) => c.backdrop;
357
+
358
+ String _hexOf(Color color) {
359
+ final int v = color.toARGB32();
360
+ final int a = (v >> 24) & 0xFF;
361
+ final int r = (v >> 16) & 0xFF;
362
+ final int g = (v >> 8) & 0xFF;
363
+ final int b = v & 0xFF;
364
+ String two(int x) => x.toRadixString(16).padLeft(2, '0').toUpperCase();
365
+ final String rgb = '${two(r)}${two(g)}${two(b)}';
366
+ return a == 0xFF ? '#$rgb' : '#$rgb${two(a)}';
367
+ }
368
+
369
+ // ---------------------------------------------------------------------------
370
+ // Colors — intro
371
+ // ---------------------------------------------------------------------------
372
+
373
+ class _ColorsIntro extends StatelessWidget {
374
+ const _ColorsIntro();
174
375
 
175
- List<Widget> _buildColorRows(BuildContext context, List<_ColorToken> tokens) {
176
- return tokens.map((t) => _ColorRow(token: t)).toList();
376
+ @override
377
+ Widget build(BuildContext context) {
378
+ return Text(
379
+ 'Built around semantic intent, not raw palettes. A small set of color '
380
+ 'roles covers most interface needs, each resolving to a light and a dark '
381
+ 'theme shown side by side.',
382
+ style: context.textTheme.bodyMedium?.copyWith(
383
+ color: context.colors.muted,
384
+ height: 1.5,
385
+ ),
386
+ );
177
387
  }
178
388
  }
179
389
 
180
- class _ColorToken {
390
+ // ---------------------------------------------------------------------------
391
+ // Category heading (name + description)
392
+ // ---------------------------------------------------------------------------
393
+
394
+ class _CategoryHeading extends StatelessWidget {
181
395
  final String name;
182
- final Color color;
183
- final bool border;
184
- const _ColorToken(this.name, this.color, {this.border = false});
396
+ final String description;
397
+ const _CategoryHeading({required this.name, required this.description});
398
+
399
+ @override
400
+ Widget build(BuildContext context) {
401
+ return Column(
402
+ crossAxisAlignment: CrossAxisAlignment.start,
403
+ children: [
404
+ Text(
405
+ name,
406
+ style: context.textTheme.titleMedium?.copyWith(
407
+ color: context.colors.onSurface,
408
+ fontWeight: FontWeight.w700,
409
+ ),
410
+ ),
411
+ const SizedBox(height: KasySpacing.xs),
412
+ Text(
413
+ description,
414
+ style: context.textTheme.bodySmall?.copyWith(
415
+ color: context.colors.muted,
416
+ height: 1.45,
417
+ ),
418
+ ),
419
+ const SizedBox(height: KasySpacing.smd),
420
+ ],
421
+ );
422
+ }
185
423
  }
186
424
 
187
- class _ColorGroupLabel extends StatelessWidget {
425
+ // ---------------------------------------------------------------------------
426
+ // Semantic category: heading + Light/Dark solid+soft preview + variant list
427
+ // ---------------------------------------------------------------------------
428
+
429
+ class _SemanticCategory extends StatelessWidget {
430
+ final String name;
431
+ final String description;
432
+ final _Pick base;
433
+ final _Pick onColor;
434
+ final _Pick? soft;
435
+ final _Pick? softForeground;
436
+ final List<_Variant> variants;
437
+
438
+ const _SemanticCategory({
439
+ required this.name,
440
+ required this.description,
441
+ required this.base,
442
+ required this.onColor,
443
+ required this.variants,
444
+ this.soft,
445
+ this.softForeground,
446
+ });
447
+
448
+ @override
449
+ Widget build(BuildContext context) {
450
+ return Column(
451
+ crossAxisAlignment: CrossAxisAlignment.stretch,
452
+ children: [
453
+ _CategoryHeading(name: name, description: description),
454
+ Row(
455
+ children: [
456
+ Expanded(child: _SemanticPreview(theme: KasyColors.light(), label: 'Light', base: base, onColor: onColor, soft: soft, softForeground: softForeground)),
457
+ const SizedBox(width: KasySpacing.smd),
458
+ Expanded(child: _SemanticPreview(theme: KasyColors.dark(), label: 'Dark', base: base, onColor: onColor, soft: soft, softForeground: softForeground)),
459
+ ],
460
+ ),
461
+ const SizedBox(height: KasySpacing.smd),
462
+ _VariantList(variants: variants),
463
+ ],
464
+ );
465
+ }
466
+ }
467
+
468
+ class _SemanticPreview extends StatelessWidget {
469
+ final KasyColors theme;
188
470
  final String label;
189
- const _ColorGroupLabel(this.label);
471
+ final _Pick base;
472
+ final _Pick onColor;
473
+ final _Pick? soft;
474
+ final _Pick? softForeground;
475
+
476
+ const _SemanticPreview({
477
+ required this.theme,
478
+ required this.label,
479
+ required this.base,
480
+ required this.onColor,
481
+ required this.soft,
482
+ required this.softForeground,
483
+ });
190
484
 
191
485
  @override
192
486
  Widget build(BuildContext context) {
193
- return Padding(
194
- padding: const EdgeInsets.fromLTRB(KasySpacing.md, KasySpacing.smd, KasySpacing.md, KasySpacing.xs),
487
+ return Container(
488
+ padding: const EdgeInsets.all(KasySpacing.smd),
489
+ decoration: BoxDecoration(
490
+ color: theme.surface,
491
+ borderRadius: BorderRadius.circular(KasyRadius.md),
492
+ border: Border.all(color: theme.border),
493
+ ),
494
+ child: Column(
495
+ crossAxisAlignment: CrossAxisAlignment.stretch,
496
+ children: [
497
+ Text(
498
+ label,
499
+ style: context.textTheme.labelSmall?.copyWith(
500
+ color: theme.foregroundMuted,
501
+ letterSpacing: 0.6,
502
+ ),
503
+ ),
504
+ const SizedBox(height: KasySpacing.sm),
505
+ _PreviewPill(bg: base(theme), fg: onColor(theme), text: 'Solid'),
506
+ if (soft != null && softForeground != null) ...[
507
+ const SizedBox(height: KasySpacing.sm),
508
+ _PreviewPill(bg: soft!(theme), fg: softForeground!(theme), text: 'Soft'),
509
+ ],
510
+ ],
511
+ ),
512
+ );
513
+ }
514
+ }
515
+
516
+ class _PreviewPill extends StatelessWidget {
517
+ final Color bg;
518
+ final Color fg;
519
+ final String text;
520
+ const _PreviewPill({required this.bg, required this.fg, required this.text});
521
+
522
+ @override
523
+ Widget build(BuildContext context) {
524
+ return Container(
525
+ height: 36,
526
+ alignment: Alignment.center,
527
+ decoration: BoxDecoration(
528
+ color: bg,
529
+ borderRadius: BorderRadius.circular(KasyRadius.sm),
530
+ ),
195
531
  child: Text(
196
- label,
197
- style: context.textTheme.labelSmall?.copyWith(
198
- color: context.colors.muted,
199
- letterSpacing: 0.6,
532
+ text,
533
+ style: context.textTheme.labelMedium?.copyWith(
534
+ color: fg,
535
+ fontWeight: FontWeight.w600,
200
536
  ),
201
537
  ),
202
538
  );
203
539
  }
204
540
  }
205
541
 
206
- class _ColorRow extends StatelessWidget {
207
- final _ColorToken token;
208
- const _ColorRow({required this.token});
542
+ // ---------------------------------------------------------------------------
543
+ // Swatch category: heading + variant list (Light/Dark chips)
544
+ // ---------------------------------------------------------------------------
545
+
546
+ class _SwatchCategory extends StatelessWidget {
547
+ final String name;
548
+ final String description;
549
+ final List<_Variant> variants;
550
+
551
+ const _SwatchCategory({
552
+ required this.name,
553
+ required this.description,
554
+ required this.variants,
555
+ });
209
556
 
210
557
  @override
211
558
  Widget build(BuildContext context) {
212
- final String hex =
213
- '#${token.color.toARGB32().toRadixString(16).substring(2).toUpperCase()}';
559
+ return Column(
560
+ crossAxisAlignment: CrossAxisAlignment.stretch,
561
+ children: [
562
+ _CategoryHeading(name: name, description: description),
563
+ _VariantList(variants: variants),
564
+ ],
565
+ );
566
+ }
567
+ }
568
+
569
+ // ---------------------------------------------------------------------------
570
+ // Variant list (one row per token: Light chip, Dark chip, name, hex pair)
571
+ // ---------------------------------------------------------------------------
572
+
573
+ class _VariantList extends StatelessWidget {
574
+ final List<_Variant> variants;
575
+ const _VariantList({required this.variants});
576
+
577
+ @override
578
+ Widget build(BuildContext context) {
579
+ final KasyColors light = KasyColors.light();
580
+ final KasyColors dark = KasyColors.dark();
581
+ return _TokenCard(
582
+ children: [
583
+ for (int i = 0; i < variants.length; i++) ...[
584
+ _VariantRow(variant: variants[i], light: light, dark: dark),
585
+ if (i < variants.length - 1) const _TokenDivider(),
586
+ ],
587
+ ],
588
+ );
589
+ }
590
+ }
591
+
592
+ class _VariantRow extends StatelessWidget {
593
+ final _Variant variant;
594
+ final KasyColors light;
595
+ final KasyColors dark;
596
+ const _VariantRow({
597
+ required this.variant,
598
+ required this.light,
599
+ required this.dark,
600
+ });
601
+
602
+ @override
603
+ Widget build(BuildContext context) {
604
+ final Color lightColor = variant.pick(light);
605
+ final Color darkColor = variant.pick(dark);
214
606
  return Padding(
215
607
  padding: const EdgeInsets.symmetric(
216
608
  horizontal: KasySpacing.md,
@@ -218,33 +610,29 @@ class _ColorRow extends StatelessWidget {
218
610
  ),
219
611
  child: Row(
220
612
  children: [
221
- Container(
222
- width: 28,
223
- height: 28,
224
- decoration: BoxDecoration(
225
- color: token.color,
226
- borderRadius: BorderRadius.circular(KasyRadius.xs),
227
- border: token.border
228
- ? Border.all(
229
- color: context.colors.outline.withValues(alpha: 0.5),
230
- )
231
- : null,
232
- ),
233
- ),
613
+ _ThemeChip(surface: light.surface, token: lightColor, border: variant.border),
614
+ const SizedBox(width: KasySpacing.xs),
615
+ _ThemeChip(surface: dark.surface, token: darkColor, border: variant.border),
234
616
  const SizedBox(width: KasySpacing.smd),
235
617
  Expanded(
236
- child: Text(
237
- token.name,
238
- style: context.textTheme.bodyMedium?.copyWith(
239
- color: context.colors.onSurface,
240
- ),
241
- ),
242
- ),
243
- Text(
244
- hex,
245
- style: context.textTheme.bodySmall?.copyWith(
246
- color: context.colors.muted,
247
- fontFeatures: const [FontFeature.tabularFigures()],
618
+ child: Column(
619
+ crossAxisAlignment: CrossAxisAlignment.start,
620
+ children: [
621
+ Text(
622
+ variant.name,
623
+ style: context.textTheme.bodyMedium?.copyWith(
624
+ color: context.colors.onSurface,
625
+ ),
626
+ ),
627
+ const SizedBox(height: 2),
628
+ Text(
629
+ '${_hexOf(lightColor)} · ${_hexOf(darkColor)}',
630
+ style: context.textTheme.bodySmall?.copyWith(
631
+ color: context.colors.muted,
632
+ fontFeatures: const [FontFeature.tabularFigures()],
633
+ ),
634
+ ),
635
+ ],
248
636
  ),
249
637
  ),
250
638
  ],
@@ -253,6 +641,40 @@ class _ColorRow extends StatelessWidget {
253
641
  }
254
642
  }
255
643
 
644
+ class _ThemeChip extends StatelessWidget {
645
+ final Color surface;
646
+ final Color token;
647
+ final bool border;
648
+ const _ThemeChip({
649
+ required this.surface,
650
+ required this.token,
651
+ this.border = false,
652
+ });
653
+
654
+ @override
655
+ Widget build(BuildContext context) {
656
+ return Container(
657
+ width: 28,
658
+ height: 28,
659
+ padding: const EdgeInsets.all(3),
660
+ decoration: BoxDecoration(
661
+ color: surface,
662
+ borderRadius: BorderRadius.circular(KasyRadius.xs),
663
+ border: Border.all(color: context.colors.outline.withValues(alpha: 0.5)),
664
+ ),
665
+ child: DecoratedBox(
666
+ decoration: BoxDecoration(
667
+ color: token,
668
+ borderRadius: BorderRadius.circular(KasyRadius.xs - 1),
669
+ border: border
670
+ ? Border.all(color: context.colors.outline.withValues(alpha: 0.5))
671
+ : null,
672
+ ),
673
+ ),
674
+ );
675
+ }
676
+ }
677
+
256
678
  // ---------------------------------------------------------------------------
257
679
  // Typography
258
680
  // ---------------------------------------------------------------------------
@@ -262,76 +684,82 @@ class _TypographySection extends StatelessWidget {
262
684
 
263
685
  @override
264
686
  Widget build(BuildContext context) {
265
- final tt = context.textTheme;
266
- final ktt = KasyTextTheme.build();
267
-
268
- final List<_TypeToken> tokens = [
269
- _TypeToken('displayLarge', tt.displayLarge, ktt.displayLarge),
270
- _TypeToken('displayMedium', tt.displayMedium, ktt.displayMedium),
271
- _TypeToken('displaySmall', tt.displaySmall, ktt.displaySmall),
272
- _TypeToken('headlineLarge', tt.headlineLarge, ktt.headlineLarge),
273
- _TypeToken('headlineMedium', tt.headlineMedium, ktt.headlineMedium),
274
- _TypeToken('headlineSmall', tt.headlineSmall, ktt.headlineSmall),
275
- _TypeToken('titleLarge', tt.titleLarge, ktt.titleLarge),
276
- _TypeToken('titleMedium', tt.titleMedium, ktt.titleMedium),
277
- _TypeToken('titleSmall', tt.titleSmall, ktt.titleSmall),
278
- _TypeToken('bodyLarge', tt.bodyLarge, ktt.bodyLarge),
279
- _TypeToken('bodyMedium', tt.bodyMedium, ktt.bodyMedium),
280
- _TypeToken('bodySmall', tt.bodySmall, ktt.bodySmall),
281
- _TypeToken('labelLarge', tt.labelLarge, ktt.labelLarge),
282
- _TypeToken('labelMedium', tt.labelMedium, ktt.labelMedium),
283
- _TypeToken('labelSmall', tt.labelSmall, ktt.labelSmall),
687
+ final Color fg = context.colors.onSurface;
688
+ final List<_TypeRole> roles = [
689
+ _TypeRole('Heading 1', KasyTextTheme.heading1, 'ExtraBold · 36/40'),
690
+ _TypeRole('Heading 2', KasyTextTheme.heading2, 'Bold · 24/32'),
691
+ _TypeRole('Heading 3', KasyTextTheme.heading3, 'SemiBold · 20/28'),
692
+ _TypeRole('Heading 4', KasyTextTheme.heading4, 'SemiBold · 16/24'),
693
+ _TypeRole('Body base', KasyTextTheme.bodyBase, 'Regular · 16/24'),
694
+ _TypeRole('Body base medium', KasyTextTheme.bodyBaseMedium, 'Medium · 16/24'),
695
+ _TypeRole('Body sm', KasyTextTheme.bodySm, 'Regular · 14/20'),
696
+ _TypeRole('Body sm medium', KasyTextTheme.bodySmMedium, 'Medium · 14/20'),
697
+ _TypeRole('Body xs', KasyTextTheme.bodyXs, 'Regular · 12/16'),
698
+ _TypeRole('Body xs medium', KasyTextTheme.bodyXsMedium, 'Medium · 12/16'),
699
+ _TypeRole('Link base', KasyTextTheme.linkBase, 'Medium · 16/24 · Underlined'),
700
+ _TypeRole('Link sm', KasyTextTheme.linkSm, 'Medium · 14/20 · Underlined'),
701
+ _TypeRole('Text field base', KasyTextTheme.textFieldBase, 'Regular · 16/24'),
702
+ _TypeRole('Text field sm', KasyTextTheme.textFieldSm, 'Regular · 14/20'),
703
+ _TypeRole('Button base', KasyTextTheme.buttonBase, 'Medium · 16/24'),
704
+ _TypeRole('Button sm', KasyTextTheme.buttonSm, 'Medium · 14/20'),
284
705
  ];
285
706
 
286
- return _TokenCard(
287
- children: tokens
288
- .map(
289
- (t) => Column(
290
- mainAxisSize: MainAxisSize.min,
291
- children: [
292
- _TypeRow(token: t),
293
- if (t != tokens.last) const _TokenDivider(),
294
- ],
295
- ),
296
- )
297
- .toList(),
707
+ return Column(
708
+ crossAxisAlignment: CrossAxisAlignment.stretch,
709
+ children: [
710
+ Text(
711
+ "Based on Tailwind's scale: Inter, neutral tracking and predictable "
712
+ 'rhythm. Functional and scalable rather than decorative.',
713
+ style: context.textTheme.bodyMedium?.copyWith(
714
+ color: context.colors.muted,
715
+ height: 1.5,
716
+ ),
717
+ ),
718
+ const SizedBox(height: KasySpacing.md),
719
+ _TokenCard(
720
+ children: [
721
+ for (int i = 0; i < roles.length; i++) ...[
722
+ _TypeRow(role: roles[i], foreground: fg),
723
+ if (i < roles.length - 1) const _TokenDivider(),
724
+ ],
725
+ ],
726
+ ),
727
+ ],
298
728
  );
299
729
  }
300
730
  }
301
731
 
302
- class _TypeToken {
732
+ class _TypeRole {
303
733
  final String name;
304
- final TextStyle? style;
305
- final TextStyle raw;
306
- const _TypeToken(this.name, this.style, this.raw);
734
+ final TextStyle style;
735
+ final String spec;
736
+ const _TypeRole(this.name, this.style, this.spec);
307
737
  }
308
738
 
309
739
  class _TypeRow extends StatelessWidget {
310
- final _TypeToken token;
311
- const _TypeRow({required this.token});
740
+ final _TypeRole role;
741
+ final Color foreground;
742
+ const _TypeRow({required this.role, required this.foreground});
312
743
 
313
744
  @override
314
745
  Widget build(BuildContext context) {
315
- final double size = token.raw.fontSize ?? 14;
316
- final int weight = token.raw.fontWeight?.value ?? 400;
317
746
  return Padding(
318
747
  padding: const EdgeInsets.symmetric(
319
748
  horizontal: KasySpacing.md,
320
749
  vertical: KasySpacing.smd,
321
750
  ),
322
- child: Row(
751
+ child: Column(
752
+ crossAxisAlignment: CrossAxisAlignment.start,
323
753
  children: [
324
- Expanded(
325
- child: Text(
326
- token.name,
327
- style: token.style,
328
- maxLines: 1,
329
- overflow: TextOverflow.ellipsis,
330
- ),
754
+ Text(
755
+ role.name,
756
+ style: role.style.copyWith(color: foreground),
757
+ maxLines: 1,
758
+ overflow: TextOverflow.ellipsis,
331
759
  ),
332
- const SizedBox(width: KasySpacing.md),
760
+ const SizedBox(height: 4),
333
761
  Text(
334
- '${size.toInt()}/$weight',
762
+ 'Inter · ${role.spec}',
335
763
  style: context.textTheme.bodySmall?.copyWith(
336
764
  color: context.colors.muted,
337
765
  fontFeatures: const [FontFeature.tabularFigures()],
@@ -436,12 +864,17 @@ class _RadiusSection extends StatelessWidget {
436
864
  const _RadiusSection();
437
865
 
438
866
  static const List<({String name, double value})> _tokens = [
439
- (name: 'xs', value: KasyRadius.xs),
440
- (name: 'sm', value: KasyRadius.sm),
441
- (name: 'md', value: KasyRadius.md),
442
- (name: 'lg', value: KasyRadius.lg),
443
- (name: 'xl', value: KasyRadius.xl),
444
- (name: 'full', value: 999),
867
+ (name: 'none', value: KasyRadius.roundedNone),
868
+ (name: 'xs', value: KasyRadius.roundedXs),
869
+ (name: 'sm', value: KasyRadius.roundedSm),
870
+ (name: 'md', value: KasyRadius.roundedMd),
871
+ (name: 'lg', value: KasyRadius.roundedLg),
872
+ (name: 'xl', value: KasyRadius.roundedXl),
873
+ (name: '2xl', value: KasyRadius.rounded2xl),
874
+ (name: '2.5xl', value: KasyRadius.rounded2_5xl),
875
+ (name: '3xl', value: KasyRadius.rounded3xl),
876
+ (name: '4xl', value: KasyRadius.rounded4xl),
877
+ (name: 'full', value: KasyRadius.roundedFull),
445
878
  ];
446
879
 
447
880
  @override
@@ -550,3 +983,208 @@ class _TokenCard extends StatelessWidget {
550
983
  );
551
984
  }
552
985
  }
986
+
987
+ // ---------------------------------------------------------------------------
988
+ // Effect Styles — Shadows, Blur, Focus Rings
989
+ // ---------------------------------------------------------------------------
990
+
991
+ class _EffectsSection extends StatelessWidget {
992
+ const _EffectsSection();
993
+
994
+ @override
995
+ Widget build(BuildContext context) {
996
+ return Column(
997
+ crossAxisAlignment: CrossAxisAlignment.stretch,
998
+ children: [
999
+ Text(
1000
+ 'How depth, separation and focus are expressed. Applied consistently '
1001
+ 'to communicate hierarchy without relying on color alone.',
1002
+ style: context.textTheme.bodyMedium?.copyWith(
1003
+ color: context.colors.muted,
1004
+ height: 1.5,
1005
+ ),
1006
+ ),
1007
+ const SizedBox(height: KasySpacing.lg),
1008
+ const _EffectGroupLabel('Shadows'),
1009
+ const SizedBox(height: KasySpacing.smd),
1010
+ const Wrap(
1011
+ spacing: KasySpacing.md,
1012
+ runSpacing: KasySpacing.md,
1013
+ children: [
1014
+ _ShadowTile(label: 'Inner', shadows: [], inner: true),
1015
+ _ShadowTile(label: 'Surface', shadows: KasyShadows.surface),
1016
+ _ShadowTile(label: 'Field', shadows: KasyShadows.field),
1017
+ _ShadowTile(label: 'Switch', shadows: KasyShadows.switchControl),
1018
+ _ShadowTile(label: 'Tab', shadows: KasyShadows.tab),
1019
+ _ShadowTile(label: 'Overlay', shadows: KasyShadows.overlay),
1020
+ ],
1021
+ ),
1022
+ const SizedBox(height: KasySpacing.lg),
1023
+ const _EffectGroupLabel('Blur'),
1024
+ const SizedBox(height: KasySpacing.smd),
1025
+ const Wrap(
1026
+ spacing: KasySpacing.md,
1027
+ runSpacing: KasySpacing.md,
1028
+ children: [
1029
+ _BlurTile(label: 'Blur', sigma: 8),
1030
+ _BlurTile(label: 'Backdrop', sigma: 3),
1031
+ ],
1032
+ ),
1033
+ const SizedBox(height: KasySpacing.lg),
1034
+ const _EffectGroupLabel('Focus Rings'),
1035
+ const SizedBox(height: KasySpacing.smd),
1036
+ const Wrap(
1037
+ spacing: KasySpacing.lg,
1038
+ runSpacing: KasySpacing.md,
1039
+ children: [
1040
+ _FocusTile(label: 'Focus Ring', field: false),
1041
+ _FocusTile(label: 'Focus Ring Field', field: true),
1042
+ ],
1043
+ ),
1044
+ ],
1045
+ );
1046
+ }
1047
+ }
1048
+
1049
+ class _EffectGroupLabel extends StatelessWidget {
1050
+ final String label;
1051
+ const _EffectGroupLabel(this.label);
1052
+
1053
+ @override
1054
+ Widget build(BuildContext context) {
1055
+ return Text(
1056
+ label,
1057
+ style: context.textTheme.titleSmall?.copyWith(
1058
+ color: context.colors.onSurface,
1059
+ fontWeight: FontWeight.w700,
1060
+ ),
1061
+ );
1062
+ }
1063
+ }
1064
+
1065
+ /// 72×72 labeled sample tile shared by the effect groups.
1066
+ class _EffectTile extends StatelessWidget {
1067
+ final String label;
1068
+ final Widget box;
1069
+ const _EffectTile({required this.label, required this.box});
1070
+
1071
+ @override
1072
+ Widget build(BuildContext context) {
1073
+ return Column(
1074
+ mainAxisSize: MainAxisSize.min,
1075
+ children: [
1076
+ box,
1077
+ const SizedBox(height: KasySpacing.sm),
1078
+ Text(
1079
+ label,
1080
+ style: context.textTheme.bodySmall?.copyWith(
1081
+ color: context.colors.muted,
1082
+ ),
1083
+ ),
1084
+ ],
1085
+ );
1086
+ }
1087
+ }
1088
+
1089
+ class _ShadowTile extends StatelessWidget {
1090
+ final String label;
1091
+ final List<BoxShadow> shadows;
1092
+ final bool inner;
1093
+ const _ShadowTile({
1094
+ required this.label,
1095
+ required this.shadows,
1096
+ this.inner = false,
1097
+ });
1098
+
1099
+ @override
1100
+ Widget build(BuildContext context) {
1101
+ final c = context.colors;
1102
+ return _EffectTile(
1103
+ label: label,
1104
+ box: Container(
1105
+ width: 72,
1106
+ height: 72,
1107
+ decoration: BoxDecoration(
1108
+ color: c.surface,
1109
+ borderRadius: BorderRadius.circular(KasyRadius.md),
1110
+ border: Border.all(color: c.separator),
1111
+ boxShadow: inner ? null : shadows,
1112
+ gradient: inner
1113
+ ? LinearGradient(
1114
+ begin: Alignment.topCenter,
1115
+ end: Alignment.center,
1116
+ colors: [KasyShadows.inner, c.surface],
1117
+ )
1118
+ : null,
1119
+ ),
1120
+ ),
1121
+ );
1122
+ }
1123
+ }
1124
+
1125
+ class _BlurTile extends StatelessWidget {
1126
+ final String label;
1127
+ final double sigma;
1128
+ const _BlurTile({required this.label, required this.sigma});
1129
+
1130
+ @override
1131
+ Widget build(BuildContext context) {
1132
+ final c = context.colors;
1133
+ return _EffectTile(
1134
+ label: label,
1135
+ box: ClipRRect(
1136
+ borderRadius: BorderRadius.circular(KasyRadius.md),
1137
+ child: SizedBox(
1138
+ width: 72,
1139
+ height: 72,
1140
+ child: Stack(
1141
+ fit: StackFit.expand,
1142
+ children: [
1143
+ DecoratedBox(
1144
+ decoration: BoxDecoration(
1145
+ gradient: LinearGradient(
1146
+ begin: Alignment.topLeft,
1147
+ end: Alignment.bottomRight,
1148
+ colors: [c.accent, c.success, c.warning, c.danger],
1149
+ ),
1150
+ ),
1151
+ ),
1152
+ BackdropFilter(
1153
+ filter: ImageFilter.blur(sigmaX: sigma, sigmaY: sigma),
1154
+ child: ColoredBox(
1155
+ color: c.surface.withValues(alpha: 0.10),
1156
+ ),
1157
+ ),
1158
+ ],
1159
+ ),
1160
+ ),
1161
+ ),
1162
+ );
1163
+ }
1164
+ }
1165
+
1166
+ class _FocusTile extends StatelessWidget {
1167
+ final String label;
1168
+ final bool field;
1169
+ const _FocusTile({required this.label, required this.field});
1170
+
1171
+ @override
1172
+ Widget build(BuildContext context) {
1173
+ final c = context.colors;
1174
+ return _EffectTile(
1175
+ label: label,
1176
+ box: Container(
1177
+ width: 72,
1178
+ height: 72,
1179
+ decoration: BoxDecoration(
1180
+ color: c.surface,
1181
+ borderRadius: BorderRadius.circular(KasyRadius.md),
1182
+ border: Border.all(color: c.separator),
1183
+ boxShadow: field
1184
+ ? KasyShadows.focusRingField(ring: c.accent)
1185
+ : KasyShadows.focusRing(ring: c.accent, gap: c.background),
1186
+ ),
1187
+ ),
1188
+ );
1189
+ }
1190
+ }