kasy-cli 1.38.0 → 1.39.1

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 (105) hide show
  1. package/lib/scaffold/CHANGELOG.json +23 -0
  2. package/lib/scaffold/backends/api/patch/README.md +15 -0
  3. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +11 -6
  4. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +9 -1
  5. package/lib/scaffold/backends/api/pubspec.yaml.tpl +1 -0
  6. package/lib/scaffold/backends/patch-base-hashes.json +6 -6
  7. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +3 -1
  8. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +3 -0
  9. package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql +62 -0
  10. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +11 -6
  11. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  12. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +1 -0
  13. package/lib/scaffold/shared/generator-utils.js +12 -6
  14. package/package.json +1 -1
  15. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
  16. package/templates/firebase/AGENTS.md +2 -2
  17. package/templates/firebase/DESIGN_SYSTEM.md +23 -8
  18. package/templates/firebase/assets/icons/apple_black.svg +3 -0
  19. package/templates/firebase/assets/icons/apple_white.svg +4 -0
  20. package/templates/firebase/assets/icons/facebook.svg +49 -0
  21. package/templates/firebase/assets/icons/google.svg +1 -0
  22. package/templates/firebase/functions/src/admin/functions.ts +2 -0
  23. package/templates/firebase/functions/src/authentication/functions.ts +13 -7
  24. package/templates/firebase/functions/src/notifications/triggers.ts +6 -2
  25. package/templates/firebase/lib/components/components.dart +5 -2
  26. package/templates/firebase/lib/components/kasy_app_bar.dart +325 -15
  27. package/templates/firebase/lib/components/kasy_card.dart +4 -0
  28. package/templates/firebase/lib/components/kasy_drop_down.dart +584 -0
  29. package/templates/firebase/lib/components/kasy_sidebar.dart +18 -6
  30. package/templates/firebase/lib/components/kasy_tabs.dart +31 -10
  31. package/templates/firebase/lib/components/kasy_text_field.dart +29 -7
  32. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +27 -18
  33. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +34 -16
  34. package/templates/firebase/lib/core/chrome/app_bar_config.dart +214 -0
  35. package/templates/firebase/lib/core/chrome/app_bar_scope.dart +102 -0
  36. package/templates/firebase/lib/core/data/api/user_api.dart +11 -0
  37. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +55 -15
  38. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +95 -30
  39. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +11 -0
  40. package/templates/firebase/lib/core/states/logout_action.dart +11 -1
  41. package/templates/firebase/lib/core/states/user_state_notifier.dart +28 -1
  42. package/templates/firebase/lib/core/theme/texts.dart +21 -6
  43. package/templates/firebase/lib/core/theme/type_scale.dart +34 -15
  44. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +51 -19
  45. package/templates/firebase/lib/core/web_viewport_scale.dart +66 -36
  46. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +14 -3
  47. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +1 -1
  48. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +52 -35
  49. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
  50. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +18 -8
  51. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +11 -61
  52. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +11 -61
  53. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +7 -5
  54. package/templates/firebase/lib/features/authentication/ui/widgets/social_auth_tile.dart +83 -0
  55. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +4 -4
  56. package/templates/firebase/lib/features/home/home_components_page.dart +253 -125
  57. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +263 -59
  58. package/templates/firebase/lib/features/home/home_feed.dart +2 -2
  59. package/templates/firebase/lib/features/home/home_image_grid.dart +3 -3
  60. package/templates/firebase/lib/features/local_reminders/providers/reminder_notifier.dart +8 -1
  61. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +111 -57
  62. package/templates/firebase/lib/features/notifications/api/device_api.dart +11 -3
  63. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -4
  64. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +7 -56
  65. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +5 -5
  66. package/templates/firebase/lib/features/notifications/ui/widgets/push_permission_banner.dart +163 -0
  67. package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +2 -2
  68. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +9 -0
  69. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +28 -0
  70. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +51 -12
  71. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +90 -32
  72. package/templates/firebase/lib/features/settings/settings_page.dart +53 -32
  73. package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +4 -0
  74. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +895 -111
  75. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  76. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +445 -233
  77. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +171 -41
  78. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +1 -1
  79. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +9 -1
  80. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +48 -47
  81. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +21 -18
  82. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +25 -10
  83. package/templates/firebase/lib/i18n/en.i18n.json +753 -712
  84. package/templates/firebase/lib/i18n/es.i18n.json +753 -712
  85. package/templates/firebase/lib/i18n/pt.i18n.json +753 -712
  86. package/templates/firebase/lib/main.dart +20 -7
  87. package/templates/firebase/lib/router.dart +32 -26
  88. package/templates/firebase/pubspec.yaml +2 -1
  89. package/templates/firebase/test/admin_shell_chrome_test.dart +11 -5
  90. package/templates/firebase/test/app_bar_config_test.dart +70 -0
  91. package/templates/firebase/test/components/kasy_text_field_height_test.dart +77 -0
  92. package/templates/firebase/test/core/web_viewport_scale_test.dart +23 -16
  93. package/templates/firebase/tool/design_check.dart +9 -0
  94. package/templates/firebase/assets/icons/apple.png +0 -0
  95. package/templates/firebase/assets/icons/facebook.png +0 -0
  96. package/templates/firebase/assets/icons/google.png +0 -0
  97. package/templates/firebase/assets/icons/google_play_games.png +0 -0
  98. package/templates/firebase/lib/components/kasy_web_header.dart +0 -218
  99. package/templates/firebase/lib/core/chrome/web_header_scope.dart +0 -20
  100. package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +0 -19
  101. package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +0 -32
  102. package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +0 -73
  103. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -66
  104. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +0 -179
  105. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +0 -106
@@ -71,39 +71,44 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
71
71
  return const ComponentPreviewDefinition(
72
72
  title: 'AppBar',
73
73
  variants: [
74
+ // Phone / tablet — the page chrome (title + back + orbs).
74
75
  ComponentPreviewVariant(
75
- label: 'Subpage',
76
+ label: 'Subpage (mobile)',
76
77
  builder: _buildAppBarSubpageVariant,
77
78
  ),
78
79
  ComponentPreviewVariant(
79
- label: 'Subpage Simple',
80
+ label: 'Subpage Simple (mobile)',
80
81
  builder: _buildAppBarSubpageSimpleVariant,
81
82
  ),
82
83
  ComponentPreviewVariant(
83
- label: 'Subpage Actions',
84
+ label: 'Subpage Actions (mobile)',
84
85
  builder: _buildAppBarSubpageActionsVariant,
85
86
  ),
86
87
  ComponentPreviewVariant(
87
- label: 'Menu opens sidebar',
88
+ label: 'Menu opens sidebar (mobile)',
88
89
  builder: _buildAppBarMenuVariant,
89
90
  ),
90
- ],
91
- );
92
- case 'Web Header':
93
- return const ComponentPreviewDefinition(
94
- title: 'Web Header',
95
- variants: [
91
+ // Desktop — the application chrome (search + actions + profile).
96
92
  ComponentPreviewVariant(
97
- label: 'Default',
98
- builder: _buildWebHeaderDefaultVariant,
93
+ label: 'Application (desktop)',
94
+ builder: _buildAppBarApplicationVariant,
95
+ ),
96
+ ComponentPreviewVariant(
97
+ label: 'Application + notification (desktop)',
98
+ builder: _buildAppBarApplicationBadgeVariant,
99
+ ),
100
+ ComponentPreviewVariant(
101
+ label: 'Application no avatar (desktop)',
102
+ builder: _buildAppBarApplicationNoAvatarVariant,
99
103
  ),
104
+ // Per-screen presets — same bar, a screen picking its own chrome.
100
105
  ComponentPreviewVariant(
101
- label: 'With notification',
102
- builder: _buildWebHeaderBadgeVariant,
106
+ label: 'Search only (desktop)',
107
+ builder: _buildAppBarApplicationSearchOnlyVariant,
103
108
  ),
104
109
  ComponentPreviewVariant(
105
- label: 'Without avatar',
106
- builder: _buildWebHeaderNoAvatarVariant,
110
+ label: 'Notifications only (desktop)',
111
+ builder: _buildAppBarApplicationNotificationsOnlyVariant,
107
112
  ),
108
113
  ],
109
114
  );
@@ -309,6 +314,24 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
309
314
  ),
310
315
  ],
311
316
  );
317
+ case 'DropDown':
318
+ return const ComponentPreviewDefinition(
319
+ title: 'DropDown',
320
+ variants: [
321
+ ComponentPreviewVariant(
322
+ label: 'Basic',
323
+ builder: _buildDropDownBasic,
324
+ ),
325
+ ComponentPreviewVariant(
326
+ label: 'With icons & subtitles',
327
+ builder: _buildDropDownRich,
328
+ ),
329
+ ComponentPreviewVariant(
330
+ label: 'States',
331
+ builder: _buildDropDownStates,
332
+ ),
333
+ ],
334
+ );
312
335
  case 'Badge':
313
336
  return const ComponentPreviewDefinition(
314
337
  title: 'Badge',
@@ -1928,16 +1951,42 @@ Widget _buildAppBarMenuVariant(BuildContext context) {
1928
1951
  );
1929
1952
  }
1930
1953
 
1931
- Widget _buildWebHeaderDefaultVariant(BuildContext context) {
1932
- return const _WebHeaderPreview();
1954
+ /// A fully-wired desktop config for previews — every element active, so each
1955
+ /// variant can switch one thing on/off and read clearly.
1956
+ KasyAppBarConfig _demoAppBarConfig() => KasyAppBarConfig(
1957
+ onToggleTheme: () {},
1958
+ onNotifications: () {},
1959
+ onCreate: () {},
1960
+ onAvatarTap: () {},
1961
+ );
1962
+
1963
+ Widget _buildAppBarApplicationVariant(BuildContext context) {
1964
+ return _ApplicationBarPreview(config: _demoAppBarConfig());
1933
1965
  }
1934
1966
 
1935
- Widget _buildWebHeaderBadgeVariant(BuildContext context) {
1936
- return const _WebHeaderPreview(showBadge: true);
1967
+ Widget _buildAppBarApplicationBadgeVariant(BuildContext context) {
1968
+ return _ApplicationBarPreview(
1969
+ config: _demoAppBarConfig().copyWith(showNotificationBadge: true),
1970
+ );
1937
1971
  }
1938
1972
 
1939
- Widget _buildWebHeaderNoAvatarVariant(BuildContext context) {
1940
- return const _WebHeaderPreview(showAvatar: false);
1973
+ Widget _buildAppBarApplicationNoAvatarVariant(BuildContext context) {
1974
+ return _ApplicationBarPreview(
1975
+ config: _demoAppBarConfig().copyWith(showAvatar: false),
1976
+ );
1977
+ }
1978
+
1979
+ // Per-screen presets: the same component, different chrome chosen by a screen.
1980
+ Widget _buildAppBarApplicationSearchOnlyVariant(BuildContext context) {
1981
+ return _ApplicationBarPreview(
1982
+ config: _demoAppBarConfig().only(search: true, themeToggle: true),
1983
+ );
1984
+ }
1985
+
1986
+ Widget _buildAppBarApplicationNotificationsOnlyVariant(BuildContext context) {
1987
+ return _ApplicationBarPreview(
1988
+ config: _demoAppBarConfig().only(notifications: true, themeToggle: true),
1989
+ );
1941
1990
  }
1942
1991
 
1943
1992
  Widget _buildButtonSizesVariant(BuildContext context) {
@@ -3866,49 +3915,57 @@ class _AccordionPreviewState extends State<_AccordionPreview> {
3866
3915
  }
3867
3916
  }
3868
3917
 
3869
- /// Presents [KasyWebHeader] inside a desktop browser-window mock (title bar +
3870
- /// faux sidebar + content) so the preview reads as the web/desktop chrome it is.
3871
- class _WebHeaderPreview extends StatelessWidget {
3872
- final bool showBadge;
3873
- final bool showAvatar;
3918
+ /// Presents [KasyAppBar.application] inside a desktop browser-window mock (title
3919
+ /// bar + faux sidebar + content) so the preview reads as the desktop chrome it
3920
+ /// is the responsive desktop half of [KasyAppBar].
3921
+ class _ApplicationBarPreview extends StatelessWidget {
3922
+ /// The exact config a screen would publish — rendered through the same
3923
+ /// [KasyAppBar.fromConfig] the shell uses, so the preview mirrors real usage.
3924
+ final KasyAppBarConfig config;
3925
+
3926
+ const _ApplicationBarPreview({required this.config});
3874
3927
 
3875
- const _WebHeaderPreview({this.showBadge = false, this.showAvatar = true});
3928
+ /// Width the mock window is laid out at. The application bar is desktop chrome
3929
+ /// (220px search + actions), so it needs a desktop-class width — we render at
3930
+ /// this width and scale the whole window down to fit the (narrow) preview card.
3931
+ /// Without this, the bar overflows on a phone-sized preview.
3932
+ static const double _mockWindowWidth = 760;
3876
3933
 
3877
3934
  @override
3878
3935
  Widget build(BuildContext context) {
3879
3936
  final KasyColors c = context.colors;
3880
- return DecoratedBox(
3881
- decoration: BoxDecoration(
3882
- color: c.surface,
3883
- borderRadius: BorderRadius.circular(KasyRadius.lg),
3884
- border: Border.all(color: c.onSurface.withValues(alpha: 0.10)),
3885
- boxShadow: [KasyShadows.component(context)],
3886
- ),
3887
- child: ClipRRect(
3888
- borderRadius: BorderRadius.circular(KasyRadius.lg),
3889
- child: Column(
3890
- mainAxisSize: MainAxisSize.min,
3891
- children: [
3892
- const _BrowserTopBar(),
3893
- // The real header, flush — no corner radius of its own.
3894
- KasyWebHeader(
3895
- showNotificationBadge: showBadge,
3896
- showAvatar: showAvatar,
3897
- onNotifications: () {},
3898
- onCreate: () {},
3899
- onAvatarTap: () {},
3900
- ),
3901
- const SizedBox(
3902
- height: 150,
3903
- child: Row(
3904
- crossAxisAlignment: CrossAxisAlignment.stretch,
3905
- children: [
3906
- _DesktopMockSidebar(),
3907
- Expanded(child: _DesktopMockContent()),
3908
- ],
3909
- ),
3937
+ return FittedBox(
3938
+ fit: BoxFit.scaleDown,
3939
+ child: SizedBox(
3940
+ width: _mockWindowWidth,
3941
+ child: DecoratedBox(
3942
+ decoration: BoxDecoration(
3943
+ color: c.surface,
3944
+ borderRadius: BorderRadius.circular(KasyRadius.lg),
3945
+ border: Border.all(color: c.onSurface.withValues(alpha: 0.10)),
3946
+ boxShadow: [KasyShadows.component(context)],
3947
+ ),
3948
+ child: ClipRRect(
3949
+ borderRadius: BorderRadius.circular(KasyRadius.lg),
3950
+ child: Column(
3951
+ mainAxisSize: MainAxisSize.min,
3952
+ children: [
3953
+ const _BrowserTopBar(),
3954
+ // The real bar, flush — no corner radius of its own.
3955
+ KasyAppBar.fromConfig(config),
3956
+ const SizedBox(
3957
+ height: 150,
3958
+ child: Row(
3959
+ crossAxisAlignment: CrossAxisAlignment.stretch,
3960
+ children: [
3961
+ _DesktopMockSidebar(),
3962
+ Expanded(child: _DesktopMockContent()),
3963
+ ],
3964
+ ),
3965
+ ),
3966
+ ],
3910
3967
  ),
3911
- ],
3968
+ ),
3912
3969
  ),
3913
3970
  ),
3914
3971
  );
@@ -9447,3 +9504,150 @@ class _DatePickerFieldStatesPreviewState
9447
9504
  );
9448
9505
  }
9449
9506
  }
9507
+
9508
+ // ─────────────────────────────────────────────────────────────────────────────
9509
+ // DropDown — single-select dropdown (HeroUI "Select")
9510
+ // ─────────────────────────────────────────────────────────────────────────────
9511
+
9512
+ const List<KasyDropDownItem<String>> _kDropDownStates = [
9513
+ KasyDropDownItem(value: 'fl', label: 'Florida'),
9514
+ KasyDropDownItem(value: 'de', label: 'Delaware'),
9515
+ KasyDropDownItem(value: 'tx', label: 'Texas'),
9516
+ KasyDropDownItem(value: 'ca', label: 'California'),
9517
+ KasyDropDownItem(value: 'ny', label: 'New York'),
9518
+ KasyDropDownItem(value: 'wy', label: 'Wyoming'),
9519
+ ];
9520
+
9521
+ Widget _buildDropDownBasic(BuildContext context) => const _DropDownBasicPreview();
9522
+
9523
+ class _DropDownBasicPreview extends StatefulWidget {
9524
+ const _DropDownBasicPreview();
9525
+
9526
+ @override
9527
+ State<_DropDownBasicPreview> createState() => _DropDownBasicPreviewState();
9528
+ }
9529
+
9530
+ class _DropDownBasicPreviewState extends State<_DropDownBasicPreview> {
9531
+ String? _state;
9532
+
9533
+ @override
9534
+ Widget build(BuildContext context) {
9535
+ return Column(
9536
+ mainAxisSize: MainAxisSize.min,
9537
+ crossAxisAlignment: CrossAxisAlignment.stretch,
9538
+ children: [
9539
+ KasyDropDown<String>(
9540
+ label: 'State',
9541
+ hint: 'Select one',
9542
+ showRequiredIndicator: true,
9543
+ value: _state,
9544
+ items: _kDropDownStates,
9545
+ onChanged: (v) => setState(() => _state = v),
9546
+ ),
9547
+ ],
9548
+ );
9549
+ }
9550
+ }
9551
+
9552
+ Widget _buildDropDownRich(BuildContext context) => const _DropDownRichPreview();
9553
+
9554
+ class _DropDownRichPreview extends StatefulWidget {
9555
+ const _DropDownRichPreview();
9556
+
9557
+ @override
9558
+ State<_DropDownRichPreview> createState() => _DropDownRichPreviewState();
9559
+ }
9560
+
9561
+ class _DropDownRichPreviewState extends State<_DropDownRichPreview> {
9562
+ String? _action;
9563
+
9564
+ @override
9565
+ Widget build(BuildContext context) {
9566
+ return Column(
9567
+ mainAxisSize: MainAxisSize.min,
9568
+ crossAxisAlignment: CrossAxisAlignment.stretch,
9569
+ children: [
9570
+ KasyDropDown<String>(
9571
+ label: 'Quick action',
9572
+ hint: 'Pick an action',
9573
+ leadingIcon: KasyIcons.idea,
9574
+ value: _action,
9575
+ items: const [
9576
+ KasyDropDownItem(
9577
+ value: 'new',
9578
+ label: 'New file',
9579
+ icon: KasyIcons.add,
9580
+ subtitle: 'Create a new file',
9581
+ ),
9582
+ KasyDropDownItem(
9583
+ value: 'copy',
9584
+ label: 'Copy link',
9585
+ icon: KasyIcons.copy,
9586
+ subtitle: 'Copy a shareable link',
9587
+ ),
9588
+ KasyDropDownItem(
9589
+ value: 'settings',
9590
+ label: 'Settings',
9591
+ icon: KasyIcons.settings,
9592
+ subtitle: 'Manage file permissions',
9593
+ ),
9594
+ ],
9595
+ onChanged: (v) => setState(() => _action = v),
9596
+ ),
9597
+ ],
9598
+ );
9599
+ }
9600
+ }
9601
+
9602
+ Widget _buildDropDownStates(BuildContext context) =>
9603
+ const _DropDownStatesPreview();
9604
+
9605
+ class _DropDownStatesPreview extends StatefulWidget {
9606
+ const _DropDownStatesPreview();
9607
+
9608
+ @override
9609
+ State<_DropDownStatesPreview> createState() => _DropDownStatesPreviewState();
9610
+ }
9611
+
9612
+ class _DropDownStatesPreviewState extends State<_DropDownStatesPreview> {
9613
+ String? _invalid;
9614
+
9615
+ @override
9616
+ Widget build(BuildContext context) {
9617
+ return Column(
9618
+ mainAxisSize: MainAxisSize.min,
9619
+ crossAxisAlignment: CrossAxisAlignment.stretch,
9620
+ children: [
9621
+ // Pre-selected value.
9622
+ KasyDropDown<String>(
9623
+ label: 'Selected',
9624
+ hint: 'Select one',
9625
+ value: 'ca',
9626
+ items: _kDropDownStates,
9627
+ onChanged: (_) {},
9628
+ ),
9629
+ const SizedBox(height: KasySpacing.lg),
9630
+ // Invalid + error text.
9631
+ KasyDropDown<String>(
9632
+ label: 'Required',
9633
+ hint: 'Select one',
9634
+ showRequiredIndicator: true,
9635
+ isInvalid: _invalid == null,
9636
+ errorText: _invalid == null ? 'Please choose a state.' : null,
9637
+ value: _invalid,
9638
+ items: _kDropDownStates,
9639
+ onChanged: (v) => setState(() => _invalid = v),
9640
+ ),
9641
+ const SizedBox(height: KasySpacing.lg),
9642
+ // Disabled trigger.
9643
+ const KasyDropDown<String>(
9644
+ label: 'Disabled',
9645
+ hint: 'Locked',
9646
+ enabled: false,
9647
+ items: _kDropDownStates,
9648
+ onChanged: null,
9649
+ ),
9650
+ ],
9651
+ );
9652
+ }
9653
+ }
@@ -336,7 +336,7 @@ class _FilterCard extends StatelessWidget {
336
336
  data.title,
337
337
  maxLines: 1,
338
338
  overflow: TextOverflow.ellipsis,
339
- style: context.textTheme.titleSmall?.copyWith(
339
+ style: context.kasyTextTheme.cardTitle.copyWith(
340
340
  color: selected ? c.primary : c.onSurface,
341
341
  ),
342
342
  ),
@@ -345,7 +345,7 @@ class _FilterCard extends StatelessWidget {
345
345
  data.subtitle,
346
346
  maxLines: 1,
347
347
  overflow: TextOverflow.ellipsis,
348
- style: context.textTheme.bodySmall?.copyWith(
348
+ style: context.kasyTextTheme.cardSubtitle.copyWith(
349
349
  color: c.muted,
350
350
  ),
351
351
  ),
@@ -234,7 +234,7 @@ class _PhotoTileState extends State<_PhotoTile> {
234
234
  photo.author,
235
235
  maxLines: 1,
236
236
  overflow: TextOverflow.ellipsis,
237
- style: context.textTheme.titleSmall?.copyWith(
237
+ style: context.kasyTextTheme.cardTitle.copyWith(
238
238
  color: context.colors.onSurface,
239
239
  ),
240
240
  ),
@@ -242,7 +242,7 @@ class _PhotoTileState extends State<_PhotoTile> {
242
242
  photo.ago,
243
243
  maxLines: 1,
244
244
  overflow: TextOverflow.ellipsis,
245
- style: context.textTheme.bodySmall?.copyWith(
245
+ style: context.kasyTextTheme.caption.copyWith(
246
246
  color: context.colors.muted,
247
247
  ),
248
248
  ),
@@ -375,7 +375,7 @@ class _LikeButtonState extends State<_LikeButton>
375
375
 
376
376
  return KasyFocusRing(
377
377
  onActivate: widget.onTap,
378
- borderRadius: BorderRadius.circular(999),
378
+ borderRadius: BorderRadius.circular(KasyRadius.full),
379
379
  child: GestureDetector(
380
380
  onTap: widget.onTap,
381
381
  behavior: HitTestBehavior.opaque,
@@ -21,7 +21,14 @@ class ReminderNotifier extends _$ReminderNotifier {
21
21
 
22
22
  Future<void> setType(ReminderType type) async {
23
23
  final current = state.requireValue;
24
- await _applyAndSave(current.copyWith(type: type));
24
+ // Leaving "specific date" drops the stored one-off date so it doesn't
25
+ // linger (and silently come back) if the user returns to that mode later.
26
+ await _applyAndSave(
27
+ current.copyWith(
28
+ type: type,
29
+ clearDate: type != ReminderType.specificDate,
30
+ ),
31
+ );
25
32
  }
26
33
 
27
34
  Future<void> setTime(int hour, int minute) async {