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.
- package/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/index.cjs +7014 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2068 -0
- package/dist/index.d.ts +2068 -0
- package/dist/index.js +6941 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/theme.css +187 -0
- package/docs/components/Amount.md +48 -0
- package/docs/components/Avatar.md +69 -0
- package/docs/components/AvatarStack.md +50 -0
- package/docs/components/Badge.md +50 -0
- package/docs/components/Blob.md +44 -0
- package/docs/components/Button.md +79 -0
- package/docs/components/ButtonGroup.md +46 -0
- package/docs/components/ButtonGroupConnected.md +62 -0
- package/docs/components/Card.md +52 -0
- package/docs/components/Checkbox.md +45 -0
- package/docs/components/Chips.md +77 -0
- package/docs/components/DatePicker.md +112 -0
- package/docs/components/Dialog.md +83 -0
- package/docs/components/Divider.md +48 -0
- package/docs/components/Dropdown.md +79 -0
- package/docs/components/FAB.md +63 -0
- package/docs/components/FABMenu.md +76 -0
- package/docs/components/Gallery.md +35 -0
- package/docs/components/Icon.md +36 -0
- package/docs/components/IconButton.md +69 -0
- package/docs/components/Img.md +52 -0
- package/docs/components/Layers.md +43 -0
- package/docs/components/Link.md +43 -0
- package/docs/components/List.md +87 -0
- package/docs/components/Loading.md +67 -0
- package/docs/components/LoadingIndicator.md +64 -0
- package/docs/components/MaterialSymbol.md +48 -0
- package/docs/components/MediaFrame.md +46 -0
- package/docs/components/Menu.md +149 -0
- package/docs/components/NavigationBar.md +78 -0
- package/docs/components/NavigationRail.md +105 -0
- package/docs/components/OverflowMenu.md +65 -0
- package/docs/components/Perspective.md +45 -0
- package/docs/components/Progress.md +83 -0
- package/docs/components/Radio.md +39 -0
- package/docs/components/Search.md +100 -0
- package/docs/components/Select.md +76 -0
- package/docs/components/Sheets.md +62 -0
- package/docs/components/Slider.md +89 -0
- package/docs/components/Snackbar.md +73 -0
- package/docs/components/SplitButton.md +75 -0
- package/docs/components/Stories.md +71 -0
- package/docs/components/Switch.md +40 -0
- package/docs/components/Table.md +67 -0
- package/docs/components/Tabs.md +67 -0
- package/docs/components/TextElement.md +37 -0
- package/docs/components/TextField.md +70 -0
- package/docs/components/TimePicker.md +83 -0
- package/docs/components/ToggleTheme.md +71 -0
- package/docs/components/Toolbar.md +102 -0
- package/docs/components/Tooltip.md +63 -0
- package/docs/components/TopAppBar.md +84 -0
- package/docs/components/Video.md +35 -0
- package/llms.txt +90 -0
- package/package.json +101 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Divider
|
|
2
|
+
|
|
3
|
+
M3 divider: a 1px rule in `outline-variant` (decorative — per M3 never use
|
|
4
|
+
`outline` for dividers).
|
|
5
|
+
|
|
6
|
+
## Import
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import {Divider} from "react-material-expressive";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## API
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
interface DividerProps extends ComponentProps<"hr"> {
|
|
16
|
+
// M3 inset dividers (16dp). Omit for a full-width rule.
|
|
17
|
+
inset?: "start" | "middle" | "end";
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`inset` mirrors the M3 spec's named insets:
|
|
22
|
+
|
|
23
|
+
| Value | M3 name | Leading / trailing |
|
|
24
|
+
| ---------- | ------------ | --------------------------- |
|
|
25
|
+
| (omitted) | full-width | 0 / 0 (100%) |
|
|
26
|
+
| `"start"` | inset | 16dp / 0 (the list divider) |
|
|
27
|
+
| `"middle"` | middle-inset | 16dp / 16dp |
|
|
28
|
+
| `"end"` | — | 0 / 16dp (RTL-symmetric) |
|
|
29
|
+
|
|
30
|
+
Margins are logical (`ms`/`me`), so the inset follows the writing
|
|
31
|
+
direction.
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Divider />
|
|
37
|
+
<Divider inset="start" /> // list divider (leading 16dp)
|
|
38
|
+
<Divider inset="middle" /> // middle-inset (16dp both)
|
|
39
|
+
<Divider className="my-2" /> // tighter spacing
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Gotchas
|
|
43
|
+
|
|
44
|
+
- Renders an `<hr>` (1px `outline-variant`); default vertical margin is
|
|
45
|
+
16dp (`my-4`) — override with `className` for context-specific spacing.
|
|
46
|
+
- Horizontal only. M3's web spec and @material/web don't define a vertical
|
|
47
|
+
divider (it exists only in Compose as `VerticalDivider`); for a vertical
|
|
48
|
+
rule use a `1px`-wide `bg-outline-variant` element directly.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Dropdown
|
|
2
|
+
|
|
3
|
+
Trigger + anchor for the shared M3E vertical [Menu](Menu.md): it positions
|
|
4
|
+
the menu below the trigger and owns the open state (toggles on trigger
|
|
5
|
+
click, closes on outside click, item activation or Escape, and returns
|
|
6
|
+
focus to the trigger). All the menu visuals and keyboard come from `Menu`,
|
|
7
|
+
so `Dropdown.Item` is an alias of `Menu.Item` and you can also use
|
|
8
|
+
`Menu.Label` / `Menu.Divider` and the `selected` / `badge` item props.
|
|
9
|
+
Standard (surface-based) by default or `vibrant` (tertiary-based).
|
|
10
|
+
|
|
11
|
+
## Import
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {Dropdown} from "react-material-expressive";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface DropdownProps {
|
|
21
|
+
apart?: boolean; // detach the menu with a 4dp gap
|
|
22
|
+
children: ReactNode; // trigger
|
|
23
|
+
className?: string;
|
|
24
|
+
menu?: ReactNode; // Dropdown.Item list
|
|
25
|
+
menuClassName?: string;
|
|
26
|
+
vibrant?: boolean; // tertiary-based scheme instead of standard
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface DropdownItemProps {
|
|
30
|
+
children?;
|
|
31
|
+
className?;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
label?: ReactNode;
|
|
34
|
+
leftElement?: ReactNode; // 20px box (use icon size 20)
|
|
35
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
36
|
+
rightElement?: ReactNode; // trailing meta (body-small)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Example
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<Dropdown
|
|
44
|
+
menu={
|
|
45
|
+
<>
|
|
46
|
+
<Dropdown.Item
|
|
47
|
+
label="Profile"
|
|
48
|
+
leftElement={<MaterialSymbol name="person" size={20} />}
|
|
49
|
+
/>
|
|
50
|
+
<Dropdown.Item
|
|
51
|
+
label="Settings"
|
|
52
|
+
rightElement="⌘S"
|
|
53
|
+
onClick={openSettings}
|
|
54
|
+
/>
|
|
55
|
+
</>
|
|
56
|
+
}>
|
|
57
|
+
<Button iconRight={<MaterialSymbol name="expand_more" size={18} />}>
|
|
58
|
+
Open
|
|
59
|
+
</Button>
|
|
60
|
+
</Dropdown>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Gotchas
|
|
64
|
+
|
|
65
|
+
- The whole wrapper toggles on click, so clicking an item also closes the
|
|
66
|
+
menu (select-like behavior).
|
|
67
|
+
- The menu is content-width, clamped to the M3 spec's 112–280dp range
|
|
68
|
+
(`w-max min-w-[112px] max-w-[280px]`); use `apart` for a detached look.
|
|
69
|
+
- Icons are 20dp in the vertical menu — pass `size={20}` to `MaterialSymbol`
|
|
70
|
+
so the glyph fills its box (the default is 24).
|
|
71
|
+
- The first and last items round their outer corners (top / bottom) to
|
|
72
|
+
corner-medium so they sit concentric with the menu's corner-large and
|
|
73
|
+
don't read as a square cut against the rounded container; the inner
|
|
74
|
+
corners stay extra-small.
|
|
75
|
+
- The expressive shape-morph (corners growing on the active state) only
|
|
76
|
+
applies to submenu parents; this flat menu keeps the extra-small corner
|
|
77
|
+
and shows hover/focus/pressed through the state layer.
|
|
78
|
+
- For corner positioning use `OverflowMenu` instead (still the baseline
|
|
79
|
+
square menu — not yet migrated to the vertical menu).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# FAB / ExtendedFAB
|
|
2
|
+
|
|
3
|
+
M3 floating action buttons. FAB sizes 56/80/96 with shapes
|
|
4
|
+
large/large-increased/extra-large and icons 24/28/36 (80 is the default
|
|
5
|
+
M3 Expressive medium FAB); Extended FAB comes in the M3 Expressive sizes
|
|
6
|
+
small 56 (large shape, title-medium, icon 24, padding 16, gap 8), medium
|
|
7
|
+
80 (large-increased, title-large, icon 28, padding 26, gap 12) and large
|
|
8
|
+
96 (extra-large, headline-small, icon 36, padding 28, gap 16). Both rest
|
|
9
|
+
at elevation level 3 and raise to 4 on hover.
|
|
10
|
+
|
|
11
|
+
## Import
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {ExtendedFAB, FAB} from "react-material-expressive";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface FABProps extends ComponentProps<"button"> {
|
|
21
|
+
icon?: ReactNode;
|
|
22
|
+
size?: "fab" | "fabMedium" | "fabLarge"; // 56 / 80 / 96, default "fabMedium"
|
|
23
|
+
variant?: "primary" | "secondary" | "tertiary"; // default "primary"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ExtendedFABProps extends ComponentProps<"button"> {
|
|
27
|
+
icon?: ReactNode; // optional leading icon (24/28/36px box per size)
|
|
28
|
+
size?: "small" | "medium" | "large"; // 56 / 80 / 96, default "small"
|
|
29
|
+
text?: string; // label fallback when no children
|
|
30
|
+
variant?: "primary" | "secondary" | "tertiary"; // default "primary"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Variants
|
|
35
|
+
|
|
36
|
+
- `primary` — primary-container (the M3 Expressive default color style).
|
|
37
|
+
- `secondary` — secondary-container.
|
|
38
|
+
- `tertiary` — tertiary-container.
|
|
39
|
+
|
|
40
|
+
The small FAB (40) and the `surface` color style were removed — both are no
|
|
41
|
+
longer recommended in M3 Expressive.
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<FAB aria-label="Create" icon={<MaterialSymbol name="add" />} />
|
|
47
|
+
<FAB aria-label="Edit" size="fabLarge" variant="tertiary"
|
|
48
|
+
icon={<MaterialSymbol name="edit" size={36} />} />
|
|
49
|
+
<ExtendedFAB icon={<MaterialSymbol name="edit" />}>Compose</ExtendedFAB>
|
|
50
|
+
<ExtendedFAB size="medium" variant="secondary"
|
|
51
|
+
icon={<MaterialSymbol name="edit" size={28} />}>Compose</ExtendedFAB>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Gotchas
|
|
55
|
+
|
|
56
|
+
- FAB is icon-only: pass `aria-label`.
|
|
57
|
+
- Use one FAB per screen (M3); prefer `secondary`/`tertiary` containers on
|
|
58
|
+
busy surfaces.
|
|
59
|
+
- Inside a DockedToolbar the FAB is flattened
|
|
60
|
+
automatically — no extra class needed.
|
|
61
|
+
- The spec's solid color styles (primary & on-primary, etc.) aren't
|
|
62
|
+
built-in variants; recolor via `className` (e.g.
|
|
63
|
+
`"bg-primary text-on-primary"`).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# FABMenu
|
|
2
|
+
|
|
3
|
+
M3 Expressive FAB menu: a 56dp FAB that morphs into a fully-round close
|
|
4
|
+
button — radius, container color (container → solid set color) and the
|
|
5
|
+
icon crossfade ride one fast-spatial spring, like Compose's
|
|
6
|
+
`ToggleFloatingActionButton` — revealing up to six menu-item pills above
|
|
7
|
+
it, right-aligned and staggered bottom-up (8dp gap to the FAB, 4dp
|
|
8
|
+
between items). Each pill reveals by width from its trailing edge
|
|
9
|
+
(fast-spatial spring) while fading in on a quicker track, and closing
|
|
10
|
+
runs the same springs in reverse, top-down — the Compose
|
|
11
|
+
`FloatingActionButtonMenu` choreography. Items use the medium button
|
|
12
|
+
metrics (height 56, `title-medium`, icon 24, paddings 24, level 3
|
|
13
|
+
shadow) on the set's container color (`md.comp.fab-menu` tokens).
|
|
14
|
+
|
|
15
|
+
## Import
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import {FABMenu} from "react-material-expressive";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## API
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface FABMenuProps {
|
|
25
|
+
children?: ReactNode; // FABMenu.Item list (2–6 items per spec)
|
|
26
|
+
className?: string;
|
|
27
|
+
icon?: ReactNode; // FAB icon while closed (24px box)
|
|
28
|
+
labels?: FABMenuLabels; // trigger aria-labels
|
|
29
|
+
variant?: "primary" | "secondary" | "tertiary"; // color set
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FABMenuLabels {
|
|
33
|
+
open?: string; // trigger aria-label while closed (default "Open menu")
|
|
34
|
+
close?: string; // trigger aria-label while open (default "Close menu")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface FABMenuItemProps {
|
|
38
|
+
className?: string;
|
|
39
|
+
icon?: ReactNode; // 24px box
|
|
40
|
+
label?: ReactNode;
|
|
41
|
+
onClick?: MouseEventHandler;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Example
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<FABMenu
|
|
49
|
+
icon={<MaterialSymbol name="add" size={24} />}
|
|
50
|
+
labels={{open: "Create"}}>
|
|
51
|
+
<FABMenu.Item
|
|
52
|
+
icon={<MaterialSymbol name="edit" size={24} />}
|
|
53
|
+
label="Compose"
|
|
54
|
+
onClick={compose}
|
|
55
|
+
/>
|
|
56
|
+
<FABMenu.Item
|
|
57
|
+
icon={<MaterialSymbol name="group" size={24} />}
|
|
58
|
+
label="New group"
|
|
59
|
+
onClick={newGroup}
|
|
60
|
+
/>
|
|
61
|
+
</FABMenu>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Gotchas
|
|
65
|
+
|
|
66
|
+
- Position the wrapper yourself — per spec the FAB sits with 16dp margins
|
|
67
|
+
(e.g. `className="fixed right-4 bottom-4"`); the menu opens upward from
|
|
68
|
+
the FAB's top trailing edge.
|
|
69
|
+
- Item clicks close the menu (so do Escape and outside clicks); run your
|
|
70
|
+
action in the item `onClick`.
|
|
71
|
+
- The spec recommends 2–6 items.
|
|
72
|
+
- Colors resolve through the `--md-sys-color-*` tokens of the chosen set,
|
|
73
|
+
so themes apply automatically.
|
|
74
|
+
- **BREAKING:** the `label` prop was replaced by `labels={{open, close}}`
|
|
75
|
+
(the open-state aria-label, previously hardcoded "Close menu", is now
|
|
76
|
+
`labels.close`).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Gallery
|
|
2
|
+
|
|
3
|
+
Stacked media gallery with 8dp gaps; compose rows with `Gallery.Row` and
|
|
4
|
+
size each media by width fractions.
|
|
5
|
+
|
|
6
|
+
## Import
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import {Gallery} from "react-material-expressive";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## API
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
type GalleryProps = ComponentProps<"div">;
|
|
16
|
+
type ImageRowProps = ComponentProps<"div">; // Gallery.Row
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<Gallery>
|
|
23
|
+
<Gallery.Row>
|
|
24
|
+
<Img alt="" aspect={21 / 9} className="w-full" radius={16} src={hero} />
|
|
25
|
+
</Gallery.Row>
|
|
26
|
+
<Gallery.Row>
|
|
27
|
+
<Img alt="" aspect={1} className="w-1/2" radius={16} src={a} />
|
|
28
|
+
<Img alt="" aspect={1} className="w-1/2" radius={16} src={b} />
|
|
29
|
+
</Gallery.Row>
|
|
30
|
+
</Gallery>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Gotchas
|
|
34
|
+
|
|
35
|
+
- Layout-only: pair with `Img`/`Video` or any media node.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Icon
|
|
2
|
+
|
|
3
|
+
Icon slot helper: wraps each provided node in a fixed square box (default
|
|
4
|
+
24px, `shrink-0`, centered, `leading-none`) so arbitrary icons align with
|
|
5
|
+
adjacent text. Used internally by buttons/menus; useful for custom rows.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import {Icon} from "react-material-expressive";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
interface IconProps {
|
|
17
|
+
className?: string;
|
|
18
|
+
icon?: ReactNode; // standalone slot
|
|
19
|
+
iconLeft?: ReactNode; // leading slot
|
|
20
|
+
iconRight?: ReactNode; // trailing slot
|
|
21
|
+
size?: number; // box px, default 24
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Example
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<span className="flex items-center gap-2 text-body-large">
|
|
29
|
+
<Icon iconLeft={<MaterialSymbol name="event" />} />
|
|
30
|
+
Aligned with text
|
|
31
|
+
</span>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Gotchas
|
|
35
|
+
|
|
36
|
+
- The box fixes ALIGNMENT; size the glyph itself via the icon you pass.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# IconButton
|
|
2
|
+
|
|
3
|
+
M3 icon button: 40×40 visual container with a 48×48 touch target, 24px
|
|
4
|
+
icon, full shape. M3 Expressive sizes, widths, shapes and the toggle
|
|
5
|
+
(`selected`) variant are available.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import {IconButton} from "react-material-expressive";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
interface IconButtonProps extends ComponentProps<"button"> {
|
|
17
|
+
icon?: ReactNode; // 24px box; children work as a fallback slot
|
|
18
|
+
selected?: boolean; // M3 Expressive toggle (adds aria-pressed)
|
|
19
|
+
shape?: "round" | "square"; // M3 Expressive; default "round"
|
|
20
|
+
size?: "xs" | "s" | "m" | "l" | "xl"; // M3 Expressive: 32/40/56/96/136, default "s"
|
|
21
|
+
variant?: "filled" | "tonal" | "outlined" | "standard"; // default "filled"
|
|
22
|
+
width?: "narrow" | "default" | "wide"; // M3 Expressive
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Variants
|
|
27
|
+
|
|
28
|
+
- `filled` — primary container.
|
|
29
|
+
- `tonal` — secondary-container.
|
|
30
|
+
- `outlined` — outline border, on-surface-variant icon.
|
|
31
|
+
- `standard` — no container, on-surface-variant icon.
|
|
32
|
+
|
|
33
|
+
## Expressive sizes, widths and toggle
|
|
34
|
+
|
|
35
|
+
Sizes use icons 20/24/24/32/40; `width` narrows or widens the container
|
|
36
|
+
per size. Pressing morphs both shapes to the per-size pressed radius
|
|
37
|
+
(8/8/12/16/16, the DSDB PressedContainerShape tokens). With `selected` the button becomes a toggle: colors follow the
|
|
38
|
+
M3 Expressive toggle tokens per variant (filled: surface-container ↔
|
|
39
|
+
primary; tonal: secondary-container ↔ secondary; outlined selected:
|
|
40
|
+
inverse-surface; standard: on-surface-variant ↔ primary) and the shape
|
|
41
|
+
flips with the selection (unselected round, selected square) unless
|
|
42
|
+
`shape` is set explicitly.
|
|
43
|
+
|
|
44
|
+
## States
|
|
45
|
+
|
|
46
|
+
State layers on hover/focus/pressed (the press ripple grows from the
|
|
47
|
+
pointer); `disabled` = container `on-surface/12` + icon `on-surface/38`
|
|
48
|
+
(standard/outlined keep no fill).
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<IconButton
|
|
54
|
+
aria-label="Settings"
|
|
55
|
+
icon={<MaterialSymbol name="settings" />}
|
|
56
|
+
variant="standard"
|
|
57
|
+
/>
|
|
58
|
+
<IconButton
|
|
59
|
+
aria-label="Favorite"
|
|
60
|
+
icon={<MaterialSymbol name="favorite" />}
|
|
61
|
+
selected={isFavorite}
|
|
62
|
+
onClick={() => setIsFavorite((value) => !value)}
|
|
63
|
+
/>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Gotchas
|
|
67
|
+
|
|
68
|
+
- Icon-only: always pass `aria-label`.
|
|
69
|
+
- The 48×48 touch target is a child span on the sub-48 sizes (`xs`/`s`).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Img
|
|
2
|
+
|
|
3
|
+
MediaFrame + image: renders a native `<img>` (object-fit, hides on error
|
|
4
|
+
so the placeholder shows) or your injected media. Accepts
|
|
5
|
+
`string | StaticImageData` sources.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import {Img} from "react-material-expressive";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
interface ImgProps extends MediaInjectionProps {
|
|
17
|
+
// children | image | render
|
|
18
|
+
alt?: string;
|
|
19
|
+
aspect?: number | string; // e.g. 16/9
|
|
20
|
+
className?: string;
|
|
21
|
+
height?: number | string;
|
|
22
|
+
width?: number | string;
|
|
23
|
+
objectFit?: "fill" | "contain" | "cover" | "none" | "scale-down"; // default cover
|
|
24
|
+
onClick?: MouseEventHandler<HTMLDivElement>;
|
|
25
|
+
placeholder?: ReactNode; // shown with no media or on error
|
|
26
|
+
radius?: number | string; // px or CSS value
|
|
27
|
+
size?: number | string; // width = height shorthand
|
|
28
|
+
src?: string | StaticImageData;
|
|
29
|
+
style?: CSSProperties;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Injection priority: `render({src, alt, className, style})` > `image` >
|
|
34
|
+
`children` > native `<img>`.
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<Img alt="Cover" aspect={16 / 9} radius={16} src="/cover.jpg" width={320} />
|
|
40
|
+
<Img alt="Broken" placeholder={<MaterialSymbol name="broken_image" />} size={140} src={maybeUrl} />
|
|
41
|
+
|
|
42
|
+
// Next.js injection
|
|
43
|
+
<Img aspect={1} size={140} src={photo} render={({src, alt, className, style}) => (
|
|
44
|
+
<Image alt={alt} className={className} fill src={src!} style={style} />
|
|
45
|
+
)} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Gotchas
|
|
49
|
+
|
|
50
|
+
- The frame paints `surface-container-highest` behind the media — visible
|
|
51
|
+
while loading and on error.
|
|
52
|
+
- `radius` accepts CSS strings (e.g. `"var(--md-sys-shape-corner-large)"`).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Layers — Container / Section
|
|
2
|
+
|
|
3
|
+
Layout helpers. `Container` is a dotted showcase panel (token-based dot
|
|
4
|
+
grid, large shape) for presenting content over a neutral, theme-aware
|
|
5
|
+
background. `Section` is a responsive content band: column on small
|
|
6
|
+
screens, row on `lg+`.
|
|
7
|
+
|
|
8
|
+
## Import
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import {Container, Section} from "react-material-expressive";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## API
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
interface ContainerProps {
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
padding?: string; // padding class override (default p-6)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SectionProps {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<Section>
|
|
33
|
+
<Card>…</Card>
|
|
34
|
+
<Container className="mx-0">
|
|
35
|
+
<Button>Showcased</Button>
|
|
36
|
+
</Container>
|
|
37
|
+
</Section>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Gotchas
|
|
41
|
+
|
|
42
|
+
- The dot grid uses `--md-sys-color-outline-variant`, so it adapts to the
|
|
43
|
+
active theme.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# LinkBox / LinkContainer
|
|
2
|
+
|
|
3
|
+
Native `<a>` links — no router coupling. `LinkBox` is a **text link** that
|
|
4
|
+
reads as a link: primary color, **underlined at rest** (the underline is not
|
|
5
|
+
color-only on purpose — an inline link must be distinguishable from body text
|
|
6
|
+
without relying on color, WCAG 1.4.1, since `primary` vs text is only
|
|
7
|
+
2.64:1 light / 1.31:1 dark). The current page (`active`) gets a heavier
|
|
8
|
+
underline (no color change) + `aria-current="page"`. `LinkContainer` is a
|
|
9
|
+
block-level link that wraps arbitrary content (cards, media) without adding
|
|
10
|
+
text styling.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import {LinkBox, LinkContainer} from "react-material-expressive";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
interface LinkBoxProps extends ComponentProps<"a"> {
|
|
22
|
+
active?: boolean; // explicit current-page (heavier underline); else derived
|
|
23
|
+
currentPath?: string; // pathname from YOUR router
|
|
24
|
+
}
|
|
25
|
+
type LinkContainerProps = ComponentProps<"a">;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<p>Read the <LinkBox href="/guidelines">guidelines</LinkBox>.</p>
|
|
32
|
+
<LinkBox currentPath={pathname} href="/docs">Docs</LinkBox>
|
|
33
|
+
|
|
34
|
+
<LinkContainer aria-label="Open article" href="/post/42">
|
|
35
|
+
<Card variant="outlined">…</Card>
|
|
36
|
+
</LinkContainer>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Gotchas
|
|
40
|
+
|
|
41
|
+
- Client-side routing: intercept `onClick` (preventDefault + router.push)
|
|
42
|
+
or wrap with your framework's Link passing these as children styles.
|
|
43
|
+
- `LinkBox` inherits font size; set type scale via `className`.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# List
|
|
2
|
+
|
|
3
|
+
M3 list: transparent container (inherits the surface it sits on) with
|
|
4
|
+
8dp vertical padding; item height follows the text-row count — **56dp**
|
|
5
|
+
one-line, **72dp** two-line, **88dp** three-line (overline + headline +
|
|
6
|
+
supporting; three-line items top-align their leading + text, the trailing
|
|
7
|
+
element stays centered). 16dp leading/trailing space, 12dp between elements.
|
|
8
|
+
Compose with `List.Item`. `variant` defaults to `"expressive"` (filled
|
|
9
|
+
`surface-container` tiles separated by a 2dp gap, with a container shape that
|
|
10
|
+
morphs on interaction (4 → 12 → 16dp) and 20px icons — recommended for new
|
|
11
|
+
designs); pass `variant="plain"` for the transparent, rectangular list with
|
|
12
|
+
24px icons, which M3E keeps available.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import {List} from "react-material-expressive";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## API
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
interface ListProps extends ComponentProps<"ul"> {
|
|
24
|
+
variant?: "expressive" | "plain"; // default "expressive"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ListItemProps {
|
|
28
|
+
body?: ReactNode; // supporting text → 72dp item
|
|
29
|
+
children?: ReactNode; // custom content replacing the text block
|
|
30
|
+
className?: string;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
headline?: ReactNode; // body-large
|
|
33
|
+
href?: string; // renders a native <a>
|
|
34
|
+
label?: ReactNode; // overline (label-small)
|
|
35
|
+
leftElement?: ReactNode; // 24px min box, on-surface-variant
|
|
36
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
37
|
+
rightElement?: ReactNode;
|
|
38
|
+
selected?: boolean; // secondary-container fill + on-secondary-container (visual)
|
|
39
|
+
target?: string;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Interactive items (`href`/`onClick`) render `<a>`/`<button>` with an M3
|
|
44
|
+
state layer; static items render a plain row. `selected` is visual only
|
|
45
|
+
(secondary-container) — the consumer owns selection semantics, as with
|
|
46
|
+
`Menu.Item`.
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<List>
|
|
52
|
+
<List.Item
|
|
53
|
+
headline="Inbox"
|
|
54
|
+
leftElement={<MaterialSymbol name="inbox" />}
|
|
55
|
+
onClick={openInbox}
|
|
56
|
+
rightElement={<span>24</span>}
|
|
57
|
+
/>
|
|
58
|
+
<List.Item
|
|
59
|
+
headline="Profile"
|
|
60
|
+
body="Account settings"
|
|
61
|
+
href="/profile"
|
|
62
|
+
leftElement={<Avatar name="A" size={40} />}
|
|
63
|
+
/>
|
|
64
|
+
</List>
|
|
65
|
+
|
|
66
|
+
// Plain (transparent, rectangular) variant.
|
|
67
|
+
<List variant="plain">
|
|
68
|
+
<List.Item
|
|
69
|
+
headline="Inbox"
|
|
70
|
+
leftElement={<MaterialSymbol name="inbox" />}
|
|
71
|
+
onClick={openInbox}
|
|
72
|
+
/>
|
|
73
|
+
</List>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Gotchas
|
|
77
|
+
|
|
78
|
+
- The container is transparent on purpose (M3) — it picks up the sheet,
|
|
79
|
+
card or surface behind it.
|
|
80
|
+
- For dividers between items use `<Divider className="my-0" inset />`.
|
|
81
|
+
- The default is the **Expressive** variant (`variant="expressive"`): filled
|
|
82
|
+
`surface-container` tiles with a shape that morphs on interaction
|
|
83
|
+
(extra-small → medium → large) and 20px icons. The filled tiles read against
|
|
84
|
+
a plain `surface` background; inside a `surface-container` set a different
|
|
85
|
+
container via `className`. Pass `variant="plain"` for the M3 **plain** list
|
|
86
|
+
(transparent, rectangular items, 24px icons), which M3E keeps fully
|
|
87
|
+
supported.
|