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:
Emily Pauli 2025-10-21 22:15:15 +02:00
commit 62ecb86b76
184 changed files with 9872 additions and 0 deletions

View file

@ -0,0 +1,196 @@
import 'package:flutter/material.dart';
@immutable
class M3EColors {
final Color emphasis;
final Color onEmphasis;
final Color info;
final Color success;
final Color warning;
final Color danger;
final Color surfaceStrong;
final Color onSurfaceStrong;
final Color outlineStrong;
// New: proxy common ColorScheme fields used across packages
final Color primary;
final Color onPrimary;
final Color primaryContainer;
final Color onPrimaryContainer;
final Color secondary;
final Color onSecondary;
final Color secondaryContainer;
final Color onSecondaryContainer;
final Color tertiary;
final Color onTertiary;
final Color tertiaryContainer;
final Color onTertiaryContainer;
final Color surface;
final Color onSurface;
final Color onSurfaceVariant;
final Color error;
final Color onError;
final Color errorContainer;
final Color onErrorContainer;
final Color outline;
final Color outlineVariant;
// New: container surface tokens not always present on older ColorScheme
final Color surfaceContainerHigh;
final Color surfaceContainerLowest;
const M3EColors({
required this.emphasis,
required this.onEmphasis,
required this.info,
required this.success,
required this.warning,
required this.danger,
required this.surfaceStrong,
required this.onSurfaceStrong,
required this.outlineStrong,
// New fields
required this.primary,
required this.onPrimary,
required this.primaryContainer,
required this.onPrimaryContainer,
required this.secondary,
required this.onSecondary,
required this.secondaryContainer,
required this.onSecondaryContainer,
required this.tertiary,
required this.onTertiary,
required this.tertiaryContainer,
required this.onTertiaryContainer,
required this.surface,
required this.onSurface,
required this.onSurfaceVariant,
required this.error,
required this.onError,
required this.errorContainer,
required this.onErrorContainer,
required this.outline,
required this.outlineVariant,
required this.surfaceContainerHigh,
required this.surfaceContainerLowest,
});
factory M3EColors.from(ColorScheme s) {
// Compute container surface variants if not available on the ColorScheme version in use.
// We prefer mild blends that work in both light/dark.
Color computeSurfaceContainerHigh() =>
Color.alphaBlend(s.primary.withValues(alpha: 0.12), s.surface);
Color computeSurfaceContainerLowest() =>
Color.alphaBlend(s.onSurface.withValues(alpha: 0.05), s.surface);
return M3EColors(
emphasis: s.primary,
onEmphasis: s.onPrimary,
info: s.tertiary,
success: Color.alphaBlend(
Colors.green.shade400.withValues(alpha: 0.2), s.primaryContainer),
warning: Color.alphaBlend(
Colors.orange.shade400.withValues(alpha: 0.2), s.secondaryContainer),
danger: Color.alphaBlend(
Colors.red.shade400.withValues(alpha: 0.2), s.errorContainer),
surfaceStrong:
Color.alphaBlend(s.primary.withValues(alpha: 0.06), s.surface),
onSurfaceStrong: s.onSurface,
outlineStrong:
Color.alphaBlend(s.primary.withValues(alpha: 0.40), s.outlineVariant),
// New fields mapped from ColorScheme
primary: s.primary,
onPrimary: s.onPrimary,
primaryContainer: s.primaryContainer,
onPrimaryContainer: s.onPrimaryContainer,
secondary: s.secondary,
onSecondary: s.onSecondary,
secondaryContainer: s.secondaryContainer,
onSecondaryContainer: s.onSecondaryContainer,
tertiary: s.tertiary,
onTertiary: s.onTertiary,
tertiaryContainer: s.tertiaryContainer,
onTertiaryContainer: s.onTertiaryContainer,
surface: s.surface,
onSurface: s.onSurface,
onSurfaceVariant: s.onSurfaceVariant,
error: s.error,
onError: s.onError,
errorContainer: s.errorContainer,
onErrorContainer: s.onErrorContainer,
outline: s.outline,
outlineVariant: s.outlineVariant,
surfaceContainerHigh: (() {
// If the ColorScheme already has a matching field, prefer that via dynamic access; otherwise compute.
try {
final dynamic dyn = s;
final c = dyn.surfaceContainerHigh as Color?;
return c ?? computeSurfaceContainerHigh();
} catch (_) {
return computeSurfaceContainerHigh();
}
})(),
surfaceContainerLowest: (() {
try {
final dynamic dyn = s;
final c = dyn.surfaceContainerLowest as Color?;
return c ?? computeSurfaceContainerLowest();
} catch (_) {
return computeSurfaceContainerLowest();
}
})(),
);
}
static M3EColors lerp(M3EColors a, M3EColors b, double t) => M3EColors(
emphasis: Color.lerp(a.emphasis, b.emphasis, t)!,
onEmphasis: Color.lerp(a.onEmphasis, b.onEmphasis, t)!,
info: Color.lerp(a.info, b.info, t)!,
success: Color.lerp(a.success, b.success, t)!,
warning: Color.lerp(a.warning, b.warning, t)!,
danger: Color.lerp(a.danger, b.danger, t)!,
surfaceStrong: Color.lerp(a.surfaceStrong, b.surfaceStrong, t)!,
onSurfaceStrong: Color.lerp(a.onSurfaceStrong, b.onSurfaceStrong, t)!,
outlineStrong: Color.lerp(a.outlineStrong, b.outlineStrong, t)!,
// New fields
primary: Color.lerp(a.primary, b.primary, t)!,
onPrimary: Color.lerp(a.onPrimary, b.onPrimary, t)!,
primaryContainer:
Color.lerp(a.primaryContainer, b.primaryContainer, t)!,
onPrimaryContainer:
Color.lerp(a.onPrimaryContainer, b.onPrimaryContainer, t)!,
secondary: Color.lerp(a.secondary, b.secondary, t)!,
onSecondary: Color.lerp(a.onSecondary, b.onSecondary, t)!,
secondaryContainer:
Color.lerp(a.secondaryContainer, b.secondaryContainer, t)!,
onSecondaryContainer:
Color.lerp(a.onSecondaryContainer, b.onSecondaryContainer, t)!,
tertiary: Color.lerp(a.tertiary, b.tertiary, t)!,
onTertiary: Color.lerp(a.onTertiary, b.onTertiary, t)!,
tertiaryContainer:
Color.lerp(a.tertiaryContainer, b.tertiaryContainer, t)!,
onTertiaryContainer:
Color.lerp(a.onTertiaryContainer, b.onTertiaryContainer, t)!,
surface: Color.lerp(a.surface, b.surface, t)!,
onSurface: Color.lerp(a.onSurface, b.onSurface, t)!,
onSurfaceVariant:
Color.lerp(a.onSurfaceVariant, b.onSurfaceVariant, t)!,
error: Color.lerp(a.error, b.error, t)!,
onError: Color.lerp(a.onError, b.onError, t)!,
errorContainer: Color.lerp(a.errorContainer, b.errorContainer, t)!,
onErrorContainer:
Color.lerp(a.onErrorContainer, b.onErrorContainer, t)!,
outline: Color.lerp(a.outline, b.outline, t)!,
outlineVariant: Color.lerp(a.outlineVariant, b.outlineVariant, t)!,
surfaceContainerHigh:
Color.lerp(a.surfaceContainerHigh, b.surfaceContainerHigh, t)!,
surfaceContainerLowest:
Color.lerp(a.surfaceContainerLowest, b.surfaceContainerLowest, t)!,
);
}

View file

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
@immutable
class M3EMotion {
final SpringDescription spatialFast;
final SpringDescription spatialMedium;
final SpringDescription spatialGentle;
final SpringDescription effectsFast;
final SpringDescription effectsMedium;
final Duration fast;
final Duration medium;
final Duration slow;
const M3EMotion({
required this.spatialFast,
required this.spatialMedium,
required this.spatialGentle,
required this.effectsFast,
required this.effectsMedium,
required this.fast,
required this.medium,
required this.slow,
});
const M3EMotion.expressive()
: spatialFast = const SpringDescription(mass: 1, stiffness: 500, damping: 30),
spatialMedium = const SpringDescription(mass: 1, stiffness: 350, damping: 28),
spatialGentle = const SpringDescription(mass: 1, stiffness: 220, damping: 24),
effectsFast = const SpringDescription(mass: 1, stiffness: 420, damping: 32),
effectsMedium = const SpringDescription(mass: 1, stiffness: 280, damping: 28),
fast = const Duration(milliseconds: 150),
medium = const Duration(milliseconds: 250),
slow = const Duration(milliseconds: 400);
static M3EMotion lerp(M3EMotion a, M3EMotion b, double t) => a;
}
class SpringDescription {
final double mass, stiffness, damping;
const SpringDescription({required this.mass, required this.stiffness, required this.damping});
}

View file

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
enum M3EShapeVariant { round, square }
@immutable
class M3EShapeSet {
final BorderRadius xs;
final BorderRadius sm;
final BorderRadius md;
final BorderRadius lg;
final BorderRadius xl;
const M3EShapeSet({required this.xs, required this.sm, required this.md, required this.lg, required this.xl});
}
@immutable
class M3EShapes {
final M3EShapeSet round;
final M3EShapeSet square;
const M3EShapes({required this.round, required this.square});
factory M3EShapes.expressive() => const M3EShapes(
round: M3EShapeSet(
xs: BorderRadius.all(Radius.circular(999)),
sm: BorderRadius.all(Radius.circular(20)),
md: BorderRadius.all(Radius.circular(28)),
lg: BorderRadius.all(Radius.circular(44)),
xl: BorderRadius.all(Radius.circular(64)),
),
square: M3EShapeSet(
xs: BorderRadius.all(Radius.circular(6)),
sm: BorderRadius.all(Radius.circular(8)),
md: BorderRadius.all(Radius.circular(12)),
lg: BorderRadius.all(Radius.circular(16)),
xl: BorderRadius.all(Radius.circular(20)),
),
);
static M3EShapes lerp(M3EShapes a, M3EShapes b, double t) => M3EShapes(
round: _lerpSet(a.round, b.round, t),
square: _lerpSet(a.square, b.square, t),
);
static M3EShapeSet _lerpSet(M3EShapeSet a, M3EShapeSet b, double t) => M3EShapeSet(
xs: BorderRadius.lerp(a.xs, b.xs, t)!,
sm: BorderRadius.lerp(a.sm, b.sm, t)!,
md: BorderRadius.lerp(a.md, b.md, t)!,
lg: BorderRadius.lerp(a.lg, b.lg, t)!,
xl: BorderRadius.lerp(a.xl, b.xl, t)!,
);
}

View file

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
@immutable
class M3ESpacing {
final double xs; // 4
final double sm; // 8
final double md; // 12
final double lg; // 16
final double xl; // 24
final double xxl; // 32
const M3ESpacing({
required this.xs,
required this.sm,
required this.md,
required this.lg,
required this.xl,
required this.xxl,
});
const M3ESpacing.regular()
: xs = 4,
sm = 8,
md = 12,
lg = 16,
xl = 24,
xxl = 32;
static M3ESpacing lerp(M3ESpacing a, M3ESpacing b, double t) => M3ESpacing(
xs: a.xs + (b.xs - a.xs) * t,
sm: a.sm + (b.sm - a.sm) * t,
md: a.md + (b.md - a.md) * t,
lg: a.lg + (b.lg - a.lg) * t,
xl: a.xl + (b.xl - a.xl) * t,
xxl: a.xxl + (b.xxl - a.xxl) * t,
);
}

View file

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
@immutable
class M3EEmphasized {
final TextStyle display;
final TextStyle headline;
final TextStyle title;
final TextStyle label;
const M3EEmphasized({
required this.display,
required this.headline,
required this.title,
required this.label,
});
static M3EEmphasized forBrightness(Brightness b) {
return const M3EEmphasized(
display: TextStyle(fontWeight: FontWeight.w800, letterSpacing: -0.5),
headline: TextStyle(fontWeight: FontWeight.w700, letterSpacing: -0.25),
title: TextStyle(fontWeight: FontWeight.w700),
label: TextStyle(fontWeight: FontWeight.w700),
);
}
static M3EEmphasized lerp(M3EEmphasized a, M3EEmphasized b, double t) =>
M3EEmphasized(
display: TextStyle.lerp(a.display, b.display, t)!,
headline: TextStyle.lerp(a.headline, b.headline, t)!,
title: TextStyle.lerp(a.title, b.title, t)!,
label: TextStyle.lerp(a.label, b.label, t)!,
);
}
@immutable
class M3ETypography {
final TextTheme base;
final M3EEmphasized emphasized;
const M3ETypography({required this.base, required this.emphasized});
factory M3ETypography.defaultFor(Brightness b) {
// Use a minimal baseline; app's ThemeData will provide fuller TextTheme.
const textTheme = TextTheme();
return M3ETypography(
base: textTheme, emphasized: M3EEmphasized.forBrightness(b));
}
static M3ETypography lerp(M3ETypography a, M3ETypography b, double t) =>
M3ETypography(
base: TextTheme.lerp(a.base, b.base, t),
emphasized: M3EEmphasized.lerp(a.emphasized, b.emphasized, t),
);
}