kasy-cli 1.17.0 → 1.19.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.
- package/bin/kasy.js +16 -2
- package/lib/commands/add.js +7 -7
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +17 -0
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +483 -324
- package/lib/commands/run.js +17 -4
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +123 -5
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/post-build.js +8 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +62 -5
- package/lib/utils/i18n/messages-es.js +62 -5
- package/lib/utils/i18n/messages-pt.js +63 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -2
- package/templates/firebase/README.en.md +1 -1
- package/templates/firebase/README.es.md +1 -1
- package/templates/firebase/README.md +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
- package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_date_picker.dart +2173 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +214 -91
- package/templates/firebase/lib/components/kasy_text_area.dart +9 -4
- package/templates/firebase/lib/components/kasy_text_field.dart +96 -36
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -2
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +88 -35
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +7 -43
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +118 -16
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +14 -20
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/core/security/secured_storage.dart +56 -15
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +18 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -6
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +6 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -2
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +949 -77
- package/templates/firebase/lib/features/home/home_page.dart +17 -40
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +1 -16
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +0 -4
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
- package/templates/firebase/lib/i18n/en.i18n.json +2 -1
- package/templates/firebase/lib/i18n/es.i18n.json +2 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
- package/templates/firebase/lib/main.dart +34 -34
- package/templates/firebase/pubspec.yaml +2 -1
- package/templates/firebase/storage.cors.json +8 -0
- package/templates/firebase/web/index.html +24 -2
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +0 -22
|
@@ -231,6 +231,36 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
|
|
|
231
231
|
),
|
|
232
232
|
],
|
|
233
233
|
);
|
|
234
|
+
case 'DatePicker':
|
|
235
|
+
return const ComponentPreviewDefinition(
|
|
236
|
+
title: 'DatePicker',
|
|
237
|
+
variants: [
|
|
238
|
+
ComponentPreviewVariant(
|
|
239
|
+
label: 'Presentations',
|
|
240
|
+
builder: _buildDatePickerPresentations,
|
|
241
|
+
),
|
|
242
|
+
ComponentPreviewVariant(
|
|
243
|
+
label: 'Range selection',
|
|
244
|
+
builder: _buildDatePickerRange,
|
|
245
|
+
),
|
|
246
|
+
ComponentPreviewVariant(
|
|
247
|
+
label: 'Multi-month',
|
|
248
|
+
builder: _buildDatePickerMultiMonth,
|
|
249
|
+
),
|
|
250
|
+
ComponentPreviewVariant(
|
|
251
|
+
label: 'Date formats',
|
|
252
|
+
builder: _buildDatePickerFormats,
|
|
253
|
+
),
|
|
254
|
+
ComponentPreviewVariant(
|
|
255
|
+
label: 'Variants',
|
|
256
|
+
builder: _buildDatePickerVariants,
|
|
257
|
+
),
|
|
258
|
+
ComponentPreviewVariant(
|
|
259
|
+
label: 'Field states',
|
|
260
|
+
builder: _buildDatePickerFieldStates,
|
|
261
|
+
),
|
|
262
|
+
],
|
|
263
|
+
);
|
|
234
264
|
case 'Dialog':
|
|
235
265
|
return const ComponentPreviewDefinition(
|
|
236
266
|
title: 'Dialog',
|
|
@@ -654,6 +684,7 @@ class _SidebarPreviewState extends State<_SidebarPreview> {
|
|
|
654
684
|
}
|
|
655
685
|
|
|
656
686
|
|
|
687
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
657
688
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
658
689
|
// Tabs — interactive demos
|
|
659
690
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -667,6 +698,83 @@ Widget _buildTabsSecondaryVariant(BuildContext context) =>
|
|
|
667
698
|
Widget _buildTabsFillModeVariant(BuildContext context) =>
|
|
668
699
|
const _TabsFillModePreview();
|
|
669
700
|
|
|
701
|
+
// ── Shared tab content helpers ────────────────────────────────────────────
|
|
702
|
+
|
|
703
|
+
/// Fades [child] in when [tabIndex] == [currentIndex], out otherwise.
|
|
704
|
+
///
|
|
705
|
+
/// Used inside an [IndexedStack] so the content area never changes height —
|
|
706
|
+
/// the stack always sizes to the tallest child and only the active one is
|
|
707
|
+
/// visible.
|
|
708
|
+
Widget _tabFade(int tabIndex, int currentIndex, Widget child) {
|
|
709
|
+
return AnimatedOpacity(
|
|
710
|
+
opacity: tabIndex == currentIndex ? 1.0 : 0.0,
|
|
711
|
+
duration: const Duration(milliseconds: 180),
|
|
712
|
+
curve: Curves.easeInOut,
|
|
713
|
+
child: child,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ── Card container helper ─────────────────────────────────────────────────
|
|
718
|
+
|
|
719
|
+
Widget _tabsCard(BuildContext context, Widget child) {
|
|
720
|
+
return DecoratedBox(
|
|
721
|
+
decoration: BoxDecoration(
|
|
722
|
+
color: context.colors.surface,
|
|
723
|
+
borderRadius: KasyRadius.lgBorderRadius,
|
|
724
|
+
border: Border.all(
|
|
725
|
+
color: context.colors.outline.withValues(alpha: 0.4),
|
|
726
|
+
),
|
|
727
|
+
),
|
|
728
|
+
child: child,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// ── Divider helper ────────────────────────────────────────────────────────
|
|
733
|
+
|
|
734
|
+
Widget _tabsDivider(BuildContext context) => Divider(
|
|
735
|
+
height: 1,
|
|
736
|
+
thickness: 1,
|
|
737
|
+
indent: KasySpacing.md,
|
|
738
|
+
endIndent: KasySpacing.md,
|
|
739
|
+
color: context.colors.outline.withValues(alpha: 0.33),
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
// ── Radio-style option row ────────────────────────────────────────────────
|
|
743
|
+
|
|
744
|
+
/// Single-select option row that looks like a radio button using KasyCheckbox.
|
|
745
|
+
class _RadioOptionTile extends StatelessWidget {
|
|
746
|
+
const _RadioOptionTile({
|
|
747
|
+
required this.label,
|
|
748
|
+
required this.description,
|
|
749
|
+
required this.selected,
|
|
750
|
+
required this.onTap,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
final String label;
|
|
754
|
+
final String description;
|
|
755
|
+
final bool selected;
|
|
756
|
+
final VoidCallback onTap;
|
|
757
|
+
|
|
758
|
+
@override
|
|
759
|
+
Widget build(BuildContext context) {
|
|
760
|
+
return Padding(
|
|
761
|
+
padding: const EdgeInsets.symmetric(
|
|
762
|
+
horizontal: KasySpacing.md,
|
|
763
|
+
vertical: KasySpacing.smd,
|
|
764
|
+
),
|
|
765
|
+
child: KasyCheckbox(
|
|
766
|
+
value: selected,
|
|
767
|
+
onChanged: (_) => onTap(),
|
|
768
|
+
label: label,
|
|
769
|
+
description: description,
|
|
770
|
+
style: const KasyCheckboxStyle(
|
|
771
|
+
shape: KasyCheckboxShape.circle,
|
|
772
|
+
),
|
|
773
|
+
),
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
670
778
|
// ── Primary variant ────────────────────────────────────────────────────────
|
|
671
779
|
|
|
672
780
|
class _TabsPrimaryPreview extends StatefulWidget {
|
|
@@ -679,40 +787,239 @@ class _TabsPrimaryPreview extends StatefulWidget {
|
|
|
679
787
|
class _TabsPrimaryPreviewState extends State<_TabsPrimaryPreview> {
|
|
680
788
|
int _index = 0;
|
|
681
789
|
|
|
682
|
-
|
|
790
|
+
// General tab
|
|
791
|
+
final TextEditingController _urlController = TextEditingController();
|
|
792
|
+
bool _analytics = true;
|
|
793
|
+
bool _errorReports = false;
|
|
683
794
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
795
|
+
// Appearance tab
|
|
796
|
+
int _theme = 0; // 0=Auto 1=Light 2=Dark
|
|
797
|
+
int _fontSize = 1; // 0=Small 1=Medium 2=Large
|
|
798
|
+
|
|
799
|
+
// Notifications tab
|
|
800
|
+
bool _pushEnabled = true;
|
|
801
|
+
bool _emailEnabled = true;
|
|
802
|
+
bool _mentions = true;
|
|
803
|
+
bool _weeklyDigest = false;
|
|
804
|
+
|
|
805
|
+
// Profile tab
|
|
806
|
+
final TextEditingController _nameController =
|
|
807
|
+
TextEditingController(text: 'Paulo Morales');
|
|
808
|
+
final TextEditingController _usernameController =
|
|
809
|
+
TextEditingController(text: 'paulomorales');
|
|
689
810
|
|
|
690
811
|
@override
|
|
691
|
-
|
|
692
|
-
|
|
812
|
+
void dispose() {
|
|
813
|
+
_urlController.dispose();
|
|
814
|
+
_nameController.dispose();
|
|
815
|
+
_usernameController.dispose();
|
|
816
|
+
super.dispose();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
Widget _generalTab(BuildContext context) {
|
|
820
|
+
return Column(
|
|
821
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
822
|
+
mainAxisSize: MainAxisSize.min,
|
|
823
|
+
children: [
|
|
824
|
+
Padding(
|
|
825
|
+
padding: const EdgeInsets.all(KasySpacing.md),
|
|
826
|
+
child: KasyTextField(
|
|
827
|
+
controller: _urlController,
|
|
828
|
+
variant: KasyTextFieldVariant.secondary,
|
|
829
|
+
label: 'Homepage URL',
|
|
830
|
+
hint: 'https://yoursite.com',
|
|
831
|
+
),
|
|
832
|
+
),
|
|
833
|
+
_tabsDivider(context),
|
|
834
|
+
KasyCheckboxTile(
|
|
835
|
+
value: _analytics,
|
|
836
|
+
onChanged: (v) => setState(() => _analytics = v),
|
|
837
|
+
label: 'Enable analytics',
|
|
838
|
+
description: 'Collect anonymous usage data to improve the app',
|
|
839
|
+
),
|
|
840
|
+
_tabsDivider(context),
|
|
841
|
+
KasyCheckboxTile(
|
|
842
|
+
value: _errorReports,
|
|
843
|
+
onChanged: (v) => setState(() => _errorReports = v),
|
|
844
|
+
label: 'Send error reports',
|
|
845
|
+
description: 'Automatically share crash reports with the team',
|
|
846
|
+
),
|
|
847
|
+
],
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
Widget _appearanceTab(BuildContext context) {
|
|
852
|
+
return Column(
|
|
853
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
854
|
+
mainAxisSize: MainAxisSize.min,
|
|
855
|
+
children: [
|
|
856
|
+
Padding(
|
|
857
|
+
padding: const EdgeInsets.fromLTRB(
|
|
858
|
+
KasySpacing.md,
|
|
859
|
+
KasySpacing.md,
|
|
860
|
+
KasySpacing.md,
|
|
861
|
+
KasySpacing.sm,
|
|
862
|
+
),
|
|
863
|
+
child: Text(
|
|
864
|
+
'Theme',
|
|
865
|
+
style: context.textTheme.labelMedium?.copyWith(
|
|
866
|
+
color: context.colors.onSurface,
|
|
867
|
+
fontWeight: FontWeight.w600,
|
|
868
|
+
),
|
|
869
|
+
),
|
|
870
|
+
),
|
|
871
|
+
_RadioOptionTile(
|
|
872
|
+
label: 'Auto',
|
|
873
|
+
description: 'Follows your system appearance',
|
|
874
|
+
selected: _theme == 0,
|
|
875
|
+
onTap: () => setState(() => _theme = 0),
|
|
876
|
+
),
|
|
877
|
+
_tabsDivider(context),
|
|
878
|
+
_RadioOptionTile(
|
|
879
|
+
label: 'Light',
|
|
880
|
+
description: 'Always use the light theme',
|
|
881
|
+
selected: _theme == 1,
|
|
882
|
+
onTap: () => setState(() => _theme = 1),
|
|
883
|
+
),
|
|
884
|
+
_tabsDivider(context),
|
|
885
|
+
_RadioOptionTile(
|
|
886
|
+
label: 'Dark',
|
|
887
|
+
description: 'Always use the dark theme',
|
|
888
|
+
selected: _theme == 2,
|
|
889
|
+
onTap: () => setState(() => _theme = 2),
|
|
890
|
+
),
|
|
891
|
+
_tabsDivider(context),
|
|
892
|
+
Padding(
|
|
893
|
+
padding: const EdgeInsets.fromLTRB(
|
|
894
|
+
KasySpacing.md,
|
|
895
|
+
KasySpacing.md,
|
|
896
|
+
KasySpacing.md,
|
|
897
|
+
KasySpacing.sm,
|
|
898
|
+
),
|
|
899
|
+
child: Text(
|
|
900
|
+
'Font size',
|
|
901
|
+
style: context.textTheme.labelMedium?.copyWith(
|
|
902
|
+
color: context.colors.onSurface,
|
|
903
|
+
fontWeight: FontWeight.w600,
|
|
904
|
+
),
|
|
905
|
+
),
|
|
906
|
+
),
|
|
907
|
+
_RadioOptionTile(
|
|
908
|
+
label: 'Small',
|
|
909
|
+
description: 'Compact text for more content on screen',
|
|
910
|
+
selected: _fontSize == 0,
|
|
911
|
+
onTap: () => setState(() => _fontSize = 0),
|
|
912
|
+
),
|
|
913
|
+
_tabsDivider(context),
|
|
914
|
+
_RadioOptionTile(
|
|
915
|
+
label: 'Medium',
|
|
916
|
+
description: 'Balanced size for most displays',
|
|
917
|
+
selected: _fontSize == 1,
|
|
918
|
+
onTap: () => setState(() => _fontSize = 1),
|
|
919
|
+
),
|
|
920
|
+
_tabsDivider(context),
|
|
921
|
+
_RadioOptionTile(
|
|
922
|
+
label: 'Large',
|
|
923
|
+
description: 'Larger text for better readability',
|
|
924
|
+
selected: _fontSize == 2,
|
|
925
|
+
onTap: () => setState(() => _fontSize = 2),
|
|
926
|
+
),
|
|
927
|
+
],
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
Widget _notificationsTab(BuildContext context) {
|
|
932
|
+
return Column(
|
|
933
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
934
|
+
mainAxisSize: MainAxisSize.min,
|
|
935
|
+
children: [
|
|
936
|
+
KasyCheckboxTile(
|
|
937
|
+
value: _pushEnabled,
|
|
938
|
+
onChanged: (v) => setState(() => _pushEnabled = v),
|
|
939
|
+
label: 'Push notifications',
|
|
940
|
+
description: 'Receive alerts directly on your device',
|
|
941
|
+
),
|
|
942
|
+
_tabsDivider(context),
|
|
943
|
+
KasyCheckboxTile(
|
|
944
|
+
value: _emailEnabled,
|
|
945
|
+
onChanged: (v) => setState(() => _emailEnabled = v),
|
|
946
|
+
label: 'Email notifications',
|
|
947
|
+
description: 'Get updates delivered to your inbox',
|
|
948
|
+
),
|
|
949
|
+
_tabsDivider(context),
|
|
950
|
+
KasyCheckboxTile(
|
|
951
|
+
value: _mentions,
|
|
952
|
+
onChanged: (v) => setState(() => _mentions = v),
|
|
953
|
+
label: 'Mentions and replies',
|
|
954
|
+
description: 'Notify when someone mentions or replies to you',
|
|
955
|
+
),
|
|
956
|
+
_tabsDivider(context),
|
|
957
|
+
KasyCheckboxTile(
|
|
958
|
+
value: _weeklyDigest,
|
|
959
|
+
onChanged: (v) => setState(() => _weeklyDigest = v),
|
|
960
|
+
label: 'Weekly digest',
|
|
961
|
+
description: 'A summary of activity sent every Monday',
|
|
962
|
+
),
|
|
963
|
+
],
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
Widget _profileTab(BuildContext context) {
|
|
968
|
+
return Padding(
|
|
969
|
+
padding: const EdgeInsets.all(KasySpacing.md),
|
|
970
|
+
child: Column(
|
|
971
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
972
|
+
mainAxisSize: MainAxisSize.min,
|
|
973
|
+
children: [
|
|
974
|
+
KasyTextField(
|
|
975
|
+
controller: _nameController,
|
|
976
|
+
variant: KasyTextFieldVariant.secondary,
|
|
977
|
+
label: 'Name',
|
|
978
|
+
hint: 'Your full name',
|
|
979
|
+
textInputAction: TextInputAction.next,
|
|
980
|
+
),
|
|
981
|
+
const SizedBox(height: KasySpacing.md),
|
|
982
|
+
KasyTextField(
|
|
983
|
+
controller: _usernameController,
|
|
984
|
+
variant: KasyTextFieldVariant.secondary,
|
|
985
|
+
label: 'Username',
|
|
986
|
+
hint: 'yourhandle',
|
|
987
|
+
textInputAction: TextInputAction.done,
|
|
988
|
+
),
|
|
989
|
+
const SizedBox(height: KasySpacing.md),
|
|
990
|
+
KasyButton(
|
|
991
|
+
label: 'Update profile',
|
|
992
|
+
onPressed: () {},
|
|
993
|
+
),
|
|
994
|
+
],
|
|
995
|
+
),
|
|
996
|
+
);
|
|
997
|
+
}
|
|
693
998
|
|
|
999
|
+
@override
|
|
1000
|
+
Widget build(BuildContext context) {
|
|
694
1001
|
return Column(
|
|
695
1002
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
696
1003
|
mainAxisSize: MainAxisSize.min,
|
|
697
1004
|
children: [
|
|
698
1005
|
KasyTabs(
|
|
699
|
-
tabs:
|
|
1006
|
+
tabs: const ['General', 'Appearance', 'Notifications', 'Profile'],
|
|
700
1007
|
selectedIndex: _index,
|
|
701
1008
|
onTabSelected: (i) => setState(() => _index = i),
|
|
702
1009
|
),
|
|
703
1010
|
const SizedBox(height: KasySpacing.md),
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
),
|
|
715
|
-
|
|
1011
|
+
// IndexedStack keeps height fixed at the tallest child (Appearance).
|
|
1012
|
+
// AnimatedOpacity on each child provides the smooth crossfade.
|
|
1013
|
+
_tabsCard(
|
|
1014
|
+
context,
|
|
1015
|
+
IndexedStack(
|
|
1016
|
+
index: _index,
|
|
1017
|
+
children: [
|
|
1018
|
+
_tabFade(0, _index, _generalTab(context)),
|
|
1019
|
+
_tabFade(1, _index, _appearanceTab(context)),
|
|
1020
|
+
_tabFade(2, _index, _notificationsTab(context)),
|
|
1021
|
+
_tabFade(3, _index, _profileTab(context)),
|
|
1022
|
+
],
|
|
716
1023
|
),
|
|
717
1024
|
),
|
|
718
1025
|
const SizedBox(height: KasySpacing.lg),
|
|
@@ -720,7 +1027,7 @@ class _TabsPrimaryPreviewState extends State<_TabsPrimaryPreview> {
|
|
|
720
1027
|
Text(
|
|
721
1028
|
'WITH DISABLED TAB',
|
|
722
1029
|
style: context.textTheme.labelSmall?.copyWith(
|
|
723
|
-
color:
|
|
1030
|
+
color: context.colors.muted,
|
|
724
1031
|
letterSpacing: 1.2,
|
|
725
1032
|
fontWeight: FontWeight.w700,
|
|
726
1033
|
),
|
|
@@ -752,41 +1059,146 @@ class _TabsSecondaryPreview extends StatefulWidget {
|
|
|
752
1059
|
class _TabsSecondaryPreviewState extends State<_TabsSecondaryPreview> {
|
|
753
1060
|
int _index = 0;
|
|
754
1061
|
|
|
755
|
-
|
|
1062
|
+
// General tab
|
|
1063
|
+
final TextEditingController _siteController = TextEditingController();
|
|
1064
|
+
bool _cacheEnabled = true;
|
|
1065
|
+
bool _debugMode = false;
|
|
756
1066
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1067
|
+
// Notifications tab
|
|
1068
|
+
bool _pushEnabled = true;
|
|
1069
|
+
bool _emailEnabled = false;
|
|
1070
|
+
bool _weeklyDigest = true;
|
|
1071
|
+
|
|
1072
|
+
// Profile tab
|
|
1073
|
+
final TextEditingController _nameController =
|
|
1074
|
+
TextEditingController(text: 'Paulo Morales');
|
|
1075
|
+
final TextEditingController _emailController =
|
|
1076
|
+
TextEditingController(text: 'paulo@kasy.dev');
|
|
762
1077
|
|
|
763
1078
|
@override
|
|
764
|
-
|
|
765
|
-
|
|
1079
|
+
void dispose() {
|
|
1080
|
+
_siteController.dispose();
|
|
1081
|
+
_nameController.dispose();
|
|
1082
|
+
_emailController.dispose();
|
|
1083
|
+
super.dispose();
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
Widget _generalTab(BuildContext context) {
|
|
1087
|
+
return Column(
|
|
1088
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
1089
|
+
mainAxisSize: MainAxisSize.min,
|
|
1090
|
+
children: [
|
|
1091
|
+
Padding(
|
|
1092
|
+
padding: const EdgeInsets.all(KasySpacing.md),
|
|
1093
|
+
child: KasyTextField(
|
|
1094
|
+
controller: _siteController,
|
|
1095
|
+
variant: KasyTextFieldVariant.secondary,
|
|
1096
|
+
label: 'Site URL',
|
|
1097
|
+
hint: 'https://yoursite.com',
|
|
1098
|
+
),
|
|
1099
|
+
),
|
|
1100
|
+
_tabsDivider(context),
|
|
1101
|
+
KasyCheckboxTile(
|
|
1102
|
+
value: _cacheEnabled,
|
|
1103
|
+
onChanged: (v) => setState(() => _cacheEnabled = v),
|
|
1104
|
+
label: 'Enable caching',
|
|
1105
|
+
description: 'Speed up page loads with local cache',
|
|
1106
|
+
),
|
|
1107
|
+
_tabsDivider(context),
|
|
1108
|
+
KasyCheckboxTile(
|
|
1109
|
+
value: _debugMode,
|
|
1110
|
+
onChanged: (v) => setState(() => _debugMode = v),
|
|
1111
|
+
label: 'Debug mode',
|
|
1112
|
+
description: 'Show detailed logs and error messages',
|
|
1113
|
+
),
|
|
1114
|
+
],
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
766
1117
|
|
|
1118
|
+
Widget _notificationsTab(BuildContext context) {
|
|
1119
|
+
return Column(
|
|
1120
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
1121
|
+
mainAxisSize: MainAxisSize.min,
|
|
1122
|
+
children: [
|
|
1123
|
+
KasyCheckboxTile(
|
|
1124
|
+
value: _pushEnabled,
|
|
1125
|
+
onChanged: (v) => setState(() => _pushEnabled = v),
|
|
1126
|
+
label: 'Push notifications',
|
|
1127
|
+
description: 'Receive alerts directly on your device',
|
|
1128
|
+
),
|
|
1129
|
+
_tabsDivider(context),
|
|
1130
|
+
KasyCheckboxTile(
|
|
1131
|
+
value: _emailEnabled,
|
|
1132
|
+
onChanged: (v) => setState(() => _emailEnabled = v),
|
|
1133
|
+
label: 'Email notifications',
|
|
1134
|
+
description: 'Get updates delivered to your inbox',
|
|
1135
|
+
),
|
|
1136
|
+
_tabsDivider(context),
|
|
1137
|
+
KasyCheckboxTile(
|
|
1138
|
+
value: _weeklyDigest,
|
|
1139
|
+
onChanged: (v) => setState(() => _weeklyDigest = v),
|
|
1140
|
+
label: 'Weekly digest',
|
|
1141
|
+
description: 'A summary of activity sent every Monday',
|
|
1142
|
+
),
|
|
1143
|
+
],
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
Widget _profileTab(BuildContext context) {
|
|
1148
|
+
return Padding(
|
|
1149
|
+
padding: const EdgeInsets.all(KasySpacing.md),
|
|
1150
|
+
child: Column(
|
|
1151
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
1152
|
+
mainAxisSize: MainAxisSize.min,
|
|
1153
|
+
children: [
|
|
1154
|
+
KasyTextField(
|
|
1155
|
+
controller: _nameController,
|
|
1156
|
+
variant: KasyTextFieldVariant.secondary,
|
|
1157
|
+
label: 'Name',
|
|
1158
|
+
hint: 'Your full name',
|
|
1159
|
+
textInputAction: TextInputAction.next,
|
|
1160
|
+
),
|
|
1161
|
+
const SizedBox(height: KasySpacing.md),
|
|
1162
|
+
KasyTextField(
|
|
1163
|
+
controller: _emailController,
|
|
1164
|
+
variant: KasyTextFieldVariant.secondary,
|
|
1165
|
+
label: 'Email',
|
|
1166
|
+
hint: 'email@example.com',
|
|
1167
|
+
contentType: KasyTextFieldContentType.email,
|
|
1168
|
+
textInputAction: TextInputAction.done,
|
|
1169
|
+
),
|
|
1170
|
+
const SizedBox(height: KasySpacing.md),
|
|
1171
|
+
KasyButton(
|
|
1172
|
+
label: 'Save changes',
|
|
1173
|
+
onPressed: () {},
|
|
1174
|
+
),
|
|
1175
|
+
],
|
|
1176
|
+
),
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
@override
|
|
1181
|
+
Widget build(BuildContext context) {
|
|
767
1182
|
return Column(
|
|
768
1183
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
769
1184
|
mainAxisSize: MainAxisSize.min,
|
|
770
1185
|
children: [
|
|
771
1186
|
KasyTabs(
|
|
772
|
-
tabs:
|
|
1187
|
+
tabs: const ['General', 'Notifications', 'Profile'],
|
|
773
1188
|
selectedIndex: _index,
|
|
774
1189
|
onTabSelected: (i) => setState(() => _index = i),
|
|
775
1190
|
variant: KasyTabsVariant.secondary,
|
|
776
1191
|
),
|
|
777
1192
|
const SizedBox(height: KasySpacing.md),
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
height: 1.5,
|
|
788
|
-
),
|
|
789
|
-
),
|
|
1193
|
+
_tabsCard(
|
|
1194
|
+
context,
|
|
1195
|
+
IndexedStack(
|
|
1196
|
+
index: _index,
|
|
1197
|
+
children: [
|
|
1198
|
+
_tabFade(0, _index, _generalTab(context)),
|
|
1199
|
+
_tabFade(1, _index, _notificationsTab(context)),
|
|
1200
|
+
_tabFade(2, _index, _profileTab(context)),
|
|
1201
|
+
],
|
|
790
1202
|
),
|
|
791
1203
|
),
|
|
792
1204
|
const SizedBox(height: KasySpacing.lg),
|
|
@@ -794,7 +1206,7 @@ class _TabsSecondaryPreviewState extends State<_TabsSecondaryPreview> {
|
|
|
794
1206
|
Text(
|
|
795
1207
|
'WITH ICONS',
|
|
796
1208
|
style: context.textTheme.labelSmall?.copyWith(
|
|
797
|
-
color:
|
|
1209
|
+
color: context.colors.muted,
|
|
798
1210
|
letterSpacing: 1.2,
|
|
799
1211
|
fontWeight: FontWeight.w700,
|
|
800
1212
|
),
|
|
@@ -826,14 +1238,19 @@ class _TabsFillModePreview extends StatefulWidget {
|
|
|
826
1238
|
|
|
827
1239
|
class _TabsFillModePreviewState extends State<_TabsFillModePreview> {
|
|
828
1240
|
int _primaryIndex = 0;
|
|
829
|
-
int
|
|
1241
|
+
int _iconIndex = 0;
|
|
1242
|
+
|
|
1243
|
+
static const List<String> _primaryContent = [
|
|
1244
|
+
'Overview — see a high-level summary of your project status and recent activity.',
|
|
1245
|
+
'Analytics — track metrics like sessions, retention, and conversion rates over time.',
|
|
1246
|
+
'Settings — manage project configuration, integrations, and team access.',
|
|
1247
|
+
];
|
|
830
1248
|
|
|
831
1249
|
@override
|
|
832
1250
|
Widget build(BuildContext context) {
|
|
833
|
-
final
|
|
834
|
-
final TextStyle labelStyle =
|
|
1251
|
+
final TextStyle sectionLabel =
|
|
835
1252
|
context.textTheme.labelSmall?.copyWith(
|
|
836
|
-
color:
|
|
1253
|
+
color: context.colors.muted,
|
|
837
1254
|
letterSpacing: 1.2,
|
|
838
1255
|
fontWeight: FontWeight.w700,
|
|
839
1256
|
) ??
|
|
@@ -843,38 +1260,52 @@ class _TabsFillModePreviewState extends State<_TabsFillModePreview> {
|
|
|
843
1260
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
844
1261
|
mainAxisSize: MainAxisSize.min,
|
|
845
1262
|
children: [
|
|
846
|
-
Text('PRIMARY
|
|
1263
|
+
Text('PRIMARY FILL', style: sectionLabel),
|
|
847
1264
|
const SizedBox(height: KasySpacing.sm),
|
|
848
1265
|
KasyTabs(
|
|
849
|
-
tabs: const ['
|
|
1266
|
+
tabs: const ['Overview', 'Analytics', 'Settings'],
|
|
850
1267
|
selectedIndex: _primaryIndex,
|
|
851
1268
|
onTabSelected: (i) => setState(() => _primaryIndex = i),
|
|
852
1269
|
mode: KasyTabsMode.fill,
|
|
853
1270
|
),
|
|
854
|
-
const SizedBox(height: KasySpacing.lg),
|
|
855
|
-
Text('SECONDARY + FILL', style: labelStyle),
|
|
856
|
-
const SizedBox(height: KasySpacing.sm),
|
|
857
|
-
KasyTabs(
|
|
858
|
-
tabs: const ['Tab 1', 'Tab 2', 'Tab 3'],
|
|
859
|
-
selectedIndex: _secondaryIndex,
|
|
860
|
-
onTabSelected: (i) => setState(() => _secondaryIndex = i),
|
|
861
|
-
variant: KasyTabsVariant.secondary,
|
|
862
|
-
mode: KasyTabsMode.fill,
|
|
863
|
-
),
|
|
864
1271
|
const SizedBox(height: KasySpacing.md),
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
key: ValueKey('fill_$_secondaryIndex'),
|
|
1272
|
+
_tabsCard(
|
|
1273
|
+
context,
|
|
1274
|
+
Padding(
|
|
869
1275
|
padding: const EdgeInsets.all(KasySpacing.md),
|
|
870
|
-
child:
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1276
|
+
child: IndexedStack(
|
|
1277
|
+
index: _primaryIndex,
|
|
1278
|
+
children: List.generate(
|
|
1279
|
+
_primaryContent.length,
|
|
1280
|
+
(i) => _tabFade(
|
|
1281
|
+
i,
|
|
1282
|
+
_primaryIndex,
|
|
1283
|
+
Text(
|
|
1284
|
+
_primaryContent[i],
|
|
1285
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
1286
|
+
color: context.colors.onSurface.withValues(alpha: 0.75),
|
|
1287
|
+
height: 1.5,
|
|
1288
|
+
),
|
|
1289
|
+
),
|
|
1290
|
+
),
|
|
874
1291
|
),
|
|
875
1292
|
),
|
|
876
1293
|
),
|
|
877
1294
|
),
|
|
1295
|
+
const SizedBox(height: KasySpacing.lg),
|
|
1296
|
+
Text('SECONDARY FILL + ICONS', style: sectionLabel),
|
|
1297
|
+
const SizedBox(height: KasySpacing.sm),
|
|
1298
|
+
KasyTabs.items(
|
|
1299
|
+
items: const [
|
|
1300
|
+
KasyTabItem(label: 'Home', icon: KasyIcons.home),
|
|
1301
|
+
KasyTabItem(label: 'Profile', icon: KasyIcons.person),
|
|
1302
|
+
KasyTabItem(label: 'Settings', icon: KasyIcons.settings),
|
|
1303
|
+
],
|
|
1304
|
+
selectedIndex: _iconIndex,
|
|
1305
|
+
onTabSelected: (i) => setState(() => _iconIndex = i),
|
|
1306
|
+
variant: KasyTabsVariant.secondary,
|
|
1307
|
+
mode: KasyTabsMode.fill,
|
|
1308
|
+
),
|
|
878
1309
|
],
|
|
879
1310
|
);
|
|
880
1311
|
}
|
|
@@ -1199,7 +1630,7 @@ Widget _buildAvatarGroupVariant(BuildContext context) {
|
|
|
1199
1630
|
// Shared gradient avatars list
|
|
1200
1631
|
final List<Widget> gradients = [
|
|
1201
1632
|
KasyAvatar.gradientFill(size: KasyAvatarSize.small, diameter: d, showShadow: false, gradient: KasyAvatarGradients.blue),
|
|
1202
|
-
KasyAvatar.gradientFill(size: KasyAvatarSize.small, diameter: d, showShadow: false, gradient: KasyAvatarGradients.
|
|
1633
|
+
KasyAvatar.gradientFill(size: KasyAvatarSize.small, diameter: d, showShadow: false, gradient: KasyAvatarGradients.sky),
|
|
1203
1634
|
KasyAvatar.gradientFill(size: KasyAvatarSize.small, diameter: d, showShadow: false, gradient: KasyAvatarGradients.orange),
|
|
1204
1635
|
KasyAvatar.gradientFill(size: KasyAvatarSize.small, diameter: d, showShadow: false, gradient: KasyAvatarGradients.red),
|
|
1205
1636
|
KasyAvatar.gradientFill(size: KasyAvatarSize.small, diameter: d, showShadow: false, gradient: KasyAvatarGradients.silver),
|
|
@@ -2096,7 +2527,7 @@ Widget _buildBadgeDefault(BuildContext context) {
|
|
|
2096
2527
|
child: KasyAvatar.gradientFill(
|
|
2097
2528
|
size: KasyAvatarSize.medium,
|
|
2098
2529
|
showShadow: false,
|
|
2099
|
-
gradient: KasyAvatarGradients.
|
|
2530
|
+
gradient: KasyAvatarGradients.sky,
|
|
2100
2531
|
),
|
|
2101
2532
|
),
|
|
2102
2533
|
const SizedBox(height: KasySpacing.sm),
|
|
@@ -4028,9 +4459,9 @@ class _BadgeColorsPreview extends StatelessWidget {
|
|
|
4028
4459
|
KasyBadgeTone.warning,
|
|
4029
4460
|
KasyBadgeTone.danger,
|
|
4030
4461
|
];
|
|
4031
|
-
final List<
|
|
4462
|
+
final List<KasyAvatarGradientData> gradients = [
|
|
4032
4463
|
KasyAvatarGradients.blue,
|
|
4033
|
-
KasyAvatarGradients.
|
|
4464
|
+
KasyAvatarGradients.sky,
|
|
4034
4465
|
KasyAvatarGradients.purple,
|
|
4035
4466
|
KasyAvatarGradients.orange,
|
|
4036
4467
|
KasyAvatarGradients.red,
|
|
@@ -4153,9 +4584,9 @@ class _BadgePlacementsPreview extends StatelessWidget {
|
|
|
4153
4584
|
class _BadgeDotPreview extends StatelessWidget {
|
|
4154
4585
|
const _BadgeDotPreview();
|
|
4155
4586
|
|
|
4156
|
-
static const List<(KasyBadgeTone,
|
|
4587
|
+
static const List<(KasyBadgeTone, KasyAvatarGradientData)> _items = [
|
|
4157
4588
|
(KasyBadgeTone.neutral, KasyAvatarGradients.blue),
|
|
4158
|
-
(KasyBadgeTone.primary, KasyAvatarGradients.
|
|
4589
|
+
(KasyBadgeTone.primary, KasyAvatarGradients.sky),
|
|
4159
4590
|
(KasyBadgeTone.success, KasyAvatarGradients.purple),
|
|
4160
4591
|
(KasyBadgeTone.warning, KasyAvatarGradients.red),
|
|
4161
4592
|
(KasyBadgeTone.danger, KasyAvatarGradients.orange),
|
|
@@ -4197,9 +4628,9 @@ class _BadgeVariantsPreview extends StatelessWidget {
|
|
|
4197
4628
|
KasyBadgeTone.danger,
|
|
4198
4629
|
];
|
|
4199
4630
|
|
|
4200
|
-
static const List<
|
|
4631
|
+
static const List<KasyAvatarGradientData> _gradients = [
|
|
4201
4632
|
KasyAvatarGradients.blue,
|
|
4202
|
-
KasyAvatarGradients.
|
|
4633
|
+
KasyAvatarGradients.sky,
|
|
4203
4634
|
KasyAvatarGradients.purple,
|
|
4204
4635
|
KasyAvatarGradients.red,
|
|
4205
4636
|
KasyAvatarGradients.orange,
|
|
@@ -4332,7 +4763,7 @@ class _BadgeWithContentPreview extends StatelessWidget {
|
|
|
4332
4763
|
class _BadgeContentItem extends StatelessWidget {
|
|
4333
4764
|
final String label;
|
|
4334
4765
|
final KasyBadge badge;
|
|
4335
|
-
final
|
|
4766
|
+
final KasyAvatarGradientData gradient;
|
|
4336
4767
|
|
|
4337
4768
|
const _BadgeContentItem({
|
|
4338
4769
|
required this.label,
|
|
@@ -8119,3 +8550,444 @@ class _SwipePreviewTile extends StatelessWidget {
|
|
|
8119
8550
|
);
|
|
8120
8551
|
}
|
|
8121
8552
|
}
|
|
8553
|
+
|
|
8554
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8555
|
+
// DatePicker — Presentations
|
|
8556
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8557
|
+
|
|
8558
|
+
/// Maps the running app's locale onto a [KasyDatePickerLocale] preset so the
|
|
8559
|
+
/// preview demonstrates the same regional defaults (month names, week start,
|
|
8560
|
+
/// date format) the user would see in their own app.
|
|
8561
|
+
KasyDatePickerLocale _datePickerLocaleForApp(BuildContext context) {
|
|
8562
|
+
switch (Localizations.localeOf(context).languageCode) {
|
|
8563
|
+
case 'es':
|
|
8564
|
+
return KasyDatePickerLocale.es;
|
|
8565
|
+
case 'pt':
|
|
8566
|
+
return KasyDatePickerLocale.pt;
|
|
8567
|
+
default:
|
|
8568
|
+
return KasyDatePickerLocale.en;
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
|
|
8572
|
+
Widget _buildDatePickerPresentations(BuildContext context) =>
|
|
8573
|
+
const _DatePickerPresentationsPreview();
|
|
8574
|
+
|
|
8575
|
+
class _DatePickerPresentationsPreview extends StatefulWidget {
|
|
8576
|
+
const _DatePickerPresentationsPreview();
|
|
8577
|
+
|
|
8578
|
+
@override
|
|
8579
|
+
State<_DatePickerPresentationsPreview> createState() =>
|
|
8580
|
+
_DatePickerPresentationsPreviewState();
|
|
8581
|
+
}
|
|
8582
|
+
|
|
8583
|
+
class _DatePickerPresentationsPreviewState
|
|
8584
|
+
extends State<_DatePickerPresentationsPreview> {
|
|
8585
|
+
DateTime? _popoverDate;
|
|
8586
|
+
DateTime? _dialogDate;
|
|
8587
|
+
DateTime? _sheetDate;
|
|
8588
|
+
|
|
8589
|
+
@override
|
|
8590
|
+
Widget build(BuildContext context) {
|
|
8591
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8592
|
+
return Column(
|
|
8593
|
+
mainAxisSize: MainAxisSize.min,
|
|
8594
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8595
|
+
children: [
|
|
8596
|
+
KasyDatePicker(
|
|
8597
|
+
label: 'Popover',
|
|
8598
|
+
placeholder: 'Choose a date',
|
|
8599
|
+
value: _popoverDate,
|
|
8600
|
+
onChanged: (d) => setState(() => _popoverDate = d),
|
|
8601
|
+
locale: locale,
|
|
8602
|
+
),
|
|
8603
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8604
|
+
KasyDatePicker(
|
|
8605
|
+
label: 'Dialog',
|
|
8606
|
+
placeholder: 'Choose a date',
|
|
8607
|
+
value: _dialogDate,
|
|
8608
|
+
onChanged: (d) => setState(() => _dialogDate = d),
|
|
8609
|
+
presentation: KasyDatePickerPresentation.dialog,
|
|
8610
|
+
locale: locale,
|
|
8611
|
+
),
|
|
8612
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8613
|
+
KasyDatePicker(
|
|
8614
|
+
label: 'Bottom sheet',
|
|
8615
|
+
placeholder: 'Choose a date',
|
|
8616
|
+
value: _sheetDate,
|
|
8617
|
+
onChanged: (d) => setState(() => _sheetDate = d),
|
|
8618
|
+
presentation: KasyDatePickerPresentation.bottomSheet,
|
|
8619
|
+
locale: locale,
|
|
8620
|
+
),
|
|
8621
|
+
],
|
|
8622
|
+
);
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
|
|
8626
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8627
|
+
// DatePicker — Range selection (Airbnb-style start + end)
|
|
8628
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8629
|
+
|
|
8630
|
+
Widget _buildDatePickerRange(BuildContext context) =>
|
|
8631
|
+
const _DatePickerRangePreview();
|
|
8632
|
+
|
|
8633
|
+
class _DatePickerRangePreview extends StatefulWidget {
|
|
8634
|
+
const _DatePickerRangePreview();
|
|
8635
|
+
|
|
8636
|
+
@override
|
|
8637
|
+
State<_DatePickerRangePreview> createState() =>
|
|
8638
|
+
_DatePickerRangePreviewState();
|
|
8639
|
+
}
|
|
8640
|
+
|
|
8641
|
+
class _DatePickerRangePreviewState extends State<_DatePickerRangePreview> {
|
|
8642
|
+
KasyDateRange? _stay;
|
|
8643
|
+
KasyDateRange? _project;
|
|
8644
|
+
|
|
8645
|
+
@override
|
|
8646
|
+
Widget build(BuildContext context) {
|
|
8647
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8648
|
+
return Column(
|
|
8649
|
+
mainAxisSize: MainAxisSize.min,
|
|
8650
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8651
|
+
children: [
|
|
8652
|
+
// Classic stay-style picker: tap a check-in date, then a check-out
|
|
8653
|
+
// date. The calendar keeps the start highlighted while waiting for
|
|
8654
|
+
// the end, then auto-closes after the second tap.
|
|
8655
|
+
KasyDatePicker(
|
|
8656
|
+
label: 'Stay',
|
|
8657
|
+
placeholder: 'Check-in -> Check-out',
|
|
8658
|
+
description: 'Tap a start date, then an end date.',
|
|
8659
|
+
selectionMode: KasyDateSelectionMode.range,
|
|
8660
|
+
range: _stay,
|
|
8661
|
+
onRangeChanged: (r) => setState(() => _stay = r),
|
|
8662
|
+
locale: locale,
|
|
8663
|
+
),
|
|
8664
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8665
|
+
// Dialog presentation of the same range mode — handy on smaller
|
|
8666
|
+
// screens where the popover may not have enough room.
|
|
8667
|
+
KasyDatePicker(
|
|
8668
|
+
label: 'Project window (dialog)',
|
|
8669
|
+
placeholder: 'Pick start -> end',
|
|
8670
|
+
selectionMode: KasyDateSelectionMode.range,
|
|
8671
|
+
range: _project,
|
|
8672
|
+
onRangeChanged: (r) => setState(() => _project = r),
|
|
8673
|
+
presentation: KasyDatePickerPresentation.dialog,
|
|
8674
|
+
locale: locale,
|
|
8675
|
+
),
|
|
8676
|
+
],
|
|
8677
|
+
);
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
|
|
8681
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8682
|
+
// DatePicker — Multi-month (2 or 3 months side by side, Airbnb desktop look)
|
|
8683
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8684
|
+
|
|
8685
|
+
Widget _buildDatePickerMultiMonth(BuildContext context) =>
|
|
8686
|
+
const _DatePickerMultiMonthPreview();
|
|
8687
|
+
|
|
8688
|
+
class _DatePickerMultiMonthPreview extends StatefulWidget {
|
|
8689
|
+
const _DatePickerMultiMonthPreview();
|
|
8690
|
+
|
|
8691
|
+
@override
|
|
8692
|
+
State<_DatePickerMultiMonthPreview> createState() =>
|
|
8693
|
+
_DatePickerMultiMonthPreviewState();
|
|
8694
|
+
}
|
|
8695
|
+
|
|
8696
|
+
class _DatePickerMultiMonthPreviewState
|
|
8697
|
+
extends State<_DatePickerMultiMonthPreview> {
|
|
8698
|
+
KasyDateRange? _stay;
|
|
8699
|
+
DateTime? _single;
|
|
8700
|
+
|
|
8701
|
+
@override
|
|
8702
|
+
Widget build(BuildContext context) {
|
|
8703
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8704
|
+
return Column(
|
|
8705
|
+
mainAxisSize: MainAxisSize.min,
|
|
8706
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8707
|
+
children: [
|
|
8708
|
+
// 3-month layout combined with range selection — the classic
|
|
8709
|
+
// Airbnb / Booking desktop pattern for picking a stay.
|
|
8710
|
+
KasyDatePicker(
|
|
8711
|
+
label: '3 months + range',
|
|
8712
|
+
placeholder: 'Check-in -> Check-out',
|
|
8713
|
+
description: 'Pass monthsToShow: 3 to opt in. Best on wide canvases.',
|
|
8714
|
+
selectionMode: KasyDateSelectionMode.range,
|
|
8715
|
+
range: _stay,
|
|
8716
|
+
onRangeChanged: (r) => setState(() => _stay = r),
|
|
8717
|
+
monthsToShow: 3,
|
|
8718
|
+
locale: locale,
|
|
8719
|
+
),
|
|
8720
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8721
|
+
// 2-month layout in single-date mode — useful when there's enough
|
|
8722
|
+
// horizontal room but you still want one tap to commit.
|
|
8723
|
+
KasyDatePicker(
|
|
8724
|
+
label: '2 months + single date',
|
|
8725
|
+
placeholder: 'Pick a date',
|
|
8726
|
+
value: _single,
|
|
8727
|
+
onChanged: (d) => setState(() => _single = d),
|
|
8728
|
+
monthsToShow: 2,
|
|
8729
|
+
locale: locale,
|
|
8730
|
+
),
|
|
8731
|
+
],
|
|
8732
|
+
);
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
|
|
8736
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8737
|
+
// DatePicker — Variants (filled, no backdrop, no suffix, no focus border)
|
|
8738
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8739
|
+
|
|
8740
|
+
Widget _buildDatePickerVariants(BuildContext context) =>
|
|
8741
|
+
const _DatePickerVariantsPreview();
|
|
8742
|
+
|
|
8743
|
+
class _DatePickerVariantsPreview extends StatefulWidget {
|
|
8744
|
+
const _DatePickerVariantsPreview();
|
|
8745
|
+
|
|
8746
|
+
@override
|
|
8747
|
+
State<_DatePickerVariantsPreview> createState() =>
|
|
8748
|
+
_DatePickerVariantsPreviewState();
|
|
8749
|
+
}
|
|
8750
|
+
|
|
8751
|
+
class _DatePickerVariantsPreviewState
|
|
8752
|
+
extends State<_DatePickerVariantsPreview> {
|
|
8753
|
+
DateTime? _filledDate;
|
|
8754
|
+
DateTime? _noBarrierDate;
|
|
8755
|
+
DateTime? _noSuffixDate;
|
|
8756
|
+
DateTime? _noFocusDate;
|
|
8757
|
+
|
|
8758
|
+
@override
|
|
8759
|
+
Widget build(BuildContext context) {
|
|
8760
|
+
final KasyDatePickerLocale locale = _datePickerLocaleForApp(context);
|
|
8761
|
+
return Column(
|
|
8762
|
+
mainAxisSize: MainAxisSize.min,
|
|
8763
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8764
|
+
children: [
|
|
8765
|
+
// Filled / flat surface — matches KasyTextField.secondary, useful on
|
|
8766
|
+
// backgrounds where the default drop shadow feels heavy.
|
|
8767
|
+
KasyDatePicker(
|
|
8768
|
+
label: 'Filled (no shadow)',
|
|
8769
|
+
placeholder: 'Choose a date',
|
|
8770
|
+
value: _filledDate,
|
|
8771
|
+
onChanged: (d) => setState(() => _filledDate = d),
|
|
8772
|
+
variant: KasyTextFieldVariant.secondary,
|
|
8773
|
+
locale: locale,
|
|
8774
|
+
),
|
|
8775
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8776
|
+
// Popover without the dimmed backdrop — feels lighter / tooltip-like.
|
|
8777
|
+
KasyDatePicker(
|
|
8778
|
+
label: 'No backdrop',
|
|
8779
|
+
placeholder: 'Choose a date',
|
|
8780
|
+
description: 'Popover floats over the page without a dimmed scrim.',
|
|
8781
|
+
value: _noBarrierDate,
|
|
8782
|
+
onChanged: (d) => setState(() => _noBarrierDate = d),
|
|
8783
|
+
showBarrier: false,
|
|
8784
|
+
locale: locale,
|
|
8785
|
+
),
|
|
8786
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8787
|
+
// No calendar icon in the suffix slot.
|
|
8788
|
+
KasyDatePicker(
|
|
8789
|
+
label: 'No suffix icon',
|
|
8790
|
+
placeholder: 'Choose a date',
|
|
8791
|
+
value: _noSuffixDate,
|
|
8792
|
+
onChanged: (d) => setState(() => _noSuffixDate = d),
|
|
8793
|
+
showSuffix: false,
|
|
8794
|
+
locale: locale,
|
|
8795
|
+
),
|
|
8796
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8797
|
+
// Disables the blue focus outline while the calendar is open — quieter
|
|
8798
|
+
// affordance for very dense forms.
|
|
8799
|
+
KasyDatePicker(
|
|
8800
|
+
label: 'No focus border',
|
|
8801
|
+
placeholder: 'Choose a date',
|
|
8802
|
+
value: _noFocusDate,
|
|
8803
|
+
onChanged: (d) => setState(() => _noFocusDate = d),
|
|
8804
|
+
focusBorder: false,
|
|
8805
|
+
locale: locale,
|
|
8806
|
+
),
|
|
8807
|
+
],
|
|
8808
|
+
);
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8813
|
+
// DatePicker — Date formats
|
|
8814
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8815
|
+
|
|
8816
|
+
Widget _buildDatePickerFormats(BuildContext context) =>
|
|
8817
|
+
const _DatePickerFormatsPreview();
|
|
8818
|
+
|
|
8819
|
+
const List<String> _kEnglishMonthsLong = [
|
|
8820
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
8821
|
+
'July', 'August', 'September', 'October', 'November', 'December',
|
|
8822
|
+
];
|
|
8823
|
+
|
|
8824
|
+
String _longUsFormat(DateTime d) =>
|
|
8825
|
+
'${_kEnglishMonthsLong[d.month - 1]} ${d.day}, ${d.year}';
|
|
8826
|
+
|
|
8827
|
+
class _DatePickerFormatsPreview extends StatefulWidget {
|
|
8828
|
+
const _DatePickerFormatsPreview();
|
|
8829
|
+
|
|
8830
|
+
@override
|
|
8831
|
+
State<_DatePickerFormatsPreview> createState() =>
|
|
8832
|
+
_DatePickerFormatsPreviewState();
|
|
8833
|
+
}
|
|
8834
|
+
|
|
8835
|
+
// Each tab maps to one preset (or to the "Auto" / "Custom" entries). The
|
|
8836
|
+
// `format` field is null for the two non-preset entries; the build method
|
|
8837
|
+
// branches on those.
|
|
8838
|
+
class _DatePickerFormatOption {
|
|
8839
|
+
const _DatePickerFormatOption({
|
|
8840
|
+
required this.label,
|
|
8841
|
+
required this.description,
|
|
8842
|
+
this.format,
|
|
8843
|
+
this.useAppLocale = false,
|
|
8844
|
+
this.customFormat,
|
|
8845
|
+
});
|
|
8846
|
+
|
|
8847
|
+
final String label;
|
|
8848
|
+
final String description;
|
|
8849
|
+
final KasyDateFormat? format;
|
|
8850
|
+
final bool useAppLocale;
|
|
8851
|
+
final String Function(DateTime)? customFormat;
|
|
8852
|
+
}
|
|
8853
|
+
|
|
8854
|
+
const List<_DatePickerFormatOption> _kDatePickerFormatOptions = [
|
|
8855
|
+
_DatePickerFormatOption(
|
|
8856
|
+
label: 'Auto',
|
|
8857
|
+
description: 'Follows the active app language (US, South American, …).',
|
|
8858
|
+
useAppLocale: true,
|
|
8859
|
+
),
|
|
8860
|
+
_DatePickerFormatOption(
|
|
8861
|
+
label: 'US',
|
|
8862
|
+
description: 'MM / DD / YYYY — United States.',
|
|
8863
|
+
format: KasyDateFormat.us,
|
|
8864
|
+
),
|
|
8865
|
+
_DatePickerFormatOption(
|
|
8866
|
+
label: 'South American',
|
|
8867
|
+
description: 'DD / MM / YYYY — South America, UK, Spain, Portugal.',
|
|
8868
|
+
format: KasyDateFormat.southAmerican,
|
|
8869
|
+
),
|
|
8870
|
+
_DatePickerFormatOption(
|
|
8871
|
+
label: 'European',
|
|
8872
|
+
description: 'DD.MM.YYYY — Germany, Austria, Switzerland.',
|
|
8873
|
+
format: KasyDateFormat.european,
|
|
8874
|
+
),
|
|
8875
|
+
_DatePickerFormatOption(
|
|
8876
|
+
label: 'ISO 8601',
|
|
8877
|
+
description: 'YYYY-MM-DD — international standard.',
|
|
8878
|
+
format: KasyDateFormat.iso,
|
|
8879
|
+
),
|
|
8880
|
+
_DatePickerFormatOption(
|
|
8881
|
+
label: 'East Asian',
|
|
8882
|
+
description: 'YYYY / MM / DD — Japan, China, Korea.',
|
|
8883
|
+
format: KasyDateFormat.eastAsian,
|
|
8884
|
+
),
|
|
8885
|
+
_DatePickerFormatOption(
|
|
8886
|
+
label: 'Custom',
|
|
8887
|
+
description: 'Any layout via the formatDate callback (long form here).',
|
|
8888
|
+
customFormat: _longUsFormat,
|
|
8889
|
+
),
|
|
8890
|
+
];
|
|
8891
|
+
|
|
8892
|
+
class _DatePickerFormatsPreviewState extends State<_DatePickerFormatsPreview> {
|
|
8893
|
+
int _tab = 0;
|
|
8894
|
+
DateTime _date = DateTime(2026, 5, 12);
|
|
8895
|
+
|
|
8896
|
+
@override
|
|
8897
|
+
Widget build(BuildContext context) {
|
|
8898
|
+
final _DatePickerFormatOption option = _kDatePickerFormatOptions[_tab];
|
|
8899
|
+
final KasyDatePickerLocale appLocale = _datePickerLocaleForApp(context);
|
|
8900
|
+
|
|
8901
|
+
return Column(
|
|
8902
|
+
mainAxisSize: MainAxisSize.min,
|
|
8903
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8904
|
+
children: [
|
|
8905
|
+
// Secondary (underline) variant keeps the format switcher visually
|
|
8906
|
+
// light on phones — pill tabs were too bulky for a switch with many
|
|
8907
|
+
// labels and hug mode lets the row scroll horizontally when needed.
|
|
8908
|
+
KasyTabs(
|
|
8909
|
+
tabs: [
|
|
8910
|
+
for (final o in _kDatePickerFormatOptions) o.label,
|
|
8911
|
+
],
|
|
8912
|
+
selectedIndex: _tab,
|
|
8913
|
+
onTabSelected: (i) => setState(() => _tab = i),
|
|
8914
|
+
variant: KasyTabsVariant.secondary,
|
|
8915
|
+
),
|
|
8916
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8917
|
+
KasyDatePicker(
|
|
8918
|
+
label: option.label,
|
|
8919
|
+
description: option.description,
|
|
8920
|
+
value: _date,
|
|
8921
|
+
onChanged: (d) => setState(() => _date = d),
|
|
8922
|
+
locale: option.useAppLocale ? appLocale : KasyDatePickerLocale.en,
|
|
8923
|
+
dateFormat: option.format,
|
|
8924
|
+
formatDate: option.customFormat,
|
|
8925
|
+
),
|
|
8926
|
+
],
|
|
8927
|
+
);
|
|
8928
|
+
}
|
|
8929
|
+
}
|
|
8930
|
+
|
|
8931
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8932
|
+
// DatePicker — Field states
|
|
8933
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8934
|
+
|
|
8935
|
+
Widget _buildDatePickerFieldStates(BuildContext context) =>
|
|
8936
|
+
const _DatePickerFieldStatesPreview();
|
|
8937
|
+
|
|
8938
|
+
const List<String> _kEnglishMonthAbbrev = [
|
|
8939
|
+
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
8940
|
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
|
8941
|
+
];
|
|
8942
|
+
|
|
8943
|
+
String _abbrevFormat(DateTime d) =>
|
|
8944
|
+
'${_kEnglishMonthAbbrev[d.month - 1]} ${d.day}, ${d.year}';
|
|
8945
|
+
|
|
8946
|
+
class _DatePickerFieldStatesPreview extends StatefulWidget {
|
|
8947
|
+
const _DatePickerFieldStatesPreview();
|
|
8948
|
+
|
|
8949
|
+
@override
|
|
8950
|
+
State<_DatePickerFieldStatesPreview> createState() =>
|
|
8951
|
+
_DatePickerFieldStatesPreviewState();
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8954
|
+
class _DatePickerFieldStatesPreviewState
|
|
8955
|
+
extends State<_DatePickerFieldStatesPreview> {
|
|
8956
|
+
DateTime? _deadline;
|
|
8957
|
+
DateTime? _shipDate;
|
|
8958
|
+
final DateTime _disabledDate = DateTime(2026, 6, 15);
|
|
8959
|
+
|
|
8960
|
+
@override
|
|
8961
|
+
Widget build(BuildContext context) {
|
|
8962
|
+
return Column(
|
|
8963
|
+
mainAxisSize: MainAxisSize.min,
|
|
8964
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
8965
|
+
children: [
|
|
8966
|
+
KasyDatePicker(
|
|
8967
|
+
label: 'Deadline',
|
|
8968
|
+
placeholder: 'Select a deadline',
|
|
8969
|
+
showRequiredIndicator: true,
|
|
8970
|
+
description: 'Required for the project timeline.',
|
|
8971
|
+
value: _deadline,
|
|
8972
|
+
onChanged: (d) => setState(() => _deadline = d),
|
|
8973
|
+
),
|
|
8974
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8975
|
+
KasyDatePicker(
|
|
8976
|
+
label: 'Ship date',
|
|
8977
|
+
placeholder: 'Choose a date',
|
|
8978
|
+
errorText: 'Please select a valid ship date.',
|
|
8979
|
+
value: _shipDate,
|
|
8980
|
+
onChanged: (d) => setState(() => _shipDate = d),
|
|
8981
|
+
),
|
|
8982
|
+
const SizedBox(height: KasySpacing.lg),
|
|
8983
|
+
KasyDatePicker(
|
|
8984
|
+
label: 'Disabled date',
|
|
8985
|
+
enabled: false,
|
|
8986
|
+
value: _disabledDate,
|
|
8987
|
+
formatDate: _abbrevFormat,
|
|
8988
|
+
description: 'The date cannot be changed when the order is locked.',
|
|
8989
|
+
),
|
|
8990
|
+
],
|
|
8991
|
+
);
|
|
8992
|
+
}
|
|
8993
|
+
}
|