kasy-cli 1.19.1 → 1.20.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 +1 -0
- package/lib/commands/new.js +9 -0
- package/lib/commands/run.js +22 -6
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +91 -2
- package/lib/scaffold/engine.js +5 -0
- package/lib/scaffold/generate.js +4 -0
- package/lib/scaffold/shared/generator-utils.js +38 -1
- package/lib/utils/flutter-run.js +16 -4
- package/lib/utils/i18n/messages-en.js +2 -1
- package/lib/utils/i18n/messages-es.js +2 -1
- package/lib/utils/i18n/messages-pt.js +2 -1
- package/package.json +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1148 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
- package/templates/firebase/lib/components/kasy_text_field.dart +39 -29
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +90 -69
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +30 -7
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +439 -232
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
- package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
- package/templates/firebase/lib/core/theme/colors.dart +6 -2
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -0
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +202 -79
- package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
- package/templates/firebase/lib/i18n/en.i18n.json +3 -1
- package/templates/firebase/lib/i18n/es.i18n.json +3 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
- package/templates/firebase/pubspec.yaml +6 -4
- package/templates/firebase/web/index.html +7 -17
- package/templates/firebase/lib/firebase_options.dart +0 -75
|
@@ -7,15 +7,24 @@ import 'package:kasy_kit/core/theme/theme.dart';
|
|
|
7
7
|
|
|
8
8
|
/// Data model for a single tab item.
|
|
9
9
|
class KasyTabItem {
|
|
10
|
+
/// Visible text. Leave empty for an icon-only tab (requires [icon]).
|
|
10
11
|
final String label;
|
|
11
12
|
final IconData? icon;
|
|
12
13
|
final bool enabled;
|
|
13
14
|
|
|
15
|
+
/// Accessibility label read by screen readers. Falls back to [label] when
|
|
16
|
+
/// not provided; required (in spirit) for icon-only tabs, which have no text.
|
|
17
|
+
final String? semanticLabel;
|
|
18
|
+
|
|
14
19
|
const KasyTabItem({
|
|
15
|
-
|
|
20
|
+
this.label = '',
|
|
16
21
|
this.icon,
|
|
17
22
|
this.enabled = true,
|
|
18
|
-
|
|
23
|
+
this.semanticLabel,
|
|
24
|
+
}) : assert(
|
|
25
|
+
label != '' || icon != null,
|
|
26
|
+
'KasyTabItem needs a label, an icon, or both.',
|
|
27
|
+
);
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -190,27 +199,41 @@ class _KasyTabsState extends State<KasyTabs> {
|
|
|
190
199
|
}
|
|
191
200
|
}
|
|
192
201
|
|
|
193
|
-
/// Scrolls the [SingleChildScrollView] so the selected tab is
|
|
202
|
+
/// Scrolls the inner [SingleChildScrollView] so the selected tab is visible.
|
|
194
203
|
///
|
|
195
|
-
///
|
|
196
|
-
///
|
|
204
|
+
/// Only the component's OWN horizontal controller is moved — never an
|
|
205
|
+
/// ancestor scrollable. (Using [Scrollable.ensureVisible] here would bubble
|
|
206
|
+
/// up and scroll the enclosing page vertically, e.g. nudging the Settings
|
|
207
|
+
/// list down when picking a middle tab.) First/last tab snap to the scroll
|
|
208
|
+
/// extremes so the container padding and the 4px pill overflow aren't clipped.
|
|
197
209
|
void _ensureSelectedVisible(int index) {
|
|
210
|
+
if (!_scrollController.hasClients) return;
|
|
211
|
+
|
|
212
|
+
final ScrollPosition position = _scrollController.position;
|
|
213
|
+
final double min = position.minScrollExtent;
|
|
214
|
+
final double max = position.maxScrollExtent;
|
|
215
|
+
// No horizontal overflow: leave every scroll position untouched.
|
|
216
|
+
if (max <= min) return;
|
|
217
|
+
|
|
198
218
|
const Duration duration = Duration(milliseconds: 250);
|
|
199
219
|
const Curve curve = Curves.easeInOut;
|
|
200
220
|
final int lastIndex = widget.items.length - 1;
|
|
201
221
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
final double target;
|
|
223
|
+
if (index == 0) {
|
|
224
|
+
target = min;
|
|
225
|
+
} else if (index == lastIndex) {
|
|
226
|
+
target = max;
|
|
227
|
+
} else {
|
|
228
|
+
// Centre the selected tab in the viewport. Measured geometry is relative
|
|
229
|
+
// to the inner Stack, which sits 8px in from the scrollable content edge
|
|
230
|
+
// (the tabsContent horizontal padding).
|
|
231
|
+
const double horizontalPadding = 8;
|
|
232
|
+
final double tabCentre =
|
|
233
|
+
horizontalPadding + _indicatorLeft + _indicatorWidth / 2;
|
|
234
|
+
target = (tabCentre - position.viewportDimension / 2).clamp(min, max);
|
|
209
235
|
}
|
|
210
|
-
|
|
211
|
-
final BuildContext? ctx = _keys[index].currentContext;
|
|
212
|
-
if (ctx == null) return;
|
|
213
|
-
Scrollable.ensureVisible(ctx, duration: duration, curve: curve);
|
|
236
|
+
_scrollController.animateTo(target, duration: duration, curve: curve);
|
|
214
237
|
}
|
|
215
238
|
|
|
216
239
|
@override
|
|
@@ -397,7 +420,31 @@ class _KasyTabsState extends State<KasyTabs> {
|
|
|
397
420
|
// Internal tab widgets
|
|
398
421
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
399
422
|
|
|
400
|
-
|
|
423
|
+
/// Adds a pointer (click) cursor and reports hover changes on web/desktop.
|
|
424
|
+
/// Disabled tabs keep the default cursor and never report hover.
|
|
425
|
+
class _TabHoverRegion extends StatelessWidget {
|
|
426
|
+
const _TabHoverRegion({
|
|
427
|
+
required this.enabled,
|
|
428
|
+
required this.onHover,
|
|
429
|
+
required this.child,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
final bool enabled;
|
|
433
|
+
final ValueChanged<bool> onHover;
|
|
434
|
+
final Widget child;
|
|
435
|
+
|
|
436
|
+
@override
|
|
437
|
+
Widget build(BuildContext context) {
|
|
438
|
+
return MouseRegion(
|
|
439
|
+
cursor: enabled ? SystemMouseCursors.click : MouseCursor.defer,
|
|
440
|
+
onEnter: enabled ? (_) => onHover(true) : null,
|
|
441
|
+
onExit: enabled ? (_) => onHover(false) : null,
|
|
442
|
+
child: child,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
class _PrimaryTab extends StatefulWidget {
|
|
401
448
|
const _PrimaryTab({
|
|
402
449
|
super.key,
|
|
403
450
|
required this.item,
|
|
@@ -411,12 +458,27 @@ class _PrimaryTab extends StatelessWidget {
|
|
|
411
458
|
final bool expand;
|
|
412
459
|
final VoidCallback? onTap;
|
|
413
460
|
|
|
461
|
+
@override
|
|
462
|
+
State<_PrimaryTab> createState() => _PrimaryTabState();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
class _PrimaryTabState extends State<_PrimaryTab> {
|
|
466
|
+
bool _hovered = false;
|
|
467
|
+
|
|
414
468
|
Widget _tabContent(BuildContext context) {
|
|
415
469
|
final KasyColors c = context.colors;
|
|
470
|
+
final KasyTabItem item = widget.item;
|
|
471
|
+
final bool selected = widget.selected;
|
|
416
472
|
final bool disabled = !item.enabled;
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
473
|
+
final bool hasLabel = item.label.isNotEmpty;
|
|
474
|
+
final bool iconOnly = item.icon != null && !hasLabel;
|
|
475
|
+
// Fill mode with a label + icon uses a vertical (Column) layout per Figma
|
|
476
|
+
// spec: icon stacked above label, 12px all-sides padding, 12px font size.
|
|
477
|
+
final bool verticalLayout = widget.expand && item.icon != null && hasLabel;
|
|
478
|
+
|
|
479
|
+
// On web/desktop, hovering an inactive tab lifts its foreground toward the
|
|
480
|
+
// selected color so it reads as interactive (mobile keeps the flat look).
|
|
481
|
+
final Color fg = (selected || _hovered) ? c.onSurface : c.muted;
|
|
420
482
|
|
|
421
483
|
final Widget iconWidget = item.icon != null
|
|
422
484
|
? Opacity(
|
|
@@ -424,7 +486,7 @@ class _PrimaryTab extends StatelessWidget {
|
|
|
424
486
|
child: Icon(
|
|
425
487
|
item.icon,
|
|
426
488
|
size: 16,
|
|
427
|
-
color:
|
|
489
|
+
color: fg,
|
|
428
490
|
),
|
|
429
491
|
)
|
|
430
492
|
: const SizedBox.shrink();
|
|
@@ -437,15 +499,15 @@ class _PrimaryTab extends StatelessWidget {
|
|
|
437
499
|
// Use labelLarge as defined in the Kasy theme (14px/w600).
|
|
438
500
|
// No fontWeight override — the theme token is the source of truth.
|
|
439
501
|
style: context.textTheme.labelLarge?.copyWith(
|
|
440
|
-
color:
|
|
502
|
+
color: fg,
|
|
441
503
|
// Fill+icon layout uses 12px (text-xs) — per Figma spec.
|
|
442
504
|
fontSize: verticalLayout ? 12 : null,
|
|
443
505
|
),
|
|
444
506
|
),
|
|
445
507
|
);
|
|
446
508
|
|
|
447
|
-
|
|
448
|
-
onTap: onTap,
|
|
509
|
+
final Widget gesture = GestureDetector(
|
|
510
|
+
onTap: widget.onTap,
|
|
449
511
|
behavior: HitTestBehavior.opaque,
|
|
450
512
|
child: Padding(
|
|
451
513
|
padding: verticalLayout
|
|
@@ -467,25 +529,42 @@ class _PrimaryTab extends StatelessWidget {
|
|
|
467
529
|
mainAxisSize: MainAxisSize.min,
|
|
468
530
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
469
531
|
children: [
|
|
470
|
-
if (item.icon != null)
|
|
471
|
-
|
|
532
|
+
if (item.icon != null) iconWidget,
|
|
533
|
+
if (item.icon != null && hasLabel)
|
|
472
534
|
const SizedBox(width: 6),
|
|
473
|
-
|
|
474
|
-
labelWidget,
|
|
535
|
+
if (hasLabel) labelWidget,
|
|
475
536
|
],
|
|
476
537
|
),
|
|
477
538
|
),
|
|
478
539
|
);
|
|
540
|
+
|
|
541
|
+
final Widget hoverable = _TabHoverRegion(
|
|
542
|
+
enabled: !disabled,
|
|
543
|
+
onHover: (value) => setState(() => _hovered = value),
|
|
544
|
+
child: gesture,
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
// Icon-only tabs carry no text, so expose the selection state and a label
|
|
548
|
+
// to screen readers explicitly (labelled tabs are described by their Text).
|
|
549
|
+
if (iconOnly) {
|
|
550
|
+
return Semantics(
|
|
551
|
+
button: true,
|
|
552
|
+
selected: selected,
|
|
553
|
+
label: item.semanticLabel,
|
|
554
|
+
child: hoverable,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
return hoverable;
|
|
479
558
|
}
|
|
480
559
|
|
|
481
560
|
@override
|
|
482
561
|
Widget build(BuildContext context) {
|
|
483
562
|
final Widget inner = _tabContent(context);
|
|
484
|
-
return expand ? Expanded(child: inner) : inner;
|
|
563
|
+
return widget.expand ? Expanded(child: inner) : inner;
|
|
485
564
|
}
|
|
486
565
|
}
|
|
487
566
|
|
|
488
|
-
class _SecondaryTab extends
|
|
567
|
+
class _SecondaryTab extends StatefulWidget {
|
|
489
568
|
const _SecondaryTab({
|
|
490
569
|
super.key,
|
|
491
570
|
required this.item,
|
|
@@ -499,12 +578,27 @@ class _SecondaryTab extends StatelessWidget {
|
|
|
499
578
|
final bool expand;
|
|
500
579
|
final VoidCallback? onTap;
|
|
501
580
|
|
|
581
|
+
@override
|
|
582
|
+
State<_SecondaryTab> createState() => _SecondaryTabState();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
class _SecondaryTabState extends State<_SecondaryTab> {
|
|
586
|
+
bool _hovered = false;
|
|
587
|
+
|
|
502
588
|
Widget _tabContent(BuildContext context) {
|
|
503
589
|
final KasyColors c = context.colors;
|
|
590
|
+
final KasyTabItem item = widget.item;
|
|
591
|
+
final bool selected = widget.selected;
|
|
504
592
|
final bool disabled = !item.enabled;
|
|
593
|
+
final bool hasLabel = item.label.isNotEmpty;
|
|
594
|
+
final bool iconOnly = item.icon != null && !hasLabel;
|
|
595
|
+
|
|
596
|
+
// On web/desktop, hovering an inactive tab lifts its foreground toward the
|
|
597
|
+
// selected color so it reads as interactive (mobile keeps the flat look).
|
|
598
|
+
final Color fg = (selected || _hovered) ? c.onSurface : c.muted;
|
|
505
599
|
|
|
506
|
-
|
|
507
|
-
onTap: onTap,
|
|
600
|
+
final Widget gesture = GestureDetector(
|
|
601
|
+
onTap: widget.onTap,
|
|
508
602
|
behavior: HitTestBehavior.opaque,
|
|
509
603
|
child: Padding(
|
|
510
604
|
// top:4 bottom:6 horizontal:12 — per Figma spec.
|
|
@@ -525,30 +619,49 @@ class _SecondaryTab extends StatelessWidget {
|
|
|
525
619
|
child: Icon(
|
|
526
620
|
item.icon,
|
|
527
621
|
size: 16,
|
|
528
|
-
color:
|
|
622
|
+
color: fg,
|
|
529
623
|
),
|
|
530
624
|
),
|
|
531
|
-
const SizedBox(width: 6),
|
|
625
|
+
if (hasLabel) const SizedBox(width: 6),
|
|
532
626
|
],
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
627
|
+
if (hasLabel)
|
|
628
|
+
Opacity(
|
|
629
|
+
opacity: disabled ? 0.4 : 1.0,
|
|
630
|
+
child: Text(
|
|
631
|
+
item.label,
|
|
632
|
+
// Use labelLarge as defined in the Kasy theme (14px/w600).
|
|
633
|
+
style: context.textTheme.labelLarge?.copyWith(
|
|
634
|
+
color: fg,
|
|
635
|
+
),
|
|
540
636
|
),
|
|
541
637
|
),
|
|
542
|
-
),
|
|
543
638
|
],
|
|
544
639
|
),
|
|
545
640
|
),
|
|
546
641
|
);
|
|
642
|
+
|
|
643
|
+
final Widget hoverable = _TabHoverRegion(
|
|
644
|
+
enabled: !disabled,
|
|
645
|
+
onHover: (value) => setState(() => _hovered = value),
|
|
646
|
+
child: gesture,
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
// Icon-only tabs carry no text, so expose the selection state and a label
|
|
650
|
+
// to screen readers explicitly (labelled tabs are described by their Text).
|
|
651
|
+
if (iconOnly) {
|
|
652
|
+
return Semantics(
|
|
653
|
+
button: true,
|
|
654
|
+
selected: selected,
|
|
655
|
+
label: item.semanticLabel,
|
|
656
|
+
child: hoverable,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return hoverable;
|
|
547
660
|
}
|
|
548
661
|
|
|
549
662
|
@override
|
|
550
663
|
Widget build(BuildContext context) {
|
|
551
664
|
final Widget inner = _tabContent(context);
|
|
552
|
-
return expand ? Expanded(child: inner) : inner;
|
|
665
|
+
return widget.expand ? Expanded(child: inner) : inner;
|
|
553
666
|
}
|
|
554
667
|
}
|
|
@@ -241,9 +241,20 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
241
241
|
// (No more web-specific padding — the field now uses the same vertical
|
|
242
242
|
// padding on every platform so primary/web TextFields render at the same
|
|
243
243
|
// height as mobile and as the KasyDatePicker trigger.)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
|
|
245
|
+
// ── Disabled state: opaque "softened" colors, NOT transparency. ────────
|
|
246
|
+
// Kit-wide rule (see KasyButton): keep the original hue but render it
|
|
247
|
+
// weaker by alpha-blending toward the surface — never use raw opacity,
|
|
248
|
+
// which would leak whatever sits behind the widget. [dimDisabled] takes
|
|
249
|
+
// any base color and returns an opaque, softer version of it (or the
|
|
250
|
+
// color itself when the field is enabled). The [alpha] parameter
|
|
251
|
+
// controls how much of the original color is kept: higher = stronger
|
|
252
|
+
// (closer to the original), lower = softer (closer to the surface).
|
|
253
|
+
final Color blendSurface = context.colors.surface;
|
|
254
|
+
Color dimDisabled(Color base, {double alpha = 0.46}) {
|
|
255
|
+
if (!isDisabled) return base;
|
|
256
|
+
return Color.alphaBlend(base.withValues(alpha: alpha), blendSurface);
|
|
257
|
+
}
|
|
247
258
|
final bool isPassword =
|
|
248
259
|
widget.contentType == KasyTextFieldContentType.password;
|
|
249
260
|
final bool isEmail = widget.contentType == KasyTextFieldContentType.email;
|
|
@@ -289,16 +300,18 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
289
300
|
final TextStyle labelStyle = labelBaseStyle.copyWith(
|
|
290
301
|
color: hasInvalidState
|
|
291
302
|
? context.colors.error
|
|
292
|
-
: (
|
|
293
|
-
|
|
303
|
+
: dimDisabled(
|
|
304
|
+
labelBaseStyle.color ?? context.colors.fieldLabel,
|
|
305
|
+
alpha: 0.55,
|
|
294
306
|
),
|
|
295
307
|
);
|
|
296
308
|
final TextStyle descriptionStyle =
|
|
297
309
|
(hasInvalidState ? errorBaseStyle : helperBaseStyle).copyWith(
|
|
298
310
|
color: hasInvalidState
|
|
299
311
|
? context.colors.error
|
|
300
|
-
: (
|
|
301
|
-
|
|
312
|
+
: dimDisabled(
|
|
313
|
+
helperBaseStyle.color ?? context.colors.muted,
|
|
314
|
+
alpha: 0.45,
|
|
302
315
|
),
|
|
303
316
|
);
|
|
304
317
|
final BorderRadius fieldRadius = BorderRadius.circular(KasyRadius.md);
|
|
@@ -372,19 +385,17 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
372
385
|
? surfaceColor.withValues(alpha: context.isDark ? 0.9 : 0.94)
|
|
373
386
|
: surfaceColor;
|
|
374
387
|
// Affix icon tint matches the kit's helper-icon tone (0.62) when the
|
|
375
|
-
// field is enabled
|
|
376
|
-
// the
|
|
377
|
-
|
|
378
|
-
final
|
|
379
|
-
? 0.62 * disabledTextOpacity
|
|
380
|
-
: 0.62;
|
|
388
|
+
// field is enabled. When disabled, dimDisabled blends that base color
|
|
389
|
+
// toward the surface — the icon stays opaque, just less saturated.
|
|
390
|
+
final Color affixBase = context.colors.onSurface.withValues(alpha: 0.62);
|
|
391
|
+
final Color affixColor = dimDisabled(affixBase);
|
|
381
392
|
final Widget? resolvedPrefix = widget.prefix == null
|
|
382
393
|
? null
|
|
383
394
|
: Center(
|
|
384
395
|
child: IconTheme.merge(
|
|
385
396
|
data: IconThemeData(
|
|
386
397
|
size: KasyTextField.iconGlyphSize,
|
|
387
|
-
color:
|
|
398
|
+
color: affixColor,
|
|
388
399
|
),
|
|
389
400
|
child: widget.prefix!,
|
|
390
401
|
),
|
|
@@ -395,7 +406,7 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
395
406
|
child: IconTheme.merge(
|
|
396
407
|
data: IconThemeData(
|
|
397
408
|
size: KasyTextField.iconGlyphSize,
|
|
398
|
-
color:
|
|
409
|
+
color: affixColor,
|
|
399
410
|
),
|
|
400
411
|
child: widget.suffix!,
|
|
401
412
|
),
|
|
@@ -419,24 +430,22 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
419
430
|
child: Icon(
|
|
420
431
|
_passwordVisible ? KasyIcons.eyeOff : KasyIcons.eye,
|
|
421
432
|
size: KasyTextField.iconGlyphSize,
|
|
422
|
-
color:
|
|
423
|
-
alpha: widget.enabled ? 0.62 : disabledTextOpacity,
|
|
424
|
-
),
|
|
433
|
+
color: affixColor,
|
|
425
434
|
),
|
|
426
435
|
),
|
|
427
436
|
),
|
|
428
437
|
),
|
|
429
438
|
)
|
|
430
439
|
: null);
|
|
440
|
+
|
|
441
|
+
final Color fieldTextColor = dimDisabled(context.colors.onSurface);
|
|
431
442
|
final TextStyle fieldTextStyle =
|
|
432
443
|
context.textTheme.bodyLarge?.copyWith(
|
|
433
|
-
color:
|
|
434
|
-
alpha: isDisabled ? disabledTextOpacity : 1,
|
|
435
|
-
),
|
|
444
|
+
color: fieldTextColor,
|
|
436
445
|
fontWeight: FontWeight.w400,
|
|
437
446
|
fontSize: 15,
|
|
438
447
|
) ??
|
|
439
|
-
|
|
448
|
+
TextStyle(fontSize: 15, color: fieldTextColor);
|
|
440
449
|
final InputDecoration decoration = InputDecoration(
|
|
441
450
|
isDense: true,
|
|
442
451
|
hintText: widget.hint,
|
|
@@ -495,11 +504,11 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
495
504
|
),
|
|
496
505
|
),
|
|
497
506
|
hintStyle: hintBaseStyle.copyWith(
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
alpha:
|
|
507
|
+
// Placeholder dims along with the field via the kit-wide softened
|
|
508
|
+
// color rule (opaque blend toward the surface, not raw opacity).
|
|
509
|
+
color: dimDisabled(
|
|
510
|
+
hintBaseStyle.color ?? context.colors.muted,
|
|
511
|
+
alpha: 0.55,
|
|
503
512
|
),
|
|
504
513
|
),
|
|
505
514
|
);
|
|
@@ -611,8 +620,9 @@ class _KasyTextFieldState extends State<KasyTextField> {
|
|
|
611
620
|
Text(
|
|
612
621
|
'${_effectiveController.text.length}/${widget.maxLength}',
|
|
613
622
|
style: context.textTheme.bodySmall?.copyWith(
|
|
614
|
-
color:
|
|
615
|
-
alpha:
|
|
623
|
+
color: dimDisabled(
|
|
624
|
+
context.colors.onSurface.withValues(alpha: 0.54),
|
|
625
|
+
alpha: 0.55,
|
|
616
626
|
),
|
|
617
627
|
fontWeight: FontWeight.w500,
|
|
618
628
|
),
|