Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,33 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added
- **`package:magic_starter/previews.dart`** (dev-only barrel): exposes all 30 component previews as `(label, slug, builder)` records via `starterComponentPreviews()`, so a consumer's dev-only preview catalog can surface the full component set (Button, Badge, ..., UserProfileDropdown, TeamSelector, NotificationDropdown) without duplication. Kept SEPARATE from the `magic_starter.dart` release barrel (the atomic-component contract keeps `*.preview.dart` out of release); the records are returned from a function (not a top-level const holding widget refs), so a consumer that only calls it behind a `kReleaseMode`/`PREVIEW_ENABLED` guard tree-shakes the whole set from release.
- **`design.md.stub`**: a `DESIGN.md` template shipped at `assets/stubs/design.md.stub` covering all 17 semantic roles (`surface`, `fg`, `primary`, `border`, `destructive`, `success`, `warning`, and their variants), typography on the 4px logical scale, rounded/spacing scales, and key component entries with `{{ placeholder }}` tokens. Consumers copy it into their project root, fill in brand hex values and fonts, then run `design:lint` to validate and `design:sync` to generate the Wind theme. Pairs with `MagicStarterTokens.defaultAliases` as the stable key contract.
- **Wave 4 design-system component library**: 23 new generic UI components are now part of the public barrel (`package:magic_starter/magic_starter.dart`). Each component lives in the canonical atomic-component folder shape (`<name>.dart`, `<name>.recipe.dart`, `<name>.preview.dart`, `index.dart`) under `lib/src/ui/components/`.
- **Form controls**: `Button` (with `ButtonIntent`, `ButtonSize`, `buttonRecipe`), `Input` (with `InputState`, `inputRecipe`), `Textarea` (with `TextareaState`, `textareaRecipe`), `Checkbox`, `Switch`, `Radio`, `Select` (with `selectRecipe`), `Combobox` (with `comboboxRecipe`).
- **Feedback and display**: `Badge` (with `BadgeTone`), `Typography` (with `TypographyVariant`), `Skeleton` (with `SkeletonShape`), `Toast` (with `ToastVariant`), `Tooltip`, `EmptyState`, `ErrorState`.
- **Layout and navigation**: `Accordion` (with `AccordionItem`, `accordionRecipe`), `SegmentedControl` (with `SegmentedControlSize`, `segmentedControlRecipe`), `Tabs` (with `tabsRecipe`), `Navbar`, `Dropdown` menu (`DropdownMenu`, `DropdownMenuItem`).
- **Overlay**: `Dialog`, `BottomSheet`.
- **Composition**: `MagicFormField` (label, hint, error wrapper).
- Previously migrated components (`Card`, `PageHeader`, `SocialDivider`, `NotificationDropdown`, `UserProfileDropdown`, `TeamSelector`, `ConfirmDialog`) were already barrel-reachable through their existing alias exports and are unchanged.
- **Breaking collision note**: the barrel now exports `Switch`, `Dialog`, `Checkbox`, `Radio`, `Badge`, `Typography`, `BottomSheet`, `Tooltip`, `DropdownMenu`, and `DropdownMenuItem`. Tests and app code that import both `package:flutter/material.dart` and `package:magic_starter/magic_starter.dart` must add a `hide` clause on the material import (or the barrel import) to resolve the ambiguity.

### Changed
- **Wave 5 view rewrite**: the auth (login, register, forgot, reset, two-factor-challenge, otp-verify), profile, notifications (list + preferences matrix), and teams (create, settings, invitation-accept) views plus both layouts (`MagicStarterAppLayout`, `MagicStarterGuestLayout`) now compose the new design-system components (`Button`, `Card`, `Switch`, `PageHeader`, `SocialDivider`, etc.) instead of inline W-widgets. Views are now Wind-exclusive: bare `package:flutter/material.dart` imports were replaced with `widgets.dart` + `material show Icons` (and the few genuinely-needed Material shells via `show`), so the new component names no longer collide. Behavior, registry keys (`auth.*`, `profile.*`, `notifications.*`, `teams.*`, `layout.app`, `layout.guest`), controller contracts, gate abilities, `refreshNotifier`, and notification polling are all preserved; only the presentation layer changed.
- **Card migrated to the atomic-component folder + `WindRecipe`**: the card now lives at `lib/src/ui/components/card/` in the canonical 4-file shape (`card.dart` → `class Card`, `card.recipe.dart`, `card.preview.dart`, `index.dart`) and resolves its root className through a theme-driven `WindRecipe` instead of inline string interpolation. The recipe output is byte-identical to the previous `_defaultClassName` for every `CardVariant` x `noPadding` combination (gated by an explicit equivalence test). `MagicStarterCard` is retained as a thin re-export alias of `Card`, and `CardVariant` plus the barrel export path (`package:magic_starter/magic_starter.dart`) are unchanged, so existing callers and the widget-test suite are untouched. This establishes the verbatim template for the Wave 4 component migration.

### Added
- **`MagicStarterTokens.defaultAliases`**: semantic token alias map with 17 roles (`surface`, `surface-container`, `surface-container-high`, `fg`, `fg-muted`, `fg-disabled`, `primary`, `on-primary`, `primary-container`, `accent`, `border`, `border-subtle`, `destructive`, `on-destructive`, `destructive-container`, `success`, `warning`). Each role maps to a light+dark wind className pair (`'bg-... dark:bg-...'` / `'text-... dark:text-...'`). Pass as `WindThemeData(aliases: MagicStarterTokens.defaultAliases)` so components resolve against semantic roles rather than palette utilities directly. This map is the stable key contract that `design:sync` (Steps 20-21) will later regenerate from `DESIGN.md`.

### Fixed
- **PR #78 review (release boundary + semantic tokens + Wind-only previews):**
- Component `index.dart` barrels no longer re-export their `*.preview.dart` (9 components: error_state, empty_state, navbar, form_field, notification_dropdown, social_divider, page_header, user_profile_dropdown, team_selector). Previews are dev-only and must stay out of the release barrel (`magic_starter.dart` re-exports every `index.dart`); `previews:refresh` and the `previews.dart` dev barrel discover `*.preview.dart` directly, so the exports leaked previews into release for no benefit. Preview tests now import the preview file directly.
- `Tooltip` default panel + `kTooltipDefaultPanelClassName` use semantic alias tokens (`bg-surface-container-high text-fg border border-color-border`) instead of hardcoded gray palette utilities, so tooltips re-skin via `MagicStarterTokens` / `design:sync`. The `Tooltip` doc comment was corrected to describe the actual `enableTriggerOnTap: true` behavior (it does not use a `PopoverController`).
- `BottomSheet` drag handle uses `bg-surface-container-high` instead of `bg-gray-300 dark:bg-gray-600`.
- `PageHeader` / `EmptyState` / `ErrorState` previews render their action with the design-system `Button` + `WText` instead of Material `ElevatedButton` / `Text`, keeping previews Wind-only and dropping the Material import churn.
- **Creating a team now switches to it.** `MagicStarterTeamController.doCreate` only set the local `currentTeamId` notifier after `POST /teams`; the backend's `current_team_id` stayed on the previous team, so the resolver-driven sidebar name and active-team highlight showed the OLD team while local state and the member fetch pointed at the new one (REPORT #14, confirmed via e2e: create opened the old team's settings). `doCreate` now calls `PUT /user/current-team` with the new id before `Auth.restore()` (Jetstream create-then-switch), so the server, resolver, sidebar, and settings all agree on the new team.
- **`Button` no longer forces full-width**: the `buttonRecipe` base dropped `justify-center`, which in Wind maps to WButton's `Container` alignment and made every default `Button()` expand to fill its constraints (stacking one-per-row in a variant row). A default button now shrinks to its content (its `inline-flex` intent), with the label centered by the shrink-wrapped padding box. Form/modal buttons are unaffected: they pass a `className` override (the form theme ships `w-full`), which bypasses the recipe base entirely.
- **Create team opens the new team's settings**: `MagicStarterTeamController.activeTeamName` now matches the local `currentTeamId` (when set) against the resolver's `allTeams()` by id, falling back to the resolver's `currentTeam()` when no match exists. Previously `activeTeamId` preferred the local `currentTeamId` notifier (set on create/switch) while `activeTeamName` read only the resolver's `currentTeam()`, so after creating a team the settings view pre-filled the OLD team's name and effectively opened the old team ([#14](https://github.com/fluttersdk/magic_starter/issues/14)).

### Removed
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Flutter starter kit for the Magic Framework. Pre-built Auth, Profile, Teams & No

| Command | Description |
|---------|-------------|
| `flutter test --coverage` | Run all tests (~52 files, ~752 cases) with coverage |
| `flutter test --coverage` | Run all tests (~62 files, ~996 cases) with coverage |
| `flutter test test/http/controllers/` | Run controller tests only |
| `flutter test --name "pattern"` | Run tests matching pattern |
| `flutter analyze --no-fatal-infos` | Static analysis (flutter_lints ^6.0) |
Expand Down
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Stop rebuilding authentication, profile management, and team features from scrat
| :bell: | **Notifications** | Real-time polling, mark read/unread, preference matrix |
| :iphone: | **OTP Login** | Phone-based guest authentication with send/verify flow |
| :art: | **Wind UI** | Tailwind-like className system — no Material widgets, dark mode built-in |
| :package: | **Reusable Widgets** | PageHeader, Card (3 variants), ConfirmDialog (3 variants), PasswordConfirmDialog, TwoFactorModal — all standalone |
| :package: | **Design-System Components** | 29 atomic components (Button, Input, Badge, Dialog, Toast, Tabs, Accordion, and more) plus `MagicStarterTokens` semantic alias layer |
| :gear: | **13 Feature Toggles** | All opt-in, configure only what you need |
| :jigsaw: | **View Registry** | Override any screen or layout from the host app |
| :hammer_and_wrench: | **CLI Tools** | install, configure, doctor, publish, uninstall |
Expand Down Expand Up @@ -191,9 +191,53 @@ MagicStarter.useSidebarFooter((context) {

---

## Design-System Components

Magic Starter ships a full atomic design-system component library exported from `package:magic_starter/magic_starter.dart`. Every component lives in a canonical 4-file atomic folder (`<name>.dart`, `<name>.recipe.dart`, `<name>.preview.dart`, `index.dart`) under `lib/src/ui/components/` and is driven by a `WindRecipe` that reads from `MagicStarterTokens.defaultAliases`.

### MagicStarterTokens

`MagicStarterTokens.defaultAliases` is a map of 17 semantic roles to light+dark Wind className pairs:

| Role | Example className pair |
|------|----------------------|
| `surface` | `bg-white dark:bg-gray-950` |
| `fg` | `text-gray-900 dark:text-gray-50` |
| `primary` | `bg-primary-600 dark:bg-primary-500` |
| `destructive` | `bg-red-600 dark:bg-red-500` |
| `border` | `border-gray-200 dark:border-gray-800` |

Pass the map when configuring your Wind theme so all components resolve against semantic roles rather than raw palette utilities:

```dart
WindApp(
theme: WindThemeData(
aliases: MagicStarterTokens.defaultAliases,
),
child: const MyApp(),
)
```

> **Breaking import note**: the barrel now exports `Switch`, `Dialog`, `Checkbox`, `Radio`, `Badge`, `Typography`, `BottomSheet`, `Tooltip`, `DropdownMenu`, and `DropdownMenuItem`. If you import both `package:flutter/material.dart` and `package:magic_starter/magic_starter.dart`, add a `hide` clause on the conflicting names.

### Component families

| Family | Components |
|--------|-----------|
| Form controls | `Button`, `Input`, `Textarea`, `Checkbox`, `Switch`, `Radio`, `Select`, `Combobox` |
| Display | `Badge`, `Typography`, `Skeleton`, `Toast`, `Tooltip`, `EmptyState`, `ErrorState` |
| Selection / navigation | `SegmentedControl`, `Tabs`, `Accordion`, `Navbar`, `DropdownMenu` |
| Overlay | `Dialog`, `BottomSheet` |
| Composition | `MagicFormField`, `Card`, `PageHeader`, `SocialDivider` |
| App chrome | `NotificationDropdown`, `UserProfileDropdown`, `TeamSelector` |

All components accept Wind `className` strings and resolve colors through the semantic alias layer when configured.

---

## Reusable Widgets

Magic Starter exports a set of standalone UI widgets that consumer apps can use directly — no internal controller coupling required.
Magic Starter also exports a set of standalone UI widgets that consumer apps can use directly. No internal controller coupling required.

### MagicStarterPageHeader

Expand Down
176 changes: 176 additions & 0 deletions assets/stubs/design.md.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
name: {{ app_name }}
description: >
Design system for {{ app_name }}. Single-brand, Wind semantic tokens,
M3-role palette. Generate the Wind theme with:
dart run {{ app_package }}:artisan design:sync
colors:
surface:
light: "#FFFFFF"
dark: "#030712"
surface-container:
light: "#F9FAFB"
dark: "#111827"
surface-container-high:
light: "#F3F4F6"
dark: "#1F2937"
fg:
light: "#111827"
dark: "#F9FAFB"
fg-muted:
light: "#6B7280"
dark: "#9CA3AF"
fg-disabled:
light: "#D1D5DB"
dark: "#4B5563"
primary:
light: "#{{ primary_hex_light }}"
dark: "#{{ primary_hex_dark }}"
on-primary:
light: "#FFFFFF"
dark: "#FFFFFF"
primary-container:
light: "#{{ primary_container_light }}"
dark: "#{{ primary_container_dark }}"
accent:
light: "#{{ accent_hex_light }}"
dark: "#{{ accent_hex_dark }}"
border:
light: "#E5E7EB"
dark: "#374151"
border-subtle:
light: "#F3F4F6"
dark: "#1F2937"
destructive:
light: "#DC2626"
dark: "#EF4444"
on-destructive:
light: "#FFFFFF"
dark: "#FFFFFF"
destructive-container:
light: "#FEE2E2"
dark: "#7F1D1D"
success:
light: "#15803D"
dark: "#16A34A"
warning:
light: "#D97706"
dark: "#B45309"
typography:
display:
fontFamily: {{ font_family }}
fontSize: 36px
fontWeight: "700"
lineHeight: 44px
letterSpacing: -0.02em
headline-lg:
fontFamily: {{ font_family }}
fontSize: 28px
fontWeight: "700"
lineHeight: 36px
letterSpacing: -0.01em
headline-md:
fontFamily: {{ font_family }}
fontSize: 22px
fontWeight: "600"
lineHeight: 30px
title-lg:
fontFamily: {{ font_family }}
fontSize: 18px
fontWeight: "600"
lineHeight: 26px
body-lg:
fontFamily: {{ font_family }}
fontSize: 16px
fontWeight: "400"
lineHeight: 26px
body-md:
fontFamily: {{ font_family }}
fontSize: 14px
fontWeight: "400"
lineHeight: 22px
label-md:
fontFamily: {{ font_family }}
fontSize: 14px
fontWeight: "600"
lineHeight: 20px
letterSpacing: 0.01em
label-sm:
fontFamily: {{ font_family }}
fontSize: 12px
fontWeight: "500"
lineHeight: 16px
rounded:
sm: 4px
DEFAULT: 8px
md: 12px
lg: 16px
xl: 24px
full: 9999px
spacing:
xs: 4px
sm: 8px
md: 16px
lg: 24px
xl: 40px
gutter: 16px
section: 32px
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
rounded: "{rounded.md}"
padding: "{spacing.md}"
button-destructive:
backgroundColor: "{colors.destructive}"
textColor: "{colors.on-destructive}"
rounded: "{rounded.md}"
padding: "{spacing.md}"
card-surface:
backgroundColor: "{colors.surface-container}"
rounded: "{rounded.lg}"
padding: "{spacing.lg}"
input-field:
backgroundColor: "{colors.surface-container-high}"
rounded: "{rounded.DEFAULT}"
padding: "{spacing.md}"
---

## Overview

[Describe the brand personality, product purpose, and style direction for
{{ app_name }}. What does this design system represent?]

For responsive and accessible usage patterns, see
[docs/design-culture/](docs/design-culture/).

## Colors

[Explain the palette rationale: why these colors, what roles they serve, how
light and dark modes differ. Reference WCAG contrast requirements for
on-X / X pairs.]

## Typography

[Describe the chosen font and why it fits the brand. List the scale levels and
their intended usage contexts (display, body, label).]

## Layout

[Describe the responsive grid: breakpoints, column model, gutter and section
spacing. Reference the wind-responsive doc for breakpoint-specific guidance.]

## Elevation & Depth

[Describe the elevation model: tonal backgrounds, shadows (if any), and how
depth signals hierarchy to the user.]

## Shapes

[Map corner-radius values to use cases: inputs, buttons, cards, dialogs,
badges.]

## Components

[List the key components and their token bindings. Variant matrices are visible
at `/preview` in debug builds. Run `design:lint` to validate token usage.]
Loading
Loading