kasy-cli 1.40.0 → 1.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/package.json +1 -1
- package/templates/firebase/lib/components/kasy_accordion.dart +4 -1
- package/templates/firebase/lib/components/kasy_alert.dart +5 -2
- package/templates/firebase/lib/components/kasy_dialog.dart +3 -1
- package/templates/firebase/lib/components/kasy_menu.dart +74 -50
- package/templates/firebase/lib/core/theme/texts.dart +0 -25
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.40.1": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"components": {
|
|
5
|
+
"pt": "Refino do submenu do Kasy Menu em telas estreitas: o submenu agora abre como sanfona embaixo do próprio item (expande inline), em vez de empilhar uma nova folha por cima. Menos custo de interação e sem te jogar numa segunda tela; no desktop ele continua cascateando ao lado (flyout). Já vale na demo web e no app gerado.",
|
|
6
|
+
"en": "Kasy Menu submenu refinement on narrow screens: the submenu now opens as an accordion right beneath its own item (expands inline) instead of stacking a new sheet on top. Lower interaction cost and no jump to a second surface; on desktop it still cascades to the side (flyout). Live in the web demo and the generated app.",
|
|
7
|
+
"es": "Refinamiento del submenú del Kasy Menu en pantallas estrechas: el submenú ahora abre como acordeón justo debajo de su propio ítem (se expande inline), en vez de apilar una nueva hoja encima. Menor costo de interacción y sin saltar a una segunda superficie; en escritorio sigue desplegándose al costado (flyout). Disponible en la demo web y en la app generada."
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"1.40.0": {
|
|
3
12
|
"modules": {
|
|
4
13
|
"components": {
|
package/package.json
CHANGED
|
@@ -274,7 +274,10 @@ class _KasyAccordionRow extends StatelessWidget {
|
|
|
274
274
|
Expanded(
|
|
275
275
|
child: Text(
|
|
276
276
|
data.title,
|
|
277
|
-
|
|
277
|
+
// Header of an inline row component: 16 / w600 (not the
|
|
278
|
+
// 18 screen-section step), matching the alert header.
|
|
279
|
+
style: context.textTheme.bodyLarge?.copyWith(
|
|
280
|
+
fontWeight: FontWeight.w600,
|
|
278
281
|
color: context.colors.onSurface,
|
|
279
282
|
),
|
|
280
283
|
),
|
|
@@ -51,14 +51,17 @@ class KasyAlert extends StatelessWidget {
|
|
|
51
51
|
Widget build(BuildContext context) {
|
|
52
52
|
final _KasyAlertPalette palette = _resolvePalette(context, tone);
|
|
53
53
|
final bool hasMessage = message != null && message!.trim().isNotEmpty;
|
|
54
|
+
// Inline component header: 16 / w600 (one step below an overlay/section
|
|
55
|
+
// title) so a compact alert card never wears a screen-section-sized title.
|
|
54
56
|
final TextStyle titleStyle =
|
|
55
|
-
context.textTheme.
|
|
57
|
+
context.textTheme.bodyLarge?.copyWith(
|
|
58
|
+
fontWeight: FontWeight.w600,
|
|
56
59
|
color: emphasizeTitleWithTone
|
|
57
60
|
? palette.accent
|
|
58
61
|
: context.colors.onSurface,
|
|
59
62
|
) ??
|
|
60
63
|
TextStyle(
|
|
61
|
-
fontSize:
|
|
64
|
+
fontSize: 16,
|
|
62
65
|
fontWeight: FontWeight.w600,
|
|
63
66
|
color: emphasizeTitleWithTone
|
|
64
67
|
? palette.accent
|
|
@@ -280,8 +280,10 @@ class KasyDialog extends StatelessWidget {
|
|
|
280
280
|
Text(
|
|
281
281
|
message!,
|
|
282
282
|
textAlign: titleCentered ? TextAlign.center : TextAlign.start,
|
|
283
|
+
// Supporting text uses the body token (14); the sheet uses the
|
|
284
|
+
// same, so dialog and sheet read identically (they are the same
|
|
285
|
+
// surface on desktop). No off-ladder 15.
|
|
283
286
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
284
|
-
fontSize: 15,
|
|
285
287
|
color: context.colors.onSurface.withValues(alpha: 0.6),
|
|
286
288
|
height: 1.5,
|
|
287
289
|
),
|
|
@@ -96,7 +96,8 @@ class KasyMenuItem {
|
|
|
96
96
|
final bool enabled;
|
|
97
97
|
|
|
98
98
|
/// When set, the row opens a nested menu instead of acting; a trailing chevron
|
|
99
|
-
/// is shown.
|
|
99
|
+
/// is shown. Where there's room it cascades as a side flyout (desktop); on a
|
|
100
|
+
/// narrow screen it expands inline beneath the row (accordion).
|
|
100
101
|
final List<KasyMenuSection>? submenu;
|
|
101
102
|
|
|
102
103
|
/// Whether tapping this row closes the menu. Null inherits the menu-level
|
|
@@ -359,17 +360,18 @@ class _KasyMenuAnchorState extends State<KasyMenuAnchor>
|
|
|
359
360
|
}
|
|
360
361
|
|
|
361
362
|
// Decide the open direction only. Position is handled by the LayerLink, so a
|
|
362
|
-
// slightly-off estimate just flips the menu — it always stays glued.
|
|
363
|
-
//
|
|
363
|
+
// slightly-off estimate just flips the menu — it always stays glued. Mirrors
|
|
364
|
+
// [KasyDropDown]: measure against the viewport minus the safe-area insets
|
|
365
|
+
// (notch / home indicator) so the panel never opens under them.
|
|
364
366
|
bool _resolveOpenUp() {
|
|
365
367
|
final RenderBox? box = context.findRenderObject() as RenderBox?;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
final
|
|
370
|
-
final double
|
|
371
|
-
|
|
372
|
-
final double spaceAbove = topLeft.dy;
|
|
368
|
+
if (box == null) return false;
|
|
369
|
+
final Offset topLeft = box.localToGlobal(Offset.zero);
|
|
370
|
+
final Size viewport = MediaQuery.sizeOf(context);
|
|
371
|
+
final EdgeInsets safe = MediaQuery.viewPaddingOf(context);
|
|
372
|
+
final double triggerBottom = topLeft.dy + box.size.height;
|
|
373
|
+
final double spaceBelow = viewport.height - safe.bottom - triggerBottom;
|
|
374
|
+
final double spaceAbove = topLeft.dy - safe.top;
|
|
373
375
|
return spaceBelow < _estimatedHeight() + 8 && spaceAbove > spaceBelow;
|
|
374
376
|
}
|
|
375
377
|
|
|
@@ -591,6 +593,9 @@ class _MenuItemRowState extends State<_MenuItemRow>
|
|
|
591
593
|
|
|
592
594
|
// Whether the flyout opens to the left of the row (no room on the right).
|
|
593
595
|
bool _openLeft = false;
|
|
596
|
+
// Whether the submenu is expanded inline beneath the row (the narrow-screen
|
|
597
|
+
// accordion fallback, when there's no room for a side cascade).
|
|
598
|
+
bool _inlineExpanded = false;
|
|
594
599
|
|
|
595
600
|
bool get _hasSubmenu => widget.item.submenu != null;
|
|
596
601
|
|
|
@@ -615,13 +620,22 @@ class _MenuItemRowState extends State<_MenuItemRow>
|
|
|
615
620
|
super.dispose();
|
|
616
621
|
}
|
|
617
622
|
|
|
618
|
-
|
|
623
|
+
// Toggle the submenu. With room on a side it cascades as a flyout (desktop);
|
|
624
|
+
// on a narrow screen it expands inline beneath the row (the accordion the
|
|
625
|
+
// NN/g mobile-subnavigation guidance recommends for a handful of items —
|
|
626
|
+
// lowest interaction cost, no disorienting second surface).
|
|
627
|
+
void _toggleSubmenu() {
|
|
628
|
+
if (_portal.isShowing) {
|
|
629
|
+
_closeSubmenu();
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (_inlineExpanded) {
|
|
633
|
+
setState(() => _inlineExpanded = false);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
619
636
|
final _FlyoutSide side = _resolveSide();
|
|
620
637
|
if (side == _FlyoutSide.none) {
|
|
621
|
-
|
|
622
|
-
// beside the menu, so present the nested menu as a sheet instead of a
|
|
623
|
-
// flyout clipped off-screen. The cascade still shows on wider layouts.
|
|
624
|
-
_openSubmenuSheet();
|
|
638
|
+
setState(() => _inlineExpanded = true);
|
|
625
639
|
return;
|
|
626
640
|
}
|
|
627
641
|
setState(() => _openLeft = side == _FlyoutSide.left);
|
|
@@ -635,28 +649,6 @@ class _MenuItemRowState extends State<_MenuItemRow>
|
|
|
635
649
|
});
|
|
636
650
|
}
|
|
637
651
|
|
|
638
|
-
Future<void> _openSubmenuSheet() {
|
|
639
|
-
final bool dark = Theme.of(context).brightness == Brightness.dark;
|
|
640
|
-
return showModalBottomSheet<void>(
|
|
641
|
-
context: context,
|
|
642
|
-
useRootNavigator: true,
|
|
643
|
-
backgroundColor: Colors.transparent,
|
|
644
|
-
barrierColor: Colors.black.withValues(alpha: dark ? 0.6 : 0.45),
|
|
645
|
-
builder: (_) => KasySheetSurface(
|
|
646
|
-
child: KasyMenu(
|
|
647
|
-
sections: widget.item.submenu!,
|
|
648
|
-
closeOnSelect: widget.menuCloseOnSelect,
|
|
649
|
-
// A row that closes the menu pops this sheet, then bubbles the close
|
|
650
|
-
// up so the parent chain dismisses too.
|
|
651
|
-
onClose: () {
|
|
652
|
-
Navigator.of(context, rootNavigator: true).pop();
|
|
653
|
-
_bubbleClose();
|
|
654
|
-
},
|
|
655
|
-
),
|
|
656
|
-
),
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
652
|
// Pick the side with room for the flyout, or [none] when neither fits (the
|
|
661
653
|
// caller then falls back to a sheet). Position is the LayerLink's job — this
|
|
662
654
|
// only decides the side, measured in the overlay's space.
|
|
@@ -677,11 +669,7 @@ class _MenuItemRowState extends State<_MenuItemRow>
|
|
|
677
669
|
|
|
678
670
|
void _handleTap() {
|
|
679
671
|
if (_hasSubmenu) {
|
|
680
|
-
|
|
681
|
-
_closeSubmenu();
|
|
682
|
-
} else {
|
|
683
|
-
_openSubmenu();
|
|
684
|
-
}
|
|
672
|
+
_toggleSubmenu();
|
|
685
673
|
return;
|
|
686
674
|
}
|
|
687
675
|
// Close first (mirrors the call-site ordering that avoids a Navigator lock
|
|
@@ -818,13 +806,20 @@ class _MenuItemRowState extends State<_MenuItemRow>
|
|
|
818
806
|
leading = SizedBox(width: iconSize);
|
|
819
807
|
}
|
|
820
808
|
|
|
821
|
-
// Suffix slot: submenu chevron, custom trailing, or a shortcut chip.
|
|
809
|
+
// Suffix slot: submenu chevron, custom trailing, or a shortcut chip. The
|
|
810
|
+
// chevron rotates a quarter turn down while the submenu is expanded inline,
|
|
811
|
+
// so the row reads like an accordion header.
|
|
822
812
|
Widget? trailing;
|
|
823
813
|
if (item.submenu != null) {
|
|
824
|
-
trailing =
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
814
|
+
trailing = AnimatedRotation(
|
|
815
|
+
turns: _inlineExpanded ? 0.25 : 0,
|
|
816
|
+
duration: const Duration(milliseconds: 150),
|
|
817
|
+
curve: Curves.easeOutCubic,
|
|
818
|
+
child: Icon(
|
|
819
|
+
KasyIcons.chevronRight,
|
|
820
|
+
size: KasyIconSize.sm,
|
|
821
|
+
color: resolvedLead,
|
|
822
|
+
),
|
|
828
823
|
);
|
|
829
824
|
} else if (item.trailing != null) {
|
|
830
825
|
trailing = item.trailing;
|
|
@@ -890,13 +885,42 @@ class _MenuItemRowState extends State<_MenuItemRow>
|
|
|
890
885
|
),
|
|
891
886
|
);
|
|
892
887
|
|
|
893
|
-
// A plain row needs no overlay machinery.
|
|
894
|
-
// through an OverlayPortal and links the panel to itself for side anchoring.
|
|
888
|
+
// A plain row needs no overlay machinery.
|
|
895
889
|
if (!_hasSubmenu) return hover;
|
|
890
|
+
|
|
891
|
+
// The trigger carries the LayerLink so a side cascade can anchor to it.
|
|
892
|
+
Widget child = CompositedTransformTarget(link: _link, child: hover);
|
|
893
|
+
|
|
894
|
+
// Narrow screens expand the submenu inline, indented beneath the row (so the
|
|
895
|
+
// nested items align under the title). Mutually exclusive with the cascade.
|
|
896
|
+
if (_inlineExpanded) {
|
|
897
|
+
final double indent = iconSize + iconGap;
|
|
898
|
+
child = Column(
|
|
899
|
+
mainAxisSize: MainAxisSize.min,
|
|
900
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
901
|
+
children: [
|
|
902
|
+
child,
|
|
903
|
+
Padding(
|
|
904
|
+
padding: EdgeInsets.only(left: indent),
|
|
905
|
+
child: KasyMenu(
|
|
906
|
+
sections: item.submenu!,
|
|
907
|
+
density: widget.compact
|
|
908
|
+
? KasyMenuDensity.compact
|
|
909
|
+
: KasyMenuDensity.comfortable,
|
|
910
|
+
closeOnSelect: widget.menuCloseOnSelect,
|
|
911
|
+
onClose: _closeChain,
|
|
912
|
+
tapGroupId: widget.tapGroupId,
|
|
913
|
+
),
|
|
914
|
+
),
|
|
915
|
+
],
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// A submenu row hosts its side flyout through an OverlayPortal.
|
|
896
920
|
return OverlayPortal(
|
|
897
921
|
controller: _portal,
|
|
898
922
|
overlayChildBuilder: _buildSubmenuOverlay,
|
|
899
|
-
child:
|
|
923
|
+
child: child,
|
|
900
924
|
);
|
|
901
925
|
}
|
|
902
926
|
}
|
|
@@ -184,31 +184,6 @@ class KasyTextTheme extends ThemeExtension<KasyTextTheme> {
|
|
|
184
184
|
/// Caption, hint, version label, footnote. 12 / w400.
|
|
185
185
|
TextStyle get caption => bodySmall;
|
|
186
186
|
|
|
187
|
-
// --- Overlay & component roles -----------------------------------------
|
|
188
|
-
// The harmonic ladder for surfaces and components: an OVERLAY (a full
|
|
189
|
-
// decision surface — dialog, bottom sheet) carries the largest title (20); an
|
|
190
|
-
// inline COMPONENT header (alert, accordion) sits a step below (16) so it
|
|
191
|
-
// reads as a header WITHIN the component, not a screen section; supporting
|
|
192
|
-
// text everywhere is 14. These are deliberately STABLE across breakpoints —
|
|
193
|
-
// only page/hero headings scale (Material/Apple model). Route every
|
|
194
|
-
// dialog/sheet/alert/accordion through these so titles never drift per
|
|
195
|
-
// component.
|
|
196
|
-
|
|
197
|
-
/// Title of a modal/overlay surface (dialog, bottom sheet, OTP sheet).
|
|
198
|
-
/// 20 / w600 — the largest title tier, for full decision surfaces.
|
|
199
|
-
TextStyle get overlayTitle => titleLarge;
|
|
200
|
-
|
|
201
|
-
/// Header of an inline component (alert, accordion). 16 / w600 — content
|
|
202
|
-
/// emphasis, one clear step below an overlay/section title and above body, so
|
|
203
|
-
/// a compact card never wears a screen-section-sized title.
|
|
204
|
-
TextStyle get componentTitle =>
|
|
205
|
-
bodyLarge.copyWith(fontWeight: FontWeight.w600);
|
|
206
|
-
|
|
207
|
-
/// Supporting text / description inside an overlay or component (the message
|
|
208
|
-
/// under a dialog/sheet/alert title, an accordion body). 14 / w400. Apply the
|
|
209
|
-
/// muted colour and any line-height at the call site.
|
|
210
|
-
TextStyle get supportingText => bodyMedium;
|
|
211
|
-
|
|
212
187
|
/// Builds the full text theme for a given [device].
|
|
213
188
|
///
|
|
214
189
|
/// Each slot resolves its size from the responsive [KasyTypeScale] ramp at the
|