Add initial configuration, tokens, and widgets for M3E components
- Introduced `.gitignore` and `.metadata` for apps and examples. - Added Flutter/Dart analysis configurations (`analysis_options.yaml`). - Implemented foundational tokens and themes for M3E (colors, shapes). - Created base implementations for `IconButtonM3E` and `SplitButtonM3E`. - Set up CI workflow (`ci.yaml`) to automate testing and analysis.
This commit is contained in:
parent
2c0f2df0b8
commit
62ecb86b76
184 changed files with 9872 additions and 0 deletions
21
packages/navigation_rail_m3e/LICENSE
Normal file
21
packages/navigation_rail_m3e/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) ...
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
84
packages/navigation_rail_m3e/README.md
Normal file
84
packages/navigation_rail_m3e/README.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# navigation_rail_m3e
|
||||
|
||||
Material 3 **Expressive** Navigation Rail for Flutter with badges, pill/stripe indicators, and token-driven styling.
|
||||
|
||||
- `NavigationRailM3E` — wrapper around Flutter's `NavigationRail` with M3E tokens
|
||||
- `RailDestinationM3E` — destination data (icon, selectedIcon, label, badge)
|
||||
- `RailBadgeM3E` — small badge/dot utility for icons
|
||||
|
||||
All styling is driven by the `m3e_design` ThemeExtension (**M3ETheme**).
|
||||
|
||||
## Monorepo Layout
|
||||
|
||||
```
|
||||
packages/
|
||||
m3e_design/
|
||||
navigation_rail_m3e/
|
||||
```
|
||||
|
||||
`pubspec.yaml` references `../m3e_design`.
|
||||
|
||||
## Usage
|
||||
|
||||
```dart
|
||||
import 'package:navigation_rail_m3e/navigation_rail_m3e.dart';
|
||||
|
||||
final items = [
|
||||
const RailDestinationM3E(
|
||||
icon: Icon(Icons.inbox_outlined),
|
||||
selectedIcon: Icon(Icons.inbox),
|
||||
label: 'Inbox',
|
||||
),
|
||||
const RailDestinationM3E(
|
||||
icon: Icon(Icons.chat_bubble_outline),
|
||||
label: 'Chat',
|
||||
badgeCount: 5,
|
||||
),
|
||||
const RailDestinationM3E(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
label: 'Settings',
|
||||
badgeDot: true,
|
||||
),
|
||||
];
|
||||
|
||||
NavigationRailM3E(
|
||||
destinations: items,
|
||||
selectedIndex: 0,
|
||||
onDestinationSelected: (i) {},
|
||||
labelBehavior: RailLabelBehavior.onlySelected, // none | onlySelected | alwaysShow
|
||||
indicatorStyle: RailIndicatorStyle.pill, // pill | stripe | none
|
||||
size: RailSize.regular, // compact | regular
|
||||
density: RailDensity.regular, // regular | compact
|
||||
shapeFamily: RailShapeFamily.round, // round | square
|
||||
extended: false, // true to show labels permanently (wide rail)
|
||||
groupAlignment: -1.0, // -1 top .. 1 bottom
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: FlutterLogo(size: 24),
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
## Tokens mapping
|
||||
|
||||
- **Container**: `surfaceContainerHigh`
|
||||
- **Indicator**: `secondaryContainer` (color). `pill` uses NavigationRail's indicator; `stripe` draws a left border on the selected icon.
|
||||
- **Selected**: `onSecondaryContainer` (icon/label)
|
||||
- **Unselected**: `onSurfaceVariant`
|
||||
- **Label style**: `labelMedium`
|
||||
- **Widths**: compact **≈64dp**, regular **≈80dp**, extended min **≈256dp**
|
||||
- **Icon size**: **24dp**
|
||||
- **Item padding**: from `spacing.sm/md`
|
||||
|
||||
## Badges
|
||||
|
||||
Use `badgeCount` for numeric badges or `badgeDot: true` for a small dot. Colors default to `errorContainer / onErrorContainer` and can be overridden via `RailBadgeM3E`.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Provide `semanticLabel` per destination (used as tooltip) or on the rail (`semanticLabel` on the widget).
|
||||
- Choose the label behavior to balance density with readability.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
library navigation_rail_m3e;
|
||||
|
||||
export 'src/enums.dart';
|
||||
export 'src/rail_tokens_adapter.dart' show RailTokensAdapter;
|
||||
export 'src/navigation_rail_m3e.dart';
|
||||
export 'src/rail_badge_m3e.dart';
|
||||
export 'src/rail_destination_m3e.dart';
|
||||
5
packages/navigation_rail_m3e/lib/src/enums.dart
Normal file
5
packages/navigation_rail_m3e/lib/src/enums.dart
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
enum RailLabelBehavior { alwaysShow, onlySelected, alwaysHide }
|
||||
enum RailSize { compact, regular }
|
||||
enum RailShapeFamily { round, square }
|
||||
enum RailDensity { regular, compact }
|
||||
enum RailIndicatorStyle { pill, stripe, none }
|
||||
177
packages/navigation_rail_m3e/lib/src/navigation_rail_m3e.dart
Normal file
177
packages/navigation_rail_m3e/lib/src/navigation_rail_m3e.dart
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
import 'rail_destination_m3e.dart';
|
||||
import 'rail_tokens_adapter.dart';
|
||||
|
||||
class NavigationRailM3E extends StatelessWidget {
|
||||
const NavigationRailM3E({
|
||||
super.key,
|
||||
required this.destinations,
|
||||
this.selectedIndex = 0,
|
||||
this.onDestinationSelected,
|
||||
this.labelBehavior = RailLabelBehavior.onlySelected,
|
||||
this.size = RailSize.regular,
|
||||
this.shapeFamily = RailShapeFamily.round,
|
||||
this.density = RailDensity.regular,
|
||||
this.backgroundColor,
|
||||
this.elevation,
|
||||
this.indicatorStyle = RailIndicatorStyle.pill,
|
||||
this.indicatorColor,
|
||||
this.padding,
|
||||
this.groupAlignment,
|
||||
this.leading,
|
||||
this.trailing,
|
||||
this.extended = false,
|
||||
this.minExtendedWidth,
|
||||
this.useSafeArea = true,
|
||||
this.semanticLabel,
|
||||
});
|
||||
|
||||
final List<RailDestinationM3E> destinations;
|
||||
final int selectedIndex;
|
||||
final ValueChanged<int>? onDestinationSelected;
|
||||
|
||||
final RailLabelBehavior labelBehavior;
|
||||
final RailSize size;
|
||||
final RailShapeFamily shapeFamily;
|
||||
final RailDensity density;
|
||||
|
||||
final Color? backgroundColor;
|
||||
final double? elevation;
|
||||
|
||||
final RailIndicatorStyle indicatorStyle;
|
||||
final Color? indicatorColor;
|
||||
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
/// Aligns the group of destinations (-1 top .. 1 bottom).
|
||||
final double? groupAlignment;
|
||||
|
||||
/// Optional leading and trailing widgets (e.g., FAB or menu).
|
||||
final Widget? leading;
|
||||
final Widget? trailing;
|
||||
|
||||
/// Whether to show the rail in extended mode (icons + labels).
|
||||
final bool extended;
|
||||
|
||||
/// Minimum width when extended.
|
||||
final double? minExtendedWidth;
|
||||
|
||||
final bool useSafeArea;
|
||||
|
||||
final String? semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(destinations.isNotEmpty, 'Provide at least one destination');
|
||||
|
||||
final tokens = RailTokensAdapter(context);
|
||||
final metrics = tokens.metrics(density);
|
||||
|
||||
final width =
|
||||
size == RailSize.compact ? metrics.widthCompact : metrics.widthRegular;
|
||||
final bg = backgroundColor ?? tokens.containerColor();
|
||||
final shape = tokens.containerShape(shapeFamily);
|
||||
|
||||
final rail = Material(
|
||||
color: bg,
|
||||
elevation: elevation ??
|
||||
0, // null means use theme default; avoids invalid zero assertion
|
||||
shape: shape,
|
||||
child: SizedBox(
|
||||
width:
|
||||
extended ? (minExtendedWidth ?? metrics.extendedMinWidth) : width,
|
||||
child: NavigationRail(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: elevation, // pass through or null
|
||||
extended: extended,
|
||||
minExtendedWidth: minExtendedWidth ?? metrics.extendedMinWidth,
|
||||
selectedIndex: selectedIndex,
|
||||
groupAlignment: groupAlignment,
|
||||
leading: leading,
|
||||
trailing: trailing,
|
||||
labelType: switch (labelBehavior) {
|
||||
RailLabelBehavior.alwaysShow => NavigationRailLabelType.all,
|
||||
RailLabelBehavior.onlySelected => NavigationRailLabelType.selected,
|
||||
RailLabelBehavior.alwaysHide => NavigationRailLabelType.none,
|
||||
},
|
||||
useIndicator: indicatorStyle != RailIndicatorStyle.none,
|
||||
indicatorColor: indicatorColor ?? tokens.indicatorColor(),
|
||||
indicatorShape: switch (indicatorStyle) {
|
||||
RailIndicatorStyle.pill => tokens.indicatorShapePill(),
|
||||
RailIndicatorStyle.stripe =>
|
||||
const StadiumBorder(), // we'll fake stripe using decoration on selected icon
|
||||
RailIndicatorStyle.none => const StadiumBorder(),
|
||||
},
|
||||
selectedIconTheme: IconThemeData(
|
||||
color: tokens.selectedColor(), size: metrics.iconSize),
|
||||
unselectedIconTheme: IconThemeData(
|
||||
color: tokens.unselectedColor(), size: metrics.iconSize),
|
||||
selectedLabelTextStyle:
|
||||
tokens.labelStyle().copyWith(color: tokens.selectedColor()),
|
||||
unselectedLabelTextStyle:
|
||||
tokens.labelStyle().copyWith(color: tokens.unselectedColor()),
|
||||
destinations: List.generate(destinations.length, (i) {
|
||||
final d = destinations[i];
|
||||
return NavigationRailDestination(
|
||||
icon: _icon(context, false, d, metrics.iconSize),
|
||||
selectedIcon: _selectedIcon(
|
||||
context, true, d, metrics.iconSize, tokens, indicatorStyle),
|
||||
label: Text(d.label),
|
||||
padding: metrics.itemPadding as EdgeInsets?,
|
||||
);
|
||||
}),
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final padded = Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: rail,
|
||||
);
|
||||
|
||||
if (!useSafeArea && semanticLabel == null) return padded;
|
||||
|
||||
final wrapped = SafeArea(
|
||||
top: true,
|
||||
bottom: true,
|
||||
left: true,
|
||||
right: false,
|
||||
child: padded,
|
||||
);
|
||||
|
||||
if (semanticLabel == null) return wrapped;
|
||||
return Semantics(container: true, label: semanticLabel!, child: wrapped);
|
||||
}
|
||||
|
||||
Widget _icon(BuildContext context, bool selected, RailDestinationM3E d,
|
||||
double iconSize) {
|
||||
return SizedBox(
|
||||
width: iconSize + 8,
|
||||
height: iconSize + 8,
|
||||
child: Center(child: d.buildIcon(selected)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _selectedIcon(
|
||||
BuildContext context,
|
||||
bool selected,
|
||||
RailDestinationM3E d,
|
||||
double iconSize,
|
||||
RailTokensAdapter tokens,
|
||||
RailIndicatorStyle style,
|
||||
) {
|
||||
final w = _icon(context, selected, d, iconSize);
|
||||
if (style != RailIndicatorStyle.stripe) return w;
|
||||
|
||||
final metrics = tokens.metrics(density);
|
||||
final deco = tokens.stripeDecoration(
|
||||
tokens.indicatorColor(), metrics.stripeThickness);
|
||||
return DecoratedBox(
|
||||
decoration: deco,
|
||||
child: w,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_design/m3e_design.dart';
|
||||
|
||||
class NavigationRailM3EWidget extends StatelessWidget {
|
||||
const NavigationRailM3EWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final m3e = context.m3e;
|
||||
return Container(
|
||||
padding: EdgeInsets.all(m3e.spacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: m3e.colors.surfaceStrong,
|
||||
borderRadius: m3e.shapes.square.md,
|
||||
),
|
||||
child: Text('NavigationRail placeholder', style: m3e.typography.base.titleMedium),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _pascal(String s) => s.split('_').map((p) => p.isEmpty ? '' : (p[0].toUpperCase() + p.substring(1))).join();
|
||||
76
packages/navigation_rail_m3e/lib/src/rail_badge_m3e.dart
Normal file
76
packages/navigation_rail_m3e/lib/src/rail_badge_m3e.dart
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_design/m3e_design.dart';
|
||||
|
||||
class RailBadgeM3E extends StatelessWidget {
|
||||
const RailBadgeM3E({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.count,
|
||||
this.showDot = false,
|
||||
this.maxCount = 99,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.semanticLabel,
|
||||
this.offset = const Offset(8, -6),
|
||||
}) : assert(count == null || count >= 0);
|
||||
|
||||
final Widget child;
|
||||
final int? count;
|
||||
final bool showDot;
|
||||
final int maxCount;
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
final String? semanticLabel;
|
||||
final Offset offset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = Theme.of(context);
|
||||
final m3e = t.extension<M3ETheme>() ?? M3ETheme.defaults(t.colorScheme);
|
||||
final bg = backgroundColor ?? m3e.colors.errorContainer;
|
||||
final fg = foregroundColor ?? m3e.colors.onErrorContainer;
|
||||
|
||||
final badge = showDot
|
||||
? _dot(bg)
|
||||
: _label(bg, fg, count == null ? '' : _format(count!, maxCount));
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
child,
|
||||
Positioned(
|
||||
right: offset.dx,
|
||||
top: offset.dy,
|
||||
child: Semantics(
|
||||
label: semanticLabel ?? (count != null ? 'Notifications: ${count!}' : 'Notifications'),
|
||||
child: badge,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _dot(Color bg) {
|
||||
return Container(
|
||||
width: 8, height: 8,
|
||||
decoration: BoxDecoration(color: bg, shape: BoxShape.circle),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _label(Color bg, Color fg, String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 18, minHeight: 18),
|
||||
child: DefaultTextStyle(
|
||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w600),
|
||||
child: Text(text, textAlign: TextAlign.center, style: TextStyle(color: fg)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _format(int c, int max) => (c > max) ? '$max+' : '$c';
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'rail_badge_m3e.dart';
|
||||
|
||||
class RailDestinationM3E {
|
||||
const RailDestinationM3E({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
this.selectedIcon,
|
||||
this.badgeCount,
|
||||
this.badgeDot = false,
|
||||
this.semanticLabel,
|
||||
});
|
||||
|
||||
final Widget icon;
|
||||
final Widget? selectedIcon;
|
||||
final String label;
|
||||
|
||||
/// Optional badge counter
|
||||
final int? badgeCount;
|
||||
|
||||
/// If true, show a small dot instead of a counter.
|
||||
final bool badgeDot;
|
||||
|
||||
final String? semanticLabel;
|
||||
|
||||
Widget buildIcon([bool selected = false]) {
|
||||
final base = selected && selectedIcon != null ? selectedIcon! : icon;
|
||||
if (badgeCount != null || badgeDot) {
|
||||
return RailBadgeM3E(
|
||||
child: base,
|
||||
count: badgeCount,
|
||||
showDot: badgeDot,
|
||||
semanticLabel: semanticLabel,
|
||||
);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_design/m3e_design.dart';
|
||||
import 'enums.dart';
|
||||
|
||||
@immutable
|
||||
class _RailMetrics {
|
||||
final double widthCompact;
|
||||
final double widthRegular;
|
||||
final double extendedMinWidth;
|
||||
final double iconSize;
|
||||
final EdgeInsetsGeometry itemPadding;
|
||||
final double stripeThickness;
|
||||
const _RailMetrics({
|
||||
required this.widthCompact,
|
||||
required this.widthRegular,
|
||||
required this.extendedMinWidth,
|
||||
required this.iconSize,
|
||||
required this.itemPadding,
|
||||
required this.stripeThickness,
|
||||
});
|
||||
}
|
||||
|
||||
_RailMetrics _metricsFor(BuildContext context, RailDensity density) {
|
||||
final theme = Theme.of(context);
|
||||
final m3e = theme.extension<M3ETheme>() ?? M3ETheme.defaults(theme.colorScheme);
|
||||
final sp = m3e.spacing;
|
||||
|
||||
double wC = 64; // compact width
|
||||
double wR = 80; // regular width
|
||||
double ext = 256; // extended min width
|
||||
double icon = 24;
|
||||
double stripe = 3;
|
||||
|
||||
if (density == RailDensity.compact) {
|
||||
wC -= 4; wR -= 4; stripe -= 1;
|
||||
}
|
||||
|
||||
return _RailMetrics(
|
||||
widthCompact: wC,
|
||||
widthRegular: wR,
|
||||
extendedMinWidth: ext,
|
||||
iconSize: icon,
|
||||
itemPadding: EdgeInsets.symmetric(horizontal: sp.md, vertical: sp.sm),
|
||||
stripeThickness: stripe,
|
||||
);
|
||||
}
|
||||
|
||||
class RailTokensAdapter {
|
||||
RailTokensAdapter(this.context);
|
||||
final BuildContext context;
|
||||
|
||||
M3ETheme get _m3e {
|
||||
final t = Theme.of(context);
|
||||
return t.extension<M3ETheme>() ?? M3ETheme.defaults(t.colorScheme);
|
||||
}
|
||||
|
||||
_RailMetrics metrics(RailDensity density) => _metricsFor(context, density);
|
||||
|
||||
// Container/background
|
||||
Color containerColor() => _m3e.colors.surfaceContainerHigh;
|
||||
|
||||
// Indicator
|
||||
Color indicatorColor() => _m3e.colors.secondaryContainer;
|
||||
|
||||
// Icon/label colors
|
||||
Color selectedColor() => _m3e.colors.onSecondaryContainer;
|
||||
Color unselectedColor() => _m3e.colors.onSurfaceVariant;
|
||||
|
||||
// Typography
|
||||
TextStyle labelStyle() => _m3e.type.labelMedium;
|
||||
|
||||
// Shapes
|
||||
ShapeBorder containerShape(RailShapeFamily family) {
|
||||
final set = family == RailShapeFamily.round ? _m3e.shapes.round : _m3e.shapes.square;
|
||||
return RoundedRectangleBorder(borderRadius: set.lg);
|
||||
}
|
||||
|
||||
ShapeBorder indicatorShapePill() => const StadiumBorder();
|
||||
|
||||
// Stripe decoration for selected destination
|
||||
BoxDecoration stripeDecoration(Color color, double thickness) {
|
||||
return BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(color: color, width: thickness),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
29
packages/navigation_rail_m3e/melos_navigation_rail_m3e.iml
Normal file
29
packages/navigation_rail_m3e/melos_navigation_rail_m3e.iml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/android/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/android/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/ios/Pods" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/macos/Flutter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/macos/Pods" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/macos/.symlinks" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Flutter Plugins" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
18
packages/navigation_rail_m3e/pubspec.yaml
Normal file
18
packages/navigation_rail_m3e/pubspec.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
name: navigation_rail_m3e
|
||||
description: Material 3 Expressive Navigation Rail for Flutter with token-driven colors, shapes, indicators, and badges.
|
||||
version: 0.1.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
m3e_design:
|
||||
path: ../m3e_design
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
4
packages/navigation_rail_m3e/pubspec_overrides.yaml
Normal file
4
packages/navigation_rail_m3e/pubspec_overrides.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# melos_managed_dependency_overrides: m3e_design
|
||||
dependency_overrides:
|
||||
m3e_design:
|
||||
path: ..\\m3e_design
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('placeholder', () {
|
||||
expect(3 + 4, 7);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue