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/toolbar_m3e/LICENSE
Normal file
21
packages/toolbar_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.
|
||||
78
packages/toolbar_m3e/README.md
Normal file
78
packages/toolbar_m3e/README.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# toolbar_m3e
|
||||
|
||||
Material 3 **Expressive** toolbar for Flutter – a compact action bar that can host a title/subtitle, leading widget, inline actions, and an overflow menu. All styling is driven by `m3e_design` tokens.
|
||||
|
||||
- `ToolbarM3E` — the main toolbar widget (`PreferredSizeWidget`) for use in `Scaffold` or as a standalone header
|
||||
- `ToolbarActionM3E` — action model consumed by the toolbar
|
||||
- Inline actions render as icon buttons; extra actions go into a `PopupMenuButton` overflow
|
||||
|
||||
## Monorepo Layout
|
||||
|
||||
```
|
||||
packages/
|
||||
m3e_design/
|
||||
toolbar_m3e/
|
||||
```
|
||||
|
||||
`pubspec.yaml` references `../m3e_design`.
|
||||
|
||||
## Usage
|
||||
|
||||
```dart
|
||||
import 'package:toolbar_m3e/toolbar_m3e.dart';
|
||||
|
||||
final actions = [
|
||||
ToolbarActionM3E(
|
||||
icon: Icons.search,
|
||||
onPressed: () {},
|
||||
tooltip: 'Search',
|
||||
),
|
||||
ToolbarActionM3E(
|
||||
icon: Icons.share_outlined,
|
||||
onPressed: () {},
|
||||
tooltip: 'Share',
|
||||
),
|
||||
ToolbarActionM3E(
|
||||
icon: Icons.delete_outline,
|
||||
onPressed: () {},
|
||||
tooltip: 'Delete',
|
||||
isDestructive: true,
|
||||
label: 'Delete', // used in overflow
|
||||
),
|
||||
];
|
||||
|
||||
ToolbarM3E(
|
||||
leading: const BackButton(),
|
||||
titleText: 'Selection',
|
||||
subtitleText: '3 items',
|
||||
actions: actions,
|
||||
maxInlineActions: 2, // remaining actions go to overflow
|
||||
variant: ToolbarM3EVariant.tonal, // surface | tonal | primary
|
||||
size: ToolbarM3ESize.medium, // small | medium | large
|
||||
density: ToolbarM3EDensity.regular,
|
||||
shapeFamily: ToolbarM3EShapeFamily.round,
|
||||
centerTitle: false,
|
||||
);
|
||||
```
|
||||
|
||||
## Tokens mapping
|
||||
|
||||
- **Container**: `surfaceContainerHigh` (surface) / `secondaryContainer` (tonal) / `primaryContainer` (primary)
|
||||
- **Foreground**: `onSurface` / `onSecondaryContainer` / `onPrimaryContainer`
|
||||
- **Shape**: uses M3E `round` / `square` set (`md` radius)
|
||||
- **Heights**: small **≈40dp**, medium **≈48dp**, large **≈56dp**
|
||||
- **Icon size**: **24dp**
|
||||
- **Padding**: horizontal from tokens (`spacing.md`)
|
||||
|
||||
## Overflow
|
||||
|
||||
Set `maxInlineActions` to the number of actions that should stay inline. Any additional actions go to the overflow menu (labels pulled from `label` or `tooltip`/`semanticLabel`). Destructive actions can be highlighted by `isDestructive: true`.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Provide `semanticLabel` on the toolbar if useful.
|
||||
- Actions expose `tooltip` and can set `semanticLabel` to improve assistive tech hints.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
4
packages/toolbar_m3e/lib/src/enums.dart
Normal file
4
packages/toolbar_m3e/lib/src/enums.dart
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
enum ToolbarM3ESize { small, medium, large }
|
||||
enum ToolbarM3EShapeFamily { round, square }
|
||||
enum ToolbarM3EDensity { regular, compact }
|
||||
enum ToolbarM3EVariant { surface, tonal, primary }
|
||||
49
packages/toolbar_m3e/lib/src/toolbar_action_m3e.dart
Normal file
49
packages/toolbar_m3e/lib/src/toolbar_action_m3e.dart
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ToolbarActionM3E {
|
||||
const ToolbarActionM3E({
|
||||
required this.icon,
|
||||
required this.onPressed,
|
||||
this.tooltip,
|
||||
this.semanticLabel,
|
||||
this.enabled = true,
|
||||
this.label, // used in overflow menu
|
||||
this.isDestructive = false,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final VoidCallback onPressed;
|
||||
final String? tooltip;
|
||||
final String? semanticLabel;
|
||||
final bool enabled;
|
||||
|
||||
/// Optional label used in the overflow menu; if null, tooltip or semanticLabel will be used.
|
||||
final String? label;
|
||||
|
||||
/// If true, the action is styled as destructive in overflow (e.g., error color).
|
||||
final bool isDestructive;
|
||||
}
|
||||
|
||||
class ToolbarIconButtonM3E extends StatelessWidget {
|
||||
const ToolbarIconButtonM3E({
|
||||
super.key,
|
||||
required this.action,
|
||||
this.color,
|
||||
this.iconSize,
|
||||
});
|
||||
|
||||
final ToolbarActionM3E action;
|
||||
final Color? color;
|
||||
final double? iconSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: action.enabled ? action.onPressed : null,
|
||||
tooltip: action.tooltip ?? action.label,
|
||||
icon: Icon(action.icon),
|
||||
color: color,
|
||||
iconSize: iconSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
244
packages/toolbar_m3e/lib/src/toolbar_m3e.dart
Normal file
244
packages/toolbar_m3e/lib/src/toolbar_m3e.dart
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_design/m3e_design.dart';
|
||||
import 'enums.dart';
|
||||
import 'toolbar_tokens_adapter.dart';
|
||||
import 'toolbar_action_m3e.dart';
|
||||
|
||||
class ToolbarM3E extends StatelessWidget implements PreferredSizeWidget {
|
||||
const ToolbarM3E({
|
||||
super.key,
|
||||
this.leading,
|
||||
this.title,
|
||||
this.titleText,
|
||||
this.subtitle,
|
||||
this.subtitleText,
|
||||
this.actions = const <ToolbarActionM3E>[],
|
||||
this.maxInlineActions = 4,
|
||||
this.overflowIcon = const Icon(Icons.more_vert),
|
||||
this.centerTitle = false,
|
||||
this.variant = ToolbarM3EVariant.surface,
|
||||
this.size = ToolbarM3ESize.medium,
|
||||
this.density = ToolbarM3EDensity.regular,
|
||||
this.shapeFamily = ToolbarM3EShapeFamily.round,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.elevation,
|
||||
this.padding,
|
||||
this.safeArea = true,
|
||||
this.clipBehavior = Clip.none,
|
||||
this.semanticLabel,
|
||||
});
|
||||
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final String? titleText;
|
||||
final Widget? subtitle;
|
||||
final String? subtitleText;
|
||||
|
||||
final List<ToolbarActionM3E> actions;
|
||||
final int maxInlineActions;
|
||||
final Widget overflowIcon;
|
||||
|
||||
final bool centerTitle;
|
||||
|
||||
final ToolbarM3EVariant variant;
|
||||
final ToolbarM3ESize size;
|
||||
final ToolbarM3EDensity density;
|
||||
final ToolbarM3EShapeFamily shapeFamily;
|
||||
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
final double? elevation;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool safeArea;
|
||||
final Clip clipBehavior;
|
||||
|
||||
final String? semanticLabel;
|
||||
|
||||
@override
|
||||
Size get preferredSize {
|
||||
// A rough default; actual height is resolved at build based on size/density.
|
||||
switch (size) {
|
||||
case ToolbarM3ESize.small: return const Size.fromHeight(40);
|
||||
case ToolbarM3ESize.medium: return const Size.fromHeight(48);
|
||||
case ToolbarM3ESize.large: return const Size.fromHeight(56);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ToolbarTokensAdapter(context);
|
||||
final metrics = tokens.metrics(density);
|
||||
final m3e = Theme.of(context).extension<M3ETheme>() ?? M3ETheme.defaults(Theme.of(context).colorScheme);
|
||||
|
||||
final height = switch (size) {
|
||||
ToolbarM3ESize.small => metrics.heightSmall,
|
||||
ToolbarM3ESize.medium => metrics.heightMedium,
|
||||
ToolbarM3ESize.large => metrics.heightLarge,
|
||||
};
|
||||
|
||||
final bg = backgroundColor ?? tokens.containerColor(variant);
|
||||
final fg = foregroundColor ?? tokens.foregroundOn(variant);
|
||||
final shape = tokens.shape(shapeFamily);
|
||||
final pad = padding ?? metrics.horizontalPadding;
|
||||
|
||||
final resolvedTitle = title ??
|
||||
(titleText != null
|
||||
? Text(titleText!, style: tokens.titleStyle().copyWith(color: fg), overflow: TextOverflow.ellipsis)
|
||||
: null);
|
||||
|
||||
final resolvedSubtitle = subtitle ??
|
||||
(subtitleText != null
|
||||
? Text(subtitleText!, style: tokens.subtitleStyle().copyWith(color: fg.withValues(alpha: 0.8)), overflow: TextOverflow.ellipsis)
|
||||
: null);
|
||||
|
||||
final toolbarRow = Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
if (leading != null) SizedBox(width: metrics.gap),
|
||||
Expanded(
|
||||
child: _TitleBlock(
|
||||
title: resolvedTitle,
|
||||
subtitle: resolvedSubtitle,
|
||||
center: centerTitle,
|
||||
),
|
||||
),
|
||||
SizedBox(width: metrics.gap),
|
||||
_ActionsRow(
|
||||
actions: actions,
|
||||
maxInline: maxInlineActions,
|
||||
overflowIcon: overflowIcon,
|
||||
iconColor: fg,
|
||||
iconSize: metrics.iconSize,
|
||||
m3e: m3e,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final bar = Material(
|
||||
color: bg,
|
||||
elevation: elevation ?? (variant == ToolbarM3EVariant.surface ? metrics.elevationSurface : metrics.elevationProminent),
|
||||
shape: shape,
|
||||
clipBehavior: clipBehavior,
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding: pad,
|
||||
child: IconTheme.merge(
|
||||
data: IconThemeData(color: fg, size: metrics.iconSize),
|
||||
child: DefaultTextStyle.merge(style: TextStyle(color: fg), child: toolbarRow),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final content = safeArea ? SafeArea(top: false, left: false, right: false, bottom: false, child: bar) : bar;
|
||||
|
||||
if (semanticLabel == null) return content;
|
||||
return Semantics(container: true, label: semanticLabel!, child: content);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleBlock extends StatelessWidget {
|
||||
const _TitleBlock({required this.title, required this.subtitle, required this.center});
|
||||
final Widget? title;
|
||||
final Widget? subtitle;
|
||||
final bool center;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (title == null && subtitle == null) return const SizedBox.shrink();
|
||||
|
||||
final col = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: center ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (title != null) DefaultTextStyle.merge(style: Theme.of(context).textTheme.titleSmall!, child: title!),
|
||||
if (subtitle != null) DefaultTextStyle.merge(style: Theme.of(context).textTheme.bodySmall!, child: subtitle!),
|
||||
],
|
||||
);
|
||||
|
||||
if (center) {
|
||||
return Align(alignment: Alignment.center, child: col);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionsRow extends StatelessWidget {
|
||||
const _ActionsRow({
|
||||
required this.actions,
|
||||
required this.maxInline,
|
||||
required this.overflowIcon,
|
||||
required this.iconColor,
|
||||
required this.iconSize,
|
||||
required this.m3e,
|
||||
});
|
||||
|
||||
final List<ToolbarActionM3E> actions;
|
||||
final int maxInline;
|
||||
final Widget overflowIcon;
|
||||
final Color iconColor;
|
||||
final double iconSize;
|
||||
final M3ETheme m3e;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (actions.isEmpty) return const SizedBox.shrink();
|
||||
final inline = actions.take(maxInline).toList(growable: false);
|
||||
final overflow = actions.length > maxInline ? actions.sublist(maxInline) : const <ToolbarActionM3E>[];
|
||||
|
||||
final row = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final a in inline) ToolbarIconButtonM3E(action: a, color: iconColor, iconSize: iconSize),
|
||||
if (overflow.isNotEmpty)
|
||||
_OverflowMenu(
|
||||
actions: overflow,
|
||||
icon: overflowIcon,
|
||||
textStyle: Theme.of(context).textTheme.labelLarge?.copyWith(color: m3e.colors.onSurface),
|
||||
destructiveColor: m3e.colors.error,
|
||||
),
|
||||
],
|
||||
);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
class _OverflowMenu extends StatelessWidget {
|
||||
const _OverflowMenu({
|
||||
required this.actions,
|
||||
required this.icon,
|
||||
this.textStyle,
|
||||
this.destructiveColor,
|
||||
});
|
||||
|
||||
final List<ToolbarActionM3E> actions;
|
||||
final Widget icon;
|
||||
final TextStyle? textStyle;
|
||||
final Color? destructiveColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<int>(
|
||||
tooltip: 'More options',
|
||||
itemBuilder: (context) => [
|
||||
for (var i = 0; i < actions.length; i++)
|
||||
PopupMenuItem<int>(
|
||||
value: i,
|
||||
enabled: actions[i].enabled,
|
||||
child: DefaultTextStyle.merge(
|
||||
style: (actions[i].isDestructive
|
||||
? (textStyle?.copyWith(color: destructiveColor) ?? TextStyle(color: destructiveColor))
|
||||
: textStyle) ??
|
||||
const TextStyle(),
|
||||
child: Text(actions[i].label ?? actions[i].tooltip ?? actions[i].semanticLabel ?? 'Action ${i + 1}'),
|
||||
),
|
||||
),
|
||||
],
|
||||
onSelected: (index) => actions[index].onPressed(),
|
||||
child: icon,
|
||||
);
|
||||
}
|
||||
}
|
||||
21
packages/toolbar_m3e/lib/src/toolbar_m3e_widget.dart
Normal file
21
packages/toolbar_m3e/lib/src/toolbar_m3e_widget.dart
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_design/m3e_design.dart';
|
||||
|
||||
class ToolbarM3EWidget extends StatelessWidget {
|
||||
const ToolbarM3EWidget({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('Toolbar placeholder', style: m3e.typography.base.titleMedium),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _pascal(String s) => s.split('_').map((p) => p.isEmpty ? '' : (p[0].toUpperCase() + p.substring(1))).join();
|
||||
91
packages/toolbar_m3e/lib/src/toolbar_tokens_adapter.dart
Normal file
91
packages/toolbar_m3e/lib/src/toolbar_tokens_adapter.dart
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3e_design/m3e_design.dart';
|
||||
import 'enums.dart';
|
||||
|
||||
@immutable
|
||||
class _ToolbarMetrics {
|
||||
final double heightSmall;
|
||||
final double heightMedium;
|
||||
final double heightLarge;
|
||||
final EdgeInsetsGeometry horizontalPadding;
|
||||
final double gap;
|
||||
final double iconSize;
|
||||
final double elevationSurface;
|
||||
final double elevationProminent;
|
||||
const _ToolbarMetrics({
|
||||
required this.heightSmall,
|
||||
required this.heightMedium,
|
||||
required this.heightLarge,
|
||||
required this.horizontalPadding,
|
||||
required this.gap,
|
||||
required this.iconSize,
|
||||
required this.elevationSurface,
|
||||
required this.elevationProminent,
|
||||
});
|
||||
}
|
||||
|
||||
_ToolbarMetrics _metricsFor(BuildContext context, ToolbarM3EDensity density) {
|
||||
final theme = Theme.of(context);
|
||||
final m3e = theme.extension<M3ETheme>() ?? M3ETheme.defaults(theme.colorScheme);
|
||||
final sp = m3e.spacing;
|
||||
|
||||
double hS = 40;
|
||||
double hM = 48;
|
||||
double hL = 56;
|
||||
double icon = 24;
|
||||
double gap = sp.sm;
|
||||
|
||||
if (density == ToolbarM3EDensity.compact) {
|
||||
hS -= 4; hM -= 4; hL -= 4;
|
||||
}
|
||||
|
||||
return _ToolbarMetrics(
|
||||
heightSmall: hS,
|
||||
heightMedium: hM,
|
||||
heightLarge: hL,
|
||||
horizontalPadding: EdgeInsets.symmetric(horizontal: sp.md),
|
||||
gap: gap,
|
||||
iconSize: icon,
|
||||
elevationSurface: 0.0,
|
||||
elevationProminent: 2.0,
|
||||
);
|
||||
}
|
||||
|
||||
class ToolbarTokensAdapter {
|
||||
ToolbarTokensAdapter(this.context);
|
||||
final BuildContext context;
|
||||
|
||||
M3ETheme get _m3e {
|
||||
final t = Theme.of(context);
|
||||
return t.extension<M3ETheme>() ?? M3ETheme.defaults(t.colorScheme);
|
||||
}
|
||||
|
||||
_ToolbarMetrics metrics(ToolbarM3EDensity density) => _metricsFor(context, density);
|
||||
|
||||
// Container/background color by variant
|
||||
Color containerColor(ToolbarM3EVariant variant) {
|
||||
switch (variant) {
|
||||
case ToolbarM3EVariant.surface: return _m3e.colors.surfaceContainerHigh;
|
||||
case ToolbarM3EVariant.tonal: return _m3e.colors.secondaryContainer;
|
||||
case ToolbarM3EVariant.primary: return _m3e.colors.primaryContainer;
|
||||
}
|
||||
}
|
||||
|
||||
Color foregroundOn(ToolbarM3EVariant variant) {
|
||||
switch (variant) {
|
||||
case ToolbarM3EVariant.surface: return _m3e.colors.onSurface;
|
||||
case ToolbarM3EVariant.tonal: return _m3e.colors.onSecondaryContainer;
|
||||
case ToolbarM3EVariant.primary: return _m3e.colors.onPrimaryContainer;
|
||||
}
|
||||
}
|
||||
|
||||
// Shapes
|
||||
ShapeBorder shape(ToolbarM3EShapeFamily family) {
|
||||
final set = family == ToolbarM3EShapeFamily.round ? _m3e.shapes.round : _m3e.shapes.square;
|
||||
return RoundedRectangleBorder(borderRadius: set.md);
|
||||
}
|
||||
|
||||
// Typography
|
||||
TextStyle titleStyle() => _m3e.type.titleSmall;
|
||||
TextStyle subtitleStyle() => _m3e.type.bodySmall;
|
||||
}
|
||||
6
packages/toolbar_m3e/lib/toolbar_m3e.dart
Normal file
6
packages/toolbar_m3e/lib/toolbar_m3e.dart
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
library toolbar_m3e;
|
||||
|
||||
export 'src/enums.dart';
|
||||
export 'src/toolbar_tokens_adapter.dart' show ToolbarTokensAdapter;
|
||||
export 'src/toolbar_action_m3e.dart';
|
||||
export 'src/toolbar_m3e.dart';
|
||||
29
packages/toolbar_m3e/melos_toolbar_m3e.iml
Normal file
29
packages/toolbar_m3e/melos_toolbar_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/toolbar_m3e/pubspec.yaml
Normal file
18
packages/toolbar_m3e/pubspec.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
name: toolbar_m3e
|
||||
description: Material 3 Expressive Toolbars for Flutter with token-driven colors, shapes, density, and overflow handling.
|
||||
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/toolbar_m3e/pubspec_overrides.yaml
Normal file
4
packages/toolbar_m3e/pubspec_overrides.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# melos_managed_dependency_overrides: m3e_design
|
||||
dependency_overrides:
|
||||
m3e_design:
|
||||
path: ..\\m3e_design
|
||||
7
packages/toolbar_m3e/test/toolbar_m3e_test.dart
Normal file
7
packages/toolbar_m3e/test/toolbar_m3e_test.dart
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('placeholder', () {
|
||||
expect(6 - 1, 5);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue