kasy-cli 1.34.0 → 1.36.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/README.md +1 -1
- package/bin/kasy.js +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +18 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_sidebar.dart +215 -178
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +107 -41
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +226 -105
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
|
@@ -12,6 +12,7 @@ import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
|
12
12
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
13
13
|
import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
|
|
14
14
|
import 'package:kasy_kit/core/widgets/kasy_pressable_depth.dart';
|
|
15
|
+
import 'package:kasy_kit/environments.dart';
|
|
15
16
|
import 'package:kasy_kit/features/authentication/providers/models/email.dart';
|
|
16
17
|
import 'package:kasy_kit/features/authentication/providers/models/password.dart';
|
|
17
18
|
import 'package:kasy_kit/features/authentication/providers/models/signin_state.dart';
|
|
@@ -152,6 +153,21 @@ class SigninPage extends ConsumerWidget {
|
|
|
152
153
|
const SocialSeparator(),
|
|
153
154
|
const SizedBox(height: KasySpacing.md),
|
|
154
155
|
const _SocialSigninRow(),
|
|
156
|
+
// Guest path: only on native in anonymous mode (on web the
|
|
157
|
+
// app requires sign-in), and only when there's no identity
|
|
158
|
+
// yet (post-logout / first run). A user who is already an
|
|
159
|
+
// anonymous guest opened sign-in to UPGRADE their account
|
|
160
|
+
// (link email/social), so "continue without account" would
|
|
161
|
+
// be nonsense for them — hide it.
|
|
162
|
+
if (!kIsWeb &&
|
|
163
|
+
user.idOrNull == null &&
|
|
164
|
+
ref
|
|
165
|
+
.watch(environmentProvider)
|
|
166
|
+
.authenticationMode ==
|
|
167
|
+
AuthenticationMode.anonymous) ...[
|
|
168
|
+
const SizedBox(height: KasySpacing.md),
|
|
169
|
+
const _GuestContinueButton(),
|
|
170
|
+
],
|
|
155
171
|
],
|
|
156
172
|
),
|
|
157
173
|
),
|
|
@@ -222,6 +238,49 @@ class _SignupPrompt extends StatelessWidget {
|
|
|
222
238
|
}
|
|
223
239
|
}
|
|
224
240
|
|
|
241
|
+
/// "Continue as guest" — creates the anonymous account on demand and lets the
|
|
242
|
+
/// router redirect carry the user into the app. Shown only on native in
|
|
243
|
+
/// anonymous mode (see the build condition in [SigninPage]).
|
|
244
|
+
class _GuestContinueButton extends ConsumerStatefulWidget {
|
|
245
|
+
const _GuestContinueButton();
|
|
246
|
+
|
|
247
|
+
@override
|
|
248
|
+
ConsumerState<_GuestContinueButton> createState() =>
|
|
249
|
+
_GuestContinueButtonState();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
class _GuestContinueButtonState extends ConsumerState<_GuestContinueButton> {
|
|
253
|
+
bool _loading = false;
|
|
254
|
+
|
|
255
|
+
Future<void> _continue() async {
|
|
256
|
+
setState(() => _loading = true);
|
|
257
|
+
try {
|
|
258
|
+
await ref.read(userStateNotifierProvider.notifier).continueAsGuest();
|
|
259
|
+
// Success: the router redirect navigates to home reactively — nothing
|
|
260
|
+
// else to do here. The widget is disposed as we leave the page.
|
|
261
|
+
} catch (_) {
|
|
262
|
+
if (!mounted) return;
|
|
263
|
+
setState(() => _loading = false);
|
|
264
|
+
showKasyToast(
|
|
265
|
+
context,
|
|
266
|
+
title: t.auth.signin.error_title,
|
|
267
|
+
tone: KasyToastTone.danger,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@override
|
|
273
|
+
Widget build(BuildContext context) {
|
|
274
|
+
return KasyButton(
|
|
275
|
+
label: t.auth.signin.continue_without,
|
|
276
|
+
variant: KasyButtonVariant.tertiary,
|
|
277
|
+
expand: true,
|
|
278
|
+
isLoading: _loading,
|
|
279
|
+
onPressed: _loading ? null : _continue,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
225
284
|
class _SocialSigninRow extends ConsumerWidget {
|
|
226
285
|
const _SocialSigninRow();
|
|
227
286
|
|
|
@@ -79,8 +79,8 @@ class AuthCardScaffold extends StatelessWidget {
|
|
|
79
79
|
Text(
|
|
80
80
|
title,
|
|
81
81
|
textAlign: TextAlign.center,
|
|
82
|
-
style: context.
|
|
83
|
-
|
|
82
|
+
style: context.kasyTextTheme.pageTitle.copyWith(
|
|
83
|
+
color: context.colors.onSurface,
|
|
84
84
|
),
|
|
85
85
|
),
|
|
86
86
|
const SizedBox(height: KasySpacing.xs),
|
package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart
CHANGED
|
@@ -42,8 +42,8 @@ class RecoverPasswordSent extends StatelessWidget {
|
|
|
42
42
|
Text(
|
|
43
43
|
t.recover_password_result.title,
|
|
44
44
|
textAlign: TextAlign.center,
|
|
45
|
-
style: context.
|
|
46
|
-
|
|
45
|
+
style: context.kasyTextTheme.pageTitle.copyWith(
|
|
46
|
+
color: context.colors.onSurface,
|
|
47
47
|
),
|
|
48
48
|
),
|
|
49
49
|
const SizedBox(height: KasySpacing.smd),
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import 'dart:ui' show ImageFilter;
|
|
2
2
|
|
|
3
3
|
import 'package:flutter/material.dart';
|
|
4
|
+
import 'package:google_fonts/google_fonts.dart';
|
|
4
5
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
6
|
+
import 'package:kasy_kit/components/kasy_tabs.dart';
|
|
5
7
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
8
|
+
import 'package:kasy_kit/core/theme/type_scale.dart';
|
|
9
|
+
import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
6
10
|
|
|
7
11
|
class DesignSystemPage extends StatelessWidget {
|
|
8
12
|
const DesignSystemPage({super.key});
|
|
@@ -576,12 +580,10 @@ class _VariantList extends StatelessWidget {
|
|
|
576
580
|
|
|
577
581
|
@override
|
|
578
582
|
Widget build(BuildContext context) {
|
|
579
|
-
final KasyColors light = KasyColors.light();
|
|
580
|
-
final KasyColors dark = KasyColors.dark();
|
|
581
583
|
return _TokenCard(
|
|
582
584
|
children: [
|
|
583
585
|
for (int i = 0; i < variants.length; i++) ...[
|
|
584
|
-
_VariantRow(variant: variants[i]
|
|
586
|
+
_VariantRow(variant: variants[i]),
|
|
585
587
|
if (i < variants.length - 1) const _TokenDivider(),
|
|
586
588
|
],
|
|
587
589
|
],
|
|
@@ -591,18 +593,14 @@ class _VariantList extends StatelessWidget {
|
|
|
591
593
|
|
|
592
594
|
class _VariantRow extends StatelessWidget {
|
|
593
595
|
final _Variant variant;
|
|
594
|
-
|
|
595
|
-
final KasyColors dark;
|
|
596
|
-
const _VariantRow({
|
|
597
|
-
required this.variant,
|
|
598
|
-
required this.light,
|
|
599
|
-
required this.dark,
|
|
600
|
-
});
|
|
596
|
+
const _VariantRow({required this.variant});
|
|
601
597
|
|
|
602
598
|
@override
|
|
603
599
|
Widget build(BuildContext context) {
|
|
604
|
-
|
|
605
|
-
|
|
600
|
+
// Single swatch resolved against the CURRENT theme. The Design System app
|
|
601
|
+
// bar already toggles light/dark, so each row shows the token in the active
|
|
602
|
+
// theme instead of doubling it up with a light + dark chip on a surface.
|
|
603
|
+
final Color color = variant.pick(context.colors);
|
|
606
604
|
return Padding(
|
|
607
605
|
padding: const EdgeInsets.symmetric(
|
|
608
606
|
horizontal: KasySpacing.md,
|
|
@@ -610,9 +608,7 @@ class _VariantRow extends StatelessWidget {
|
|
|
610
608
|
),
|
|
611
609
|
child: Row(
|
|
612
610
|
children: [
|
|
613
|
-
|
|
614
|
-
const SizedBox(width: KasySpacing.xs),
|
|
615
|
-
_ThemeChip(surface: dark.surface, token: darkColor, border: variant.border),
|
|
611
|
+
_Swatch(color: color, border: variant.border),
|
|
616
612
|
const SizedBox(width: KasySpacing.smd),
|
|
617
613
|
Expanded(
|
|
618
614
|
child: Column(
|
|
@@ -626,7 +622,7 @@ class _VariantRow extends StatelessWidget {
|
|
|
626
622
|
),
|
|
627
623
|
const SizedBox(height: 2),
|
|
628
624
|
Text(
|
|
629
|
-
|
|
625
|
+
_hexOf(color),
|
|
630
626
|
style: context.textTheme.bodySmall?.copyWith(
|
|
631
627
|
color: context.colors.muted,
|
|
632
628
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
@@ -641,34 +637,24 @@ class _VariantRow extends StatelessWidget {
|
|
|
641
637
|
}
|
|
642
638
|
}
|
|
643
639
|
|
|
644
|
-
class
|
|
645
|
-
final Color
|
|
646
|
-
|
|
640
|
+
class _Swatch extends StatelessWidget {
|
|
641
|
+
final Color color;
|
|
642
|
+
|
|
643
|
+
/// Force a stronger hairline (near-surface / transparent tones); a subtle
|
|
644
|
+
/// hairline is always drawn so every swatch reads cleanly against the card.
|
|
647
645
|
final bool border;
|
|
648
|
-
const
|
|
649
|
-
required this.surface,
|
|
650
|
-
required this.token,
|
|
651
|
-
this.border = false,
|
|
652
|
-
});
|
|
646
|
+
const _Swatch({required this.color, this.border = false});
|
|
653
647
|
|
|
654
648
|
@override
|
|
655
649
|
Widget build(BuildContext context) {
|
|
656
650
|
return Container(
|
|
657
651
|
width: 28,
|
|
658
652
|
height: 28,
|
|
659
|
-
padding: const EdgeInsets.all(3),
|
|
660
653
|
decoration: BoxDecoration(
|
|
661
|
-
color:
|
|
654
|
+
color: color,
|
|
662
655
|
borderRadius: BorderRadius.circular(KasyRadius.xs),
|
|
663
|
-
border: Border.all(
|
|
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,
|
|
656
|
+
border: Border.all(
|
|
657
|
+
color: context.colors.outline.withValues(alpha: border ? 0.5 : 0.28),
|
|
672
658
|
),
|
|
673
659
|
),
|
|
674
660
|
);
|
|
@@ -679,48 +665,98 @@ class _ThemeChip extends StatelessWidget {
|
|
|
679
665
|
// Typography
|
|
680
666
|
// ---------------------------------------------------------------------------
|
|
681
667
|
|
|
682
|
-
|
|
668
|
+
/// One breakpoint shown by the typography preview: its label, the [DeviceType]
|
|
669
|
+
/// it maps to, and a short range caption.
|
|
670
|
+
class _Breakpoint {
|
|
671
|
+
final String label;
|
|
672
|
+
final DeviceType device;
|
|
673
|
+
final String range;
|
|
674
|
+
const _Breakpoint(this.label, this.device, this.range);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const List<_Breakpoint> _breakpoints = [
|
|
678
|
+
_Breakpoint('Mobile', DeviceType.small, '< 768px'),
|
|
679
|
+
_Breakpoint('Tablet', DeviceType.medium, '768 - 1024px'),
|
|
680
|
+
_Breakpoint('Desktop', DeviceType.large, '>= 1024px'),
|
|
681
|
+
];
|
|
682
|
+
|
|
683
|
+
class _TypographySection extends StatefulWidget {
|
|
683
684
|
const _TypographySection();
|
|
684
685
|
|
|
686
|
+
@override
|
|
687
|
+
State<_TypographySection> createState() => _TypographySectionState();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
class _TypographySectionState extends State<_TypographySection> {
|
|
691
|
+
/// Selected breakpoint tab. Null until the first build, where it defaults to
|
|
692
|
+
/// the device currently viewing the screen; a tap pins it from then on.
|
|
693
|
+
int? _index;
|
|
694
|
+
|
|
695
|
+
static const List<_TypeRole> _roles = [
|
|
696
|
+
_TypeRole('Heading 1', KasyTypeScale.heading1, FontWeight.w800, 'ExtraBold'),
|
|
697
|
+
_TypeRole('Heading 2', KasyTypeScale.heading2, FontWeight.w700, 'Bold'),
|
|
698
|
+
_TypeRole('Heading 3', KasyTypeScale.heading3, FontWeight.w600, 'SemiBold'),
|
|
699
|
+
_TypeRole('Heading 4', KasyTypeScale.heading4, FontWeight.w600, 'SemiBold'),
|
|
700
|
+
_TypeRole('Body base', KasyTypeScale.bodyBase, FontWeight.w400, 'Regular'),
|
|
701
|
+
_TypeRole('Body base medium', KasyTypeScale.bodyBase, FontWeight.w500, 'Medium'),
|
|
702
|
+
_TypeRole('Body sm', KasyTypeScale.bodySm, FontWeight.w400, 'Regular'),
|
|
703
|
+
_TypeRole('Body sm medium', KasyTypeScale.bodySm, FontWeight.w500, 'Medium'),
|
|
704
|
+
_TypeRole('Body xs', KasyTypeScale.bodyXs, FontWeight.w400, 'Regular'),
|
|
705
|
+
_TypeRole('Body xs medium', KasyTypeScale.bodyXs, FontWeight.w500, 'Medium'),
|
|
706
|
+
_TypeRole('Link base', KasyTypeScale.bodyBase, FontWeight.w500, 'Medium · Underlined', underline: true),
|
|
707
|
+
_TypeRole('Link sm', KasyTypeScale.bodySm, FontWeight.w500, 'Medium · Underlined', underline: true),
|
|
708
|
+
_TypeRole('Text field base', KasyTypeScale.bodyBase, FontWeight.w400, 'Regular'),
|
|
709
|
+
_TypeRole('Text field sm', KasyTypeScale.bodySm, FontWeight.w400, 'Regular'),
|
|
710
|
+
_TypeRole('Button base', KasyTypeScale.bodyBase, FontWeight.w500, 'Medium'),
|
|
711
|
+
_TypeRole('Button sm', KasyTypeScale.bodySm, FontWeight.w500, 'Medium'),
|
|
712
|
+
];
|
|
713
|
+
|
|
714
|
+
int _defaultIndexFor(double width) => switch (DeviceType.fromWidth(width)) {
|
|
715
|
+
DeviceType.small => 0,
|
|
716
|
+
DeviceType.medium => 1,
|
|
717
|
+
DeviceType.large || DeviceType.xlarge => 2,
|
|
718
|
+
};
|
|
719
|
+
|
|
685
720
|
@override
|
|
686
721
|
Widget build(BuildContext context) {
|
|
687
722
|
final Color fg = context.colors.onSurface;
|
|
688
|
-
final
|
|
689
|
-
|
|
690
|
-
|
|
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'),
|
|
705
|
-
];
|
|
723
|
+
final double width = MediaQuery.sizeOf(context).width;
|
|
724
|
+
final int index = _index ?? _defaultIndexFor(width);
|
|
725
|
+
final _Breakpoint bp = _breakpoints[index];
|
|
706
726
|
|
|
707
727
|
return Column(
|
|
708
728
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
709
729
|
children: [
|
|
710
730
|
Text(
|
|
711
|
-
|
|
712
|
-
'
|
|
731
|
+
'Inter, neutral tracking, predictable rhythm. Each role has an '
|
|
732
|
+
'explicit size per breakpoint: headings step down from desktop to '
|
|
733
|
+
'mobile, while body and labels stay constant for stable reading.',
|
|
713
734
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
714
735
|
color: context.colors.muted,
|
|
715
736
|
height: 1.5,
|
|
716
737
|
),
|
|
717
738
|
),
|
|
718
739
|
const SizedBox(height: KasySpacing.md),
|
|
740
|
+
KasyTabs(
|
|
741
|
+
tabs: [for (final b in _breakpoints) b.label],
|
|
742
|
+
selectedIndex: index,
|
|
743
|
+
mode: KasyTabsMode.fill,
|
|
744
|
+
onTabSelected: (i) => setState(() => _index = i),
|
|
745
|
+
),
|
|
746
|
+
const SizedBox(height: KasySpacing.sm),
|
|
747
|
+
Text(
|
|
748
|
+
'${bp.label} · ${bp.range}',
|
|
749
|
+
style: context.textTheme.bodySmall?.copyWith(
|
|
750
|
+
color: context.colors.muted,
|
|
751
|
+
fontFeatures: const [FontFeature.tabularFigures()],
|
|
752
|
+
),
|
|
753
|
+
),
|
|
754
|
+
const SizedBox(height: KasySpacing.md),
|
|
719
755
|
_TokenCard(
|
|
720
756
|
children: [
|
|
721
|
-
for (int i = 0; i <
|
|
722
|
-
_TypeRow(role:
|
|
723
|
-
if (i <
|
|
757
|
+
for (int i = 0; i < _roles.length; i++) ...[
|
|
758
|
+
_TypeRow(role: _roles[i], foreground: fg, device: bp.device),
|
|
759
|
+
if (i < _roles.length - 1) const _TokenDivider(),
|
|
724
760
|
],
|
|
725
761
|
],
|
|
726
762
|
),
|
|
@@ -729,20 +765,45 @@ class _TypographySection extends StatelessWidget {
|
|
|
729
765
|
}
|
|
730
766
|
}
|
|
731
767
|
|
|
768
|
+
/// Formats a size: integers stay integer (16), fractions show one decimal (17.0).
|
|
769
|
+
String _fmt(double v) =>
|
|
770
|
+
v == v.roundToDouble() ? v.toInt().toString() : v.toStringAsFixed(1);
|
|
771
|
+
|
|
732
772
|
class _TypeRole {
|
|
733
773
|
final String name;
|
|
734
|
-
final
|
|
735
|
-
final
|
|
736
|
-
|
|
774
|
+
final RampSize ramp;
|
|
775
|
+
final FontWeight weight;
|
|
776
|
+
|
|
777
|
+
/// Human-readable weight (and decoration) description for the spec line.
|
|
778
|
+
final String label;
|
|
779
|
+
final bool underline;
|
|
780
|
+
const _TypeRole(
|
|
781
|
+
this.name,
|
|
782
|
+
this.ramp,
|
|
783
|
+
this.weight,
|
|
784
|
+
this.label, {
|
|
785
|
+
this.underline = false,
|
|
786
|
+
});
|
|
737
787
|
}
|
|
738
788
|
|
|
739
789
|
class _TypeRow extends StatelessWidget {
|
|
740
790
|
final _TypeRole role;
|
|
741
791
|
final Color foreground;
|
|
742
|
-
|
|
792
|
+
|
|
793
|
+
/// The breakpoint whose size this row previews.
|
|
794
|
+
final DeviceType device;
|
|
795
|
+
|
|
796
|
+
const _TypeRow({
|
|
797
|
+
required this.role,
|
|
798
|
+
required this.foreground,
|
|
799
|
+
required this.device,
|
|
800
|
+
});
|
|
743
801
|
|
|
744
802
|
@override
|
|
745
803
|
Widget build(BuildContext context) {
|
|
804
|
+
final double size = role.ramp.size(device);
|
|
805
|
+
final double line = role.ramp.lineHeight(device);
|
|
806
|
+
|
|
746
807
|
return Padding(
|
|
747
808
|
padding: const EdgeInsets.symmetric(
|
|
748
809
|
horizontal: KasySpacing.md,
|
|
@@ -753,13 +814,20 @@ class _TypeRow extends StatelessWidget {
|
|
|
753
814
|
children: [
|
|
754
815
|
Text(
|
|
755
816
|
role.name,
|
|
756
|
-
style:
|
|
817
|
+
style: GoogleFonts.inter(
|
|
818
|
+
fontSize: size,
|
|
819
|
+
fontWeight: role.weight,
|
|
820
|
+
height: role.ramp.lineHeightRatio,
|
|
821
|
+
letterSpacing: 0,
|
|
822
|
+
color: foreground,
|
|
823
|
+
decoration: role.underline ? TextDecoration.underline : null,
|
|
824
|
+
),
|
|
757
825
|
maxLines: 1,
|
|
758
826
|
overflow: TextOverflow.ellipsis,
|
|
759
827
|
),
|
|
760
828
|
const SizedBox(height: 4),
|
|
761
829
|
Text(
|
|
762
|
-
'Inter · ${role.
|
|
830
|
+
'Inter · ${role.label} · ${_fmt(size)}/${_fmt(line)}',
|
|
763
831
|
style: context.textTheme.bodySmall?.copyWith(
|
|
764
832
|
color: context.colors.muted,
|
|
765
833
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
@@ -1054,9 +1122,8 @@ class _EffectGroupLabel extends StatelessWidget {
|
|
|
1054
1122
|
Widget build(BuildContext context) {
|
|
1055
1123
|
return Text(
|
|
1056
1124
|
label,
|
|
1057
|
-
style: context.
|
|
1125
|
+
style: context.kasyTextTheme.sectionTitle.copyWith(
|
|
1058
1126
|
color: context.colors.onSurface,
|
|
1059
|
-
fontWeight: FontWeight.w700,
|
|
1060
1127
|
),
|
|
1061
1128
|
);
|
|
1062
1129
|
}
|
|
@@ -94,9 +94,10 @@ class HomeComponentsPage extends StatelessWidget {
|
|
|
94
94
|
// Lighter weight (w500) to match the
|
|
95
95
|
// app's row/list scale instead of the
|
|
96
96
|
// heavier heading weight.
|
|
97
|
-
style: context.
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
style: context.kasyTextTheme.rowTitle
|
|
98
|
+
.copyWith(
|
|
99
|
+
color:
|
|
100
|
+
context.colors.onSurface,
|
|
100
101
|
),
|
|
101
102
|
),
|
|
102
103
|
),
|
|
@@ -142,9 +142,7 @@ class _HomeComponentsPreviewPageState extends State<HomeComponentsPreviewPage>
|
|
|
142
142
|
Widget _buildFooterVariantTitle(BuildContext context) {
|
|
143
143
|
final Color labelBase = context.colors.onBackground;
|
|
144
144
|
final TextStyle baseStyle =
|
|
145
|
-
context.textTheme.
|
|
146
|
-
fontWeight: FontWeight.w600,
|
|
147
|
-
fontSize: 17,
|
|
145
|
+
context.textTheme.titleMedium?.copyWith(
|
|
148
146
|
letterSpacing: 0.1,
|
|
149
147
|
) ??
|
|
150
148
|
const TextStyle(
|