kasy-cli 1.39.1 → 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 +23 -0
- package/package.json +1 -1
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +27 -27
- package/templates/firebase/lib/components/components.dart +2 -0
- 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_bottom_sheet.dart +25 -7
- package/templates/firebase/lib/components/kasy_dialog.dart +3 -1
- package/templates/firebase/lib/components/kasy_menu.dart +926 -0
- package/templates/firebase/lib/components/kasy_popover.dart +267 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +20 -10
- package/templates/firebase/lib/core/navigation/kasy_route_observer.dart +8 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +23 -4
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +320 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +38 -94
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +32 -107
- package/templates/firebase/lib/features/subscriptions/ui/widgets/comparison_table.dart +21 -21
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_close_button.dart +35 -27
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +22 -17
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +12 -7
- package/templates/firebase/lib/router.dart +2 -0
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +0 -81
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// KasyPopover — desktop-native counterpart to a mobile menu bottom sheet.
|
|
6
|
+
//
|
|
7
|
+
// Instead of dimming the whole screen and dropping a centered card (what a
|
|
8
|
+
// bottom sheet "adapted" to desktop does), a popover opens right next to the
|
|
9
|
+
// control the user clicked, like a native desktop menu. Position is computed
|
|
10
|
+
// from the trigger's render box — no LayerLink wiring at the call site — so any
|
|
11
|
+
// `onTap` that still has the trigger's BuildContext can anchor one. It flips
|
|
12
|
+
// above the trigger when there's no room below, clamps to the viewport, and
|
|
13
|
+
// dismisses on tap-outside / Esc.
|
|
14
|
+
//
|
|
15
|
+
// Used by [showKasyMenuSheet] (see kasy_bottom_sheet.dart) as the desktop form
|
|
16
|
+
// of a menu sheet; callers normally go through that, not this directly.
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/// Horizontal anchoring of the popover panel relative to its trigger.
|
|
20
|
+
enum KasyPopoverAlign {
|
|
21
|
+
/// Panel's left edge aligns with the trigger's left edge.
|
|
22
|
+
start,
|
|
23
|
+
|
|
24
|
+
/// Panel's right edge aligns with the trigger's right edge (menus that hang
|
|
25
|
+
/// off a right-aligned value, e.g. a settings row).
|
|
26
|
+
end,
|
|
27
|
+
|
|
28
|
+
/// Panel is centered horizontally on the trigger.
|
|
29
|
+
center,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Side of the trigger the popover opens toward. Each side flips to its opposite
|
|
33
|
+
/// when there isn't room (e.g. [bottom] flips to top near the screen edge).
|
|
34
|
+
enum KasyPopoverPlacement { bottom, top, left, right }
|
|
35
|
+
|
|
36
|
+
const double _kPopoverDefaultWidth = 220;
|
|
37
|
+
const double _kPopoverGap = 6;
|
|
38
|
+
const double _kViewportMargin = 8;
|
|
39
|
+
|
|
40
|
+
// Below this much room on the chosen side we consider flipping to the opposite.
|
|
41
|
+
const double _kFlipThreshold = 220;
|
|
42
|
+
|
|
43
|
+
/// Presents [builder] as a floating panel anchored to the widget that owns
|
|
44
|
+
/// [anchorContext] (the control the user clicked). The content is wrapped in a
|
|
45
|
+
/// [KasyPopoverSurface] automatically, so [builder] returns the bare menu body.
|
|
46
|
+
///
|
|
47
|
+
/// [placement] picks the side it opens toward (default below the trigger);
|
|
48
|
+
/// [align] sets cross-axis alignment for top/bottom placements. Pop the panel
|
|
49
|
+
/// with a value (`Navigator.pop(context, value)`) to return it through the
|
|
50
|
+
/// future, mirroring [showModalBottomSheet].
|
|
51
|
+
Future<T?> showKasyPopover<T>({
|
|
52
|
+
required BuildContext anchorContext,
|
|
53
|
+
required WidgetBuilder builder,
|
|
54
|
+
KasyPopoverAlign align = KasyPopoverAlign.start,
|
|
55
|
+
KasyPopoverPlacement placement = KasyPopoverPlacement.bottom,
|
|
56
|
+
double width = _kPopoverDefaultWidth,
|
|
57
|
+
double gap = _kPopoverGap,
|
|
58
|
+
}) {
|
|
59
|
+
// Snapshot the trigger geometry at open time (global coords). A transient menu
|
|
60
|
+
// doesn't need to follow scroll — tap-outside closes it.
|
|
61
|
+
final RenderBox? anchorBox = anchorContext.findRenderObject() as RenderBox?;
|
|
62
|
+
final Size viewport = MediaQuery.sizeOf(anchorContext);
|
|
63
|
+
final Offset anchorTopLeft =
|
|
64
|
+
anchorBox?.localToGlobal(Offset.zero) ??
|
|
65
|
+
Offset(viewport.width / 2, viewport.height / 2);
|
|
66
|
+
final Size anchorSize = anchorBox?.size ?? Size.zero;
|
|
67
|
+
|
|
68
|
+
return showGeneralDialog<T>(
|
|
69
|
+
context: anchorContext,
|
|
70
|
+
barrierDismissible: true,
|
|
71
|
+
barrierLabel: MaterialLocalizations.of(
|
|
72
|
+
anchorContext,
|
|
73
|
+
).modalBarrierDismissLabel,
|
|
74
|
+
// No scrim: a desktop popover keeps the page visible behind it.
|
|
75
|
+
barrierColor: Colors.transparent,
|
|
76
|
+
transitionDuration: const Duration(milliseconds: 160),
|
|
77
|
+
pageBuilder: (ctx, animation, _) => _KasyPopoverLayout(
|
|
78
|
+
anchorTopLeft: anchorTopLeft,
|
|
79
|
+
anchorSize: anchorSize,
|
|
80
|
+
width: width,
|
|
81
|
+
gap: gap,
|
|
82
|
+
align: align,
|
|
83
|
+
placement: placement,
|
|
84
|
+
animation: animation,
|
|
85
|
+
child: Builder(builder: builder),
|
|
86
|
+
),
|
|
87
|
+
transitionBuilder: (_, _, _, child) => child,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Positions and animates the popover panel over a full-screen, transparent
|
|
92
|
+
/// layer. Resolves [placement] (flipping to the opposite side when room is
|
|
93
|
+
/// tight) and clamps the panel within the viewport.
|
|
94
|
+
class _KasyPopoverLayout extends StatelessWidget {
|
|
95
|
+
final Offset anchorTopLeft;
|
|
96
|
+
final Size anchorSize;
|
|
97
|
+
final double width;
|
|
98
|
+
final double gap;
|
|
99
|
+
final KasyPopoverAlign align;
|
|
100
|
+
final KasyPopoverPlacement placement;
|
|
101
|
+
final Animation<double> animation;
|
|
102
|
+
final Widget child;
|
|
103
|
+
|
|
104
|
+
const _KasyPopoverLayout({
|
|
105
|
+
required this.anchorTopLeft,
|
|
106
|
+
required this.anchorSize,
|
|
107
|
+
required this.width,
|
|
108
|
+
required this.gap,
|
|
109
|
+
required this.align,
|
|
110
|
+
required this.placement,
|
|
111
|
+
required this.animation,
|
|
112
|
+
required this.child,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
@override
|
|
116
|
+
Widget build(BuildContext context) {
|
|
117
|
+
final Size viewport = MediaQuery.sizeOf(context);
|
|
118
|
+
final EdgeInsets safe = MediaQuery.viewPaddingOf(context);
|
|
119
|
+
final double anchorRight = anchorTopLeft.dx + anchorSize.width;
|
|
120
|
+
final double anchorBottom = anchorTopLeft.dy + anchorSize.height;
|
|
121
|
+
|
|
122
|
+
final double spaceBelow = viewport.height - safe.bottom - anchorBottom;
|
|
123
|
+
final double spaceAbove = anchorTopLeft.dy - safe.top;
|
|
124
|
+
final double spaceRight = viewport.width - _kViewportMargin - anchorRight;
|
|
125
|
+
final double spaceLeft = anchorTopLeft.dx - _kViewportMargin;
|
|
126
|
+
|
|
127
|
+
// Resolve the effective side, flipping to the opposite when too tight.
|
|
128
|
+
final KasyPopoverPlacement side = switch (placement) {
|
|
129
|
+
KasyPopoverPlacement.bottom =>
|
|
130
|
+
spaceBelow < _kFlipThreshold && spaceAbove > spaceBelow
|
|
131
|
+
? KasyPopoverPlacement.top
|
|
132
|
+
: KasyPopoverPlacement.bottom,
|
|
133
|
+
KasyPopoverPlacement.top =>
|
|
134
|
+
spaceAbove < _kFlipThreshold && spaceBelow > spaceAbove
|
|
135
|
+
? KasyPopoverPlacement.bottom
|
|
136
|
+
: KasyPopoverPlacement.top,
|
|
137
|
+
KasyPopoverPlacement.right =>
|
|
138
|
+
spaceRight < width + gap && spaceLeft > spaceRight
|
|
139
|
+
? KasyPopoverPlacement.left
|
|
140
|
+
: KasyPopoverPlacement.right,
|
|
141
|
+
KasyPopoverPlacement.left =>
|
|
142
|
+
spaceLeft < width + gap && spaceRight > spaceLeft
|
|
143
|
+
? KasyPopoverPlacement.right
|
|
144
|
+
: KasyPopoverPlacement.left,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
double? left;
|
|
148
|
+
double? top;
|
|
149
|
+
double? right;
|
|
150
|
+
double? bottom;
|
|
151
|
+
double maxHeight;
|
|
152
|
+
Alignment scaleAlign;
|
|
153
|
+
|
|
154
|
+
final bool vertical =
|
|
155
|
+
side == KasyPopoverPlacement.bottom || side == KasyPopoverPlacement.top;
|
|
156
|
+
if (vertical) {
|
|
157
|
+
left = switch (align) {
|
|
158
|
+
KasyPopoverAlign.start => anchorTopLeft.dx,
|
|
159
|
+
KasyPopoverAlign.end => anchorRight - width,
|
|
160
|
+
KasyPopoverAlign.center =>
|
|
161
|
+
anchorTopLeft.dx + (anchorSize.width - width) / 2,
|
|
162
|
+
}.clamp(_kViewportMargin, viewport.width - width - _kViewportMargin);
|
|
163
|
+
if (side == KasyPopoverPlacement.bottom) {
|
|
164
|
+
top = anchorBottom + gap;
|
|
165
|
+
maxHeight = spaceBelow - gap - _kViewportMargin;
|
|
166
|
+
scaleAlign = Alignment.topCenter;
|
|
167
|
+
} else {
|
|
168
|
+
bottom = viewport.height - (anchorTopLeft.dy - gap);
|
|
169
|
+
maxHeight = spaceAbove - gap - _kViewportMargin;
|
|
170
|
+
scaleAlign = Alignment.bottomCenter;
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// Left / right: align the panel's top with the trigger, clamped so it
|
|
174
|
+
// never runs past the bottom edge.
|
|
175
|
+
top = anchorTopLeft.dy.clamp(
|
|
176
|
+
safe.top + _kViewportMargin,
|
|
177
|
+
viewport.height - safe.bottom - _kViewportMargin,
|
|
178
|
+
);
|
|
179
|
+
maxHeight = viewport.height - safe.bottom - top - _kViewportMargin;
|
|
180
|
+
if (side == KasyPopoverPlacement.right) {
|
|
181
|
+
left = anchorRight + gap;
|
|
182
|
+
scaleAlign = Alignment.centerLeft;
|
|
183
|
+
} else {
|
|
184
|
+
right = viewport.width - (anchorTopLeft.dx - gap);
|
|
185
|
+
scaleAlign = Alignment.centerRight;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
final CurvedAnimation curved = CurvedAnimation(
|
|
190
|
+
parent: animation,
|
|
191
|
+
curve: Curves.easeOutCubic,
|
|
192
|
+
reverseCurve: Curves.easeIn,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return Stack(
|
|
196
|
+
children: [
|
|
197
|
+
Positioned(
|
|
198
|
+
left: left,
|
|
199
|
+
top: top,
|
|
200
|
+
right: right,
|
|
201
|
+
bottom: bottom,
|
|
202
|
+
child: FadeTransition(
|
|
203
|
+
opacity: curved,
|
|
204
|
+
child: ScaleTransition(
|
|
205
|
+
scale: Tween<double>(begin: 0.96, end: 1.0).animate(curved),
|
|
206
|
+
alignment: scaleAlign,
|
|
207
|
+
child: KasyPopoverSurface(
|
|
208
|
+
width: width,
|
|
209
|
+
maxHeight: maxHeight.clamp(0, viewport.height),
|
|
210
|
+
child: child,
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
],
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Floating surface chrome for popover content: design-system surface tone,
|
|
221
|
+
/// fully-rounded corners, hairline border and elevation shadow, with an internal
|
|
222
|
+
/// scroll so tall menus never overflow. Mirrors the dropdown panel look.
|
|
223
|
+
class KasyPopoverSurface extends StatelessWidget {
|
|
224
|
+
final Widget child;
|
|
225
|
+
final double width;
|
|
226
|
+
final double maxHeight;
|
|
227
|
+
|
|
228
|
+
const KasyPopoverSurface({
|
|
229
|
+
super.key,
|
|
230
|
+
required this.child,
|
|
231
|
+
this.width = _kPopoverDefaultWidth,
|
|
232
|
+
this.maxHeight = double.infinity,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
@override
|
|
236
|
+
Widget build(BuildContext context) {
|
|
237
|
+
final BorderRadius radius = BorderRadius.circular(KasyRadius.xl);
|
|
238
|
+
return Material(
|
|
239
|
+
type: MaterialType.transparency,
|
|
240
|
+
child: Container(
|
|
241
|
+
width: width,
|
|
242
|
+
constraints: BoxConstraints(maxHeight: maxHeight),
|
|
243
|
+
decoration: BoxDecoration(
|
|
244
|
+
color: context.colors.surface,
|
|
245
|
+
borderRadius: radius,
|
|
246
|
+
border: Border.all(
|
|
247
|
+
color: context.colors.outline.withValues(alpha: 0.22),
|
|
248
|
+
),
|
|
249
|
+
boxShadow: [
|
|
250
|
+
KasyShadows.component(
|
|
251
|
+
context,
|
|
252
|
+
blurRadius: 24,
|
|
253
|
+
spreadRadius: -2,
|
|
254
|
+
offset: const Offset(0, 10),
|
|
255
|
+
),
|
|
256
|
+
],
|
|
257
|
+
),
|
|
258
|
+
child: ClipRRect(
|
|
259
|
+
borderRadius: radius,
|
|
260
|
+
child: SingleChildScrollView(
|
|
261
|
+
child: child,
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -393,6 +393,12 @@ class _KasySidebarState extends State<KasySidebar> {
|
|
|
393
393
|
/// Viewport width below which the sidebar auto-collapses (tablet breakpoint).
|
|
394
394
|
static const double _kBreakpoint = 1024.0;
|
|
395
395
|
|
|
396
|
+
/// Phone breakpoint. Below this the sidebar is always an overlay (drawer or
|
|
397
|
+
/// full-screen preview), so the logo gets its mobile placement (nudged down,
|
|
398
|
+
/// band trimmed). Above it the inline rail keeps its divider aligned with the
|
|
399
|
+
/// page app bar / web header.
|
|
400
|
+
static const double _kMobileBreakpoint = 768.0;
|
|
401
|
+
|
|
396
402
|
|
|
397
403
|
/// True when wired to Bart's navigation (real, tappable screens).
|
|
398
404
|
bool get _connected =>
|
|
@@ -982,18 +988,22 @@ class _KasySidebarState extends State<KasySidebar> {
|
|
|
982
988
|
// border: the web header (68) on desktop, but the shorter KasyAppBar on
|
|
983
989
|
// tablet (medium), where the page keeps its own app bar instead of the
|
|
984
990
|
// header. Without this the line breaks between the rail and the app bar.
|
|
985
|
-
final
|
|
986
|
-
|
|
987
|
-
//
|
|
988
|
-
//
|
|
991
|
+
final double viewportWidth = MediaQuery.sizeOf(context).width;
|
|
992
|
+
final bool isCompact = viewportWidth < _kBreakpoint;
|
|
993
|
+
// On phones the sidebar is always an overlay (drawer or full-screen preview),
|
|
994
|
+
// never the inline rail beside a page app bar — so mobile positioning is a
|
|
995
|
+
// sidebar default keyed off the phone breakpoint, not isDrawer.
|
|
996
|
+
final bool isMobile = viewportWidth < _kMobileBreakpoint;
|
|
997
|
+
// Mobile trims a little off the band so there's less dead space below the
|
|
998
|
+
// wordmark before the divider; the overlay has no app-bar line to align with.
|
|
989
999
|
final double bandHeight = !isCompact
|
|
990
1000
|
? _kTopBandHeight
|
|
991
|
-
: kasyAppBarBodyTopOverlap(context) - (
|
|
992
|
-
//
|
|
993
|
-
//
|
|
994
|
-
//
|
|
995
|
-
//
|
|
996
|
-
final double logoTopInset =
|
|
1001
|
+
: kasyAppBarBodyTopOverlap(context) - (isMobile ? 26.0 : 0.0);
|
|
1002
|
+
// On mobile the band starts right under the status bar notch, so nudge the
|
|
1003
|
+
// brand (and the collapsed toggle, which shares this band) down to breathe.
|
|
1004
|
+
// Tablet/desktop keep it centred so the rail's divider stays aligned with the
|
|
1005
|
+
// app bar / web header.
|
|
1006
|
+
final double logoTopInset = isMobile ? 44.0 : 0.0;
|
|
997
1007
|
// The collapse toggle is available on every breakpoint so any config can be
|
|
998
1008
|
// switched thin↔wide — except a drawer, which is a dismissible overlay you
|
|
999
1009
|
// close whole rather than collapse in place.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import 'package:flutter/widgets.dart';
|
|
2
|
+
|
|
3
|
+
/// Shared route observer on the root navigator so screens can react to being
|
|
4
|
+
/// revealed again after a route pushed on top of them pops, via
|
|
5
|
+
/// [RouteAware.didPopNext]. Register it in the router's `observers` list and
|
|
6
|
+
/// subscribe a [RouteAware] state to it.
|
|
7
|
+
final RouteObserver<ModalRoute<dynamic>> kasyRouteObserver =
|
|
8
|
+
RouteObserver<ModalRoute<dynamic>>();
|
|
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|
|
2
2
|
import 'package:go_router/go_router.dart';
|
|
3
3
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
4
4
|
import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
|
|
5
|
+
import 'package:kasy_kit/core/navigation/kasy_route_observer.dart';
|
|
5
6
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
6
7
|
import 'package:kasy_kit/core/widgets/kasy_hover.dart';
|
|
7
8
|
import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
|
|
@@ -46,18 +47,34 @@ class HomeComponentsCatalog extends StatefulWidget {
|
|
|
46
47
|
State<HomeComponentsCatalog> createState() => _HomeComponentsCatalogState();
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
class _HomeComponentsCatalogState extends State<HomeComponentsCatalog>
|
|
50
|
+
class _HomeComponentsCatalogState extends State<HomeComponentsCatalog>
|
|
51
|
+
with RouteAware {
|
|
50
52
|
final TextEditingController _searchCtrl = TextEditingController();
|
|
51
53
|
String _query = '';
|
|
52
54
|
|
|
55
|
+
@override
|
|
56
|
+
void didChangeDependencies() {
|
|
57
|
+
super.didChangeDependencies();
|
|
58
|
+
final ModalRoute<dynamic>? route = ModalRoute.of(context);
|
|
59
|
+
if (route != null) {
|
|
60
|
+
kasyRouteObserver.subscribe(this, route);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
@override
|
|
54
65
|
void dispose() {
|
|
66
|
+
kasyRouteObserver.unsubscribe(this);
|
|
55
67
|
_searchCtrl.dispose();
|
|
56
68
|
super.dispose();
|
|
57
69
|
}
|
|
58
70
|
|
|
59
|
-
///
|
|
60
|
-
/// full catalog instead of the stale search term.
|
|
71
|
+
/// When a pushed preview pops back to the catalog, reset the filter so the
|
|
72
|
+
/// list shows the full catalog instead of the stale search term. Backed up by
|
|
73
|
+
/// the navigation future's [_clearSearch] for the admin-embedded case, whose
|
|
74
|
+
/// nested navigator the root observer does not see.
|
|
75
|
+
@override
|
|
76
|
+
void didPopNext() => _clearSearch();
|
|
77
|
+
|
|
61
78
|
void _clearSearch() {
|
|
62
79
|
if (!mounted) return;
|
|
63
80
|
if (_query.isEmpty && _searchCtrl.text.isEmpty) return;
|
|
@@ -325,6 +342,7 @@ const Set<String> _kReadyComponents = <String>{
|
|
|
325
342
|
'Dialog',
|
|
326
343
|
'DropDown',
|
|
327
344
|
'Hover',
|
|
345
|
+
'Menu',
|
|
328
346
|
'Tabs',
|
|
329
347
|
'TextArea',
|
|
330
348
|
'TextField',
|
|
@@ -352,6 +370,7 @@ const Set<String> _kWebReadyComponents = <String>{
|
|
|
352
370
|
'Dialog',
|
|
353
371
|
'DropDown',
|
|
354
372
|
'Hover',
|
|
373
|
+
'Menu',
|
|
355
374
|
'Sidebar',
|
|
356
375
|
'Skeleton',
|
|
357
376
|
'Status Tag',
|
|
@@ -384,6 +403,7 @@ const List<_CatalogRow> _kCatalog = [
|
|
|
384
403
|
_CatalogRow('Dialog'),
|
|
385
404
|
_CatalogRow('DropDown'),
|
|
386
405
|
_CatalogRow('Hover'),
|
|
406
|
+
_CatalogRow('Menu'),
|
|
387
407
|
_CatalogRow('Sidebar'),
|
|
388
408
|
_CatalogRow('Skeleton'),
|
|
389
409
|
_CatalogRow('Status Tag'),
|
|
@@ -412,7 +432,6 @@ const List<_CatalogRow> _kCatalog = [
|
|
|
412
432
|
_CatalogRow('LineChart'),
|
|
413
433
|
_CatalogRow('LinkButton'),
|
|
414
434
|
_CatalogRow('ListGroup'),
|
|
415
|
-
_CatalogRow('Menu'),
|
|
416
435
|
_CatalogRow('NumberField'),
|
|
417
436
|
_CatalogRow('NumberStepper'),
|
|
418
437
|
_CatalogRow('NumberValue'),
|