react-material-expressive 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +286 -0
  4. package/dist/index.cjs +7014 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +2068 -0
  7. package/dist/index.d.ts +2068 -0
  8. package/dist/index.js +6941 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/styles.css +2 -0
  11. package/dist/theme.css +187 -0
  12. package/docs/components/Amount.md +48 -0
  13. package/docs/components/Avatar.md +69 -0
  14. package/docs/components/AvatarStack.md +50 -0
  15. package/docs/components/Badge.md +50 -0
  16. package/docs/components/Blob.md +44 -0
  17. package/docs/components/Button.md +79 -0
  18. package/docs/components/ButtonGroup.md +46 -0
  19. package/docs/components/ButtonGroupConnected.md +62 -0
  20. package/docs/components/Card.md +52 -0
  21. package/docs/components/Checkbox.md +45 -0
  22. package/docs/components/Chips.md +77 -0
  23. package/docs/components/DatePicker.md +112 -0
  24. package/docs/components/Dialog.md +83 -0
  25. package/docs/components/Divider.md +48 -0
  26. package/docs/components/Dropdown.md +79 -0
  27. package/docs/components/FAB.md +63 -0
  28. package/docs/components/FABMenu.md +76 -0
  29. package/docs/components/Gallery.md +35 -0
  30. package/docs/components/Icon.md +36 -0
  31. package/docs/components/IconButton.md +69 -0
  32. package/docs/components/Img.md +52 -0
  33. package/docs/components/Layers.md +43 -0
  34. package/docs/components/Link.md +43 -0
  35. package/docs/components/List.md +87 -0
  36. package/docs/components/Loading.md +67 -0
  37. package/docs/components/LoadingIndicator.md +64 -0
  38. package/docs/components/MaterialSymbol.md +48 -0
  39. package/docs/components/MediaFrame.md +46 -0
  40. package/docs/components/Menu.md +149 -0
  41. package/docs/components/NavigationBar.md +78 -0
  42. package/docs/components/NavigationRail.md +105 -0
  43. package/docs/components/OverflowMenu.md +65 -0
  44. package/docs/components/Perspective.md +45 -0
  45. package/docs/components/Progress.md +83 -0
  46. package/docs/components/Radio.md +39 -0
  47. package/docs/components/Search.md +100 -0
  48. package/docs/components/Select.md +76 -0
  49. package/docs/components/Sheets.md +62 -0
  50. package/docs/components/Slider.md +89 -0
  51. package/docs/components/Snackbar.md +73 -0
  52. package/docs/components/SplitButton.md +75 -0
  53. package/docs/components/Stories.md +71 -0
  54. package/docs/components/Switch.md +40 -0
  55. package/docs/components/Table.md +67 -0
  56. package/docs/components/Tabs.md +67 -0
  57. package/docs/components/TextElement.md +37 -0
  58. package/docs/components/TextField.md +70 -0
  59. package/docs/components/TimePicker.md +83 -0
  60. package/docs/components/ToggleTheme.md +71 -0
  61. package/docs/components/Toolbar.md +102 -0
  62. package/docs/components/Tooltip.md +63 -0
  63. package/docs/components/TopAppBar.md +84 -0
  64. package/docs/components/Video.md +35 -0
  65. package/llms.txt +90 -0
  66. package/package.json +101 -0
@@ -0,0 +1,40 @@
1
+ # Switch
2
+
3
+ M3 switch: 52×32 track with 2dp outline, handle 16↔24 (28 while pressed),
4
+ 40px state layer. Optional handle icons (the handle stays 24 with a 16px
5
+ icon). Controllable via `useControlled`.
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import {Switch} from "react-material-expressive";
11
+ ```
12
+
13
+ ## API
14
+
15
+ ```ts
16
+ interface SwitchProps extends Omit<
17
+ ComponentProps<"input">,
18
+ "type" | "size" | "children" | "checked" | "defaultChecked"
19
+ > {
20
+ checked?: boolean; // controlled
21
+ className?: string; // wrapping <label>
22
+ defaultChecked?: boolean; // uncontrolled seed
23
+ icon?: ReactNode; // selected handle icon (16px)
24
+ label?: ReactNode;
25
+ uncheckedIcon?: ReactNode;
26
+ }
27
+ ```
28
+
29
+ ## Examples
30
+
31
+ ```tsx
32
+ <Switch defaultChecked label="Wi-Fi" />
33
+ <Switch checked={on} onChange={(e) => setOn(e.target.checked)} label="Controlled" />
34
+ <Switch icon={<MaterialSymbol name="check" size={16} />} label="With icon" />
35
+ ```
36
+
37
+ ## Gotchas
38
+
39
+ - `role="switch"` is set on the input.
40
+ - Disabled follows M3: track `on-surface/12`, handle 38%.
@@ -0,0 +1,67 @@
1
+ # Table
2
+
3
+ Data table with a horizontal-scroll wrapper, surface-container header and
4
+ zebra rows. Fully data-agnostic — rows come from the consumer. Sub-parts:
5
+ `Table.Head`, `Table.Body`, `Table.Row`, `Table.Cell`, `Table.HeaderCell`,
6
+ `Table.TextContainer`.
7
+
8
+ > **Note**: M3 publishes no data table component (it was retired after
9
+ > M2), so this one is an in-house design over M3 foundations: official
10
+ > color tokens, `title-small` headers, `body-medium` cells and zebra rows
11
+ > on `surface-container/50` instead of dividers. Rows are non-interactive
12
+ > by default, like Card.
13
+
14
+ ## Import
15
+
16
+ ```tsx
17
+ import {Table} from "react-material-expressive";
18
+ ```
19
+
20
+ ## API
21
+
22
+ ```ts
23
+ interface TableProps extends ComponentProps<"table"> {
24
+ wrapperClassName?: string; // scroll wrapper
25
+ }
26
+ // Head/Body/Row: native thead/tbody/tr props
27
+ interface TableCellProps extends ComponentProps<"td"> {
28
+ data?: ReactNode;
29
+ }
30
+ interface TableHeaderCellProps extends ComponentProps<"th"> {
31
+ data?: ReactNode;
32
+ }
33
+ interface TextContainerProps {
34
+ children?;
35
+ className?;
36
+ data?;
37
+ width?: string;
38
+ } // default w-[300px]
39
+ ```
40
+
41
+ ## Example
42
+
43
+ ```tsx
44
+ <Table>
45
+ <Table.Head>
46
+ <Table.Row>
47
+ <Table.HeaderCell>Name</Table.HeaderCell>
48
+ <Table.HeaderCell>Role</Table.HeaderCell>
49
+ </Table.Row>
50
+ </Table.Head>
51
+ <Table.Body>
52
+ {rows.map((row) => (
53
+ <Table.Row key={row.id}>
54
+ <Table.Cell>{row.name}</Table.Cell>
55
+ <Table.Cell>
56
+ <Table.TextContainer>{row.longText}</Table.TextContainer>
57
+ </Table.Cell>
58
+ </Table.Row>
59
+ ))}
60
+ </Table.Body>
61
+ </Table>
62
+ ```
63
+
64
+ ## Gotchas
65
+
66
+ - Wrap long copy in `Table.TextContainer` to keep a readable measure.
67
+ - Cells accept `data` as a children fallback (handy when mapping objects).
@@ -0,0 +1,67 @@
1
+ # TabsPrimary / TabsSecondary
2
+
3
+ M3 tabs with `title-small` labels and proper a11y roles. Primary: 48dp
4
+ container (64 with 24px icons) and a 3px content-width indicator (inset 2dp
5
+ each side, min length 24dp, fully-rounded top corners). Secondary: 48dp with
6
+ inline 24px icons and a 2px full-width indicator. Active label/icon is
7
+ `primary` (primary) / `on-surface` (secondary); inactive is
8
+ `on-surface-variant` and darkens to `on-surface` on hover/focus. The
9
+ indicator slides between tabs with the default-spatial spring (FLIP).
10
+ Controllable; each tab may carry panel `content`.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import {TabsPrimary, TabsSecondary} from "react-material-expressive";
16
+ ```
17
+
18
+ ## API
19
+
20
+ ```ts
21
+ interface TabItem {
22
+ content?: ReactNode; // panel rendered below when selected
23
+ disabled?: boolean;
24
+ header?: ReactNode;
25
+ icon?: ReactNode;
26
+ id: string;
27
+ }
28
+
29
+ interface TabsPrimaryProps {
30
+ className?: string;
31
+ defaultSelected?: string; // falls back to the first tab
32
+ onChange?: (id: string) => void;
33
+ panelClassName?: string;
34
+ selected?: string; // controlled
35
+ tabs: TabItem[];
36
+ }
37
+ // TabsSecondaryProps: identical
38
+ ```
39
+
40
+ ## Example
41
+
42
+ ```tsx
43
+ <TabsPrimary
44
+ onChange={setSection}
45
+ tabs={[
46
+ {
47
+ content: <Flights />,
48
+ header: "Flights",
49
+ icon: <MaterialSymbol name="flight" />,
50
+ id: "f",
51
+ },
52
+ {
53
+ content: <Hotels />,
54
+ header: "Hotels",
55
+ icon: <MaterialSymbol name="hotel" />,
56
+ id: "h",
57
+ },
58
+ ]}
59
+ />
60
+ ```
61
+
62
+ ## Gotchas
63
+
64
+ - Omit `content` to use the tabs as pure navigation and render panels
65
+ yourself (wire `onChange`).
66
+ - Primary's indicator hugs the icon+label width per M3 (it sits inside the
67
+ content wrapper).
@@ -0,0 +1,37 @@
1
+ # TextElement
2
+
3
+ Label / title / body text stack on the M3 type scale: overline
4
+ `label-medium` (on-surface-variant), title `title-medium` (on-surface),
5
+ body `body-medium` (on-surface-variant).
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import {TextElement} from "react-material-expressive";
11
+ ```
12
+
13
+ ## API
14
+
15
+ ```ts
16
+ interface TextElementProps {
17
+ body?: ReactNode;
18
+ bodyStyle?: string; // class override
19
+ className?: string;
20
+ label?: ReactNode;
21
+ labelStyle?: string;
22
+ title?: ReactNode;
23
+ titleStyle?: string;
24
+ }
25
+ ```
26
+
27
+ ## Examples
28
+
29
+ ```tsx
30
+ <TextElement label="OVERLINE" title="Headline" body="Supporting copy" />
31
+ <TextElement title="Bigger" titleStyle="text-headline-small" />
32
+ ```
33
+
34
+ ## Gotchas
35
+
36
+ - Each slot renders only when provided; `*Style` props are class strings
37
+ merged with `cn`.
@@ -0,0 +1,70 @@
1
+ # Text fields — InputOutlined / InputFilled / TextFieldOutlined / TextFieldFilled
2
+
3
+ M3 text fields: height 56, shape **extra-small** (4 — filled: top corners
4
+ only), `body-large` input text, `on-surface-variant` placeholder, 24px
5
+ leading/trailing icons (12dp from the edge, 16dp gap to the text → 52dp
6
+ input padding on that side). The floating label is driven by **focus/value
7
+ state** (not `:placeholder-shown`), so it always lands in the same place.
8
+ `Input*` are single-line `<input>`; `TextField*` are `<textarea>`.
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import {
14
+ InputFilled,
15
+ InputOutlined,
16
+ TextFieldFilled,
17
+ TextFieldOutlined,
18
+ } from "react-material-expressive";
19
+ ```
20
+
21
+ ## API (shared shape)
22
+
23
+ ```ts
24
+ interface InputOutlinedProps extends Omit<ComponentProps<"input">, "size"> {
25
+ className?: string; // wrapper
26
+ error?: boolean;
27
+ errorText?: ReactNode; // replaces supportingText while error
28
+ inputClassName?: string;
29
+ label?: string; // floating label
30
+ leftElement?: ReactNode; // 24px icon box
31
+ rightElement?: ReactNode;
32
+ supportingText?: ReactNode;
33
+ }
34
+ // InputFilled: same. TextField*: extends ComponentProps<"textarea"> (rows = 4).
35
+ ```
36
+
37
+ Controlled (`value` + `onChange`) and uncontrolled (`defaultValue`) both
38
+ work — the float state tracks either.
39
+
40
+ ## Anatomy notes
41
+
42
+ - Outlined: the label notches the border via `fieldset/legend`, so it works
43
+ over any background (cards, sheets, colored sections).
44
+ - Filled: surface-container-highest container, hover state layer and an
45
+ active indicator line (1px on-surface-variant → on-surface on hover →
46
+ 2px primary on focus, error on error).
47
+ - `placeholder` only shows while the label is floated (or when no label).
48
+ - The float animation is a transform-only tween between two label copies
49
+ (the @material/web technique) — font-size never interpolates, so the
50
+ text doesn't re-rasterize mid-motion.
51
+
52
+ ## Example
53
+
54
+ ```tsx
55
+ <InputOutlined
56
+ label="Email"
57
+ leftElement={<MaterialSymbol name="mail" />}
58
+ placeholder="you@example.com"
59
+ supportingText="We never share it"
60
+ type="email"
61
+ />
62
+ <InputFilled error errorText="Taken" label="Username" defaultValue="grace" />
63
+ <TextFieldOutlined label="Message" rows={5} />
64
+ ```
65
+
66
+ ## Gotchas
67
+
68
+ - Pass `label` for the M3 look; with only `placeholder` they behave as
69
+ plain fields.
70
+ - `disabled` follows M3 (38% content, 4% filled container).
@@ -0,0 +1,83 @@
1
+ # TimePicker
2
+
3
+ M3 modal time picker built over the library's `Dialog`: a 256dp clock dial
4
+ with a draggable selector, the big HH:MM time selector, an AM/PM period
5
+ selector, and a keyboard-input toggle. Container surface-container-high,
6
+ level 3, extra-large (28). Controllable and presentational.
7
+
8
+ - **Dial** (default) — pick the hour on the clock (releasing advances to
9
+ minutes), then the minute. The selected field is `primary-container`; the
10
+ dial selector is `primary` (2dp track, 48dp handle, 8dp centre).
11
+ - **Input** (`input`) — two editable fields (display-medium, focus
12
+ `primary-container` + 2dp primary outline) plus the AM/PM selector.
13
+
14
+ Selection is drafted and committed on **OK**.
15
+
16
+ ## Import
17
+
18
+ ```tsx
19
+ import {TimePicker, type TimeValue} from "react-material-expressive";
20
+ ```
21
+
22
+ ## API
23
+
24
+ ```ts
25
+ interface TimeValue {
26
+ hours: number; // 0–23
27
+ minutes: number; // 0–59
28
+ }
29
+
30
+ interface TimePickerProps {
31
+ input?: boolean; // start in keyboard-input mode
32
+ isVisible: boolean;
33
+ labels?: TimePickerLabels; // every user-facing string (see below)
34
+ onClose: () => void;
35
+ onConfirm?: (value: TimeValue) => void; // OK
36
+ value?: TimeValue; // default { hours: 12, minutes: 0 }
37
+ }
38
+
39
+ // All text lives in `labels` — each key optional, English defaults shown.
40
+ interface TimePickerLabels {
41
+ supporting?: ReactNode; // "Select time"
42
+ cancel?: ReactNode; // "Cancel"
43
+ confirm?: ReactNode; // "OK"
44
+ am?: string; // "AM"
45
+ pm?: string; // "PM"
46
+ switchToDial?: string; // aria (input→dial toggle)
47
+ switchToInput?: string; // aria (dial→input toggle)
48
+ hour?: string; // aria "Hour"
49
+ minute?: string; // aria "Minute"
50
+ }
51
+ ```
52
+
53
+ ## Example
54
+
55
+ ```tsx
56
+ const [open, setOpen] = useState(false);
57
+ const [time, setTime] = useState<TimeValue>({hours: 10, minutes: 30});
58
+
59
+ <Button onClick={() => setOpen(true)}>Pick a time</Button>
60
+ <TimePicker
61
+ isVisible={open}
62
+ onClose={() => setOpen(false)}
63
+ onConfirm={setTime}
64
+ value={time}
65
+ />;
66
+ ```
67
+
68
+ ## Gotchas
69
+
70
+ - `value.hours` is 24-hour (0–23); the dial/fields display 12-hour with the
71
+ AM/PM selector, which rewrites `hours` by ±12.
72
+ - The dial is pointer-driven (tap or drag a number). The keyboard-input
73
+ mode is the accessible path for exact entry.
74
+ - Every user-facing string (visible text **and** aria-labels) is in
75
+ `labels`, e.g. `labels={{cancel: "Cancelar", am: "a.m.", pm: "p.m."}}`;
76
+ unset keys keep the English defaults.
77
+
78
+ ## Accepted gaps (criterion)
79
+
80
+ - 12-hour with AM/PM only; the 24-hour dial (inner ring) is not
81
+ implemented.
82
+ - The dial selection is pointer-only; there is no arrow-key rotation of the
83
+ hand (use input mode for keyboard).
@@ -0,0 +1,71 @@
1
+ # ToggleTheme / ToggleThemeMenu
2
+
3
+ Prebuilt theme controls. `ToggleTheme` flips
4
+ light↔dark; `ToggleThemeMenu` opens an OverflowMenu listing the built-in
5
+ themes (light/dark) or a custom set, marking the active one.
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import {ToggleTheme, ToggleThemeMenu} from "react-material-expressive";
11
+ ```
12
+
13
+ ## API
14
+
15
+ ```ts
16
+ interface ToggleThemeProps {
17
+ className?: string;
18
+ darkIcon?: ReactNode; // shown while light (click → dark)
19
+ label?: boolean; // show the current theme name
20
+ labels?: ToggleThemeLabels; // accessible names
21
+ lightIcon?: ReactNode; // shown while dark (click → light)
22
+ variant?: IconButtonVariant; // default "tonal"
23
+ }
24
+
25
+ interface ToggleThemeLabels {
26
+ toLight?: string; // aria-label while dark (default "Switch to light theme")
27
+ toDark?: string; // aria-label while light (default "Switch to dark theme")
28
+ name?: string; // visible suffix when `label` is shown (default "theme")
29
+ }
30
+
31
+ interface ToggleThemeMenuProps {
32
+ className?: string;
33
+ icon?: ReactNode; // trigger icon (default palette)
34
+ label?: boolean;
35
+ labels?: ToggleThemeMenuLabels; // accessible names
36
+ themes?: {id: "light" | "dark"; label: string}[];
37
+ variant?: IconButtonVariant;
38
+ }
39
+
40
+ interface ToggleThemeMenuLabels {
41
+ trigger?: string; // trigger aria-label (default "Choose theme")
42
+ name?: string; // visible suffix when `label` is shown (default "theme")
43
+ }
44
+ ```
45
+
46
+ ## Example
47
+
48
+ ```tsx
49
+ <ToggleTheme label />
50
+ <ToggleThemeMenu themes={[{id: "light", label: "Claro"}, {id: "dark", label: "Oscuro"}]} />
51
+ ```
52
+
53
+ ## Gotchas
54
+
55
+ - They mutate `data-theme`/`.dark` on `<html>` and persist to
56
+ `localStorage("md-theme")`.
57
+ - Default icons are inline SVGs; pass your own for brand consistency.
58
+ - **Not an M3 component.** Theme switching has no Material 3 spec — these are
59
+ app-level helpers. `ToggleTheme` is an [IconButton](IconButton.md) (default
60
+ `tonal`) whose icon/`aria-label` swap with the resolved theme;
61
+ `ToggleThemeMenu` is an [OverflowMenu](OverflowMenu.md) over the shared M3E
62
+ [vertical Menu](Menu.md). All M3 fidelity (sizing, shape morph, state
63
+ layers, menu visuals) is inherited from those components.
64
+ - `ToggleThemeMenu` marks the active theme **visually only** (the item turns
65
+ `tertiary-container`, no checkmark) — same convention as `Menu.Item`
66
+ `selected`; there is no `aria-checked`/`aria-current`. Pass a custom
67
+ `themes` list when shipping custom token sets (`id` must match the theme
68
+ you set via tokens).
69
+ - The theme **names** ("Light"/"Dark") are translated through the existing
70
+ `themes` prop (an `{id, label}` list), not through `labels` — `labels` only
71
+ covers the trigger aria-label and the visible "theme" suffix.
@@ -0,0 +1,102 @@
1
+ # DockedToolbar and FloatingToolbar
2
+
3
+ M3 Expressive toolbars (`md.comp.toolbar` tokens, DSDB v34): frequently
4
+ used actions for the current page. Two variants:
5
+
6
+ - **DockedToolbar** — spans the full window width, 64dp high, square
7
+ corners, `surface-container`, 16dp edge paddings and a 32dp default gap
8
+ (the spec's max-spacing; min is 4). Bottom of the window only; successor
9
+ of the baseline bottom app bar. FABs placed inside sit flat.
10
+ - **FloatingToolbar** — a 64dp pill (8dp paddings, 4dp gap, shape full,
11
+ elevation level 3) floating above the content with a minimum 16dp screen
12
+ margin (24dp when vertical). Can pair with a FAB (8dp gap) and collapse
13
+ on scroll.
14
+
15
+ Both support the **standard** (`surface-container`, low emphasis) and
16
+ **vibrant** (`primary-container`, high emphasis) color schemes. Ghost
17
+ buttons inside (`IconButton variant="standard"`, `Button variant="text"`)
18
+ follow the toolbar token sets automatically: selected (`aria-pressed`,
19
+ e.g. the IconButton `selected` toggle) becomes `secondary-container` /
20
+ `on-secondary-container` in standard and `surface-container` /
21
+ `on-surface` in vibrant. Unselected content is `on-surface-variant` in
22
+ standard and `on-primary-container` in vibrant (so a text button drops its
23
+ default `primary` to match the toolbar). Filled/tonal buttons keep their
24
+ own colors for single-action emphasis.
25
+
26
+ ## Import
27
+
28
+ ```tsx
29
+ import {DockedToolbar, FloatingToolbar} from "react-material-expressive";
30
+ ```
31
+
32
+ ## API
33
+
34
+ ```ts
35
+ interface DockedToolbarProps extends ComponentProps<"div"> {
36
+ vibrant?: boolean; // primary-container scheme
37
+ }
38
+
39
+ interface FloatingToolbarProps extends ComponentProps<"div"> {
40
+ expanded?: boolean; // default true; false plays the collapse
41
+ fab?: ReactNode; // adjacent <FAB>, 8dp away (level 2, hover 3)
42
+ fabPosition?: "start" | "end"; // FAB side (default end)
43
+ flat?: boolean; // remove the level-3 elevation
44
+ leading?: ReactNode; // collapses away when expanded={false}
45
+ trailing?: ReactNode; // collapses away when expanded={false}
46
+ vertical?: boolean; // 64dp-wide column
47
+ vibrant?: boolean; // primary-container scheme
48
+ }
49
+ ```
50
+
51
+ ## Examples
52
+
53
+ ```tsx
54
+ // Docked: global actions pinned to the bottom of the window.
55
+ <DockedToolbar className="fixed bottom-0">
56
+ <IconButton aria-label="Undo" variant="standard">…</IconButton>
57
+ <IconButton aria-label="Redo" variant="standard">…</IconButton>
58
+ <IconButton aria-label="Attach" variant="standard">…</IconButton>
59
+ </DockedToolbar>
60
+
61
+ // Floating with FAB: collapse on scroll — the pill clips toward the FAB,
62
+ // which grows to the 80dp medium size (fast-spatial spring).
63
+ <FloatingToolbar
64
+ className="fixed bottom-4 left-1/2 -translate-x-1/2"
65
+ expanded={!scrolledDown}
66
+ fab={
67
+ <FAB aria-label="Add" variant="secondary">
68
+ <MaterialSymbol name="add" />
69
+ </FAB>
70
+ }>
71
+ <IconButton aria-label="Bold" selected={bold} variant="standard">…</IconButton>
72
+ <IconButton aria-label="Italic" selected={italic} variant="standard">…</IconButton>
73
+ </FloatingToolbar>
74
+ ```
75
+
76
+ ## Motion
77
+
78
+ Expand/collapse ports the Compose fast-spatial spring (damping 0.6 /
79
+ stiffness 800) as a 350ms CSS `linear()` curve with a 9.5% overshoot
80
+ (`--md-sys-motion-spring-fast-spatial`). With a FAB the whole pill
81
+ clip-reveals toward the FAB side while the FAB lerps 56 → 80dp (icon
82
+ 24 → 28); without one, only the `leading`/`trailing` slots collapse
83
+ (1fr → 0fr grid morph), keeping the core children. Collapsed regions are
84
+ `inert`, so they drop out of focus order and the accessibility tree.
85
+
86
+ ## Gotchas
87
+
88
+ - Position the toolbar yourself (`fixed` wrapper): docked bars belong to
89
+ the window bottom; floating toolbars keep ≥16dp margins (≥24dp when
90
+ vertical) and must stay fully on screen.
91
+ - `expanded` is plain-controlled (no internal toggle) — drive it from your
92
+ scroll handler; the spec recommends collapsing on scroll-down and
93
+ expanding on scroll-up (Compose uses a 40dp threshold).
94
+ - Don't show a docked toolbar together with a navigation bar, and don't
95
+ use wide icon buttons in vertical floating toolbars (spec guidelines).
96
+ - The FAB slot expects a default 56dp `<FAB>`; the toolbar overrides its
97
+ elevation (level 2, hover 3 — not the standalone 3 → 4) and animates its
98
+ size, so don't pass `size`.
99
+ - Per spec the FAB pairs as `secondary` in standard toolbars and
100
+ `tertiary` in vibrant ones (`md.comp.toolbar.floating.fab` tokens).
101
+ - Square icon buttons are fine in docked toolbars but discouraged inside
102
+ floating ones (shape clash with the fully-round container).
@@ -0,0 +1,63 @@
1
+ # Tooltip
2
+
3
+ M3 tooltips shown on hover/focus of the wrapped trigger, animating in with
4
+ a fade + scale (0.8→1) on the fast-spatial spring. Plain: shape extra-small
5
+ on inverse-surface with `body-small` text. Rich: shape medium on
6
+ surface-container (elevation 2, max 320dp wide) with optional `title-small`
7
+ subhead and an action slot.
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import {Tooltip} from "react-material-expressive";
13
+ ```
14
+
15
+ ## API
16
+
17
+ ```ts
18
+ interface TooltipProps {
19
+ action?: ReactNode; // rich only (e.g. <Button variant="text">)
20
+ bottomLeft?: boolean;
21
+ bottomRight?: boolean;
22
+ topLeft?: boolean;
23
+ topRight?: boolean; // default bottomLeft
24
+ children?: ReactNode; // trigger
25
+ className?: string;
26
+ text?: ReactNode;
27
+ title?: ReactNode; // rich subhead
28
+ variant?: "plain" | "rich"; // default "plain"
29
+ }
30
+ ```
31
+
32
+ ## Examples
33
+
34
+ ```tsx
35
+ <Tooltip text="Save to favorites" topLeft>
36
+ <IconButton aria-label="Favorite" icon={<MaterialSymbol name="favorite" />} variant="standard" />
37
+ </Tooltip>
38
+
39
+ <Tooltip variant="rich" title="Rich tooltip" text="Longer supporting copy"
40
+ action={<Button variant="text">Learn more</Button>} bottomRight>
41
+ <IconButton aria-label="Info" icon={<MaterialSymbol name="info" />} variant="standard" />
42
+ </Tooltip>
43
+ ```
44
+
45
+ ## Gotchas
46
+
47
+ - Display is pure CSS (hover/focus-within) with a 150ms appearance delay,
48
+ animating fade + scale 0.8→1 from center on the fast-spatial spring (the
49
+ same FadeInFadeOutWithScale choreography as the Snackbar); hide is
50
+ immediate. Hover is gated under `@media (hover: hover)` so a touch tap
51
+ doesn't leave it stuck open. Rich tooltips become interactive
52
+ (`pointer-events`) while shown so the action is clickable.
53
+ - Keep the trigger inside the wrapper — the tooltip positions against it.
54
+
55
+ ## Accessibility
56
+
57
+ - The tooltip container is `role="tooltip"` with a generated `id`. When the
58
+ trigger is a single element, the library merges that id into the trigger's
59
+ `aria-describedby`, so the supporting text is announced when the trigger is
60
+ focused (the WAI-ARIA tooltip pattern). Any `aria-describedby` you already
61
+ set on the trigger is preserved.
62
+ - Give the trigger its own accessible name (e.g. `aria-label` on an icon-only
63
+ `IconButton`) — the tooltip describes the control, it does not name it.
@@ -0,0 +1,84 @@
1
+ # TopAppBar
2
+
3
+ M3 top app bar container with the variants as sub-parts:
4
+ `TopAppBar.Small` / `.Center` (64dp, `title-large`), `.Medium` and
5
+ `.Large`. The container is `surface` (swap to `surface-container` on
6
+ scroll); bar padding is 4dp. Every variant takes an optional `subtitle`
7
+ (rendered under the title).
8
+
9
+ `.Medium` and `.Large` render the **M3 Expressive flexible** variants:
10
+ Medium is `headline-medium` (112dp, 136dp with a subtitle), Large is
11
+ `display-small` (120dp, 152dp with a subtitle). `.Center` is the small bar
12
+ with centered title text (M3E merged center-aligned into small).
13
+
14
+ > **M3 Expressive note** — flexible is the only Medium/Large variant. The
15
+ > baseline Medium/Large bars were removed because M3E marks them "no longer
16
+ > recommended".
17
+
18
+ ## Import
19
+
20
+ ```tsx
21
+ import {TopAppBar} from "react-material-expressive";
22
+ ```
23
+
24
+ ## API
25
+
26
+ ```ts
27
+ type TopAppBarProps = ComponentProps<"header">; // bg-surface by default
28
+
29
+ // Small / Center
30
+ interface AppBarRowProps {
31
+ children?: ReactNode; // title fallback
32
+ className?: string;
33
+ leftElement?: ReactNode; // nav icon
34
+ rightElement?: ReactNode; // action icons
35
+ subtitle?: ReactNode; // label-medium, under the title
36
+ title?: ReactNode;
37
+ }
38
+
39
+ // Medium / Large (flexible only)
40
+ interface CollapsingAppBarProps {
41
+ children?: ReactNode; // title fallback
42
+ className?: string;
43
+ leftElement?: ReactNode; // nav icon
44
+ rightElement?: ReactNode; // action icons
45
+ subtitle?: ReactNode; // medium label-large / large title-medium
46
+ title?: ReactNode;
47
+ }
48
+ ```
49
+
50
+ ## Example
51
+
52
+ ```tsx
53
+ <TopAppBar>
54
+ <TopAppBar.Small
55
+ leftElement={
56
+ <IconButton
57
+ aria-label="Menu"
58
+ icon={<MaterialSymbol name="menu" />}
59
+ variant="standard"
60
+ />
61
+ }
62
+ rightElement={
63
+ <IconButton
64
+ aria-label="More"
65
+ icon={<MaterialSymbol name="more_vert" />}
66
+ variant="standard"
67
+ />
68
+ }
69
+ title="Page title"
70
+ />
71
+ </TopAppBar>
72
+ ```
73
+
74
+ ## Gotchas
75
+
76
+ - On-scroll surface change is the consumer's call: toggle
77
+ `className="bg-surface-container shadow-mm-2"` when `scrollY > 0` (the M3
78
+ on-scroll color + elevation level 2). The collapse-on-scroll motion
79
+ (height shrink + title resize) is not built in.
80
+ - `Center` keeps the title optically centered by reserving symmetric side
81
+ slots — pass compact elements (icon buttons / avatar).
82
+ - Leading/trailing icon colors come from the `IconButton`s you pass; M3's
83
+ leading-`on-surface` / trailing-`on-surface-variant` split is yours to
84
+ set if you need it.