prime-ui-kit 0.9.1 → 0.9.3
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/README.md +30 -12
- package/dist/chunks/{chunk-NHOWNIAR.js → chunk-6JTZOA5I.js} +1472 -671
- package/dist/chunks/{chunk-DB2Y3XXA.js → chunk-OAE4IQX2.js} +2845 -1732
- package/dist/chunks/{chunk-KCQFOORX.js → chunk-U5SR2LZR.js} +17 -4
- package/dist/color-picker.js +2 -2
- package/dist/components/button/Button.d.ts +19 -2
- package/dist/components/card/Card.d.ts +1 -1
- package/dist/components/code-block/CodeBlock.d.ts +1 -1
- package/dist/components/color-picker/ColorPicker.d.ts +1 -1
- package/dist/components/data-table/DataTable.d.ts +6 -1
- package/dist/components/data-table/Row.d.ts +3 -1
- package/dist/components/data-table/rows.d.ts +4 -0
- package/dist/components/data-table/types.d.ts +5 -0
- package/dist/components/digit-input/DigitInput.d.ts +6 -1
- package/dist/components/dnd/Dnd.d.ts +1 -1
- package/dist/components/dnd/dragSession.d.ts +9 -0
- package/dist/components/dnd/useDropTarget.d.ts +5 -0
- package/dist/components/dnd/useFlipList.d.ts +3 -3
- package/dist/components/drawer/Drawer.d.ts +8 -5
- package/dist/components/dropdown/Dropdown.d.ts +17 -0
- package/dist/components/index.d.ts +9 -2
- package/dist/components/input/Input.d.ts +16 -1
- package/dist/components/input/passwordStrength.d.ts +9 -0
- package/dist/components/kanban/Kanban.d.ts +129 -0
- package/dist/components/modal/Modal.d.ts +5 -2
- package/dist/components/page-toolbar/PageToolbar.d.ts +53 -0
- package/dist/components/progress-bar/ProgressBar.d.ts +16 -1
- package/dist/components/segmented-control/SegmentedControl.d.ts +5 -2
- package/dist/components/sparkline/Sparkline.d.ts +26 -0
- package/dist/components/tabs/Tabs.d.ts +5 -2
- package/dist/hooks/useEdgeOverflow.d.ts +16 -0
- package/dist/hooks/useMediaQuery.d.ts +7 -0
- package/dist/hooks/useSwipeDismiss.d.ts +34 -0
- package/dist/icons/registry.d.ts +2 -0
- package/dist/index.css +2977 -1306
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -3
- package/dist/internal/CheckMark.d.ts +12 -0
- package/dist/internal/MorphText.d.ts +12 -0
- package/dist/internal/RollingNumber.d.ts +14 -0
- package/dist/internal/overlay/FloatingPanel.d.ts +3 -1
- package/dist/internal/overlay/useFloatingLayer.d.ts +11 -1
- package/dist/internal/surfaceDepth.d.ts +25 -0
- package/dist/internal/swatch.d.ts +4 -1
- package/dist/internal/touchTarget.d.ts +4 -0
- package/dist/layout/app-header/AppHeader.d.ts +110 -0
- package/dist/layout/app-shell/AppShell.d.ts +16 -11
- package/dist/layout/bottom-nav/BottomNav.d.ts +80 -0
- package/dist/layout/index.d.ts +5 -1
- package/dist/motion/celebrate.d.ts +18 -0
- package/dist/tokens/layers.d.ts +37 -0
- package/dist/tokens/naming.d.ts +1 -1
- package/dist/tokens/primitives.d.ts +4 -0
- package/dist/tokens/semantic.d.ts +122 -56
- package/dist/tokens/themes/dark.d.ts +10 -14
- package/package.json +3 -2
- package/src/components/accordion/COMPONENT.md +5 -5
- package/src/components/avatar/COMPONENT.md +1 -1
- package/src/components/badge/COMPONENT.md +2 -2
- package/src/components/banner/COMPONENT.md +1 -1
- package/src/components/button/COMPONENT.md +26 -5
- package/src/components/button/examples/download.tsx +45 -0
- package/src/components/button/examples/hold-to-confirm.tsx +23 -0
- package/src/components/button/examples/label-morph.tsx +33 -0
- package/src/components/card/COMPONENT.md +4 -2
- package/src/components/card/examples/narrow.tsx +2 -2
- package/src/components/card/examples/panel-chart.tsx +1 -1
- package/src/components/card/examples/variants.tsx +1 -1
- package/src/components/checkbox/examples/examples.module.css +5 -3
- package/src/components/code-block/COMPONENT.md +6 -6
- package/src/components/code-block/examples/overview.tsx +1 -1
- package/src/components/code-block/examples/variants.tsx +1 -1
- package/src/components/color-picker/COMPONENT.md +1 -1
- package/src/components/color-swatches/COMPONENT.md +5 -5
- package/src/components/command-menu/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/crossfade/examples/overview.tsx +3 -3
- package/src/components/data-table/COMPONENT.md +24 -10
- package/src/components/data-table/examples/columns-visibility.tsx +167 -0
- package/src/components/digit-input/COMPONENT.md +15 -13
- package/src/components/digit-input/examples/examples.module.css +8 -0
- package/src/components/digit-input/examples/in-form.tsx +2 -5
- package/src/components/digit-input/examples/on-complete.tsx +7 -6
- package/src/components/digit-input/examples/sizes.tsx +1 -1
- package/src/components/divider/COMPONENT.md +1 -1
- package/src/components/dnd/COMPONENT.md +3 -3
- package/src/components/dnd/examples/examples.module.css +4 -4
- package/src/components/drawer/COMPONENT.md +17 -9
- package/src/components/drawer/examples/date-sheet.tsx +41 -0
- package/src/components/drawer/examples/examples.module.css +5 -0
- package/src/components/drawer/examples/placement.tsx +4 -2
- package/src/components/dropdown/COMPONENT.md +20 -6
- package/src/components/dropdown/examples/checkbox-items.tsx +48 -0
- package/src/components/dropdown/examples/examples.module.css +1 -1
- package/src/components/empty-page/COMPONENT.md +1 -1
- package/src/components/empty-page/examples/examples.module.css +1 -1
- package/src/components/example-frame/COMPONENT.md +1 -1
- package/src/components/input/COMPONENT.md +10 -1
- package/src/components/input/examples/password-strength.tsx +12 -0
- package/src/components/kanban/COMPONENT.md +181 -0
- package/src/components/kanban/examples/controlled.tsx +78 -0
- package/src/components/kanban/examples/examples.module.css +31 -0
- package/src/components/kanban/examples/limits.tsx +58 -0
- package/src/components/kanban/examples/narrow.tsx +76 -0
- package/src/components/kanban/examples/overview.tsx +130 -0
- package/src/components/kanban/examples/states.tsx +75 -0
- package/src/components/kanban/examples/structure.tsx +76 -0
- package/src/components/login-form/COMPONENT.md +5 -5
- package/src/components/login-form/examples/register.tsx +2 -2
- package/src/components/login-form/examples/reset-password.tsx +2 -2
- package/src/components/modal/COMPONENT.md +11 -8
- package/src/components/modal/examples/examples.module.css +2 -2
- package/src/components/notification/COMPONENT.md +2 -2
- package/src/components/page-content/COMPONENT.md +1 -1
- package/src/components/page-content/examples/examples.module.css +1 -1
- package/src/components/page-toolbar/COMPONENT.md +142 -0
- package/src/components/page-toolbar/examples/examples.module.css +16 -0
- package/src/components/page-toolbar/examples/narrow.tsx +103 -0
- package/src/components/page-toolbar/examples/overview.tsx +113 -0
- package/src/components/page-toolbar/examples/save-changes.tsx +40 -0
- package/src/components/page-toolbar/examples/without-search.tsx +60 -0
- package/src/components/popover/COMPONENT.md +5 -4
- package/src/components/progress-bar/COMPONENT.md +6 -2
- package/src/components/progress-bar/examples/examples.module.css +7 -0
- package/src/components/progress-bar/examples/milestone.tsx +43 -0
- package/src/components/progress-bar/examples/steps.tsx +35 -0
- package/src/components/progress-circle/COMPONENT.md +1 -1
- package/src/components/scroll-container/COMPONENT.md +1 -1
- package/src/components/segmented-control/COMPONENT.md +8 -8
- package/src/components/segmented-control/examples/full-width.tsx +1 -1
- package/src/components/select/COMPONENT.md +2 -1
- package/src/components/skeleton/COMPONENT.md +1 -1
- package/src/components/skeleton/examples/with-crossfade.tsx +3 -3
- package/src/components/smart-filter/COMPONENT.md +2 -0
- package/src/components/sparkline/COMPONENT.md +98 -0
- package/src/components/sparkline/examples/examples.module.css +13 -0
- package/src/components/sparkline/examples/overview.tsx +27 -0
- package/src/components/sparkline/examples/period.tsx +46 -0
- package/src/components/spinner/COMPONENT.md +1 -1
- package/src/components/tabs/COMPONENT.md +24 -22
- package/src/components/tabs/examples/closable.tsx +1 -1
- package/src/components/tabs/examples/examples.module.css +14 -0
- package/src/components/tabs/examples/orientation.tsx +87 -33
- package/src/components/tooltip/COMPONENT.md +8 -6
- package/src/components/tooltip/examples/examples.module.css +2 -2
- package/src/layout/app-header/COMPONENT.md +178 -0
- package/src/layout/app-header/examples/breadcrumbs.tsx +36 -0
- package/src/layout/app-header/examples/examples.module.css +47 -0
- package/src/layout/app-header/examples/in-app-shell.tsx +70 -0
- package/src/layout/app-header/examples/narrow.tsx +29 -0
- package/src/layout/app-header/examples/overview.tsx +36 -0
- package/src/layout/app-shell/COMPONENT.md +35 -29
- package/src/layout/app-shell/examples/examples.module.css +17 -2
- package/src/layout/app-shell/examples/footer.tsx +59 -0
- package/src/layout/app-shell/examples/overview.tsx +19 -8
- package/src/layout/bottom-nav/COMPONENT.md +153 -0
- package/src/layout/bottom-nav/examples/examples.module.css +39 -0
- package/src/layout/bottom-nav/examples/floating.tsx +89 -0
- package/src/layout/bottom-nav/examples/icon-only.tsx +41 -0
- package/src/layout/bottom-nav/examples/in-app-shell.tsx +64 -0
- package/src/layout/bottom-nav/examples/overview.tsx +44 -0
- package/src/layout/bottom-nav/examples/router.tsx +31 -0
- package/src/layout/bottom-nav/examples/structure.tsx +47 -0
- package/src/layout/sidebar/COMPONENT.md +14 -13
- package/src/layout/sidebar/examples/examples.module.css +5 -5
- package/src/styles/globals.css +125 -2
- package/src/styles/theme-dark.css +39 -13
- package/src/styles/theme-light.css +84 -16
- package/src/styles/tokens.css +2 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** The header in the app frame: one row with the Sidebar brand across the two planes, sticky over main — `AppShell`, `Sidebar.Brand`. */
|
|
2
|
+
import { AppHeader, AppShell, Icon, PageContent, Sidebar, Typography } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const ORDERS = [
|
|
7
|
+
"№ 4821 · ООО «Север» · 84 000 ₽",
|
|
8
|
+
"№ 4820 · ИП Котов · 12 500 ₽",
|
|
9
|
+
"№ 4819 · ООО «Вектор» · 230 000 ₽",
|
|
10
|
+
"№ 4818 · АО «Меридиан» · 47 900 ₽",
|
|
11
|
+
"№ 4817 · ООО «Полюс» · 9 800 ₽",
|
|
12
|
+
"№ 4816 · ИП Белова · 156 000 ₽",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export default function AppHeaderInAppShellExample() {
|
|
16
|
+
return (
|
|
17
|
+
<div className={styles.stage}>
|
|
18
|
+
<AppShell.Root fillViewport className={styles.shell}>
|
|
19
|
+
<AppShell.Nav>
|
|
20
|
+
<Sidebar.Root offCanvas="never">
|
|
21
|
+
<Sidebar.Header>
|
|
22
|
+
<Sidebar.Brand href="#home" description="Отдел продаж">
|
|
23
|
+
<Sidebar.BrandLogo>
|
|
24
|
+
<span className={styles.logo}>
|
|
25
|
+
<Icon name="nav.layoutGrid" />
|
|
26
|
+
</span>
|
|
27
|
+
</Sidebar.BrandLogo>
|
|
28
|
+
Acme CRM
|
|
29
|
+
</Sidebar.Brand>
|
|
30
|
+
</Sidebar.Header>
|
|
31
|
+
<Sidebar.Content>
|
|
32
|
+
<Sidebar.Item>
|
|
33
|
+
<Sidebar.ItemIcon>
|
|
34
|
+
<Icon name="nav.home" />
|
|
35
|
+
</Sidebar.ItemIcon>
|
|
36
|
+
Обзор
|
|
37
|
+
</Sidebar.Item>
|
|
38
|
+
<Sidebar.Item current>
|
|
39
|
+
<Sidebar.ItemIcon>
|
|
40
|
+
<Icon name="object.package" />
|
|
41
|
+
</Sidebar.ItemIcon>
|
|
42
|
+
Заказы
|
|
43
|
+
</Sidebar.Item>
|
|
44
|
+
</Sidebar.Content>
|
|
45
|
+
</Sidebar.Root>
|
|
46
|
+
</AppShell.Nav>
|
|
47
|
+
<AppHeader.Root>
|
|
48
|
+
<AppHeader.Start>
|
|
49
|
+
<AppHeader.Title>
|
|
50
|
+
Заказы
|
|
51
|
+
<AppHeader.Description>128 в работе</AppHeader.Description>
|
|
52
|
+
</AppHeader.Title>
|
|
53
|
+
</AppHeader.Start>
|
|
54
|
+
<AppHeader.Search>Поиск</AppHeader.Search>
|
|
55
|
+
</AppHeader.Root>
|
|
56
|
+
<AppShell.Main>
|
|
57
|
+
<PageContent.Section>
|
|
58
|
+
<PageContent.Body>
|
|
59
|
+
{ORDERS.map((order) => (
|
|
60
|
+
<Typography key={order} variant="body-m">
|
|
61
|
+
{order}
|
|
62
|
+
</Typography>
|
|
63
|
+
))}
|
|
64
|
+
</PageContent.Body>
|
|
65
|
+
</PageContent.Section>
|
|
66
|
+
</AppShell.Main>
|
|
67
|
+
</AppShell.Root>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** On a phone: the menu button opens the navigation, the description goes and the search folds into an icon — `AppHeader.MenuButton`, `show`. */
|
|
2
|
+
import { AppHeader, Button, Icon } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
export default function AppHeaderNarrowExample() {
|
|
7
|
+
return (
|
|
8
|
+
<div className={`${styles.window} ${styles.phone}`}>
|
|
9
|
+
<AppHeader.Root>
|
|
10
|
+
<AppHeader.Start>
|
|
11
|
+
{/* `always`: this frame is phone-wide on any screen; in an app keep the default `narrow`. */}
|
|
12
|
+
<AppHeader.MenuButton show="always" aria-expanded={false} />
|
|
13
|
+
<AppHeader.Title>
|
|
14
|
+
Заказы
|
|
15
|
+
<AppHeader.Description>128 в работе</AppHeader.Description>
|
|
16
|
+
</AppHeader.Title>
|
|
17
|
+
</AppHeader.Start>
|
|
18
|
+
<AppHeader.Search>Поиск заказов</AppHeader.Search>
|
|
19
|
+
<AppHeader.Actions>
|
|
20
|
+
<Button.Root variant="soft" tone="neutral" aria-label="Новый заказ">
|
|
21
|
+
<Button.Icon>
|
|
22
|
+
<Icon name="action.add" />
|
|
23
|
+
</Button.Icon>
|
|
24
|
+
</Button.Root>
|
|
25
|
+
</AppHeader.Actions>
|
|
26
|
+
</AppHeader.Root>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** The top bar of a screen: where you are, global search, notifications and the primary action — `AppHeader.Title`, `AppHeader.Search`, `AppHeader.Actions`. */
|
|
2
|
+
import { AppHeader, Button, Icon } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
export default function AppHeaderOverviewExample() {
|
|
7
|
+
return (
|
|
8
|
+
<div className={styles.window}>
|
|
9
|
+
<AppHeader.Root>
|
|
10
|
+
<AppHeader.Start>
|
|
11
|
+
<AppHeader.Title>
|
|
12
|
+
<AppHeader.Icon>
|
|
13
|
+
<Icon name="nav.dashboard" />
|
|
14
|
+
</AppHeader.Icon>
|
|
15
|
+
Обзор
|
|
16
|
+
<AppHeader.Description>Отдел продаж · октябрь</AppHeader.Description>
|
|
17
|
+
</AppHeader.Title>
|
|
18
|
+
</AppHeader.Start>
|
|
19
|
+
<AppHeader.Search>Поиск заказов и клиентов</AppHeader.Search>
|
|
20
|
+
<AppHeader.Actions>
|
|
21
|
+
<Button.Root variant="soft" tone="neutral" aria-label="Уведомления">
|
|
22
|
+
<Button.Icon>
|
|
23
|
+
<Icon name="object.bell" />
|
|
24
|
+
</Button.Icon>
|
|
25
|
+
</Button.Root>
|
|
26
|
+
<Button.Root>
|
|
27
|
+
<Button.Icon>
|
|
28
|
+
<Icon name="action.add" />
|
|
29
|
+
</Button.Icon>
|
|
30
|
+
Новый заказ
|
|
31
|
+
</Button.Root>
|
|
32
|
+
</AppHeader.Actions>
|
|
33
|
+
</AppHeader.Root>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# AppShell
|
|
2
2
|
|
|
3
|
-
**Category:**
|
|
3
|
+
**Category:** page
|
|
4
4
|
**Kind:** layout
|
|
5
5
|
|
|
6
|
-
> The app frame: a
|
|
6
|
+
> The app frame: a content panel that is the page itself and a navigation rail one layer above it.
|
|
7
7
|
|
|
8
8
|
## When to use
|
|
9
|
-
- The root layout of an application: navigation column, optional
|
|
10
|
-
- Any page that needs the kit's responsive gutters and the surface
|
|
9
|
+
- The root layout of an application: navigation column, an optional AppHeader, scrolling main column, a bottom bar for phone navigation.
|
|
10
|
+
- Any page that needs the kit's responsive gutters and the surface ladder for cards and fields.
|
|
11
11
|
|
|
12
12
|
## When not to use
|
|
13
|
-
- The navigation itself → use [Sidebar](../sidebar/COMPONENT.md) inside `AppShell.Nav`.
|
|
13
|
+
- The navigation itself → use [Sidebar](../sidebar/COMPONENT.md) inside `AppShell.Nav`, [BottomNav](../bottom-nav/COMPONENT.md) inside `AppShell.Footer`.
|
|
14
|
+
- The top bar (title, search, actions, the menu button) → use [AppHeader](../app-header/COMPONENT.md) as a child of Root.
|
|
14
15
|
- Page heading, actions and sections inside main → use [PageContent](../../components/page-content/COMPONENT.md).
|
|
15
16
|
- A temporary side panel over the page → use [Drawer](../../components/drawer/COMPONENT.md).
|
|
16
17
|
- A scroll region inside a page → use [ScrollContainer](../../components/scroll-container/COMPONENT.md).
|
|
@@ -22,11 +23,12 @@ import { AppShell } from "prime-ui-kit";
|
|
|
22
23
|
|
|
23
24
|
## Anatomy
|
|
24
25
|
```
|
|
25
|
-
AppShell.Root grid: nav column | content panel (
|
|
26
|
-
├─ AppShell.Nav navigation column
|
|
27
|
-
├─
|
|
28
|
-
|
|
29
|
-
AppShell.
|
|
26
|
+
AppShell.Root grid: nav column | content panel (the page, layer 0)
|
|
27
|
+
├─ AppShell.Nav navigation column (usually Sidebar.Root, a surface one layer above the page)
|
|
28
|
+
├─ AppHeader.Root sticky top bar of the panel (a separate component)
|
|
29
|
+
├─ AppShell.Main <main>, scrolls, carries the gutters
|
|
30
|
+
└─ AppShell.Footer sticky bottom bar of the panel (usually BottomNav.Root)
|
|
31
|
+
AppShell.Template Root + Nav + the header + Main + Footer in one component
|
|
30
32
|
```
|
|
31
33
|
Every child of Root that is not `AppShell.Nav` is placed into the content panel.
|
|
32
34
|
|
|
@@ -35,12 +37,12 @@ Every child of Root that is not `AppShell.Nav` is placed into the content panel.
|
|
|
35
37
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
36
38
|
|
|
37
39
|
### AppShell.Root
|
|
38
|
-
`ref` → `HTMLDivElement`. Grid of the nav column (
|
|
40
|
+
`ref` → `HTMLDivElement`. Grid of the nav column (Sidebar, a surface one layer above the page) and the content panel (the page itself); every child that is not `AppShell.Nav` goes into the panel.
|
|
39
41
|
|
|
40
42
|
| Prop | Type | Default | Description |
|
|
41
43
|
|---|---|---|---|
|
|
42
44
|
| `fillViewport` | `boolean` | `false` | The shell is exactly the viewport high and only `AppShell.Main` scrolls; otherwise the document scrolls and the nav is sticky. |
|
|
43
|
-
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (Nav,
|
|
45
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (Nav, `AppHeader.Root`, Main, Footer), `className` and the other div attributes. |
|
|
44
46
|
|
|
45
47
|
### AppShell.Nav
|
|
46
48
|
`ref` → `HTMLDivElement`. The navigation column slot (not a landmark: Sidebar renders the `<nav>`).
|
|
@@ -49,28 +51,29 @@ Every child of Root that is not `AppShell.Nav` is placed into the content panel.
|
|
|
49
51
|
|---|---|---|---|
|
|
50
52
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (usually `Sidebar.Root`), `className` and the other div attributes. |
|
|
51
53
|
|
|
52
|
-
### AppShell.
|
|
53
|
-
`ref` → `HTMLElement`.
|
|
54
|
+
### AppShell.Main
|
|
55
|
+
`ref` → `HTMLElement`. The `<main>` with the canonical gutters: a vertical `ScrollContainer`.
|
|
54
56
|
|
|
55
57
|
| Prop | Type | Default | Description |
|
|
56
58
|
|---|---|---|---|
|
|
59
|
+
| `contentWidth` | `"contained" \| "full"` | `"full"` | `full` — the whole panel with gutters; `contained` — a centred column up to `--prime-layout-content-max-width`. |
|
|
57
60
|
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `children`, `className` and the other attributes. |
|
|
58
61
|
|
|
59
|
-
### AppShell.
|
|
60
|
-
`ref` → `
|
|
62
|
+
### AppShell.Footer
|
|
63
|
+
`ref` → `HTMLDivElement`. Sticky bottom bar of the panel for `BottomNav`; no padding of its own. A container (`prime-shell-footer`): BottomNav inside shows only while the panel is narrower than 640px.
|
|
61
64
|
|
|
62
65
|
| Prop | Type | Default | Description |
|
|
63
66
|
|---|---|---|---|
|
|
64
|
-
| `
|
|
65
|
-
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `children`, `className` and the other attributes. |
|
|
67
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (usually `BottomNav.Root`), `className` and the other div attributes. |
|
|
66
68
|
|
|
67
69
|
### AppShell.Template
|
|
68
|
-
`ref` → the `<main>`. Root + Nav +
|
|
70
|
+
`ref` → the `<main>`. Root + Nav + the header + Main + Footer in one; main scrolls to the top when `scrollResetKey` changes.
|
|
69
71
|
|
|
70
72
|
| Prop | Type | Default | Description |
|
|
71
73
|
|---|---|---|---|
|
|
72
74
|
| `nav` | `ReactNode` | — | Navigation column content; without it the panel takes the full width. |
|
|
73
|
-
| `header` | `ReactNode` | — |
|
|
75
|
+
| `header` | `ReactNode` | — | The top bar of the panel, an `AppHeader.Root`; no header when omitted. |
|
|
76
|
+
| `footer` | `ReactNode` | — | Footer content (`BottomNav`); no footer when omitted. |
|
|
74
77
|
| `mainProps` | `Omit<AppShellMainProps, "children">` | — | Props for Main (e.g. `contentWidth`). |
|
|
75
78
|
| `scrollResetKey` | `unknown` | — | Main scrolls back to the top whenever this value changes; pass the router pathname. |
|
|
76
79
|
| `children` | `ReactNode` | — | Page content inside Main. |
|
|
@@ -93,9 +96,10 @@ Every child of Root that is not `AppShell.Nav` is placed into the content panel.
|
|
|
93
96
|
### Structure
|
|
94
97
|
| Value | Looks like | Use when | Default |
|
|
95
98
|
|---|---|---|---|
|
|
96
|
-
| with `AppShell.Nav` | Two columns:
|
|
99
|
+
| with `AppShell.Nav` | Two columns: a rail one layer above the page + the panel that is the page | Apps with navigation | |
|
|
97
100
|
| without Nav | Panel takes the full width | Single-page tools, auth screens | |
|
|
98
|
-
| with `
|
|
101
|
+
| with `AppHeader.Root` | Sticky bar at the top of the panel | Title or breadcrumbs, global search, actions, the mobile menu button | |
|
|
102
|
+
| with `AppShell.Footer` | Sticky bar at the bottom of the panel, shown while it holds a visible bar | BottomNav on a phone | |
|
|
99
103
|
|
|
100
104
|
`AppShell.Template` with `nav={<Sidebar.Root …/>}` and `PageContent` inside is the standard app; `contentWidth="contained"` + `PageContent.Root maxWidth="readable"` for docs and articles.
|
|
101
105
|
|
|
@@ -104,12 +108,13 @@ Every child of Root that is not `AppShell.Nav` is placed into the content panel.
|
|
|
104
108
|
|---|---|---|
|
|
105
109
|
| fixed frame | `fillViewport` | `data-fill-viewport="true"` on Root |
|
|
106
110
|
| content width | `contentWidth` | `data-content-width` on Main |
|
|
111
|
+
| footer bar | panel width (container `prime-shell-footer` on Footer) | BottomNav inside leaves from 640px of panel width; the empty footer takes no room |
|
|
107
112
|
|
|
108
113
|
## Layout & spacing
|
|
109
|
-
- Two full-height planes edge to edge: no inset, radius, shadow or border — the boundary is the fill change (
|
|
110
|
-
- Gutters in Main
|
|
111
|
-
-
|
|
112
|
-
- The panel is
|
|
114
|
+
- Two full-height planes edge to edge: no inset, radius, shadow or border — the boundary is the fill change (the Sidebar rail is layer 1, the panel the page: white rail beside a gray page in light, a rail one step lighter than the near-black page in dark).
|
|
115
|
+
- Gutters in Main: `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px. Main top padding `--prime-space-6` (24) → `--prime-space-10` (40) from 1024px; bottom `--prime-space-16`.
|
|
116
|
+
- Footer: no padding of its own (BottomNav keeps the bottom safe-area inset), sticky at the bottom, `z-index: --prime-z-sticky`.
|
|
117
|
+
- The panel is the page (layer 0 of the surface ladder, gray in light): fields on it take the page fill, a Card inside is layer 1 — white in light with the raised whisper — and cards in cards go deeper.
|
|
113
118
|
|
|
114
119
|
## Accessibility
|
|
115
120
|
|
|
@@ -117,7 +122,7 @@ Every child of Root that is not `AppShell.Nav` is placed into the content panel.
|
|
|
117
122
|
No keyboard interaction.
|
|
118
123
|
|
|
119
124
|
### ARIA
|
|
120
|
-
- Main is the `<main>` landmark
|
|
125
|
+
- Main is the `<main>` landmark; the banner comes from AppHeader (`<header>`), the navigation landmarks from Sidebar and BottomNav (`<nav>`).
|
|
121
126
|
- Give the page one `<h1>` (PageContent.Title).
|
|
122
127
|
|
|
123
128
|
### Labels
|
|
@@ -128,8 +133,9 @@ No `labels`.
|
|
|
128
133
|
## Examples
|
|
129
134
|
| Example | Shows |
|
|
130
135
|
|---|---|
|
|
131
|
-
| [overview.tsx](examples/overview.tsx) | The app frame: Sidebar in the nav column, breadcrumbs in the
|
|
136
|
+
| [overview.tsx](examples/overview.tsx) | The app frame: Sidebar in the nav column, breadcrumbs in the AppHeader, the page in main; only main scrolls — `fillViewport`. |
|
|
132
137
|
| [contained.tsx](examples/contained.tsx) | A shell without navigation whose main column is centred and capped for long reads — `contentWidth`. |
|
|
138
|
+
| [footer.tsx](examples/footer.tsx) | A phone-wide app: the bar in the footer stays at the bottom while main scrolls under it, and leaves once the panel is 640px wide — `AppShell.Footer`. |
|
|
133
139
|
| [template.tsx](examples/template.tsx) | Root, nav, header and main in one component; main scrolls back to the top whenever the page changes — `AppShell.Template` with `scrollResetKey`. |
|
|
134
140
|
|
|
135
141
|
## Mistakes
|
|
@@ -141,4 +147,4 @@ No `labels`.
|
|
|
141
147
|
|
|
142
148
|
## Related
|
|
143
149
|
- **Built from:** [ScrollContainer](../../components/scroll-container/COMPONENT.md)
|
|
144
|
-
- **See also:** [Sidebar](../sidebar/COMPONENT.md), [PageContent](../../components/page-content/COMPONENT.md), [Breadcrumb](../../components/breadcrumb/COMPONENT.md)
|
|
150
|
+
- **See also:** [AppHeader](../app-header/COMPONENT.md), [Sidebar](../sidebar/COMPONENT.md), [BottomNav](../bottom-nav/COMPONENT.md), [PageContent](../../components/page-content/COMPONENT.md), [Breadcrumb](../../components/breadcrumb/COMPONENT.md)
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
height: calc(var(--prime-space-24) * 5);
|
|
8
8
|
overflow: hidden;
|
|
9
9
|
border-radius: var(--prime-card-radius);
|
|
10
|
-
background: var(--prime-color-
|
|
11
|
-
/* The
|
|
10
|
+
background: var(--prime-color-layer-current);
|
|
11
|
+
/* The window is a page on the preview's page: a hairline keeps its edge visible. */
|
|
12
12
|
box-shadow:
|
|
13
13
|
0 0 0 var(--prime-border-width) var(--prime-color-border-subtle),
|
|
14
14
|
var(--prime-shadow-raised);
|
|
@@ -27,3 +27,18 @@
|
|
|
27
27
|
.shell {
|
|
28
28
|
height: 100%;
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/* footer.tsx: the same window, phone-wide, so the bar in the footer stays (it leaves from 640px). */
|
|
32
|
+
.phone {
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
display: flex;
|
|
35
|
+
width: min(100%, var(--prime-drawer-width-s));
|
|
36
|
+
height: calc(var(--prime-space-24) * 5);
|
|
37
|
+
margin-inline: auto;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
border-radius: var(--prime-card-radius);
|
|
40
|
+
background: var(--prime-color-layer-current);
|
|
41
|
+
box-shadow:
|
|
42
|
+
0 0 0 var(--prime-border-width) var(--prime-color-border-subtle),
|
|
43
|
+
var(--prime-shadow-raised);
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** A phone-wide app: the bar in the footer stays at the bottom while main scrolls under it, and leaves once the panel is 640px wide — `AppShell.Footer`. */
|
|
2
|
+
import { AppHeader, AppShell, BottomNav, Icon, PageContent, Typography } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const SECTIONS = [
|
|
7
|
+
{ label: "Главная", icon: "nav.home" },
|
|
8
|
+
{ label: "Счета", icon: "object.receipt" },
|
|
9
|
+
{ label: "Клиенты", icon: "object.users" },
|
|
10
|
+
{ label: "Ещё", icon: "action.more" },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
const INVOICES = [
|
|
14
|
+
"Счёт № 1207 · 84 000 ₽",
|
|
15
|
+
"Счёт № 1206 · 12 500 ₽",
|
|
16
|
+
"Счёт № 1205 · 230 000 ₽",
|
|
17
|
+
"Счёт № 1204 · 47 900 ₽",
|
|
18
|
+
"Счёт № 1203 · 9 800 ₽",
|
|
19
|
+
"Счёт № 1202 · 156 000 ₽",
|
|
20
|
+
"Счёт № 1201 · 31 200 ₽",
|
|
21
|
+
"Счёт № 1200 · 64 700 ₽",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export default function AppShellFooterExample() {
|
|
25
|
+
return (
|
|
26
|
+
<div className={styles.phone}>
|
|
27
|
+
<AppShell.Root fillViewport className={styles.shell}>
|
|
28
|
+
<AppHeader.Root>
|
|
29
|
+
<AppHeader.Start>
|
|
30
|
+
<AppHeader.Title>Счета</AppHeader.Title>
|
|
31
|
+
</AppHeader.Start>
|
|
32
|
+
</AppHeader.Root>
|
|
33
|
+
<AppShell.Main>
|
|
34
|
+
<PageContent.Section>
|
|
35
|
+
<PageContent.Body>
|
|
36
|
+
{INVOICES.map((invoice) => (
|
|
37
|
+
<Typography key={invoice} variant="body-m">
|
|
38
|
+
{invoice}
|
|
39
|
+
</Typography>
|
|
40
|
+
))}
|
|
41
|
+
</PageContent.Body>
|
|
42
|
+
</PageContent.Section>
|
|
43
|
+
</AppShell.Main>
|
|
44
|
+
<AppShell.Footer>
|
|
45
|
+
<BottomNav.Root>
|
|
46
|
+
{SECTIONS.map((item, index) => (
|
|
47
|
+
<BottomNav.Item key={item.label} current={index === 1}>
|
|
48
|
+
<BottomNav.ItemIcon>
|
|
49
|
+
<Icon name={item.icon} />
|
|
50
|
+
</BottomNav.ItemIcon>
|
|
51
|
+
{item.label}
|
|
52
|
+
</BottomNav.Item>
|
|
53
|
+
))}
|
|
54
|
+
</BottomNav.Root>
|
|
55
|
+
</AppShell.Footer>
|
|
56
|
+
</AppShell.Root>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
/** The app frame: Sidebar in the nav column, breadcrumbs in the
|
|
2
|
-
import {
|
|
1
|
+
/** The app frame: Sidebar in the nav column, breadcrumbs in the AppHeader, the page in main; only main scrolls — `fillViewport`. */
|
|
2
|
+
import {
|
|
3
|
+
AppHeader,
|
|
4
|
+
AppShell,
|
|
5
|
+
Breadcrumb,
|
|
6
|
+
Button,
|
|
7
|
+
Icon,
|
|
8
|
+
PageContent,
|
|
9
|
+
Sidebar,
|
|
10
|
+
Typography,
|
|
11
|
+
} from "prime-ui-kit";
|
|
3
12
|
|
|
4
13
|
import styles from "./examples.module.css";
|
|
5
14
|
|
|
@@ -37,12 +46,14 @@ export default function AppShellOverviewExample() {
|
|
|
37
46
|
</Sidebar.Content>
|
|
38
47
|
</Sidebar.Root>
|
|
39
48
|
</AppShell.Nav>
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
<Breadcrumb.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
<AppHeader.Root>
|
|
50
|
+
<AppHeader.Start>
|
|
51
|
+
<Breadcrumb.Root>
|
|
52
|
+
<Breadcrumb.Item href="#crm">CRM</Breadcrumb.Item>
|
|
53
|
+
<Breadcrumb.Item current>Обзор</Breadcrumb.Item>
|
|
54
|
+
</Breadcrumb.Root>
|
|
55
|
+
</AppHeader.Start>
|
|
56
|
+
</AppHeader.Root>
|
|
46
57
|
<AppShell.Main>
|
|
47
58
|
<PageContent.Section>
|
|
48
59
|
<PageContent.Header>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# BottomNav
|
|
2
|
+
|
|
3
|
+
**Category:** navigation
|
|
4
|
+
**Kind:** layout
|
|
5
|
+
|
|
6
|
+
> Phone navigation: a bar of 3–5 main sections at the bottom of the screen.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
- The main sections of an app on a phone, one tap away (Главная, Заказы, Клиенты, Ещё).
|
|
10
|
+
- Next to Sidebar in one app: Sidebar navigates on a wide screen, BottomNav on a phone (inside `AppShell.Footer` it leaves by itself once the panel is 640px wide).
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
- More than five destinations or nested sections → use [Sidebar](../sidebar/COMPONENT.md) (off-canvas on a phone), put the rest under «Ещё».
|
|
14
|
+
- Switching views inside one page → use [Tabs](../../components/tabs/COMPONENT.md) or [SegmentedControl](../../components/segmented-control/COMPONENT.md).
|
|
15
|
+
- Actions (create, filter) → use [Button](../../components/button/COMPONENT.md); the bar only navigates.
|
|
16
|
+
|
|
17
|
+
## Import
|
|
18
|
+
```tsx
|
|
19
|
+
import { BottomNav } from "prime-ui-kit";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Anatomy
|
|
23
|
+
```
|
|
24
|
+
BottomNav.Root <nav> bar, equal columns, safe-area inset below
|
|
25
|
+
└─ BottomNav.Item <button>, <a href> or the asChild element
|
|
26
|
+
├─ BottomNav.ItemIcon the glyph above the label (aria-hidden)
|
|
27
|
+
├─ BottomNav.ItemCount Badge on the icon corner
|
|
28
|
+
└─ label one short word
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
34
|
+
|
|
35
|
+
### BottomNav.Root
|
|
36
|
+
`ref` → `HTMLElement`. The `<nav>` bar: equal columns, surface fill with a faint top divider, the bottom safe-area inset below the items. Inside `AppShell.Footer` it shows only while the panel is narrower than 640px.
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `iconOnly` | `boolean` | `false` | Icons without visible labels; the labels stay in the DOM as visually hidden text and name the items. |
|
|
41
|
+
| `floating` | `boolean` | `false` | A Liquid Glass capsule over the content (the iOS 26 tab bar): a clear `--prime-color-bg-glass` tint over a light backdrop blur, a specular top edge, a glass selection capsule under the current item, inset `--prime-bottom-nav-floating-inset` from the edges and above the home indicator. Positions itself at the bottom of its positioned container (`AppShell.Footer`, which then takes no height), so the page scrolls under it; with `iconOnly` the capsule hugs its square items, centred. Opaque under `prefers-reduced-transparency`. |
|
|
42
|
+
| `labels` | `Partial<BottomNavLabels>` | — | Built-in strings, see Labels. |
|
|
43
|
+
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `children` (3–5 `BottomNav.Item`), `aria-label` (replaces `labels.nav`), `className` and the other attributes. |
|
|
44
|
+
|
|
45
|
+
### BottomNav.Item
|
|
46
|
+
`ref` → the rendered element. `<button type="button">`, `<a>` with `href`, or the single child with `asChild`: the icon (20) above a short label (10).
|
|
47
|
+
|
|
48
|
+
| Prop | Type | Default | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `current` | `boolean` | `false` | Current section: `aria-current="page"`; the icon and the label turn primary (the others are muted). |
|
|
51
|
+
| `disabled` | `boolean` | `false` | Not interactive: `disabled` / `aria-disabled`, `data-disabled`; a link loses its `href`. |
|
|
52
|
+
| `href` | `string` | — | Renders an `<a>` (with `target`, `rel`). |
|
|
53
|
+
| `asChild` | `boolean` | `false` | Renders the single child (e.g. a router `NavLink`) as the item; its children are the label and parts. |
|
|
54
|
+
| `children` | `ReactNode` | — | `BottomNav.ItemIcon`, an optional `BottomNav.ItemCount` and a short label (one word). |
|
|
55
|
+
| `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `onClick`, `aria-*`, `className` and the other attributes. |
|
|
56
|
+
|
|
57
|
+
### BottomNav.ItemIcon
|
|
58
|
+
`ref` → `HTMLSpanElement`. The item glyph (`aria-hidden`, 20) above the label.
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Default | Description |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `children` | `ReactNode` | — (required) | The icon, e.g. `<Icon name="nav.home" />`. |
|
|
63
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other span attributes. |
|
|
64
|
+
|
|
65
|
+
### BottomNav.ItemCount
|
|
66
|
+
`ref` → `HTMLSpanElement` (the Badge). An `xs` Badge on the icon's top-end corner; read after the label («Заказы 12»).
|
|
67
|
+
|
|
68
|
+
| Prop | Type | Default | Description |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `children` | `ReactNode` | — (required) | The number (or a short status such as «99+»). |
|
|
71
|
+
| `color` | `PaletteColor` | `"red"` | Badge hue: a count on a section asks for attention. |
|
|
72
|
+
| `variant` | `"solid" \| "soft" \| "outline"` | `"solid"` | Badge treatment. |
|
|
73
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children" \| "color">` | — | `className` and the other span attributes. |
|
|
74
|
+
|
|
75
|
+
## Variants
|
|
76
|
+
|
|
77
|
+
Two item states only — current (primary color) and not (muted); no hover, no fill.
|
|
78
|
+
|
|
79
|
+
### Flags (Root)
|
|
80
|
+
| Flag | Looks like | Use when | Default |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `floating` | a glass capsule over the content: translucent fill, backdrop blur, bright rim, inset from the edges | the app's look wants depth over the page, the iOS tab-bar feel | |
|
|
83
|
+
| `iconOnly` | icons alone; labels visually hidden, still the items' names | 3–4 sections with unmistakable icons | |
|
|
84
|
+
|
|
85
|
+
Without flags the bar is flat at the screen edge on the surface, with a faint top divider.
|
|
86
|
+
|
|
87
|
+
### Flags (Item)
|
|
88
|
+
| Flag | Part | Effect |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `current` | Item | `aria-current="page"`, icon and label in the primary color |
|
|
91
|
+
| `disabled` | Item | not interactive, disabled text color |
|
|
92
|
+
| `asChild` | Item | the child element (router link) is the item |
|
|
93
|
+
|
|
94
|
+
## States
|
|
95
|
+
| State | Driven by | DOM |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| current | `current` or a router link setting `aria-current` | `aria-current="page"` on the item |
|
|
98
|
+
| focus | keyboard | focus ring inside the item edge |
|
|
99
|
+
| disabled | `disabled` | `disabled` / `aria-disabled`, `data-disabled` |
|
|
100
|
+
| hidden | inside `AppShell.Footer` 640px wide or more | `display: none` (container `prime-shell-footer`) |
|
|
101
|
+
| floating / icon only | `floating` / `iconOnly` | `data-floating="true"` / `data-icon-only="true"` on the `<nav>` |
|
|
102
|
+
| less transparency | `prefers-reduced-transparency: reduce` | a floating capsule turns opaque (`--prime-color-layer-floating-bg`), no blur |
|
|
103
|
+
|
|
104
|
+
Motion: the color of the icon and the label changes over `fast`; in the floating capsule the selection capsule grows in from 92% and fades in (`fast`, enter easing), instant under reduced motion. No hover or press feedback: an item is either current or not.
|
|
105
|
+
|
|
106
|
+
## Layout & spacing
|
|
107
|
+
- Bar `--prime-bottom-nav-height` (52) plus `env(safe-area-inset-bottom)`; side padding `--prime-bottom-nav-padding-x`. With `iconOnly` the bar is `--prime-bottom-nav-icon-only-height` (48).
|
|
108
|
+
- Items share the width equally; the icon (`--prime-bottom-nav-icon-size`, 20 — a light glyph, so the bar stays quiet next to the page) sits in a `--prime-bottom-nav-indicator-width` × `--prime-bottom-nav-indicator-height` box (28 × 20) that anchors the count; the label is `--prime-bottom-nav-label-size` (10, medium, line 12 — the iOS tab-bar size) 4 below it and truncates.
|
|
109
|
+
- Floating (the iOS 26 tab bar): items `--prime-bottom-nav-floating-item-size` (44) high inset `--prime-bottom-nav-floating-padding` (4) inside a full-radius capsule (52 in all); with `iconOnly` the items are 44 squares and the capsule hugs them. `--prime-bottom-nav-floating-inset` (12) from the sides and above `env(safe-area-inset-bottom)`. Liquid Glass: a clear `--prime-color-bg-glass` tint over a light `blur(--prime-bottom-nav-blur)` (8) with `saturate(180%)`, so the page reads through; a specular top edge in `--prime-color-bg-glass-edge`, a hairline of ink around it and the overlay shadow. The current item sits on a glass selection capsule (ink at 8% with its own top highlight). Inside `AppShell.Footer` main gets extra bottom padding so the last line clears the capsule.
|
|
110
|
+
- Surface fill with a faint top divider; inside `AppShell.Footer` the bar sticks to the bottom of the panel.
|
|
111
|
+
|
|
112
|
+
## Accessibility
|
|
113
|
+
|
|
114
|
+
### Keyboard
|
|
115
|
+
| Key | Action |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `Tab` · `Shift+Tab` | Moves focus through the sections; a disabled one is skipped. |
|
|
118
|
+
| `Enter` | Opens the section (a link or a button). |
|
|
119
|
+
|
|
120
|
+
### ARIA
|
|
121
|
+
- A `<nav>` landmark named by `labels.nav` («Основные разделы») or its own `aria-label`.
|
|
122
|
+
- The current section has `aria-current="page"`; visually it is the primary-color item among muted ones (a contrast step, not a hue).
|
|
123
|
+
- The icon is hidden from assistive tech; the item is named by its label, the count is read after it («Заказы 12»).
|
|
124
|
+
- A disabled item is a `disabled` button or an `aria-disabled` link without `href`.
|
|
125
|
+
|
|
126
|
+
### Labels
|
|
127
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
128
|
+
|
|
129
|
+
| Key | Default | Used for |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `nav` | `"Основные разделы"` | `aria-label` of the `<nav>` landmark. |
|
|
132
|
+
|
|
133
|
+
## Examples
|
|
134
|
+
| Example | Shows |
|
|
135
|
+
|---|---|
|
|
136
|
+
| [overview.tsx](examples/overview.tsx) | Four sections of the app, the current one in the primary color and a tap moves it — `current`, `BottomNav.ItemIcon`. |
|
|
137
|
+
| [structure.tsx](examples/structure.tsx) | Counts on the icons and a section that is not available yet — `BottomNav.ItemCount`, `disabled`. |
|
|
138
|
+
| [icon-only.tsx](examples/icon-only.tsx) | Icons alone: the labels are hidden but still name the sections for screen readers — `iconOnly`. |
|
|
139
|
+
| [floating.tsx](examples/floating.tsx) | A glass capsule over the page: the client list scrolls under its blur, with labels or icons alone — `floating`, `iconOnly`. |
|
|
140
|
+
| [router.tsx](examples/router.tsx) | A router link as the item: the router sets `aria-current` and the item shows as current; render it inside a router — `asChild`. |
|
|
141
|
+
| [in-app-shell.tsx](examples/in-app-shell.tsx) | The app frame on a phone: the AppHeader, the page and the bar in the footer, which leaves once the panel is 640px wide — `AppShell.Footer`. |
|
|
142
|
+
|
|
143
|
+
## Mistakes
|
|
144
|
+
- Six or more items → keep 3–5; the rest goes to a «Ещё» section or Sidebar.
|
|
145
|
+
- Long labels («Управление заказами») → one word; the label truncates.
|
|
146
|
+
- `iconOnly` with icons that need a caption to be understood → keep the labels.
|
|
147
|
+
- A floating bar outside a positioned container → it sits at the bottom of the nearest positioned ancestor; put it in `AppShell.Footer`.
|
|
148
|
+
- An action item («Создать») in the bar → the bar navigates; put the action in the page header.
|
|
149
|
+
- Hiding the bar with your own media query inside `AppShell.Footer` → the footer already hides it from 640px of panel width.
|
|
150
|
+
|
|
151
|
+
## Related
|
|
152
|
+
- **Built from:** Badge, VisuallyHidden (internal)
|
|
153
|
+
- **See also:** [AppShell](../app-shell/COMPONENT.md), [Sidebar](../sidebar/COMPONENT.md), [Tabs](../../components/tabs/COMPONENT.md)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Layout of the BottomNav scenarios: a phone-wide screen frame. Semantic tokens only. */
|
|
2
|
+
|
|
3
|
+
/* A phone screen: the content above, the bar pinned to the bottom edge. */
|
|
4
|
+
.phone {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
width: min(100%, var(--prime-drawer-width-s));
|
|
9
|
+
height: calc(var(--prime-space-24) * 5);
|
|
10
|
+
margin-inline: auto;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
border-radius: var(--prime-card-radius);
|
|
13
|
+
background: var(--prime-color-layer-current);
|
|
14
|
+
box-shadow:
|
|
15
|
+
0 0 0 var(--prime-border-width) var(--prime-color-border-subtle),
|
|
16
|
+
var(--prime-shadow-raised);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* The page above the bar. */
|
|
20
|
+
.screen {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex: 1 1 auto;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: var(--prime-space-2);
|
|
25
|
+
min-height: 0;
|
|
26
|
+
padding: var(--prime-space-6) var(--prime-layout-gutter-s);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* in-app-shell.tsx: the shell fills the phone frame instead of the viewport. */
|
|
30
|
+
.shell {
|
|
31
|
+
height: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* floating.tsx: a client row — avatar and two lines of text. */
|
|
35
|
+
.client {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: var(--prime-space-3);
|
|
39
|
+
}
|