From 66274c8885543c07528a68e898b1d16612c09364 Mon Sep 17 00:00:00 2001 From: Adonis Date: Thu, 20 Aug 2026 05:35:20 +0100 Subject: [PATCH 1/2] update readme --- README.md | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6d75e77..9b4af7a 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Colour roles use **grouped semantic naming** — `textPrimary`, `bgBase`, `actio | Opacity | `opacity_primitives.dart` | `opacity_semantic.dart` | | Duration | `duration_primitives.dart` | — | | Offset | `offset_primitives.dart` | `offset_semantic.dart` | -| Animation | — | `animation_semantic.dart` | +| Animation | `duration_primitives.dart` | `animation_semantic.dart` | | Breakpoint | `breakpoint_primitives.dart` | `breakpoint_semantic.dart` | | Easing | `easing_primitives.dart` | `easing_semantic.dart` | | Z-Index | — | `z_index_semantic.dart` | @@ -99,16 +99,16 @@ DievasThemeData ← abstract interface (the contract) └── [AppThemeData] ← consumer app's brand (lives in the app, not here) ``` -`DievasTheme` is an `InheritedModel`. Each sub system (`colors`, `typography`, `spacing`, `sizing`, `border`, `elevation`, `opacity`, `components`, `material`) is a named aspect; a widget that depends only on `colors` does not rebuild when `spacing` changes. +`DievasTheme` is an `InheritedModel`. Each sub system (`colours`, `typography`, `spacing`, `sizing`, `border`, `elevation`, `opacity`, `animation`, `components`, `material`) is a named aspect; a widget that depends only on `colours` does not rebuild when `spacing` changes. ### Context extension — flat API ```dart // Inside any widget's build method -final color = context.colors.action.actionPrimary; +final color = context.colours.action.actionPrimary; final style = context.typography.labelMd; -final gap = context.spacing.s4; -final cols = context.grid.columns; +final gap = context.spacing.md; +final dur = context.animation.standard; ``` ### Plugging in a brand theme @@ -120,9 +120,9 @@ Consumer apps never touch `DievasLightThemeData` directly. They extend `DievasGl class ExampleLightThemeData extends DievasGlobalThemeData { ExampleLightThemeData({super.components}) : super( - colors: DievasColourThemeData( + colours: DievasColourThemeData( brightness: Brightness.light, - action: ActionColors(actionPrimary: Color(0xFF7C3AED)), + action: ActionColours(actionPrimary: Color(0xFF7C3AED)), ), border: const DievasBorderThemeData(...), ); @@ -168,17 +168,17 @@ Rules components always follow: 37 components shipped across nine groups: -| Group | Count | Components | -| -------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Buttons** | 4 | `DievasFilledButton`, `DievasOutlinedButton`, `DievasTextButton`, `DievasIconButton` | -| **Display** | 9 | `DievasAvatar`, `DievasBadge`, `DievasCircularProgress`, `DievasDivider`, `DievasDotIndicator`, `DievasEmptyState`, `DievasIcon`, `DievasLinearProgress`, `DievasTag` | -| **Form** | 8 | `DievasAuthCode`, `DievasCheckbox`, `DievasRadio`, `DievasSegmentedControl`, `DievasSwitch`, `DievasTextArea`, `DievasTextInput`, `DievasTextInputGroup` | -| **Feedback** | 3 | `DievasAlert`, `DievasBanner`, `DievasSnackbar` | -| **Menu** | 2 | `DievasDropdown`, `DievasMenuItem` | -| **Overlays** | 3 | `DievasBottomSheet`, `DievasModal`, `DievasTooltip` | -| **Search** | 2 | `DievasSearchWithList`, `DievasSearchWithDropdown` | -| **Nav / Disclosure** | 5 | `DievasAccordion`, `DievasBreadcrumb`, `DievasDrawer`, `DievasPopover`, `DievasTabBar` | -| **Utility** | 1 | `DievasLoader` | +| Group | Count | Components | +| -------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Buttons** | 4 | `DievasFilledButton`, `DievasOutlinedButton`, `DievasTextButton`, `DievasIconButton` | +| **Display** | 9 | `DievasAvatar`, `DievasBadge`, `DievasCircularProgress`, `DievasDivider`, `DievasDotIndicator`, `DievasEmptyState`, `DievasIcon`, `DievasLinearProgress`, `DievasTag` | +| **Form** | 8 | `DievasAuthCode`, `DievasCheckbox`, `DievasRadio`, `DievasSegmentedControl`, `DievasSwitch`, `DievasTextArea`, `DievasTextInput`, `DievasTextInputGroup` | +| **Feedback** | 3 | `DievasAlert`, `DievasBanner`, `DievasSnackbar` | +| **Menu** | 2 | `DievasDropdown`, `DievasMenuItem` | +| **Overlays** | 3 | `DievasBottomSheet`, `DievasModal`, `DievasTooltip` | +| **Search** | 2 | `DievasSearchWithList`, `DievasSearchWithDropdown` | +| **Nav / Disclosure** | 5 | `DievasAccordion`, `DievasBreadcrumb`, `DievasDrawer`, `DievasPopover`, `DievasTabBar` | +| **Utility** | 1 | `DievasLoader` | ## Grid System @@ -221,15 +221,19 @@ flutter run SDK: >=3.11.4 <4.0.0 ``` -Dart 3 idioms throughout — pattern matching with `switch` expressions, sealed classes, records, exhaustive matching. No legacy pre null safety patterns. +Dart 3 idioms throughout: pattern matching with `switch` expressions, sealed classes, records, exhaustive matching. + +No legacy pre null safety patterns. ## Reference Systems | Reference | Role in Dievas | -| --------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------ | -| [Moon Design System](https://flutter.moon.io) | Component catalogue, visual language | **Landing page** — Jaspr SSR + Tailwind v4, deployed on Railway at `dievas.serticode.com`. | +| --------------------------------------------- | ------------------------------------ | +| [Moon Design System](https://flutter.moon.io) | Component catalogue, visual language | + +The landing page is a Jaspr SSR + Tailwind v4 site deployed on Railway at `dievas.serticode.com`. -Token bridge: `landing/tool/generate_theme.dart` reads `dievas_tokens` constants and emits Tailwind `@theme {}` CSS — the same token values power both the Flutter widgets and the server rendered landing page. +Token bridge: `landing/tool/generate_theme.dart` reads `dievas_tokens` constants and emits Tailwind `@theme {}` CSS; the same token values power both the Flutter widgets and the server rendered landing page. ## Team From d24bf8a5fd871ac9657ac6a373b93de3941e6673 Mon Sep 17 00:00:00 2001 From: Adonis Date: Fri, 21 Aug 2026 00:17:32 +0100 Subject: [PATCH 2/2] Fix DievasScope.of to rebuild dependents on theme mode changes and derive Material bridge fonts from brand typography; add dievas/example to the workspace with a Cascadia Code brand theme (tokens/typography/light+dark), overview/foundations/components screens, and tests for both fixes --- README.md | 2 +- packages/dievas/example/.gitignore | 48 +++ packages/dievas/example/README.md | 67 +++- packages/dievas/example/analysis_options.yaml | 11 + .../lib/example_theme/example_dark_theme.dart | 120 +++++++ .../example_theme/example_light_theme.dart | 127 +++++++ .../lib/example_theme/example_theme.dart | 20 ++ .../lib/example_theme/example_tokens.dart | 69 ++++ .../lib/example_theme/example_typography.dart | 72 ++++ packages/dievas/example/lib/main.dart | 106 ++++-- .../screens/example_components_screen.dart | 234 +++++++++++++ .../screens/example_foundations_screen.dart | 321 ++++++++++++++++++ .../lib/screens/example_overview_screen.dart | 218 ++++++++++++ packages/dievas/example/pubspec.yaml | 12 +- packages/dievas/example/web/favicon.png | Bin 0 -> 7258 bytes packages/dievas/example/web/favicon.svg | 5 + .../dievas/example/web/icons/Icon-192.png | Bin 0 -> 7258 bytes .../dievas/example/web/icons/Icon-512.png | Bin 0 -> 25061 bytes .../example/web/icons/Icon-maskable-192.png | Bin 0 -> 4380 bytes .../example/web/icons/Icon-maskable-512.png | Bin 0 -> 16333 bytes packages/dievas/example/web/index.html | 47 +++ packages/dievas/example/web/manifest.json | 35 ++ .../dievas/lib/src/scope/dievas_scope.dart | 41 ++- .../src/themes/dievas_global_theme_data.dart | 58 +--- .../dievas/test/theme/dievas_theme_test.dart | 73 ++++ pubspec.lock | 120 +++++++ pubspec.yaml | 1 + 27 files changed, 1716 insertions(+), 91 deletions(-) create mode 100644 packages/dievas/example/.gitignore create mode 100644 packages/dievas/example/analysis_options.yaml create mode 100644 packages/dievas/example/lib/example_theme/example_dark_theme.dart create mode 100644 packages/dievas/example/lib/example_theme/example_light_theme.dart create mode 100644 packages/dievas/example/lib/example_theme/example_theme.dart create mode 100644 packages/dievas/example/lib/example_theme/example_tokens.dart create mode 100644 packages/dievas/example/lib/example_theme/example_typography.dart create mode 100644 packages/dievas/example/lib/screens/example_components_screen.dart create mode 100644 packages/dievas/example/lib/screens/example_foundations_screen.dart create mode 100644 packages/dievas/example/lib/screens/example_overview_screen.dart create mode 100644 packages/dievas/example/web/favicon.png create mode 100644 packages/dievas/example/web/favicon.svg create mode 100644 packages/dievas/example/web/icons/Icon-192.png create mode 100644 packages/dievas/example/web/icons/Icon-512.png create mode 100644 packages/dievas/example/web/icons/Icon-maskable-192.png create mode 100644 packages/dievas/example/web/icons/Icon-maskable-512.png create mode 100644 packages/dievas/example/web/index.html create mode 100644 packages/dievas/example/web/manifest.json diff --git a/README.md b/README.md index 9b4af7a..cf85687 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ DievasThemeData ← abstract interface (the contract) final color = context.colours.action.actionPrimary; final style = context.typography.labelMd; final gap = context.spacing.md; -final dur = context.animation.standard; +final cols = context.grid.columns; // inside a DievasGrid ``` ### Plugging in a brand theme diff --git a/packages/dievas/example/.gitignore b/packages/dievas/example/.gitignore new file mode 100644 index 0000000..79f7eca --- /dev/null +++ b/packages/dievas/example/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Widget Preview related +.widget_preview/ diff --git a/packages/dievas/example/README.md b/packages/dievas/example/README.md index bbfad8b..96673fe 100644 --- a/packages/dievas/example/README.md +++ b/packages/dievas/example/README.md @@ -1,3 +1,66 @@ -# dievas_example +# Dievas Example App -A new Flutter project. +A complete, runnable reference app for the [Dievas design system](../README.md). + +This is the app to study before wiring Dievas into your own Flutter project. + +It evolves as the design system grows into maturity and at its core, it demonstrates two things end to end: + +1. **How to set up your app's theme properly**: brand tokens, a custom typography ramp, and light/dark theme data that extend `DievasGlobalThemeData` (the exact pattern every consumer app follows). +2. **How to consume the system**: every context extension, the `DievasTheme` statics, the responsive grid, overlays, localizations, and a broad slice of the component library. + +No names from any other design system appear here. Everything is branded "Example" so you can rename it to your own app. + +## Run it + +```bash +cd packages/dievas/example +flutter pub get +flutter run +``` + +Requires the Melos workspace to be bootstrapped first (`melos bootstrap` at the repo root), because the example depends on `dievas` and `dievas_tokens` by path. + +## What each file teaches + +### `lib/main.dart` + +The entry point. A single `DievasScope` wraps the whole app and: + +- resolves the active theme from `themeMode` + platform brightness, +- inserts the `DievasTheme` InheritedModel into the tree, +- bridges to Material by passing `theme.material` to `MaterialApp`, +- registers `DievasLocalizations` so component copy resolves via `context.l10n`, +- hosts a `NavigationBar` shell with three demo screens. + +### This is the only place an app talks to Dievas directly. Everything below reads the theme through `context.*` + +### `lib/example_theme/` — the brand theme (copy this folder) + +The template every consumer app copies. Study the files in order: + +| File | Teaches | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `example_tokens.dart` | Raw `int` brand colours + fonts. Pure values, no `Color` wrapping, no Flutter. | +| `example_typography.dart` | Building a `DievasTypographyThemeData` over the `DievasTypographySemantic` ramp with your own font (Cascadia Code via `google_fonts`). | +| `example_light_theme.dart` | `ExampleLightThemeData extends DievasGlobalThemeData` — supplies the light colour set and typography; everything else is derived. | +| `example_dark_theme.dart` | The same for dark mode. | + +To make it your brand: rename `Example*` → `YourBrand*`, replace +`ExampleColours` with your palette, and point the fonts at your typefaces. + +### `lib/screens/` + +| Screen | Teaches | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `example_overview_screen.dart` | Theme switching (`DievasScope.of(context).setThemeMode`), banner/snackbar overlays (`showBanner` / `showSnackbar`), the responsive grid (`DievasGrid` + `context.grid`), and `context.l10n`. | +| `example_foundations_screen.dart` | Every aspect scoped subsystem — `colours`, `typography`, `spacing`, `sizing`, `border`, `elevation`, `opacity`, `animation` — plus the `DievasTheme.of` statics and a per instance component override via `copyWith`. | +| `example_components_screen.dart` | The component library: buttons, form controls, display components, progress, feedback, overlays, tooltips, loaders, and empty states — all driven by the theme. | + +## Key concepts to notice + +- **Granular rebuilds.** `DievasTheme` is an `InheritedModel`; reading `context.colours` rebuilds only when colours change, never when `spacing` changes. +- **Nothing is hardcoded.** No component in the screens hardcodes a colour, size, or spacing; every value comes from `context.*`. +- **Contrast is a contract.** Dievas draws text on action surfaces using `colours.core.onBrand`, so `onBrand` must contrast `actionPrimary`. This brand's light-mode primary action is ink, so `onBrand` is lavender here — read the notes in `example_light_theme.dart` / `example_dark_theme.dart` before remapping any role. +- **Per instance overrides.** Wrap a subtree in `DievasTheme(data: copy)` to override a component for that subtree only (see the Foundations screen). +- **Material bridge.** Standard Flutter widgets (`NavigationBar`, `Scaffold`, `MaterialApp`) work automatically through `theme.material`. diff --git a/packages/dievas/example/analysis_options.yaml b/packages/dievas/example/analysis_options.yaml new file mode 100644 index 0000000..1b67a16 --- /dev/null +++ b/packages/dievas/example/analysis_options.yaml @@ -0,0 +1,11 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** diff --git a/packages/dievas/example/lib/example_theme/example_dark_theme.dart b/packages/dievas/example/lib/example_theme/example_dark_theme.dart new file mode 100644 index 0000000..f13e02b --- /dev/null +++ b/packages/dievas/example/lib/example_theme/example_dark_theme.dart @@ -0,0 +1,120 @@ +part of 'example_theme.dart'; + +/// Example App dark brand theme. +/// +/// Mirror of [ExampleLightThemeData] using the dark colour set. Consumer apps +/// subclass [DievasGlobalThemeData] directly (as done here) rather than the +/// built-in [DievasDarkThemeData], so both modes carry the same brand. +final class ExampleDarkThemeData extends DievasGlobalThemeData { + ExampleDarkThemeData({super.components, DievasTypographyThemeData? typography}) + : super( + colours: _darkColours, + typography: typography ?? buildExampleTypography(textColor: _darkColours.text.textPrimary), + ); + + static const DievasColourThemeData _darkColours = DievasColourThemeData( + brightness: .dark, + core: CoreColours( + brand: Color(ExampleColours.brand), + brandSubtle: Color(ExampleColours.brandSubtle), + onBrand: Color(ExampleColours.onBrand), + ), + text: TextColours( + textPrimary: Color(ExampleColours.textLight), + textSecondary: Color(DievasColourSemanticDark.textSecondary), + textTertiary: Color(DievasColourSemanticDark.textTertiary), + textDisabled: Color(DievasColourSemanticDark.textDisabled), + // textInverse colours text on elevated surfaces (e.g. tooltips). In dark + // mode those surfaces are dark, so the inverse text must be light. + textInverse: Color(ExampleColours.textLight), + textOnBrand: Color(ExampleColours.onBrand), + ), + icon: IconColours( + iconPrimary: Color(DievasColourSemanticDark.iconPrimary), + iconSecondary: Color(DievasColourSemanticDark.iconSecondary), + iconDisabled: Color(DievasColourSemanticDark.iconDisabled), + iconOnBrand: Color(ExampleColours.onBrand), + ), + background: BackgroundColours( + // Brand scaffold: the near-black indigo this product surfaces on. + bgBase: Color(ExampleColours.bgDark), + bgSubtle: Color(DievasColourSemanticDark.bgSubtle), + bgElevated: Color(DievasColourSemanticDark.bgElevated), + bgOverlay: Color(DievasColourSemanticDark.bgOverlay), + ), + border: BorderColours( + borderDefault: Color(DievasColourSemanticDark.borderDefault), + borderStrong: Color(DievasColourSemanticDark.borderStrong), + borderDisabled: Color(DievasColourSemanticDark.borderDisabled), + borderFocus: Color(DievasColourSemanticDark.borderFocus), + borderBrand: Color(ExampleColours.borderBrand), + ), + action: ActionColours( + actionPrimary: Color(ExampleColours.actionPrimaryDark), + actionPrimaryHover: Color(ExampleColours.actionPrimaryHoverDark), + actionPrimaryActive: Color(ExampleColours.actionPrimaryActiveDark), + actionPrimaryDisabled: Color(DievasColourSemanticDark.actionPrimaryDisabled), + actionSecondary: Color(ExampleColours.actionSecondary), + actionSecondaryHover: Color(ExampleColours.actionSecondaryHover), + actionError: Color(DievasColourSemanticDark.actionError), + actionErrorHover: Color(DievasColourSemanticDark.actionErrorHover), + actionSuccess: Color(DievasColourSemanticDark.actionSuccess), + ), + feedback: FeedbackColours( + feedbackSuccess: FeedbackColour( + background: Color(DievasColourSemanticDark.feedbackSuccessBackground), + border: Color(DievasColourSemanticDark.feedbackSuccessBorder), + icon: Color(DievasColourSemanticDark.feedbackSuccessIcon), + text: Color(DievasColourSemanticDark.feedbackSuccessText), + ), + feedbackWarning: FeedbackColour( + background: Color(DievasColourSemanticDark.feedbackWarningBackground), + border: Color(DievasColourSemanticDark.feedbackWarningBorder), + icon: Color(DievasColourSemanticDark.feedbackWarningIcon), + text: Color(DievasColourSemanticDark.feedbackWarningText), + ), + feedbackError: FeedbackColour( + background: Color(DievasColourSemanticDark.feedbackErrorBackground), + border: Color(DievasColourSemanticDark.feedbackErrorBorder), + icon: Color(DievasColourSemanticDark.feedbackErrorIcon), + text: Color(DievasColourSemanticDark.feedbackErrorText), + ), + feedbackInfo: FeedbackColour( + background: Color(DievasColourSemanticDark.feedbackInfoBackground), + border: Color(DievasColourSemanticDark.feedbackInfoBorder), + icon: Color(DievasColourSemanticDark.feedbackInfoIcon), + text: Color(DievasColourSemanticDark.feedbackInfoText), + ), + ), + input: InputColours( + inputBg: Color(DievasColourSemanticDark.inputBg), + inputBorder: Color(DievasColourSemanticDark.inputBorder), + inputBorderFocus: Color(DievasColourSemanticDark.inputBorderFocus), + inputBorderError: Color(DievasColourSemanticDark.inputBorderError), + inputBorderWarning: Color(DievasColourSemanticDark.inputBorderWarning), + inputText: Color(DievasColourSemanticDark.inputText), + inputPlaceholder: Color(DievasColourSemanticDark.inputPlaceholder), + ), + surface: SurfaceColours( + surfaceCanvas: Color(DievasColourSemanticDark.surfaceCanvas), + surfaceCode: Color(DievasColourSemanticDark.surfaceCode), + surfaceSidebar: Color(DievasColourSemanticDark.surfaceSidebar), + surfaceTableStriped: Color(DievasColourSemanticDark.surfaceTableStriped), + ), + switchColours: SwitchColours( + switchTrackOn: Color(DievasColourSemanticDark.switchTrackOn), + switchTrackOff: Color(DievasColourSemanticDark.switchTrackOff), + switchThumbOn: Color(DievasColourSemanticDark.switchThumbOn), + switchThumbOff: Color(DievasColourSemanticDark.switchThumbOff), + ), + staticColours: StaticColours( + staticWhite: Color(ExampleColours.staticWhite), + staticBlack: Color(ExampleColours.staticBlack), + staticTransparent: Color(DievasColourSemanticDark.staticTransparent), + ), + ); + + @override + DievasThemeData copyWith({DievasComponentThemeData? components, DievasTypographyThemeData? typography}) => + ExampleDarkThemeData(components: components, typography: typography); +} diff --git a/packages/dievas/example/lib/example_theme/example_light_theme.dart b/packages/dievas/example/lib/example_theme/example_light_theme.dart new file mode 100644 index 0000000..36186fd --- /dev/null +++ b/packages/dievas/example/lib/example_theme/example_light_theme.dart @@ -0,0 +1,127 @@ +part of 'example_theme.dart'; + +/// Example App light brand theme. +/// +/// Extends [DievasGlobalThemeData] and supplies the light colour set plus a +/// brand typography built over the Dievas type ramp. Everything else — +/// component theme data, elevation, Material bridge — is derived automatically +/// inside the constructor. +/// +/// Copy this file to create your own brand's light theme: swap +/// [ExampleColours] for your tokens and pass a brand `typography`. +final class ExampleLightThemeData extends DievasGlobalThemeData { + ExampleLightThemeData({super.components, DievasTypographyThemeData? typography}) + : super( + colours: _lightColours, + typography: typography ?? buildExampleTypography(textColor: _lightColours.text.textPrimary), + ); + + static final DievasColourThemeData _lightColours = DievasColourThemeData( + brightness: .light, + core: const CoreColours( + brand: Color(ExampleColours.brand), + brandSubtle: Color(ExampleColours.brandSubtle), + // Text on action surfaces. This brand's primary action is ink + // (ExampleColours.actionPrimary), so onBrand must be a light colour — + // lavender mirrors the brand's real ink-button + light-label look. + onBrand: Color(ExampleColours.textLight), + ), + text: const TextColours( + textPrimary: Color(ExampleColours.textDark), + textSecondary: Color(DievasColourSemanticLight.textSecondary), + textTertiary: Color(DievasColourSemanticLight.textTertiary), + textDisabled: Color(DievasColourSemanticLight.textDisabled), + // textInverse colours text on elevated surfaces (e.g. tooltips). In light + // mode those surfaces are white, so the inverse text must be dark. + textInverse: Color(ExampleColours.textDark), + textOnBrand: Color(ExampleColours.onBrand), + ), + icon: const IconColours( + iconPrimary: Color(DievasColourSemanticLight.iconPrimary), + iconSecondary: Color(DievasColourSemanticLight.iconSecondary), + iconDisabled: Color(DievasColourSemanticLight.iconDisabled), + iconOnBrand: Color(ExampleColours.onBrand), + ), + background: const BackgroundColours( + // Brand scaffold: the warm off-white this product surfaces on. + bgBase: Color(ExampleColours.scaffoldBG), + bgSubtle: Color(DievasColourSemanticLight.bgSubtle), + bgElevated: Color(DievasColourSemanticLight.bgElevated), + bgOverlay: Color(DievasColourSemanticLight.bgOverlay), + ), + border: const BorderColours( + borderDefault: Color(DievasColourSemanticLight.borderDefault), + borderStrong: Color(DievasColourSemanticLight.borderStrong), + borderDisabled: Color(DievasColourSemanticLight.borderDisabled), + borderFocus: Color(DievasColourSemanticLight.borderFocus), + borderBrand: Color(ExampleColours.borderBrand), + ), + action: const ActionColours( + actionPrimary: Color(ExampleColours.actionPrimary), + actionPrimaryHover: Color(ExampleColours.actionPrimaryHover), + actionPrimaryActive: Color(ExampleColours.actionPrimaryActive), + actionPrimaryDisabled: Color(DievasColourSemanticLight.actionPrimaryDisabled), + actionSecondary: Color(ExampleColours.actionSecondary), + actionSecondaryHover: Color(ExampleColours.actionSecondaryHover), + actionError: Color(DievasColourSemanticLight.actionError), + actionErrorHover: Color(DievasColourSemanticLight.actionErrorHover), + actionSuccess: Color(DievasColourSemanticLight.actionSuccess), + ), + feedback: const FeedbackColours( + feedbackSuccess: FeedbackColour( + background: Color(DievasColourSemanticLight.feedbackSuccessBackground), + border: Color(DievasColourSemanticLight.feedbackSuccessBorder), + icon: Color(DievasColourSemanticLight.feedbackSuccessIcon), + text: Color(DievasColourSemanticLight.feedbackSuccessText), + ), + feedbackWarning: FeedbackColour( + background: Color(DievasColourSemanticLight.feedbackWarningBackground), + border: Color(DievasColourSemanticLight.feedbackWarningBorder), + icon: Color(DievasColourSemanticLight.feedbackWarningIcon), + text: Color(DievasColourSemanticLight.feedbackWarningText), + ), + feedbackError: FeedbackColour( + background: Color(DievasColourSemanticLight.feedbackErrorBackground), + border: Color(DievasColourSemanticLight.feedbackErrorBorder), + icon: Color(DievasColourSemanticLight.feedbackErrorIcon), + text: Color(DievasColourSemanticLight.feedbackErrorText), + ), + feedbackInfo: FeedbackColour( + background: Color(DievasColourSemanticLight.feedbackInfoBackground), + border: Color(DievasColourSemanticLight.feedbackInfoBorder), + icon: Color(DievasColourSemanticLight.feedbackInfoIcon), + text: Color(DievasColourSemanticLight.feedbackInfoText), + ), + ), + input: const InputColours( + inputBg: Color(DievasColourSemanticLight.inputBg), + inputBorder: Color(DievasColourSemanticLight.inputBorder), + inputBorderFocus: Color(DievasColourSemanticLight.inputBorderFocus), + inputBorderError: Color(DievasColourSemanticLight.inputBorderError), + inputBorderWarning: Color(DievasColourSemanticLight.inputBorderWarning), + inputText: Color(DievasColourSemanticLight.inputText), + inputPlaceholder: Color(DievasColourSemanticLight.inputPlaceholder), + ), + surface: const SurfaceColours( + surfaceCanvas: Color(DievasColourSemanticLight.surfaceCanvas), + surfaceCode: Color(DievasColourSemanticLight.surfaceCode), + surfaceSidebar: Color(DievasColourSemanticLight.surfaceSidebar), + surfaceTableStriped: Color(DievasColourSemanticLight.surfaceTableStriped), + ), + switchColours: const SwitchColours( + switchTrackOn: Color(DievasColourSemanticLight.switchTrackOn), + switchTrackOff: Color(DievasColourSemanticLight.switchTrackOff), + switchThumbOn: Color(DievasColourSemanticLight.switchThumbOn), + switchThumbOff: Color(DievasColourSemanticLight.switchThumbOff), + ), + staticColours: const StaticColours( + staticWhite: Color(ExampleColours.staticWhite), + staticBlack: Color(ExampleColours.staticBlack), + staticTransparent: Color(DievasColourSemanticLight.staticTransparent), + ), + ); + + @override + DievasThemeData copyWith({DievasComponentThemeData? components, DievasTypographyThemeData? typography}) => + ExampleLightThemeData(components: components, typography: typography); +} diff --git a/packages/dievas/example/lib/example_theme/example_theme.dart b/packages/dievas/example/lib/example_theme/example_theme.dart new file mode 100644 index 0000000..0952493 --- /dev/null +++ b/packages/dievas/example/lib/example_theme/example_theme.dart @@ -0,0 +1,20 @@ +/// The Example App brand theme. +/// +/// This folder is the template every consumer app copies to define its own +/// brand: raw tokens → typography → light/dark theme data. Study the four +/// files in order: +/// +/// 1. `example_tokens.dart` — raw brand values (colours, fonts) +/// 2. `example_typography.dart` — the type ramp built over Dievas tokens +/// 3. `example_light_theme.dart` / `example_dark_theme.dart` — the theme classes +library; + +import 'package:dievas/dievas.dart'; +import 'package:dievas_tokens/dievas_tokens.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +part 'example_tokens.dart'; +part 'example_typography.dart'; +part 'example_light_theme.dart'; +part 'example_dark_theme.dart'; diff --git a/packages/dievas/example/lib/example_theme/example_tokens.dart b/packages/dievas/example/lib/example_theme/example_tokens.dart new file mode 100644 index 0000000..0fa7000 --- /dev/null +++ b/packages/dievas/example/lib/example_theme/example_tokens.dart @@ -0,0 +1,69 @@ +part of 'example_theme.dart'; + +/// Raw brand token values for the Example App. +/// +/// Pure `int` ARGB values — no `Color` wrapping here. Wrapping happens in the +/// colour sets of [ExampleLightThemeData] / [ExampleDarkThemeData]. +/// +/// Theme-independent values live on this class; anything that differs per mode +/// lives in the theme files. Values that don't need a brand identity fall back +/// to the Dievas semantic defaults (`DievasColourSemanticLight` / `Dark`). +abstract final class ExampleColours { + ExampleColours._(); + + /// Brand — theme-independent. + static const int brand = 0xFFC9C3F2; // lavender + static const int brandSubtle = 0xFFE6A889; // peach + static const int onBrand = 0xFF100C23; // near-black indigo + + /// Text / icon — one value per mode. + static const int textDark = 0xFF1E163E; + static const int textLight = 0xFFC9C3F2; + static const int textLavender = 0xFF6C609C; + + /// Borders. + static const int borderBrand = 0xFFC9C3F2; + + /// Actions — light mode. + static const int actionPrimary = 0xFF1E163E; + static const int actionPrimaryHover = 0xFF352D57; + static const int actionPrimaryActive = 0xFF474170; + static const int actionSecondary = 0xFFE6A889; + static const int actionSecondaryHover = 0xFFD9977A; + + /// Actions — dark mode. + static const int actionPrimaryDark = 0xFFC9C3F2; + static const int actionPrimaryHoverDark = 0xFFB4ADE8; + static const int actionPrimaryActiveDark = 0xFFA099D8; + + /// Static — theme-independent. + static const int staticWhite = 0xFFF2F0E8; + static const int staticBlack = 0xFF171717; + static const int staticGrey = 0xFFE7E6EE; + static const int actualWhite = 0xFFFFFFFF; + static const int warning = 0xFFAD6239; + static const int warningBG = 0xFFFAE9DF; + static const int transparent = 0x00000000; + + /// Backgrounds. + static const int bgLight = brand; + static const int bgDark = onBrand; + static const int scaffoldBG = 0xFFF2F0E8; + + /// Plan-card gradients. + static const int cardProGradientStart = 0xFF1A103B; + static const int cardProGradientEnd = 0xFF2B1B56; +} + +/// Resolved font families for the Example App. +/// +/// Uses [GoogleFonts] to bundle Cascadia Code at runtime. The Dievas default +/// themes ship MartianMono; brand apps override typography to use their own +/// typefaces, exactly as shown in [buildExampleTypography]. +abstract final class ExampleFonts { + ExampleFonts._(); + static String? _cachedFont; + + /// Cascadia Code — display, body, and code in this example brand. + static String get cascadiaCode => _cachedFont ??= GoogleFonts.cascadiaCode().fontFamily ?? 'Cascadia Code'; +} diff --git a/packages/dievas/example/lib/example_theme/example_typography.dart b/packages/dievas/example/lib/example_theme/example_typography.dart new file mode 100644 index 0000000..2b35357 --- /dev/null +++ b/packages/dievas/example/lib/example_theme/example_typography.dart @@ -0,0 +1,72 @@ +part of 'example_theme.dart'; + +/// Builds the Example App typography scale from Dievas semantic tokens. +/// +/// The Dievas type ramp is expressed as [DievasTypographySemantic] records (raw doubles/ints). +/// +/// This function wraps them into [TextStyle] objects with the brand font family and a baked in text colour; the same colour that is passed by [ExampleLightThemeData] / [ExampleDarkThemeData] so components never need to pass a colour to [Text]. +/// +/// Replace the font arguments to use your own typefaces. +DievasTypographyThemeData buildExampleTypography({ + required Color textColor, + String displayFont = 'Cascadia Code', + String bodyFont = 'Cascadia Code', + String codeFont = 'Cascadia Code', +}) { + final resolvedDisplay = displayFont == 'Cascadia Code' ? ExampleFonts.cascadiaCode : displayFont; + final resolvedBody = bodyFont == 'Cascadia Code' ? ExampleFonts.cascadiaCode : bodyFont; + final resolvedCode = codeFont == 'Cascadia Code' ? ExampleFonts.cascadiaCode : codeFont; + + return DievasTypographyThemeData( + // Display + displayLg: _exampleStyle(DievasTypographySemantic.displayLg, resolvedDisplay, textColor), + displayMd: _exampleStyle(DievasTypographySemantic.displayMd, resolvedDisplay, textColor), + displaySm: _exampleStyle(DievasTypographySemantic.displaySm, resolvedDisplay, textColor), + // Heading + headingXl: _exampleStyle(DievasTypographySemantic.headingXl, resolvedDisplay, textColor), + headingLg: _exampleStyle(DievasTypographySemantic.headingLg, resolvedDisplay, textColor), + headingMd: _exampleStyle(DievasTypographySemantic.headingMd, resolvedDisplay, textColor), + headingSm: _exampleStyle(DievasTypographySemantic.headingSm, resolvedDisplay, textColor), + headingXs: _exampleStyle(DievasTypographySemantic.headingXs, resolvedDisplay, textColor), + // Title + titleLg: _exampleStyle(DievasTypographySemantic.titleLg, resolvedDisplay, textColor), + titleMd: _exampleStyle(DievasTypographySemantic.titleMd, resolvedDisplay, textColor), + titleSm: _exampleStyle(DievasTypographySemantic.titleSm, resolvedDisplay, textColor), + titleXsm: _exampleStyle(DievasTypographySemantic.titleXsm, resolvedDisplay, textColor), + // Body + bodyLg: _exampleStyle(DievasTypographySemantic.bodyLg, resolvedBody, textColor), + bodyMd: _exampleStyle(DievasTypographySemantic.bodyMd, resolvedBody, textColor), + bodySm: _exampleStyle(DievasTypographySemantic.bodySm, resolvedBody, textColor), + bodyXs: _exampleStyle(DievasTypographySemantic.bodyXs, resolvedBody, textColor), + // Label + labelLg: _exampleStyle(DievasTypographySemantic.labelLg, resolvedBody, textColor), + labelMd: _exampleStyle(DievasTypographySemantic.labelMd, resolvedBody, textColor), + labelSm: _exampleStyle(DievasTypographySemantic.labelSm, resolvedBody, textColor), + labelXs: _exampleStyle(DievasTypographySemantic.labelXs, resolvedBody, textColor), + // Code + codeMd: _exampleStyle(DievasTypographySemantic.codeMd, resolvedCode, textColor), + codeSm: _exampleStyle(DievasTypographySemantic.codeSm, resolvedCode, textColor), + ); +} + +FontWeight _exampleWeight(int w) => switch (w) { + 100 => .w100, + 200 => .w200, + 300 => .w300, + 400 => .w400, + 500 => .w500, + 600 => .w600, + 700 => .w700, + 800 => .w800, + 900 => .w900, + _ => .w400, +}; + +TextStyle _exampleStyle(DievasTextStyleToken token, String fontFamily, Color color) => TextStyle( + fontSize: token.fontSize, + fontWeight: _exampleWeight(token.fontWeight), + letterSpacing: token.letterSpacing, + height: token.lineHeight, + fontFamily: fontFamily, + color: color, +); diff --git a/packages/dievas/example/lib/main.dart b/packages/dievas/example/lib/main.dart index aa76115..d9bc165 100644 --- a/packages/dievas/example/lib/main.dart +++ b/packages/dievas/example/lib/main.dart @@ -1,40 +1,90 @@ import 'package:flutter/material.dart'; + import 'package:dievas/dievas.dart'; -void main() => runApp( - DievasScope(lightTheme: DievasLightThemeData(), darkTheme: DievasDarkThemeData(), child: const DievasExampleApp()), -); +import 'example_theme/example_theme.dart'; +import 'screens/example_components_screen.dart'; +import 'screens/example_foundations_screen.dart'; +import 'screens/example_overview_screen.dart'; + +// 1. Entry point +// +// The DievasScope is the root of every Dievas app. It: +// - resolves the active theme from `themeMode` + platform brightness +// - inserts `DievasTheme` (an InheritedModel) into the tree, so every widget below can read the theme via `context.colours`, `context.spacing`, `DievasTheme.of(context)`, etc. +// - bridges to Material via `theme.material` (used below on MaterialApp) +// - owns the banner/snackbar overlay controller (`DievasScope.of(context)`) +// +// This is the only place the app talks to Dievas directly. +// +// Everything below reads the theme through the context — no theme plumbing ever leaks again. +void main() => runApp(const _DievasExampleApp()); -class DievasExampleApp extends StatelessWidget { - const DievasExampleApp({super.key}); +class _DievasExampleApp extends StatelessWidget { + const _DievasExampleApp(); @override - Widget build(BuildContext context) => const MaterialApp(debugShowCheckedModeBanner: false, home: ExampleScreen()); + Widget build(BuildContext context) => DievasScope( + // 2. Brand themes + // + // The Example App defines its own light and dark themes in `example_theme/` (tokens → typography → theme data). + // Swap these for your own brand and the whole design system re-skins itself. + themeMode: .system, + lightTheme: ExampleLightThemeData(), + darkTheme: ExampleDarkThemeData(), + + // 3. Material bridge + // + // DievasScope rebuilds `builder` whenever the resolved theme changes (manual switch or system brightness change), so `theme.material` is always the ThemeData for the active mode. + // Register the Dievas localizations so component internal copy resolves via `context.l10n`. + builder: (context, theme, child) => MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Dievas Example App', + theme: theme.material, + localizationsDelegates: DievasLocalizations.localizationsDelegates, + supportedLocales: DievasLocalizations.supportedLocales, + home: const _ExampleShell(), + ), + ); } -class ExampleScreen extends StatelessWidget { - const ExampleScreen({super.key}); +// 4. Shell +// +// A plain Material NavigationBar hosting the three demo screens. +// +// Using the Material bridge here shows that standard Flutter widgets (NavigationBar, +// Scaffold, AppBar) pick up the Dievas theme automatically via `theme.material`. +class _ExampleShell extends StatefulWidget { + const _ExampleShell(); @override - Widget build(BuildContext context) { - final spacing = context.spacing; - - return Scaffold( - backgroundColor: context.colours.background.bgBase, - appBar: AppBar(title: const Text('Dievas Example')), - body: Center( - child: Padding( - padding: .all(spacing.md), - child: Column( - mainAxisSize: .min, - children: [ - Text('Dievas Design System', style: context.typography.titleLg), - SizedBox(height: spacing.lg), - DievasFilledButton(label: 'Get Started', onPressed: () {}), - ], - ), + State<_ExampleShell> createState() => __ExampleShellState(); +} + +class __ExampleShellState extends State<_ExampleShell> { + int _index = 0; + + @override + Widget build(BuildContext context) => Scaffold( + body: IndexedStack(index: _index, children: _screens), + bottomNavigationBar: NavigationBar( + selectedIndex: _index, + onDestinationSelected: (i) => setState(() => _index = i), + destinations: const [ + NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: 'Overview'), + NavigationDestination( + icon: Icon(Icons.palette_outlined), + selectedIcon: Icon(Icons.palette), + label: 'Foundations', ), - ), - ); - } + NavigationDestination( + icon: Icon(Icons.widgets_outlined), + selectedIcon: Icon(Icons.widgets), + label: 'Components', + ), + ], + ), + ); } + +const _screens = [ExampleOverviewScreen(), ExampleFoundationsScreen(), ExampleComponentsScreen()]; diff --git a/packages/dievas/example/lib/screens/example_components_screen.dart b/packages/dievas/example/lib/screens/example_components_screen.dart new file mode 100644 index 0000000..c50f68b --- /dev/null +++ b/packages/dievas/example/lib/screens/example_components_screen.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; + +import 'package:dievas/dievas.dart'; + +/// Components — the Dievas widget library in action. +/// +/// One section per component group. Every component reads its colours, sizes +/// and spacing from the theme — none of the values below are hardcoded. +class ExampleComponentsScreen extends StatefulWidget { + const ExampleComponentsScreen({super.key}); + + @override + State createState() => _ExampleComponentsScreenState(); +} + +class _ExampleComponentsScreenState extends State { + final TextEditingController _inputController = TextEditingController(); + final TextEditingController _areaController = TextEditingController(); + DievasCheckboxValue _checkbox = .checked; + bool _switchOn = true; + int _radioGroup = 1; + String _segmented = 'Compact'; + String? _dropdown; + + @override + void dispose() { + _inputController.dispose(); + _areaController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => SingleChildScrollView( + padding: .symmetric(horizontal: context.spacing.xl, vertical: context.spacing.x2l), + child: Column( + crossAxisAlignment: .start, + children: [ + Text('Components', style: context.typography.displaySm), + SizedBox(height: context.spacing.sm), + Text( + 'All values come from context.* — nothing is hardcoded.', + style: context.typography.bodyMd.copyWith(color: context.colours.text.textSecondary), + ), + SizedBox(height: context.spacing.x2l), + + const _GroupLabel('Buttons'), + _ButtonRow(), + SizedBox(height: context.spacing.x3l), + + const _GroupLabel('Form'), + DievasTextInput( + controller: _inputController, + label: 'Email', + hint: 'you@example.com', + helperText: 'We never share your email.', + ), + SizedBox(height: context.spacing.md), + DievasTextArea(controller: _areaController, label: 'Message', hint: 'Write something…'), + SizedBox(height: context.spacing.md), + DievasCheckbox( + value: _checkbox, + onChanged: (v) => setState(() => _checkbox = v), + label: 'Subscribe to updates', + ), + SizedBox(height: context.spacing.md), + DievasSwitch(value: _switchOn, onChanged: (v) => setState(() => _switchOn = v), label: 'Notifications'), + SizedBox(height: context.spacing.md), + DievasRadio( + value: 1, + groupValue: _radioGroup, + onChanged: (v) => setState(() => _radioGroup = v), + label: 'Option one', + ), + DievasRadio( + value: 2, + groupValue: _radioGroup, + onChanged: (v) => setState(() => _radioGroup = v), + label: 'Option two', + ), + SizedBox(height: context.spacing.md), + DievasSegmentedControl( + options: const ['Compact', 'Comfortable', 'Spacious'], + value: _segmented, + onChanged: (v) => setState(() => _segmented = v), + ), + SizedBox(height: context.spacing.md), + DievasDropdown( + options: const ['Personal', 'Business', 'Enterprise'], + value: _dropdown, + onChanged: (v) => setState(() => _dropdown = v), + hint: 'Select a plan', + label: 'Plan', + ), + SizedBox(height: context.spacing.x3l), + + const _GroupLabel('Display'), + const _DisplayRow(), + SizedBox(height: context.spacing.lg), + const _ProgressRow(), + SizedBox(height: context.spacing.x3l), + + const _GroupLabel('Feedback & overlays'), + _FeedbackRow(), + SizedBox(height: context.spacing.lg), + const DievasLoader(label: 'Loading…'), + SizedBox(height: context.spacing.x3l), + + const _GroupLabel('Empty state'), + const DievasEmptyState(title: 'No projects yet', description: 'Create your first project to get started.'), + SizedBox(height: context.spacing.x3l), + ], + ), + ); +} + +class _GroupLabel extends StatelessWidget { + const _GroupLabel(this.text); + + final String text; + + @override + Widget build(BuildContext context) => Padding( + padding: .only(bottom: context.spacing.md), + child: Text( + text.toUpperCase(), + style: context.typography.labelXs.copyWith(letterSpacing: 1.8, color: context.colours.text.textTertiary), + ), + ); +} + +class _ButtonRow extends StatelessWidget { + const _ButtonRow(); + + @override + Widget build(BuildContext context) => Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + crossAxisAlignment: .center, + children: [ + DievasFilledButton(label: 'Filled', onPressed: () {}), + DievasOutlinedButton(label: 'Outlined', onPressed: () {}), + DievasTextButton(label: 'Text', onPressed: () {}), + DievasIconButton(icon: const Icon(Icons.favorite_outline), semanticLabel: 'Like', onPressed: () {}), + DievasFilledButton(label: 'Disabled', onPressed: () {}, state: .disabled), + DievasOutlinedButton(label: 'Loading', onPressed: () {}, state: .loading), + ], + ); +} + +class _DisplayRow extends StatelessWidget { + const _DisplayRow(); + + @override + Widget build(BuildContext context) => Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + crossAxisAlignment: .center, + children: [ + // Per-instance colour overrides (added for per-component customization). + const DievasAvatar(initials: 'DX'), + DievasAvatar( + initials: 'DX', + backgroundColour: context.colours.action.actionPrimary, + initialsColour: context.colours.core.onBrand, + ), + const DievasBadge(label: 'New', tone: .primary), + const DievasBadge(label: 'Shipped', tone: .success), + const DievasTag(label: 'Flutter'), + DievasTag(label: 'Removable', onRemove: () {}, backgroundColor: context.colours.action.actionSecondary), + const DievasIcon(Icons.bolt, size: .lg, semanticLabel: 'Bolt'), + const DievasDivider(), + ], + ); +} + +class _ProgressRow extends StatelessWidget { + const _ProgressRow(); + + @override + Widget build(BuildContext context) => Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + crossAxisAlignment: .center, + children: [ + SizedBox(width: 160, child: DievasLinearProgress(value: 0.65)), + SizedBox(width: 160, child: DievasLinearProgress(value: null)), + const DievasCircularProgress(value: 0.65), + const DievasCircularProgress(value: null), + ], + ); +} + +class _FeedbackRow extends StatelessWidget { + const _FeedbackRow(); + + @override + Widget build(BuildContext context) => Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + children: [ + const DievasAlert(tone: .info, title: 'Heads up', description: 'The system will update at 2am.'), + const DievasAlert(tone: .success, title: 'Saved', description: 'Your changes are live.'), + const DievasAlert(tone: .warning, title: 'Almost there', description: 'Complete your profile to unlock invites.'), + const DievasAlert(tone: .error, title: 'Connection lost', description: 'Check your network and retry.'), + DievasFilledButton( + label: 'Bottom sheet', + onPressed: () => showDievasBottomSheet( + context: context, + builder: (context) => DievasBottomSheet( + child: Padding( + padding: .all(context.spacing.lg), + child: Text('Bottom sheet content', style: context.typography.bodyMd), + ), + ), + ), + ), + DievasOutlinedButton( + label: 'Modal', + onPressed: () => showDievasModal( + context: context, + builder: (context) => DievasModal( + title: 'Confirm', + body: 'Do you want to continue?', + actions: [DievasFilledButton(label: 'Confirm', onPressed: () => Navigator.of(context).pop())], + ), + ), + ), + DievasTooltip( + message: 'Tooltips wrap their child.', + child: DievasFilledButton(label: 'Hover me', onPressed: () {}), + ), + ], + ); +} diff --git a/packages/dievas/example/lib/screens/example_foundations_screen.dart b/packages/dievas/example/lib/screens/example_foundations_screen.dart new file mode 100644 index 0000000..d67a8a5 --- /dev/null +++ b/packages/dievas/example/lib/screens/example_foundations_screen.dart @@ -0,0 +1,321 @@ +import 'package:flutter/material.dart'; + +import 'package:dievas/dievas.dart'; + +/// Foundations — every Dievas sub-system, live. +/// +/// Dievas splits the theme into aspect-scoped sub-systems. Widgets read the +/// one they need and only rebuild when it changes. This screen shows each +/// getter (via the flat `context.*` extension and the [DievasTheme] statics), +/// plus a per-instance component override via `copyWith`. +class ExampleFoundationsScreen extends StatelessWidget { + const ExampleFoundationsScreen({super.key}); + + @override + Widget build(BuildContext context) => SingleChildScrollView( + padding: .symmetric(horizontal: context.spacing.xl, vertical: context.spacing.x2l), + child: Column( + crossAxisAlignment: .start, + children: [ + Text('Foundations', style: context.typography.displaySm), + SizedBox(height: context.spacing.sm), + Text( + 'Each card reads one aspect-scoped sub-system.', + style: context.typography.bodyMd.copyWith(color: context.colours.text.textSecondary), + ), + SizedBox(height: context.spacing.x2l), + + const _SubsystemCard( + title: 'context.colours', + description: 'Every semantic colour role, grouped by domain.', + child: _ColourSwatches(), + ), + SizedBox(height: context.spacing.lg), + + const _SubsystemCard( + title: 'context.typography', + description: 'The full type ramp as pre-coloured TextStyles.', + child: _TypographyScale(), + ), + SizedBox(height: context.spacing.lg), + + const _SubsystemCard( + title: 'context.spacing', + description: '4pt-grid spacing tokens.', + child: _SpacingReadout(), + ), + SizedBox(height: context.spacing.lg), + + const _SubsystemCard( + title: 'context.sizing / border / elevation / opacity / animation', + description: 'Component dimensions, radii, shadows, opacity values, motion durations.', + child: _ValueReadouts(), + ), + SizedBox(height: context.spacing.lg), + + const _SubsystemCard( + title: 'DievasTheme statics', + description: 'The same data via DievasTheme.of / coloursOf / … — identical values.', + child: _ThemeStatics(), + ), + SizedBox(height: context.spacing.lg), + + const _SubsystemCard( + title: 'Per-instance override (copyWith)', + description: + 'Wrap a subtree in DievasTheme with a copyWith-ed theme to override ' + 'a component for that subtree only.', + child: _ComponentOverride(), + ), + SizedBox(height: context.spacing.x3l), + ], + ), + ); +} + +class _SubsystemCard extends StatelessWidget { + const _SubsystemCard({required this.title, required this.description, required this.child}); + + final String title; + final String description; + final Widget child; + + @override + Widget build(BuildContext context) => Container( + width: .infinity, + padding: .all(context.spacing.lg), + decoration: BoxDecoration( + color: context.colours.background.bgSubtle, + border: .all(color: context.colours.border.borderDefault), + borderRadius: context.border.lg, + ), + child: Column( + crossAxisAlignment: .start, + children: [ + Text(title, style: context.typography.codeMd), + SizedBox(height: context.spacing.xs), + Text(description, style: context.typography.bodySm.copyWith(color: context.colours.text.textSecondary)), + SizedBox(height: context.spacing.lg), + child, + ], + ), + ); +} + +class _ColourSwatches extends StatelessWidget { + const _ColourSwatches(); + + @override + Widget build(BuildContext context) { + final colours = context.colours; + + return Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + children: [ + _Swatch(label: 'brand', colour: colours.core.brand), + _Swatch(label: 'textPrimary', colour: colours.text.textPrimary), + _Swatch(label: 'bgBase', colour: colours.background.bgBase), + _Swatch(label: 'bgSubtle', colour: colours.background.bgSubtle), + _Swatch(label: 'actionPrimary', colour: colours.action.actionPrimary), + _Swatch(label: 'actionError', colour: colours.action.actionError), + _Swatch(label: 'borderBrand', colour: colours.border.borderBrand), + ], + ); + } +} + +class _Swatch extends StatelessWidget { + const _Swatch({required this.label, required this.colour}); + + final String label; + final Color colour; + + @override + Widget build(BuildContext context) => Container( + width: 96, + padding: .all(context.spacing.sm), + decoration: BoxDecoration( + color: colour, + borderRadius: context.border.md, + border: .all(color: context.colours.border.borderDefault), + ), + child: Text( + label, + style: context.typography.labelXs.copyWith( + color: ThemeData.estimateBrightnessForColor(colour) == Brightness.dark + ? context.colours.staticColours.staticWhite + : context.colours.staticColours.staticBlack, + ), + ), + ); +} + +class _TypographyScale extends StatelessWidget { + const _TypographyScale(); + + @override + Widget build(BuildContext context) { + final t = context.typography; + + return Column( + crossAxisAlignment: .start, + children: [ + Text('displaySm — Display', style: t.displaySm), + SizedBox(height: context.spacing.md), + Text('headingMd — Heading', style: t.headingMd), + SizedBox(height: context.spacing.md), + Text('titleLg — Title', style: t.titleLg), + SizedBox(height: context.spacing.md), + Text('bodyMd — Body copy', style: t.bodyMd), + SizedBox(height: context.spacing.md), + Text('labelMd — Label', style: t.labelMd), + SizedBox(height: context.spacing.md), + Text('codeMd — Inline code', style: t.codeMd), + ], + ); + } +} + +class _SpacingReadout extends StatelessWidget { + const _SpacingReadout(); + + @override + Widget build(BuildContext context) { + final s = context.spacing; + + return Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + children: [ + _StatChip(label: 'xs', value: '${s.xs}'), + _StatChip(label: 'sm', value: '${s.sm}'), + _StatChip(label: 'md', value: '${s.md}'), + _StatChip(label: 'lg', value: '${s.lg}'), + _StatChip(label: 'xl', value: '${s.xl}'), + _StatChip(label: 'x2l', value: '${s.x2l}'), + ], + ); + } +} + +class _ValueReadouts extends StatelessWidget { + const _ValueReadouts(); + + @override + Widget build(BuildContext context) { + final sizing = context.sizing; + final border = context.border; + final elevation = context.elevation; + final opacity = context.opacity; + final animation = context.animation; + + return Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + children: [ + _StatChip(label: 'sizing.buttonHeightMd', value: '${sizing.buttonHeightMd}'), + _StatChip(label: 'sizing.inputHeightMd', value: '${sizing.inputHeightMd}'), + _StatChip(label: 'border.lg radius', value: '${border.lg.topLeft.x}'), + _StatChip(label: 'border.strokeThin', value: '${border.strokeThin}'), + _StatChip(label: 'elevation.md shadows', value: '${elevation.md.length}'), + _StatChip(label: 'opacity.disabled', value: opacity.disabled.toStringAsFixed(2)), + _StatChip(label: 'animation.standard', value: '${animation.standard.inMilliseconds}ms'), + ], + ); + } +} + +class _ThemeStatics extends StatelessWidget { + const _ThemeStatics(); + + @override + Widget build(BuildContext context) => Column( + crossAxisAlignment: .start, + children: [ + _CodeLine('DievasTheme.of(context) → ${DievasTheme.of(context).runtimeType}'), + SizedBox(height: context.spacing.sm), + _CodeLine('DievasTheme.coloursOf(context).core.brand → ${DievasTheme.coloursOf(context).core.brand}'), + SizedBox(height: context.spacing.sm), + _CodeLine('DievasTheme.animationOf(context).fast → ${DievasTheme.animationOf(context).fast}'), + SizedBox(height: context.spacing.sm), + _CodeLine( + 'DievasTheme.materialOf(context).colorScheme.brightness → ' + '${DievasTheme.materialOf(context).colorScheme.brightness}', + ), + ], + ); +} + +class _CodeLine extends StatelessWidget { + const _CodeLine(this.text); + + final String text; + + @override + Widget build(BuildContext context) => Text(text, style: context.typography.codeSm); +} + +class _ComponentOverride extends StatelessWidget { + const _ComponentOverride(); + + @override + Widget build(BuildContext context) { + // Build a theme copy with the avatar component overridden for this subtree + // only. Everything else inherits from the scope theme above. + final overridden = DievasTheme.of(context).copyWith( + components: DievasTheme.of(context).components.copyWith( + avatar: DievasTheme.of(context).components.avatar.copyWith( + backgroundColour: context.colours.action.actionPrimary, + initialsColour: context.colours.core.onBrand, + ), + ), + ); + + return Row( + children: [ + Column( + crossAxisAlignment: .start, + children: [ + Text('Default', style: context.typography.labelSm), + SizedBox(height: context.spacing.md), + const DievasAvatar(initials: 'DX'), + ], + ), + SizedBox(width: context.spacing.x2l), + Column( + crossAxisAlignment: .start, + children: [ + Text('copyWith override', style: context.typography.labelSm), + SizedBox(height: context.spacing.md), + DievasTheme( + data: overridden, + child: const DievasAvatar(initials: 'DX'), + ), + ], + ), + ], + ); + } +} + +class _StatChip extends StatelessWidget { + const _StatChip({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) => Container( + padding: .symmetric(horizontal: context.spacing.md, vertical: context.spacing.sm), + decoration: BoxDecoration(color: context.colours.background.bgElevated, borderRadius: context.border.md), + child: Column( + crossAxisAlignment: .start, + children: [ + Text(label, style: context.typography.labelXs.copyWith(color: context.colours.text.textTertiary)), + SizedBox(height: context.spacing.xs), + Text(value, style: context.typography.labelMd), + ], + ), + ); +} diff --git a/packages/dievas/example/lib/screens/example_overview_screen.dart b/packages/dievas/example/lib/screens/example_overview_screen.dart new file mode 100644 index 0000000..fe93b46 --- /dev/null +++ b/packages/dievas/example/lib/screens/example_overview_screen.dart @@ -0,0 +1,218 @@ +import 'package:flutter/material.dart'; + +import 'package:dievas/dievas.dart'; + +/// Overview — how the app wires into the design system. +/// +/// This screen demonstrates: +/// - theme mode switching via [DievasScope.of] +/// - banner / snackbar overlays via [DievasScope.of] +/// - the responsive grid ([DievasGrid] + `context.grid`) +/// - localizations via `context.l10n` +class ExampleOverviewScreen extends StatelessWidget { + const ExampleOverviewScreen({super.key}); + + @override + Widget build(BuildContext context) { + final spacing = context.spacing; + + return DievasGrid( + child: SingleChildScrollView( + padding: .symmetric(horizontal: spacing.xl, vertical: spacing.x2l), + child: Column( + crossAxisAlignment: .start, + children: [ + Text('Dievas Example App', style: context.typography.displaySm), + SizedBox(height: spacing.sm), + Text( + 'Theme setup · overlays · grid · l10n', + style: context.typography.bodyMd.copyWith(color: context.colours.text.textSecondary), + ), + SizedBox(height: spacing.x2l), + + const _SectionCard( + title: 'Theme mode', + description: + 'DievasScope.of(context).setThemeMode(...) drives the active theme. ' + 'Try switching; every widget below updates instantly.', + child: _ThemeModeControl(), + ), + SizedBox(height: spacing.lg), + + const _SectionCard( + title: 'Overlays', + description: + 'DievasScope owns a banner and a snackbar overlay. ' + 'The builder returns a Dievas feedback component; the scope animates it.', + child: _OverlayControls(), + ), + SizedBox(height: spacing.lg), + + const _SectionCard( + title: 'Responsive grid', + description: + 'Wrap content in DievasGrid to read the current breakpoint ' + 'via context.grid. Columns, margin and gutter change with width.', + child: _GridReadout(), + ), + SizedBox(height: spacing.lg), + + _SectionCard( + title: 'Localizations', + description: + 'Component internal copy resolves through context.l10n. ' + 'Register DievasLocalizations delegates on MaterialApp to use it.', + child: _L10nReadout(), + ), + SizedBox(height: spacing.x3l), + ], + ), + ), + ); + } +} + +class _SectionCard extends StatelessWidget { + const _SectionCard({required this.title, required this.description, required this.child}); + + final String title; + final String description; + final Widget child; + + @override + Widget build(BuildContext context) { + final spacing = context.spacing; + final colours = context.colours; + + return Container( + width: .infinity, + padding: .all(spacing.lg), + decoration: BoxDecoration( + color: colours.background.bgSubtle, + border: .all(color: colours.border.borderDefault), + borderRadius: context.border.lg, + ), + child: Column( + crossAxisAlignment: .start, + children: [ + Text(title, style: context.typography.titleLg), + SizedBox(height: spacing.xs), + Text(description, style: context.typography.bodySm.copyWith(color: colours.text.textSecondary)), + SizedBox(height: spacing.lg), + child, + ], + ), + ); + } +} + +class _ThemeModeControl extends StatelessWidget { + const _ThemeModeControl(); + + @override + Widget build(BuildContext context) => DievasSegmentedControl( + options: const [ThemeMode.system, ThemeMode.light, ThemeMode.dark], + value: DievasScope.of(context).themeMode, + onChanged: (mode) => DievasScope.of(context).setThemeMode(mode), + labelBuilder: (mode) => switch (mode) { + .system => 'System', + .light => 'Light', + .dark => 'Dark', + }, + ); +} + +class _OverlayControls extends StatelessWidget { + const _OverlayControls(); + + @override + Widget build(BuildContext context) { + final scope = DievasScope.of(context); + + return Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + children: [ + DievasFilledButton( + label: 'Show banner', + onPressed: () => _showDievasBanner(scope: scope), + ), + DievasOutlinedButton( + label: 'Show snackbar', + onPressed: () => _showDievasSnackbar(scope: scope), + ), + ], + ); + } + + void _showDievasBanner({required DievasScopeController scope}) => scope.showBanner( + (context, _) => DievasBanner( + tone: .info, + message: 'This banner is rendered by DievasScope.showBanner.', + onDismiss: scope.hideBanner, + ), + ); + + void _showDievasSnackbar({required DievasScopeController scope}) => scope.showSnackbar( + (context, _) => DievasSnackbar( + message: 'Snackbar rendered by DievasScope.showSnackbar.', + action: DievasSnackbarAction(label: 'Dismiss', onPressed: scope.hideSnackbar), + ), + ); +} + +class _GridReadout extends StatelessWidget { + const _GridReadout(); + + @override + Widget build(BuildContext context) { + final grid = context.grid; + + return Wrap( + spacing: context.spacing.md, + runSpacing: context.spacing.md, + children: [ + _StatChip(label: 'Breakpoint', value: grid.name), + _StatChip(label: 'Columns', value: '${grid.columns}'), + _StatChip(label: 'Margin', value: '${grid.margin}'), + _StatChip(label: 'Gutter', value: '${grid.gutter}'), + _StatChip(label: 'Family', value: grid.family), + ], + ); + } +} + +class _StatChip extends StatelessWidget { + const _StatChip({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) { + final spacing = context.spacing; + + return Container( + padding: .symmetric(horizontal: spacing.md, vertical: spacing.sm), + decoration: BoxDecoration(color: context.colours.background.bgElevated, borderRadius: context.border.md), + child: Column( + crossAxisAlignment: .start, + children: [ + Text(label, style: context.typography.labelXs.copyWith(color: context.colours.text.textTertiary)), + SizedBox(height: context.spacing.xs), + Text(value, style: context.typography.labelMd), + ], + ), + ); + } +} + +class _L10nReadout extends StatelessWidget { + const _L10nReadout(); + + @override + Widget build(BuildContext context) => Text( + 'context.l10n.emptyStateDefaultTitle → "${context.l10n.emptyStateDefaultTitle}"', + style: context.typography.codeMd.copyWith(color: context.colours.text.textSecondary), + ); +} diff --git a/packages/dievas/example/pubspec.yaml b/packages/dievas/example/pubspec.yaml index 3f0ad5e..6e8a7ef 100644 --- a/packages/dievas/example/pubspec.yaml +++ b/packages/dievas/example/pubspec.yaml @@ -1,6 +1,10 @@ name: dievas_example -description: Example usage of the Dievas design system. +description: >- + Example usage of the Dievas design system — brand theme setup and component + consumption. This is the reference app every consumer should study before + wiring Dievas into their own Flutter app. publish_to: none +resolution: workspace environment: sdk: ">=3.11.4 <4.0.0" @@ -11,3 +15,9 @@ dependencies: sdk: flutter dievas: path: ../ + dievas_tokens: + path: ../../dievas_tokens + google_fonts: ^8.1.0 + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/packages/dievas/example/web/favicon.png b/packages/dievas/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7a4a7d884d858daeadc7857a8bad474d4b8c2146 GIT binary patch literal 7258 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 + + + + diff --git a/packages/dievas/example/web/icons/Icon-192.png b/packages/dievas/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..7a4a7d884d858daeadc7857a8bad474d4b8c2146 GIT binary patch literal 7258 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 Jb2V-S{SSR}@`L~Y literal 0 HcmV?d00001 diff --git a/packages/dievas/example/web/icons/Icon-maskable-192.png b/packages/dievas/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..2f501e4da1b6005f110b826e32a13b1c312268c5 GIT binary patch literal 4380 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 AdH?_b literal 0 HcmV?d00001 diff --git a/packages/dievas/example/web/index.html b/packages/dievas/example/web/index.html new file mode 100644 index 0000000..6ab49cd --- /dev/null +++ b/packages/dievas/example/web/index.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + Dievas Example + + + + + + + diff --git a/packages/dievas/example/web/manifest.json b/packages/dievas/example/web/manifest.json new file mode 100644 index 0000000..8a2e852 --- /dev/null +++ b/packages/dievas/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "dievas_example", + "short_name": "dievas_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/dievas/lib/src/scope/dievas_scope.dart b/packages/dievas/lib/src/scope/dievas_scope.dart index d2491f6..6319636 100644 --- a/packages/dievas/lib/src/scope/dievas_scope.dart +++ b/packages/dievas/lib/src/scope/dievas_scope.dart @@ -60,10 +60,12 @@ class DievasScope extends StatefulWidget { /// Returns the [DievasScopeController] from the nearest [DievasScope] ancestor. /// - /// Use this to call [DievasScopeController.setThemeMode] programmatically, e.g. - /// from a settings screen. + /// Establishing a dependency on the scope, so the calling widget rebuilds + /// whenever the active [themeMode] changes. Use this to call + /// [DievasScopeController.setThemeMode] programmatically, e.g. from a settings + /// screen. static DievasScopeController of(BuildContext context) { - if (context.getInheritedWidgetOfExactType<_DievasScopeStateMarker>() case final result?) { + if (context.dependOnInheritedWidgetOfExactType<_DievasScopeStateMarker>() case final result?) { return result.state; } @@ -178,15 +180,16 @@ class DievasScopeState extends State } @override - Widget build(BuildContext context) => _DievasScopeStateMarker( - state: this, - child: ValueListenableBuilder( - valueListenable: _themeModeNotifier, - builder: (context, mode, child) { - final theme = _themeFor(mode); - final content = widget.builder?.call(context, theme, widget.child) ?? widget.child ?? child ?? const SizedBox(); - - return DievasTheme( + Widget build(BuildContext context) => ValueListenableBuilder( + valueListenable: _themeModeNotifier, + builder: (context, mode, child) { + final theme = _themeFor(mode); + final content = widget.builder?.call(context, theme, widget.child) ?? widget.child ?? child ?? const SizedBox(); + + return _DievasScopeStateMarker( + state: this, + themeMode: mode, + child: DievasTheme( data: theme, child: Stack( alignment: Alignment.topCenter, @@ -205,10 +208,10 @@ class DievasScopeState extends State ), ], ), - ); - }, - child: widget.child, - ), + ), + ); + }, + child: widget.child, ); DievasThemeData _themeFor(ThemeMode mode) => switch (mode) { @@ -219,12 +222,14 @@ class DievasScopeState extends State } class _DievasScopeStateMarker extends InheritedWidget { - const _DievasScopeStateMarker({required super.child, required this.state}); + const _DievasScopeStateMarker({required super.child, required this.state, required this.themeMode}); final DievasScopeController state; + final ThemeMode themeMode; @override - bool updateShouldNotify(_DievasScopeStateMarker oldWidget) => oldWidget.state != state; + bool updateShouldNotify(_DievasScopeStateMarker oldWidget) => + oldWidget.state != state || oldWidget.themeMode != themeMode; } class _DievasOverlaySlot extends StatelessWidget { diff --git a/packages/dievas/lib/src/themes/dievas_global_theme_data.dart b/packages/dievas/lib/src/themes/dievas_global_theme_data.dart index 71ff453..c5615d2 100644 --- a/packages/dievas/lib/src/themes/dievas_global_theme_data.dart +++ b/packages/dievas/lib/src/themes/dievas_global_theme_data.dart @@ -53,7 +53,7 @@ base class DievasGlobalThemeData implements DievasThemeData { _elevation, components, ); - _material = _buildMaterial(colours); + _material = _buildMaterial(colours, _typography); } DievasGlobalThemeData._raw({ @@ -219,46 +219,30 @@ base class DievasGlobalThemeData implements DievasThemeData { ); } - static ThemeData _buildMaterial(DievasColourThemeData colours) { + static ThemeData _buildMaterial(DievasColourThemeData colours, DievasTypographyThemeData typography) { final brightness = colours.brightness; final seedColour = colours.action.actionPrimary; final scaffoldBg = colours.background.bgBase; final textColour = colours.text.textPrimary; + final baseFont = typography.bodyMd.fontFamily ?? DievasFontFamilyPrimitives.sans; + final displayFont = typography.displaySm.fontFamily ?? DievasFontFamilyPrimitives.sansExtended; final base = ThemeData( brightness: brightness, colorScheme: .fromSeed(seedColor: seedColour, brightness: brightness), scaffoldBackgroundColor: scaffoldBg, useMaterial3: true, - fontFamily: DievasFontFamilyPrimitives.sans, + fontFamily: baseFont, ); return base.copyWith( textTheme: base.textTheme.copyWith( - displayLarge: base.textTheme.displayLarge?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - color: textColour, - ), - displayMedium: base.textTheme.displayMedium?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - color: textColour, - ), - displaySmall: base.textTheme.displaySmall?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - color: textColour, - ), - headlineLarge: base.textTheme.headlineLarge?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - color: textColour, - ), - headlineMedium: base.textTheme.headlineMedium?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - color: textColour, - ), - headlineSmall: base.textTheme.headlineSmall?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - color: textColour, - ), + displayLarge: base.textTheme.displayLarge?.copyWith(fontFamily: displayFont, color: textColour), + displayMedium: base.textTheme.displayMedium?.copyWith(fontFamily: displayFont, color: textColour), + displaySmall: base.textTheme.displaySmall?.copyWith(fontFamily: displayFont, color: textColour), + headlineLarge: base.textTheme.headlineLarge?.copyWith(fontFamily: displayFont, color: textColour), + headlineMedium: base.textTheme.headlineMedium?.copyWith(fontFamily: displayFont, color: textColour), + headlineSmall: base.textTheme.headlineSmall?.copyWith(fontFamily: displayFont, color: textColour), titleLarge: base.textTheme.titleLarge?.copyWith(color: textColour), titleMedium: base.textTheme.titleMedium?.copyWith(color: textColour), titleSmall: base.textTheme.titleSmall?.copyWith(color: textColour), @@ -270,20 +254,12 @@ base class DievasGlobalThemeData implements DievasThemeData { labelSmall: base.textTheme.labelSmall?.copyWith(color: textColour), ), primaryTextTheme: base.primaryTextTheme.copyWith( - displayLarge: base.primaryTextTheme.displayLarge?.copyWith(fontFamily: DievasFontFamilyPrimitives.sansExtended), - displayMedium: base.primaryTextTheme.displayMedium?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - ), - displaySmall: base.primaryTextTheme.displaySmall?.copyWith(fontFamily: DievasFontFamilyPrimitives.sansExtended), - headlineLarge: base.primaryTextTheme.headlineLarge?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - ), - headlineMedium: base.primaryTextTheme.headlineMedium?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - ), - headlineSmall: base.primaryTextTheme.headlineSmall?.copyWith( - fontFamily: DievasFontFamilyPrimitives.sansExtended, - ), + displayLarge: base.primaryTextTheme.displayLarge?.copyWith(fontFamily: displayFont), + displayMedium: base.primaryTextTheme.displayMedium?.copyWith(fontFamily: displayFont), + displaySmall: base.primaryTextTheme.displaySmall?.copyWith(fontFamily: displayFont), + headlineLarge: base.primaryTextTheme.headlineLarge?.copyWith(fontFamily: displayFont), + headlineMedium: base.primaryTextTheme.headlineMedium?.copyWith(fontFamily: displayFont), + headlineSmall: base.primaryTextTheme.headlineSmall?.copyWith(fontFamily: displayFont), ), ); } diff --git a/packages/dievas/test/theme/dievas_theme_test.dart b/packages/dievas/test/theme/dievas_theme_test.dart index 075cc1e..aeac1b8 100644 --- a/packages/dievas/test/theme/dievas_theme_test.dart +++ b/packages/dievas/test/theme/dievas_theme_test.dart @@ -136,6 +136,15 @@ void main() { expect(DievasLightThemeData().material, isNotNull); }); + test('material bridge derives font family from typography', () { + final typography = _typographyWithFamily(DievasLightThemeData().typography, 'TestBrandFont'); + final theme = DievasLightThemeData(typography: typography); + + expect(theme.material.textTheme.bodyMedium?.fontFamily, 'TestBrandFont'); + expect(theme.material.textTheme.labelMedium?.fontFamily, 'TestBrandFont'); + expect(theme.material.textTheme.displaySmall?.fontFamily, 'TestBrandFont'); + }); + test('copyWith returns different instance', () { final t1 = DievasLightThemeData(); final t2 = t1.copyWith(); @@ -143,6 +152,41 @@ void main() { }); }); + group('DievasScope', () { + testWidgets('DievasScope.of dependents rebuild when themeMode changes', (tester) async { + var buildCount = 0; + var currentMode = ThemeMode.system; + + await tester.pumpWidget( + DievasScope( + lightTheme: DievasLightThemeData(), + darkTheme: DievasDarkThemeData(), + child: MaterialApp( + home: Builder( + builder: (context) { + buildCount++; + currentMode = DievasScope.of(context).themeMode; + return TextButton( + onPressed: () => DievasScope.of(context).setThemeMode(ThemeMode.dark), + child: const Text('toggle'), + ); + }, + ), + ), + ), + ); + + expect(currentMode, ThemeMode.system); + final buildsBeforeTap = buildCount; + + await tester.tap(find.text('toggle')); + await tester.pump(); + + expect(currentMode, ThemeMode.dark); + expect(buildCount, greaterThan(buildsBeforeTap)); + }); + }); + group('DievasDarkThemeData', () { test('default constructor does not throw', () { expect(DievasDarkThemeData(), isA()); @@ -366,3 +410,32 @@ void main() { }); }); } + +DievasTypographyThemeData _typographyWithFamily(DievasTypographyThemeData t, String family) { + TextStyle withFamily(TextStyle style) => style.copyWith(fontFamily: family); + + return DievasTypographyThemeData( + displayLg: withFamily(t.displayLg), + displayMd: withFamily(t.displayMd), + displaySm: withFamily(t.displaySm), + headingXl: withFamily(t.headingXl), + headingLg: withFamily(t.headingLg), + headingMd: withFamily(t.headingMd), + headingSm: withFamily(t.headingSm), + headingXs: withFamily(t.headingXs), + titleLg: withFamily(t.titleLg), + titleMd: withFamily(t.titleMd), + titleSm: withFamily(t.titleSm), + titleXsm: withFamily(t.titleXsm), + bodyLg: withFamily(t.bodyLg), + bodyMd: withFamily(t.bodyMd), + bodySm: withFamily(t.bodySm), + bodyXs: withFamily(t.bodyXs), + labelLg: withFamily(t.labelLg), + labelMd: withFamily(t.labelMd), + labelSm: withFamily(t.labelSm), + labelXs: withFamily(t.labelXs), + codeMd: withFamily(t.codeMd), + codeSm: withFamily(t.codeSm), + ); +} diff --git a/pubspec.lock b/pubspec.lock index 7db5777..d16c4e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: cfd4f5f575a49c5f10ca856e9846073f1e6c3ee94912377eea5f6cefc5272941 + url: "https://pub.dev" + source: hosted + version: "2.0.0" collection: dependency: transitive description: @@ -248,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + google_fonts: + dependency: transitive + description: + name: google_fonts + sha256: e3cb3ee6b47fd2472c23de6da5744796a4da195137759ddb3fbcc9467b7b3c7d + url: "https://pub.dev" + source: hosted + version: "8.2.1" graphs: dependency: transitive description: @@ -256,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f + url: "https://pub.dev" + source: hosted + version: "2.2.0" http: dependency: transitive description: @@ -296,6 +320,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" json_annotation: dependency: transitive description: @@ -408,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3 + url: "https://pub.dev" + source: hosted + version: "9.6.0" package_config: dependency: transitive description: @@ -424,6 +480,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -496,6 +600,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" + url: "https://pub.dev" + source: hosted + version: "1.1.1" resizable_widget: dependency: transitive description: @@ -765,6 +877,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.11.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9cd5ee3..3f3d154 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: workspace: - packages/dievas + - packages/dievas/example - packages/dievas_gallery - packages/dievas_tokens