prime-ui-kit 0.17.0 → 0.18.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/README.md +2 -1
- package/dist/chunks/{chunk-PRP4B6OG.js → chunk-IVS3J7AS.js} +1 -1
- package/dist/chunks/{chunk-A4WE3UNC.js → chunk-MB4CXSLL.js} +18473 -17911
- package/dist/chunks/{chunk-7WWZYGRC.js → chunk-OAXJWF3L.js} +4477 -3882
- package/dist/chunks/{chunk-2ODXLPW3.js → chunk-QFHE5GKW.js} +1 -0
- package/dist/chunks/{chunk-SVFWPLNW.js → chunk-X3JT5CLR.js} +14 -3
- package/dist/color-picker.js +3 -3
- package/dist/components/badge/Badge.d.ts +17 -0
- package/dist/components/breadcrumb/Breadcrumb.d.ts +11 -2
- package/dist/components/button/Button.d.ts +1 -1
- package/dist/components/button-group/ButtonGroup.d.ts +1 -1
- package/dist/components/card/Card.d.ts +8 -4
- package/dist/components/chat/Chat.d.ts +3 -3
- package/dist/components/chat/MessageList.d.ts +5 -1
- package/dist/components/chat/groupMessages.d.ts +12 -4
- package/dist/components/chat-list/ChatList.d.ts +18 -9
- package/dist/components/chat-list/formatChatTime.d.ts +3 -3
- package/dist/components/checkbox/Checkbox.d.ts +6 -1
- package/dist/components/color-field/ColorField.d.ts +1 -1
- package/dist/components/color-picker/ColorPicker.d.ts +6 -1
- package/dist/components/color-picker/ColorPresets.d.ts +9 -1
- package/dist/components/crossfade/Crossfade.d.ts +1 -1
- package/dist/components/data-table/DataTable.d.ts +2 -0
- package/dist/components/data-table/Head.d.ts +3 -1
- package/dist/components/data-table/types.d.ts +5 -0
- package/dist/components/digit-input/DigitInput.d.ts +1 -1
- package/dist/components/drawer/Drawer.d.ts +3 -2
- package/dist/components/field-list/FieldList.d.ts +18 -6
- package/dist/components/hint/Hint.d.ts +8 -2
- package/dist/components/index.d.ts +6 -3
- package/dist/components/input/Input.d.ts +27 -5
- package/dist/components/input/inputSuggestions.d.ts +39 -0
- package/dist/components/kanban/Kanban.d.ts +4 -9
- package/dist/components/list/List.d.ts +19 -5
- package/dist/components/message/Message.d.ts +15 -3
- package/dist/components/message/MessageSticker.d.ts +27 -0
- package/dist/components/message/formatMessageTime.d.ts +5 -2
- package/dist/components/modal/Modal.d.ts +3 -2
- package/dist/components/native-select/NativeSelect.d.ts +1 -1
- package/dist/components/radio/Radio.d.ts +11 -2
- package/dist/components/rich-text/RichText.d.ts +23 -0
- package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
- package/dist/components/scroll-container/scrollGlide.d.ts +26 -0
- package/dist/components/scroll-container/useScrollMotion.d.ts +25 -0
- package/dist/components/segmented-control/SegmentedControl.d.ts +1 -1
- package/dist/components/select/selectContext.d.ts +7 -0
- package/dist/components/slider/Slider.d.ts +1 -1
- package/dist/components/switch/Switch.d.ts +6 -1
- package/dist/components/tag-select/TagSelect.d.ts +1 -1
- package/dist/components/textarea/Textarea.d.ts +6 -1
- package/dist/components/timeline/Timeline.d.ts +1 -1
- package/dist/icon-set.js +2 -2
- package/dist/index.css +1 -1
- package/dist/index.js +5 -3
- package/dist/internal/ChoiceField.d.ts +3 -1
- package/dist/internal/FieldFrame.d.ts +8 -2
- package/dist/internal/fieldCell.d.ts +1 -1
- package/dist/internal/formatClock.d.ts +23 -2
- package/dist/internal/leaveGuard.d.ts +21 -0
- package/dist/internal/messageList.d.ts +2 -0
- package/dist/internal/overlay/DialogParts.d.ts +27 -2
- package/dist/internal/swatch.d.ts +5 -0
- package/dist/internal/tokenDefaults.d.ts +1 -0
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/page/Page.d.ts +47 -8
- package/dist/skill/anti-slop.md +5 -0
- package/dist/skill/api-contract.md +14 -7
- package/dist/skill/cheatsheet.md +22 -7
- package/dist/skill/checklist.md +5 -0
- package/dist/skill/choosing.md +25 -12
- package/dist/skill/components.md +1 -0
- package/dist/skill/composition.md +19 -5
- package/dist/skill/layouts.md +5 -3
- package/dist/skill/reference/components/badge/COMPONENT.md +14 -1
- package/dist/skill/reference/components/badge/examples/examples.module.css +7 -0
- package/dist/skill/reference/components/badge/examples/tag-row.tsx +34 -0
- package/dist/skill/reference/components/breadcrumb/COMPONENT.md +9 -4
- package/dist/skill/reference/components/breadcrumb/examples/router.tsx +17 -0
- package/dist/skill/reference/components/button/COMPONENT.md +1 -1
- package/dist/skill/reference/components/button-group/COMPONENT.md +1 -0
- package/dist/skill/reference/components/card/COMPONENT.md +7 -1
- package/dist/skill/reference/components/card/examples/states.tsx +59 -0
- package/dist/skill/reference/components/chat/COMPONENT.md +4 -1
- package/dist/skill/reference/components/chat/examples/time-zone.tsx +83 -0
- package/dist/skill/reference/components/chat-list/COMPONENT.md +12 -6
- package/dist/skill/reference/components/chat-list/examples/links.tsx +1 -1
- package/dist/skill/reference/components/chat-list/examples/router.tsx +63 -0
- package/dist/skill/reference/components/checkbox/COMPONENT.md +1 -0
- package/dist/skill/reference/components/color-field/COMPONENT.md +2 -1
- package/dist/skill/reference/components/color-picker/COMPONENT.md +14 -5
- package/dist/skill/reference/components/color-picker/examples/presets-custom.tsx +31 -0
- package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
- package/dist/skill/reference/components/crossfade/COMPONENT.md +1 -1
- package/dist/skill/reference/components/data-table/COMPONENT.md +7 -2
- package/dist/skill/reference/components/data-table/examples/numeric.tsx +9 -2
- package/dist/skill/reference/components/datepicker/COMPONENT.md +3 -1
- package/dist/skill/reference/components/datepicker/examples/deadline.tsx +33 -0
- package/dist/skill/reference/components/digit-input/COMPONENT.md +1 -0
- package/dist/skill/reference/components/drawer/COMPONENT.md +15 -2
- package/dist/skill/reference/components/drawer/examples/client-card.tsx +32 -8
- package/dist/skill/reference/components/field-list/COMPONENT.md +15 -3
- package/dist/skill/reference/components/field-list/examples/saving.tsx +83 -0
- package/dist/skill/reference/components/field-list/examples/totals.tsx +82 -0
- package/dist/skill/reference/components/file-upload/COMPONENT.md +1 -0
- package/dist/skill/reference/components/hint/COMPONENT.md +16 -2
- package/dist/skill/reference/components/hint/examples/variants.tsx +48 -0
- package/dist/skill/reference/components/input/COMPONENT.md +39 -9
- package/dist/skill/reference/components/input/examples/affixes.tsx +4 -2
- package/dist/skill/reference/components/input/examples/dismiss.tsx +29 -0
- package/dist/skill/reference/components/input/examples/suggestions.tsx +26 -0
- package/dist/skill/reference/components/input/examples/time.tsx +24 -0
- package/dist/skill/reference/components/input/examples/with-select.tsx +44 -0
- package/dist/skill/reference/components/kanban/COMPONENT.md +6 -6
- package/dist/skill/reference/components/kanban/examples/columns.tsx +4 -2
- package/dist/skill/reference/components/list/COMPONENT.md +23 -7
- package/dist/skill/reference/components/list/examples/row-controls.tsx +80 -0
- package/dist/skill/reference/components/list/examples/with-icon.tsx +23 -2
- package/dist/skill/reference/components/login-form/COMPONENT.md +8 -7
- package/dist/skill/reference/components/login-form/examples/states.tsx +12 -14
- package/dist/skill/reference/components/message/COMPONENT.md +30 -9
- package/dist/skill/reference/components/message/examples/preview.tsx +28 -0
- package/dist/skill/reference/components/message/examples/sticker.tsx +35 -0
- package/dist/skill/reference/components/modal/COMPONENT.md +15 -3
- package/dist/skill/reference/components/modal/examples/long-content.tsx +37 -18
- package/dist/skill/reference/components/native-select/COMPONENT.md +1 -0
- package/dist/skill/reference/components/notification/COMPONENT.md +1 -1
- package/dist/skill/reference/components/radio/COMPONENT.md +2 -0
- package/dist/skill/reference/components/rich-text/COMPONENT.md +93 -0
- package/dist/skill/reference/components/rich-text/examples/overview.tsx +19 -0
- package/dist/skill/reference/components/scroll-container/COMPONENT.md +8 -5
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
- package/dist/skill/reference/components/segmented-control/examples/in-form.tsx +1 -1
- package/dist/skill/reference/components/select/COMPONENT.md +7 -3
- package/dist/skill/reference/components/slider/COMPONENT.md +1 -0
- package/dist/skill/reference/components/slider/examples/controlled.tsx +1 -1
- package/dist/skill/reference/components/switch/COMPONENT.md +1 -0
- package/dist/skill/reference/components/tag-select/COMPONENT.md +1 -0
- package/dist/skill/reference/components/textarea/COMPONENT.md +1 -0
- package/dist/skill/reference/components/timeline/COMPONENT.md +3 -3
- package/dist/skill/reference/components/timeline/examples/structure.tsx +2 -1
- package/dist/skill/reference/layout/app-shell/COMPONENT.md +2 -2
- package/dist/skill/reference/layout/bottom-nav/COMPONENT.md +1 -0
- package/dist/skill/reference/layout/page/COMPONENT.md +37 -8
- package/dist/skill/reference/layout/page/examples/examples.module.css +7 -0
- package/dist/skill/reference/layout/page/examples/guard.tsx +87 -0
- package/dist/skill/reference/layout/page/examples/loading.tsx +57 -0
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +1 -0
- package/dist/skill/responsive.md +12 -7
- package/dist/skill/ux.md +5 -3
- package/dist/tokens/semantic.d.ts +2 -0
- package/package.json +2 -2
- package/src/components/badge/COMPONENT.md +14 -1
- package/src/components/badge/examples/examples.module.css +7 -0
- package/src/components/badge/examples/tag-row.tsx +34 -0
- package/src/components/breadcrumb/COMPONENT.md +9 -4
- package/src/components/breadcrumb/examples/router.tsx +17 -0
- package/src/components/button/COMPONENT.md +1 -1
- package/src/components/button-group/COMPONENT.md +1 -0
- package/src/components/card/COMPONENT.md +7 -1
- package/src/components/card/examples/states.tsx +59 -0
- package/src/components/chat/COMPONENT.md +4 -1
- package/src/components/chat/examples/time-zone.tsx +83 -0
- package/src/components/chat-list/COMPONENT.md +12 -6
- package/src/components/chat-list/examples/links.tsx +1 -1
- package/src/components/chat-list/examples/router.tsx +63 -0
- package/src/components/checkbox/COMPONENT.md +1 -0
- package/src/components/color-field/COMPONENT.md +2 -1
- package/src/components/color-picker/COMPONENT.md +14 -5
- package/src/components/color-picker/examples/presets-custom.tsx +31 -0
- package/src/components/color-swatches/COMPONENT.md +1 -0
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/data-table/COMPONENT.md +7 -2
- package/src/components/data-table/examples/numeric.tsx +9 -2
- package/src/components/datepicker/COMPONENT.md +3 -1
- package/src/components/datepicker/examples/deadline.tsx +33 -0
- package/src/components/digit-input/COMPONENT.md +1 -0
- package/src/components/drawer/COMPONENT.md +15 -2
- package/src/components/drawer/examples/client-card.tsx +32 -8
- package/src/components/field-list/COMPONENT.md +15 -3
- package/src/components/field-list/examples/saving.tsx +83 -0
- package/src/components/field-list/examples/totals.tsx +82 -0
- package/src/components/file-upload/COMPONENT.md +1 -0
- package/src/components/hint/COMPONENT.md +16 -2
- package/src/components/hint/examples/variants.tsx +48 -0
- package/src/components/input/COMPONENT.md +39 -9
- package/src/components/input/examples/affixes.tsx +4 -2
- package/src/components/input/examples/dismiss.tsx +29 -0
- package/src/components/input/examples/suggestions.tsx +26 -0
- package/src/components/input/examples/time.tsx +24 -0
- package/src/components/input/examples/with-select.tsx +44 -0
- package/src/components/kanban/COMPONENT.md +6 -6
- package/src/components/kanban/examples/columns.tsx +4 -2
- package/src/components/list/COMPONENT.md +23 -7
- package/src/components/list/examples/row-controls.tsx +80 -0
- package/src/components/list/examples/with-icon.tsx +23 -2
- package/src/components/login-form/COMPONENT.md +8 -7
- package/src/components/login-form/examples/states.tsx +12 -14
- package/src/components/message/COMPONENT.md +30 -9
- package/src/components/message/examples/preview.tsx +28 -0
- package/src/components/message/examples/sticker.tsx +35 -0
- package/src/components/modal/COMPONENT.md +15 -3
- package/src/components/modal/examples/long-content.tsx +37 -18
- package/src/components/native-select/COMPONENT.md +1 -0
- package/src/components/notification/COMPONENT.md +1 -1
- package/src/components/radio/COMPONENT.md +2 -0
- package/src/components/rich-text/COMPONENT.md +93 -0
- package/src/components/rich-text/examples/overview.tsx +19 -0
- package/src/components/scroll-container/COMPONENT.md +8 -5
- package/src/components/segmented-control/COMPONENT.md +1 -0
- package/src/components/segmented-control/examples/in-form.tsx +1 -1
- package/src/components/select/COMPONENT.md +7 -3
- package/src/components/slider/COMPONENT.md +1 -0
- package/src/components/slider/examples/controlled.tsx +1 -1
- package/src/components/switch/COMPONENT.md +1 -0
- package/src/components/tag-select/COMPONENT.md +1 -0
- package/src/components/textarea/COMPONENT.md +1 -0
- package/src/components/timeline/COMPONENT.md +3 -3
- package/src/components/timeline/examples/structure.tsx +2 -1
- package/src/layout/app-shell/COMPONENT.md +2 -2
- package/src/layout/bottom-nav/COMPONENT.md +1 -0
- package/src/layout/page/COMPONENT.md +37 -8
- package/src/layout/page/examples/examples.module.css +7 -0
- package/src/layout/page/examples/guard.tsx +87 -0
- package/src/layout/page/examples/loading.tsx +57 -0
- package/src/layout/sidebar/COMPONENT.md +1 -0
- package/src/styles/theme-light.css +1 -0
- package/dist/components/scroll-container/useWheelToInline.d.ts +0 -10
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# RichText
|
|
2
|
+
|
|
3
|
+
**Category:** data-display
|
|
4
|
+
**Kind:** primitive
|
|
5
|
+
|
|
6
|
+
> Text in the chat's Markdown subset outside a message — a bot's welcome text, a template, a comment — rendered as elements, never as HTML.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
- A text written with the formatting toolbar of `Composer` or `Textarea formatting` is shown somewhere other than a chat: a bot's settings, a template list, a card.
|
|
10
|
+
- Text from users or bots that may carry `**bold**`, links or a `||spoiler||` and must be rendered safely.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
- The text of a chat message → [Message](../message/COMPONENT.md) `Message.Text` (it marks search matches and holds the meta's room).
|
|
14
|
+
- A whole message shown outside a chat (a bot's welcome message with its keyboard) → `Message.Root preview`.
|
|
15
|
+
- Plain text with a role → [Typography](../typography/COMPONENT.md).
|
|
16
|
+
- Source code → [CodeBlock](../code-block/COMPONENT.md).
|
|
17
|
+
|
|
18
|
+
## Import
|
|
19
|
+
```tsx
|
|
20
|
+
import { RichText } from "prime-ui-kit";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Anatomy
|
|
24
|
+
```
|
|
25
|
+
RichText <div>; the source as children
|
|
26
|
+
├─ <p> a run of text, line breaks kept
|
|
27
|
+
├─ <blockquote> lines starting with "> "
|
|
28
|
+
└─ <pre> fenced code
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
34
|
+
|
|
35
|
+
### RichText
|
|
36
|
+
`ref` → `HTMLDivElement`. Text in the chat's Markdown subset outside a message, rendered as elements in the type of its host.
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `children` | `string` | — (required) | The source: `**bold**`, `_italic_`, `~~strike~~`, `\|\|spoiler\|\|`, inline and fenced code, links (`[label]` then the address in parentheses), bare URLs, `> ` quotes; line breaks are kept. |
|
|
41
|
+
| `labels` | `Partial<RichTextLabels>` | — | Built-in strings, see Labels. |
|
|
42
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "children">` | — | `className`, `id`, `data-*` and the other div attributes. |
|
|
43
|
+
|
|
44
|
+
## Variants
|
|
45
|
+
One look: the type of its host. Wrap it in `Typography as="div"` to pick a text role.
|
|
46
|
+
|
|
47
|
+
## States
|
|
48
|
+
| State | Driven by | DOM |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| masked spoiler | `\|\|text\|\|` in `children` | `role="button"`, `data-state="masked"`: a wash over transparent words |
|
|
51
|
+
| revealed spoiler | a press, `Enter` or `Space` | `data-state="revealed"`: the wash and the words cross-fade on the fade clock; one way, like a chat |
|
|
52
|
+
|
|
53
|
+
## Layout & spacing
|
|
54
|
+
- Paragraphs, quotes and code blocks keep their line breaks and wrap anywhere, so a long address never widens the host; blocks stand 4px apart.
|
|
55
|
+
- A quote is a wash with an accent bar on its start edge; code is the mono face on a wash.
|
|
56
|
+
- No size of its own: the root takes the font and color of its host.
|
|
57
|
+
|
|
58
|
+
## Accessibility
|
|
59
|
+
|
|
60
|
+
### Keyboard
|
|
61
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
62
|
+
|
|
63
|
+
| Key | Action |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `Tab` | Moves through the links and the masked spoilers. |
|
|
66
|
+
| `Enter` · `Space` | On a masked spoiler: reveals it and moves the focus onto its text. |
|
|
67
|
+
|
|
68
|
+
### ARIA
|
|
69
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
70
|
+
|
|
71
|
+
- A masked spoiler is a `role="button"` named `labels.spoiler`; its words are out of the accessibility tree until it is revealed.
|
|
72
|
+
- Links take only http(s), `mailto:` and `tel:` addresses and open in a new tab (`rel="noopener noreferrer"`); markup is never parsed as HTML.
|
|
73
|
+
|
|
74
|
+
### Labels
|
|
75
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
76
|
+
|
|
77
|
+
| Key | Default | Used for |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `spoiler` | `"Скрытый текст"` | The name of a masked spoiler until it is revealed. |
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
| Example | Shows |
|
|
83
|
+
|---|---|
|
|
84
|
+
| [overview.tsx](examples/overview.tsx) | The welcome text of a support bot shown outside the chat: bold, a link, a quote, code and a spoiler from the chat's Markdown — `children`. |
|
|
85
|
+
|
|
86
|
+
## Mistakes
|
|
87
|
+
- `dangerouslySetInnerHTML` with a Markdown library for a chat text → `RichText`: the same subset as the chat, never HTML.
|
|
88
|
+
- A `Message.Root` with no time and no author only to render formatted text → `RichText`.
|
|
89
|
+
- Sizing the text with a class on `RichText` → put it inside the host that sets the type.
|
|
90
|
+
|
|
91
|
+
## Related
|
|
92
|
+
- **Built from:** `internal/richText` (the parser and the elements, shared with `Message.Text`)
|
|
93
|
+
- **See also:** [Message](../message/COMPONENT.md), [Composer](../composer/COMPONENT.md), [Textarea](../textarea/COMPONENT.md), [Typography](../typography/COMPONENT.md)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The welcome text of a support bot shown outside the chat: bold, a link, a quote, code and a spoiler from the chat's Markdown — `children`. */
|
|
2
|
+
import { Card, RichText } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
const WELCOME = [
|
|
5
|
+
"**Здравствуйте!** Это бот поддержки «Прайм».",
|
|
6
|
+
"Счета и акты — в [личном кабинете](https://prime.app/billing), номер договора начинается с `PR-`.",
|
|
7
|
+
"> Отвечаем в рабочие дни с 9:00 до 19:00 по Москве.",
|
|
8
|
+
"Код для входа в приложение: ||4821||",
|
|
9
|
+
].join("\n");
|
|
10
|
+
|
|
11
|
+
export default function RichTextOverviewExample() {
|
|
12
|
+
return (
|
|
13
|
+
<Card.Root>
|
|
14
|
+
<Card.Body>
|
|
15
|
+
<RichText>{WELCOME}</RichText>
|
|
16
|
+
</Card.Body>
|
|
17
|
+
</Card.Root>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -44,7 +44,7 @@ ScrollContainer <div> (or the `as` tag); thin scrollbar, soft hidden edges
|
|
|
44
44
|
| `edges` | `"fade" \| "shadow" \| "none"` | `"fade"` | Edges that hide content: `fade` dissolves them over any background; `shadow` — a soft shadow on the region's own fill (a floating panel, a code well); `none` — plain edges. |
|
|
45
45
|
| `scrollbar` | `"thin" \| "hidden"` | `"thin"` | `thin` — the kit's quiet scrollbar; `hidden` — no scrollbar, keep `edges` on so the overflow stays visible. |
|
|
46
46
|
| `lock` | `boolean` | `false` | The person cannot scroll the region (wheel, touch, keys, a drag's auto-scroll); code still can. Sets `data-lock`. |
|
|
47
|
-
| `snap` | `boolean \| "mandatory"` | `false` |
|
|
47
|
+
| `snap` | `boolean \| "mandatory"` | `false` | Once a scroll ends, the region glides to the start of a child: `true` near one (strips of tiles), `"mandatory"` always (board columns, a carousel). Native scrolling meanwhile; any input stops the glide. |
|
|
48
48
|
| `anchor` | `"start" \| "end"` | `"start"` | `end` — a chat or a log: opens at the end and follows new content while the person is at the end; scrolled away, the view holds still. |
|
|
49
49
|
| `onReachStart` | `() => void \| Promise<void>` | — | Fires within 120 px of the start, once per approach: load older content. A returned promise blocks repeats and sets `aria-busy`; content added above keeps the view. |
|
|
50
50
|
| `onReachEnd` | `() => void \| Promise<void>` | — | The scroll position came within 120 px of the end: load the next page. Same once-per-approach and promise rules as `onReachStart`. |
|
|
@@ -93,11 +93,12 @@ Only the reader scrolling back unpins the end: content that keeps growing (an an
|
|
|
93
93
|
| Value | Looks like | Use when | Default |
|
|
94
94
|
|---|---|---|---|
|
|
95
95
|
| `lock` | nothing the person does scrolls the region (wheel, touch, keys, a drag's edge auto-scroll); the position stays, and code may still set it | a list behind an open review, a gesture that must not move it | off |
|
|
96
|
-
| `snap` | a scroll
|
|
97
|
-
| `snap="mandatory"` | a scroll always
|
|
96
|
+
| `snap` | once a scroll ends near the start of a direct child (within 30% of the view), the region glides there; a host marks deeper items with `scroll-snap-align` itself | strips of tiles or cards | off |
|
|
97
|
+
| `snap="mandatory"` | once a scroll ends, the region always glides to the nearest start of a child | board columns on a phone, a carousel of cards | off |
|
|
98
98
|
|
|
99
99
|
### Wheel and auto-scroll
|
|
100
|
-
-
|
|
100
|
+
- Snapping never takes over the scroll: the trackpad, touch, the scrollbar and the keys scroll natively with their momentum, and the browser does not snap (no `scroll-snap-type` — it damps a trackpad's momentum and pulls a scroll near the end back to merged rest points). Once the scroll ends — no movement for a moment, nothing held — the region glides to its rest point on the kit's own frames, and any wheel, touch, press or key stops that glide at once; it settles again when the input goes quiet. A Dnd drag inside never settles.
|
|
101
|
+
- `axis="horizontal"`: a vertical mouse wheel (whole notches; a trackpad stays native) scrolls the strip sideways while it can move — by the wheel's distance, or a rest point per notch on a strip that snaps — on the kit's frames (instant under reduced motion), each notch from where the glide is heading, so a turn the other way goes back mid-glide. Shift + wheel stays native; a wheel gesture that began outside the strip keeps scrolling the page; at the strip's edge the wheel chains to the page. There is no drag-to-scroll: a press-and-drag in a strip belongs to its items (reordering tiles, tabs); touch pans natively.
|
|
101
102
|
- A gesture held near an edge — a Dnd drag, a Gantt bar moved or drawn, a drag that paints a selection over table rows or messages — scrolls the region toward it, faster the closer the pointer (`src/internal/edgeAutoScroll`, one speed curve for the kit). A `lock`ed region or one whose content fits never moves.
|
|
102
103
|
|
|
103
104
|
### Edge events
|
|
@@ -116,7 +117,7 @@ Each fires once per approach — again after the position leaves the zone or the
|
|
|
116
117
|
| edge treatment | `edges` | `data-edges="fade" \| "shadow"` (none for `none`) |
|
|
117
118
|
| content hidden at an edge | scroll position (with `edges="fade"`), measured at most once per frame on scroll, on resize of the region or a child and when children are added; only the scroll axes; logical in RTL | `data-overflow-top`, `data-overflow-bottom`, `data-overflow-start`, `data-overflow-end` = `"true"`; `--edge-*` depths written only while they change (within the last fade depth of an edge, `--scroll-fade-depth`), on non-inherited properties |
|
|
118
119
|
| locked | `lock` | `data-lock`, `overflow: hidden` |
|
|
119
|
-
| snapping | `snap` | `data-snap="proximity" \| "mandatory"
|
|
120
|
+
| snapping | `snap` | `data-snap="proximity" \| "mandatory"`; `scroll-snap-align: start` on the children marks the rest points (no `scroll-snap-type`); no settling while a Dnd list inside carries an item (`data-dnd-carrying`) |
|
|
120
121
|
| kit anchoring | `anchor="end"` or `onReachStart` | `data-anchor="end" \| "start"`, `overflow-anchor: none`: the kit keeps the visible entry in place (or the end, while pinned) before paint, the same in every browser |
|
|
121
122
|
| pinned to the end | `anchor="end"` and the position within 4 px of the end | content changes scroll to the new end at once (high-frequency: no animation) |
|
|
122
123
|
| loading at an edge | a pending promise from `onReachStart` / `onReachEnd` | `aria-busy="true"` |
|
|
@@ -176,6 +177,8 @@ No `labels`.
|
|
|
176
177
|
- `edges="fade"` on a region that paints its own fill or shadow → the mask dissolves them; use `edges="shadow"`.
|
|
177
178
|
- A hand-made edge auto-scroll loop for a drag → `createEdgeAutoScroll` from `src/internal/edgeAutoScroll`.
|
|
178
179
|
- `snap` together with `anchor="end"` → snapping fights the pin to the end.
|
|
180
|
+
- `scroll-snap-type` on a region or its host → `snap`: the browser's snapping damps a trackpad's momentum and pulls a scroll near the end back.
|
|
181
|
+
- `snap="mandatory"` on a strip of children narrower than the view (a wide board) → their rest points merge at the end; snap only where each child fills most of the view, or use `snap`.
|
|
179
182
|
- Wrapping a Modal body or Dropdown panel in another ScrollContainer → they already scroll.
|
|
180
183
|
- `scrollTop = scrollHeight` in an effect after every message → `anchor="end"` (it also leaves a reader who scrolled up alone).
|
|
181
184
|
- `flex-direction: column-reverse` to keep a chat at the bottom → it reverses the reading and Tab order; use `anchor="end"`.
|
|
@@ -53,6 +53,7 @@ The root is a field root: `className`, `ref` and the native attributes land on t
|
|
|
53
53
|
| `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the `radiogroup`. |
|
|
54
54
|
| `optional` | `boolean` | `false` | Muted marker after the label (`labels.optional`). |
|
|
55
55
|
| `hint` | `ReactNode` | — | Help text under the control (`aria-describedby`). Hidden while `error` is shown. |
|
|
56
|
+
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
56
57
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
57
58
|
| `invalid` | `boolean` | `false` | Danger inset ring, `aria-invalid` and `data-invalid` on the `radiogroup`; the control shakes once when an error arrives. |
|
|
58
59
|
| `reserveSupportRow` | `boolean` | `false` | Always render the support row, so an appearing error does not shift the layout. |
|
|
@@ -15,7 +15,7 @@ export default function SegmentedControlInFormExample() {
|
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<form className={styles.form} noValidate onSubmit={submit}>
|
|
18
|
-
<Input.Root label="
|
|
18
|
+
<Input.Root label="Сумма" required>
|
|
19
19
|
<Input.Wrapper>
|
|
20
20
|
<Input.Field name="amount" inputMode="decimal" defaultValue="12 400" />
|
|
21
21
|
<Input.InlineAffix side="end">₽</Input.InlineAffix>
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
- A value out of thousands of records searched on the server (`search`, `onSearchChange`, `filter={false}`, `hasMore` + `onLoadMore`).
|
|
12
12
|
- A few values from a short list shown as comma-separated text (`multiple`).
|
|
13
13
|
- Options that need a picture, an avatar, a second line, a badge or a trailing value (rich options).
|
|
14
|
+
- A pick that belongs to a text field (an icon before a name): put the Select inside `Input.Wrapper` — it becomes the box's start segment, named by its `aria-label`.
|
|
14
15
|
|
|
15
16
|
## When not to use
|
|
16
17
|
- 2–5 options that should all be visible → use [Radio](../radio/COMPONENT.md) or [SegmentedControl](../segmented-control/COMPONENT.md).
|
|
17
18
|
- The OS picker on a mobile-first form → use [NativeSelect](../native-select/COMPONENT.md).
|
|
18
19
|
- Many values shown as removable tags, or creating new values → use [TagSelect](../tag-select/COMPONENT.md).
|
|
19
|
-
- Free text → use [Input](../input/COMPONENT.md).
|
|
20
|
+
- Free text, even with suggestions → use [Input](../input/COMPONENT.md) (`suggestions`).
|
|
20
21
|
- A menu of actions (not a value) → use [Dropdown](../dropdown/COMPONENT.md).
|
|
21
22
|
- Global search / command palette → use [CommandMenu](../command-menu/COMPONENT.md).
|
|
22
23
|
- Dates → use [Datepicker](../datepicker/COMPONENT.md).
|
|
@@ -60,7 +61,7 @@ an icon) goes inside `Select.ItemMeta`.
|
|
|
60
61
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
61
62
|
|
|
62
63
|
### Select.Root
|
|
63
|
-
`ref` → `HTMLDivElement`. The field frame (label row · control · support row) holding the value, the open state and the search query.
|
|
64
|
+
`ref` → `HTMLDivElement`. The field frame (label row · control · support row) holding the value, the open state and the search query. Inside `Input.Wrapper` it is the box's start segment instead: no frame, the trigger named by `aria-label` and the value.
|
|
64
65
|
|
|
65
66
|
| Prop | Type | Default | Description |
|
|
66
67
|
|---|---|---|---|
|
|
@@ -75,6 +76,7 @@ an icon) goes inside `Select.ItemMeta`.
|
|
|
75
76
|
| `closeOnOutsideClick` | `boolean` | `true` | A press outside the list and its trigger closes it; focus follows the pointer. |
|
|
76
77
|
| `label` | `ReactNode` | — | Field label above; names the trigger. |
|
|
77
78
|
| `hint` | `ReactNode` | — | Support text under the field; linked by `aria-describedby`. |
|
|
79
|
+
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
78
80
|
| `error` | `ReactNode` | — | Error message; replaces the hint and implies `invalid`. |
|
|
79
81
|
| `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the trigger. |
|
|
80
82
|
| `optional` | `boolean` | — | Muted `labels.optional` after the label. |
|
|
@@ -224,6 +226,7 @@ No `variant` or `tone`. The trigger is the field look (fill, inset control borde
|
|
|
224
226
|
| searching | typing in the search | panel `data-searching="true"`; groups without matches and separators hide; the match in an option's title, description and plain text is a `<mark>` in `accent-text` (not in the trigger); the query clears when the list closes (`onSearchChange("")`) |
|
|
225
227
|
| nothing to show | no option matches (and not loading) | a compact EmptyPage with `role="status"`: `labels.empty`, plus `labels.emptyHint` while a query is typed; a `Select.Action` stays under it, highlighted |
|
|
226
228
|
| sheet | viewport below 640px | a scrim and a full-width bottom sheet with an `aria-hidden` grab handle around the panel; the sheet carries `data-state`, the panel keeps its role and ref; page scroll locked; a swipe down from the handle closes it |
|
|
229
|
+
| segment | inside `Input.Wrapper` | no frame (label, hint, error, support row): the root is `data-field-segment`, the trigger `data-segment="true"` — a tinted section flush with the box's start edge, a stronger fill on hover and while open, an inset focus ring; named «`aria-label`: value» |
|
|
227
230
|
| option highlighted / selected / disabled | keyboard and pointer / value / `disabled` | `data-highlighted`, `data-selected` + `aria-selected`, `data-disabled` + `aria-disabled`; highlight is a fill only |
|
|
228
231
|
|
|
229
232
|
## Layout & spacing
|
|
@@ -350,8 +353,9 @@ No `variant` or `tone`. The trigger is the field look (fill, inset control borde
|
|
|
350
353
|
- `<ClientAvatar />` or `<RoleBadge />` (your own component) inside an item → the item reads parts by element type and puts an unknown component in the text column; call the helper as a function (`{clientAvatar(client)}`) so `Avatar.Root` / `Badge.Root` is the direct child.
|
|
351
354
|
- A custom trailing element (a Kbd, an icon with a price) as a bare child → wrap it in `Select.ItemMeta`.
|
|
352
355
|
- A «Новый клиент» row faked as a `Select.Item` with a sentinel value, or a button under the field → use `Select.Action`: never filtered, reached by the arrows, never a value.
|
|
356
|
+
- An icon-only Select set beside an Input for one value (an icon + a name) → put it inside `Input.Wrapper` with an `aria-label`; `label`, `hint` and `error` belong to the Input there.
|
|
353
357
|
- Saving the choice in `onValueChange` and resetting the value by hand on failure → return the promise; the Select shows the value, waits and rolls back by itself.
|
|
354
358
|
|
|
355
359
|
## Related
|
|
356
|
-
- **Built from:** [ScrollContainer](../scroll-container/COMPONENT.md) (list), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in `multiple`), [Spinner](../spinner/COMPONENT.md) (`loading` and saving in the trigger), [Skeleton](../skeleton/COMPONENT.md) (loading rows), [EmptyPage](../empty-page/COMPONENT.md) (empty state, load error), [Button](../button/COMPONENT.md) (retry), [Divider](../divider/COMPONENT.md) (`Separator`), [Thumbnail](../thumbnail/COMPONENT.md) and [Avatar](../avatar/COMPONENT.md) (row media), [Badge](../badge/COMPONENT.md) (row badge), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading and save-failure announcements), `Icon` (chevron, clear, check, search, retry), [Label](../label/COMPONENT.md) and [Hint](../hint/COMPONENT.md) (field frame)
|
|
360
|
+
- **Built from:** [ScrollContainer](../scroll-container/COMPONENT.md) (list), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in `multiple`), [Spinner](../spinner/COMPONENT.md) (`loading` and saving in the trigger), [Skeleton](../skeleton/COMPONENT.md) (loading rows), [EmptyPage](../empty-page/COMPONENT.md) (empty state, load error), [Button](../button/COMPONENT.md) (retry), [Divider](../divider/COMPONENT.md) (`Separator`), [Thumbnail](../thumbnail/COMPONENT.md) and [Avatar](../avatar/COMPONENT.md) (row media), [Badge](../badge/COMPONENT.md) (row badge), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading and save-failure announcements), `Icon` (chevron, clear, check, search, retry), [Label](../label/COMPONENT.md) and [Hint](../hint/COMPONENT.md) (field frame), [Input](../input/COMPONENT.md) (the start segment of `Input.Wrapper`)
|
|
357
361
|
- **See also:** [NativeSelect](../native-select/COMPONENT.md), [TagSelect](../tag-select/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md), [Radio](../radio/COMPONENT.md), [SegmentedControl](../segmented-control/COMPONENT.md)
|
|
@@ -59,6 +59,7 @@ Slider field frame (FieldFrame): className, ref, rest
|
|
|
59
59
|
| `required` | `boolean` | — | Red `*` after the label (`aria-hidden`); a range always has a value, so there is no native `required`. |
|
|
60
60
|
| `optional` | `boolean` | — | Muted marker right after the label text (`labels.optional`). |
|
|
61
61
|
| `hint` | `ReactNode` | — | Help text under the track (`aria-describedby`). Hidden while `error` is shown. |
|
|
62
|
+
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
62
63
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
63
64
|
| `invalid` | `boolean` | — | `aria-invalid` on the range input, a danger fill (over any `tone`) and a danger focus ring on the thumb. A non-empty `error` implies it. |
|
|
64
65
|
| `showValue` | `boolean` | `false` | Shows the current value at the end of the label row (tabular numbers, `aria-hidden` — the input announces it). |
|
|
@@ -46,6 +46,7 @@ Switch.Root field grid; ref and input props go to the native input (rol
|
|
|
46
46
|
| `optional` | `boolean` | `false` | Muted `labels.optional` after the label text; the field may stay off. |
|
|
47
47
|
| `labels` | `Partial<SwitchLabels>` | — | Built-in strings, see Labels. |
|
|
48
48
|
| `hint` | `ReactNode` | — | Help text under the label text. Hidden while `error` is shown. |
|
|
49
|
+
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
49
50
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
50
51
|
| `invalid` | `boolean` | `false` | Danger ring on the off track and `aria-invalid`. A non-empty `error` implies it. |
|
|
51
52
|
| `disabled` | `boolean` | `false` | No toggling; muted track (on = `accent-soft`), dimmed text and hint. |
|
|
@@ -60,6 +60,7 @@ TagSelect field frame: label · control · hint/error (sin
|
|
|
60
60
|
| `onOptionDelete` | `(value: string) => void \| Promise<void>` | — | Enables «Удалить» in the row menu; the value also leaves the selection. A returned Promise defers it: resolve deletes, reject keeps everything (e.g. after a confirmation). |
|
|
61
61
|
| `label` | `ReactNode` | — | Field label above; names the input. |
|
|
62
62
|
| `hint` | `ReactNode` | — | Support text under the field; linked by `aria-describedby`. |
|
|
63
|
+
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
63
64
|
| `error` | `ReactNode` | — | Error message; replaces the hint and implies `invalid`. |
|
|
64
65
|
| `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the input. |
|
|
65
66
|
| `optional` | `boolean` | — | Muted `labels.optional` after the label. |
|
|
@@ -46,6 +46,7 @@ Unlike Input, Textarea has no Wrapper / Field parts: `Textarea.Root` renders the
|
|
|
46
46
|
| `required` | `boolean` | — | Native `required` on the textarea and a red `*` after the label (`aria-hidden`). |
|
|
47
47
|
| `optional` | `boolean` | `false` | Muted marker right after the label text (`labels.optional`). |
|
|
48
48
|
| `hint` | `ReactNode` | — | Help text under the field. Hidden while `error` is shown. |
|
|
49
|
+
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
49
50
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
50
51
|
| `invalid` | `boolean` | `false` | Danger inset ring on the field box, `aria-invalid` on the textarea. A non-empty `error` implies it. |
|
|
51
52
|
| `focusRing` | `boolean` | `true` | `false` hides only the visual focus ring on the field box (`data-focus-ring="false"`); focus, keyboard, ARIA and the invalid ring stay. |
|
|
@@ -27,7 +27,7 @@ Timeline.Root size container, sets the tier (size, else the h
|
|
|
27
27
|
└─ Timeline.Group label labelled <ol role="list">; the line runs from its first dot to its last
|
|
28
28
|
├─ Timeline.Item <li> + row (div | button | a | asChild element) with the dot
|
|
29
29
|
│ ├─ Timeline.ItemTitle first line
|
|
30
|
-
│ ├─ Timeline.ItemMeta second line (date); <strong> emphasizes a part
|
|
30
|
+
│ ├─ Timeline.ItemMeta second line (date); <strong> emphasizes a part; repeats, lines stack
|
|
31
31
|
│ └─ Timeline.ItemValue trailing amount
|
|
32
32
|
│ └─ Timeline.ItemMeta second line under the amount
|
|
33
33
|
└─ Timeline.Gap <li>: interval between events (dashed segment, hollow dot)
|
|
@@ -73,7 +73,7 @@ Timeline.Root size container, sets the tier (size, else the h
|
|
|
73
73
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "children" \| "color" \| "onClick">` | — | The other row attributes (`target`, `rel`, `download` with `href`). |
|
|
74
74
|
|
|
75
75
|
### Timeline.ItemTitle · Timeline.ItemMeta
|
|
76
|
-
`ref` → `HTMLSpanElement`. `<span>` lines: the event (medium,
|
|
76
|
+
`ref` → `HTMLSpanElement`. `<span>` lines: the event (medium, wraps) and the muted secondary line, tabular — under the title the date (`<strong>` emphasizes a part; several `Timeline.ItemMeta` stack), inside `Timeline.ItemValue` the value's second line.
|
|
77
77
|
|
|
78
78
|
| Prop | Type | Default | Description |
|
|
79
79
|
|---|---|---|---|
|
|
@@ -180,7 +180,7 @@ No `labels`.
|
|
|
180
180
|
| [overview.tsx](examples/overview.tsx) | An operations feed: one labelled group, a line through the dots, the row under the pointer highlighted — `Timeline.Group`, `Timeline.ItemValue`. |
|
|
181
181
|
| [variants.tsx](examples/variants.tsx) | Dot hues for categories at reduced emphasis, and status tones on items, values and gaps at full emphasis — `color`, `tone`. |
|
|
182
182
|
| [sizes.tsx](examples/sizes.tsx) | Every tier scales text, dot and row rhythm, rows 40 to 76 px — `size`. |
|
|
183
|
-
| [structure.tsx](examples/structure.tsx) | A service history with intervals between events, a caption on the right of a gap and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. |
|
|
183
|
+
| [structure.tsx](examples/structure.tsx) | A service history with intervals between events, a caption on the right of a gap, two meta lines under a repair and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. |
|
|
184
184
|
| [links.tsx](examples/links.tsx) | Rows that open something: an operation page by a link or by your router link through the slot, a receipt in place by a button; one Tab stop each with a focus ring — `href`, `asChild`, `onClick`. |
|
|
185
185
|
| [narrow.tsx](examples/narrow.tsx) | At 375 px the title wraps and the amount stays right; below 20rem of its own width the amount moves under the meta line — `Timeline.ItemValue`. |
|
|
186
186
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A service history with intervals between events, a caption on the right of a gap and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. */
|
|
1
|
+
/** A service history with intervals between events, a caption on the right of a gap, two meta lines under a repair and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. */
|
|
2
2
|
import { Timeline } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
@@ -36,6 +36,7 @@ export default function TimelineStructureExample() {
|
|
|
36
36
|
<Timeline.ItemMeta>
|
|
37
37
|
<strong>07.06.26</strong> · 12 100 км
|
|
38
38
|
</Timeline.ItemMeta>
|
|
39
|
+
<Timeline.ItemMeta>Мастер Олег Смирнов · гарантия 6 месяцев</Timeline.ItemMeta>
|
|
39
40
|
<Timeline.ItemValue>
|
|
40
41
|
2 800 ₽<Timeline.ItemMeta>Ремонт</Timeline.ItemMeta>
|
|
41
42
|
</Timeline.ItemValue>
|
|
@@ -126,8 +126,8 @@ The navigation of the nearest `AppShell.Root` for a trigger outside the rail (a
|
|
|
126
126
|
|
|
127
127
|
## Layout & spacing
|
|
128
128
|
- 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).
|
|
129
|
-
- Main hands the page its edge air: `--page-gutter` is the page gutter `--prime-layout-gutter` — `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px; `--page-top` is `--prime-space-6` (24) → `--prime-space-8` (32) from 1024px. `Page.Root` pads itself with them (its top bar steps out to the panel's edges). Main itself pads only the bottom: `--prime-space-16
|
|
130
|
-
- A page that fills the panel height (`Page.Root fill`): Main's bottom padding drops to the side gutter, so no long scroll tail stays under it.
|
|
129
|
+
- Main hands the page its edge air: `--page-gutter` is the page gutter `--prime-layout-gutter` — `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px; `--page-top` is `--prime-space-6` (24) → `--prime-space-8` (32) from 1024px. `Page.Root` pads itself with them (its top bar steps out to the panel's edges). Main itself pads only the bottom: `--prime-space-16`, the scroll tail of a long page. Inside Main the page gutter is the small one again, so a frame nested in the page starts from its own gutter, not the shell's.
|
|
130
|
+
- A page that fills the panel height (`Page.Root fill`): Main's bottom padding drops to the side gutter, so no long scroll tail stays under it — beside the Sidebar rail too.
|
|
131
131
|
- Main scrolls with `overscroll-behavior: contain`, so its scroll never chains to the document.
|
|
132
132
|
- Nav column: sticky at the top, viewport high, `z-index: --prime-z-sticky` (above the panel, so the compact Sidebar toggle can ride the rail edge); with `fillViewport` it fills the shell height instead.
|
|
133
133
|
- Footer: no padding of its own (BottomNav keeps the bottom safe-area inset), sticky at the bottom, `z-index: --prime-z-sticky`.
|
|
@@ -107,6 +107,7 @@ Without flags the bar is flat at the screen edge on the surface, with a faint to
|
|
|
107
107
|
| State | Driven by | DOM |
|
|
108
108
|
|---|---|---|
|
|
109
109
|
| current | `current` or a router link setting `aria-current` | `aria-current="page"` on the item; flat, its glyph on a `fill-muted` pill |
|
|
110
|
+
| held | a `Page.Guard` that is `busy` or `dirty` on the page | a press on an item stops before it navigates (a router link included); `dirty` asks first, then repeats it |
|
|
110
111
|
| pressed | pointer down | the glyph scales to `--prime-motion-press-scale-compact` (`internal/press`), flat and floating |
|
|
111
112
|
| overflow | items wider than the bar | the items scroll inside the bar with faded edges; no label is cut |
|
|
112
113
|
| focus | keyboard | focus ring inside the item edge |
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- A new topic of the page with its own heading and a link or button beside it (`Page.Section`); a settings page with a heading column beside each panel (`layout="aside"`).
|
|
16
16
|
- A page whose body takes the rest of the panel's height — a log table, a board, a chat (`fill`).
|
|
17
17
|
- Mail, a messenger, a ticket queue: a list and the chosen record side by side, one at a time on a narrow panel (`Page.Split`).
|
|
18
|
+
- A form that is the whole page (a record's editor, settings with one save): `Page.Guard dirty busy` holds leaving it through the navigation, the path and the browser.
|
|
18
19
|
|
|
19
20
|
## When not to use
|
|
20
21
|
- The app frame (navigation rail, scrolling main, phone footer) → use [AppShell](../app-shell/COMPONENT.md); there is no global app header — a bar belongs to its page.
|
|
@@ -33,6 +34,7 @@ import { Page } from "prime-ui-kit";
|
|
|
33
34
|
## Anatomy
|
|
34
35
|
```
|
|
35
36
|
Page.Root the page column (<div>); maxWidth, fill, labels
|
|
37
|
+
├─ Page.Guard no DOM: holds leaving the page; busy, dirty (anywhere inside)
|
|
36
38
|
├─ Page.TopBar <header> strip at the top edge, sticky; sticky, collapse
|
|
37
39
|
│ │ (+ the menu button while the Sidebar is off-canvas)
|
|
38
40
|
│ │ groups in any JSX order, rendered in this order:
|
|
@@ -43,7 +45,8 @@ Page.Root the page column (<div>); maxWidth, fill, labe
|
|
|
43
45
|
│ ├─ Page.Tools the page's controls in the order written, one wrapper each
|
|
44
46
|
│ │ ├─ SegmentedControl.Root sections, view (fullWidth unless set)
|
|
45
47
|
│ │ ├─ SmartFilter.Toolbar search and filter: grows first on a line of its own
|
|
46
|
-
│ │
|
|
48
|
+
│ │ ├─ Datepicker.Root a period (fullWidth unless set)
|
|
49
|
+
│ │ └─ Select.Root … a period, columns; the trigger fills its place
|
|
47
50
|
│ ├─ Page.Actions every button of the page; icons, then «⋯» when short
|
|
48
51
|
│ └─ Page.Chips SmartFilter.Chips on a line under everything
|
|
49
52
|
├─ Page.Header the document header in the flow
|
|
@@ -114,10 +117,11 @@ Page.Root the page column (<div>); maxWidth, fill, labe
|
|
|
114
117
|
|
|
115
118
|
| Prop | Type | Default | Description |
|
|
116
119
|
|---|---|---|---|
|
|
120
|
+
| `loading` | `boolean` | `false` | Its data is loading: a Skeleton keeps the line's place, so the title never moves; the data then fades in. Sets `aria-busy`. |
|
|
117
121
|
| `…rest` | `HTMLAttributes<HTMLParagraphElement>` | — | `children`, `className` and the other paragraph attributes. |
|
|
118
122
|
|
|
119
123
|
### Page.Tools
|
|
120
|
-
`ref` → `HTMLDivElement`. The page's tools
|
|
124
|
+
`ref` → `HTMLDivElement`. The page's tools in the order written: sections, `SmartFilter.Toolbar`, view. On a line of their own a search grows first, the rest share the line: a `SegmentedControl.Root` and a `Datepicker.Root` are `fullWidth` unless set. Named, a `group`.
|
|
121
125
|
|
|
122
126
|
| Prop | Type | Default | Description |
|
|
123
127
|
|---|---|---|---|
|
|
@@ -155,14 +159,14 @@ Page.Root the page column (<div>); maxWidth, fill, labe
|
|
|
155
159
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content; `Page.Actions` goes to the heading row), `className` and the other section attributes. |
|
|
156
160
|
|
|
157
161
|
### Page.Grid
|
|
158
|
-
`ref` → `HTMLDivElement`. The grid of the page's tiles (KPI cards, charts, panels): 12 columns, 16 apart on both axes, so its rows read as one board. A size container: cells fold by its own width (see Grid columns).
|
|
162
|
+
`ref` → `HTMLDivElement`. The grid of the page's tiles (KPI cards, charts, panels): 12 columns, 16 apart on both axes, so its rows read as one board. A size container: cells fold by its own width, a wide cell next to a narrow one pairs up (see Grid columns).
|
|
159
163
|
|
|
160
164
|
| Prop | Type | Default | Description |
|
|
161
165
|
|---|---|---|---|
|
|
162
166
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (`Page.Cell`), `className` and the other div attributes. |
|
|
163
167
|
|
|
164
168
|
### Page.Cell
|
|
165
|
-
`ref` → `HTMLDivElement`. One tile place in `Page.Grid`; its child (a Card, a ChartFrame) stretches to it, so the tiles of a row share one height.
|
|
169
|
+
`ref` → `HTMLDivElement`. One tile place in `Page.Grid`; its child (a Card, a ChartFrame) stretches to it, so the tiles of a row share one height; a `Crossfade` child gets `fill` unless it sets it.
|
|
166
170
|
|
|
167
171
|
| Prop | Type | Default | Description |
|
|
168
172
|
|---|---|---|---|
|
|
@@ -170,6 +174,14 @@ Page.Root the page column (<div>); maxWidth, fill, labe
|
|
|
170
174
|
| `rows` | `1 \| 2 \| 3 \| 4` | `1` | Rows the cell spans from 40rem: a chart beside two stacked tiles (`ChartFrame.Plot height="fill"` fills the height). |
|
|
171
175
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (one tile), `className` and the other div attributes. |
|
|
172
176
|
|
|
177
|
+
### Page.Guard
|
|
178
|
+
No DOM, no ref. Holds the page from anywhere inside it: a press on a `Sidebar.Item`, `BottomNav.Item` or `Breadcrumb.Item` link (a router link too) stops before it navigates, and the browser asks before the tab closes. Renders only its `ConfirmDialog`.
|
|
179
|
+
|
|
180
|
+
| Prop | Type | Default | Description |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| `busy` | `boolean` | `false` | A request runs (a save): a press on those items does nothing until it ends; the browser asks before the tab closes. |
|
|
183
|
+
| `dirty` | `boolean` | `false` | Changes are not saved: a press on those items asks «leave without saving?»; its confirm (`labels.leaveConfirm`) repeats the press, its cancel keeps the page. The browser asks before the tab closes. |
|
|
184
|
+
|
|
173
185
|
### Page.Split
|
|
174
186
|
`ref` → `HTMLDivElement`. List + detail (mail, a messenger, a ticket queue): side by side from 48rem of its own width, one pane at a time below it, focus following the pane that comes into view.
|
|
175
187
|
|
|
@@ -224,6 +236,8 @@ The size follows the place; the consumer passes nothing, and there are no title
|
|
|
224
236
|
| `fill` (Root) | the page is as high as main; the body takes what the bar and the header leave; main keeps gutter-high air under it | a full-height table, board, chat or `Page.Split` in an `AppShell.Root fillViewport` | `false` |
|
|
225
237
|
| `sticky` (TopBar) | the bar stays at the top of main while the page scrolls under it | every page; `false` for a bar that should scroll away | `true` |
|
|
226
238
|
| `collapse` (TopBar) | the flow title rises into the bar's title row once it scrolls away | a document page or an overview with the title in the flow | `false` |
|
|
239
|
+
| `dirty` (Guard) | nothing until a person leaves: a press on the navigation or the path asks «leave without saving?» | a form that is the page has unsaved changes | `false` |
|
|
240
|
+
| `busy` (Guard) | nothing until a person leaves: a press on the navigation or the path does nothing | the page's save request runs | `false` |
|
|
227
241
|
|
|
228
242
|
### layout (Section)
|
|
229
243
|
| Value | Looks like | Use when | Default |
|
|
@@ -237,7 +251,7 @@ One plan, chosen from the natural widths of the groups (`data-plan` on the bar's
|
|
|
237
251
|
| Step | Looks like | Use when | Default |
|
|
238
252
|
|---|---|---|---|
|
|
239
253
|
| `one` | `[Heading 1fr][Tools][Actions]` on one line | everything fits | yes |
|
|
240
|
-
| `below` | the tools on a line of their own under the heading and the actions, filling it: a search (`SmartFilter.Toolbar`, `Input.Root`) grows first, switchers share the rest and wrap when one line is not enough | heading + tools + actions do not fit | |
|
|
254
|
+
| `below` | the tools on a line of their own under the heading and the actions, filling it: a search (`SmartFilter.Toolbar`, `Input.Root`) grows first, switchers and fields (`SegmentedControl.Root`, `Datepicker.Root`, a Select) share the rest and wrap when one line is not enough | heading + tools + actions do not fit | |
|
|
241
255
|
| `icons-secondary` | as `below`; secondary Buttons with a `Button.Icon` show only the icon, the label is their name and their tooltip | heading + actions still do not fit | |
|
|
242
256
|
| `icons` | the primary Button gives way to its icon too | still too wide | |
|
|
243
257
|
| `fold` | the secondary Buttons fold into «⋯» (`labels.more`); the primary stays | still too wide, and «⋯» is narrower than their icons (a single secondary keeps its icon); past it a Breadcrumb path folds into «…»; if the path and the title still do not share the line, the heading stacks: the path on its own line over the title, the title and the description wrapping (`data-heading="stack"`); a heading without a path truncates | |
|
|
@@ -249,9 +263,11 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
249
263
|
|---|---|---|---|---|---|
|
|
250
264
|
| < 30rem | the row | the row | the row | the row | — |
|
|
251
265
|
| 30–40rem | half | the row | the row | the row | — |
|
|
252
|
-
| 40–64rem | half | a third | half | the row | spans |
|
|
266
|
+
| 40–64rem | half | a third | half (a third in a pair) | the row (2/3 in a pair) | spans |
|
|
253
267
|
| ≥ 64rem | its span | its span | its span | its span | spans |
|
|
254
268
|
|
|
269
|
+
A wide cell (`span` 7–11) written next to a narrow one (`span` 4–5) is a pair: on a 40–64rem grid it takes 2/3 and its neighbour 1/3, so an 8 + 4 row keeps no hole. Cells pair in the order written, two at a time; a `span` 12 never pairs. The flow is `dense`: a small cell fills a hole left by a wide one.
|
|
270
|
+
|
|
255
271
|
### Split layout
|
|
256
272
|
| Value | Looks like | Use when | Default |
|
|
257
273
|
|---|---|---|---|
|
|
@@ -262,6 +278,7 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
262
278
|
| State | Driven by | DOM |
|
|
263
279
|
|---|---|---|
|
|
264
280
|
| capped page | `maxWidth` | `data-max-width="readable" \| "wide"` on Root (absent for `full`) |
|
|
281
|
+
| line of data loading | `loading` on Description | a Skeleton in the line (its height kept, so the title never moves), `aria-busy`; the data fades in under the same element |
|
|
265
282
|
| filling | `fill` | `data-fill="true"` on Root; `AppShell.Main` drops its long bottom air for it |
|
|
266
283
|
| sticky bar | `sticky` | `data-sticky="true"` on TopBar |
|
|
267
284
|
| title repeated in the bar | `collapse` and the flow title out of view | `data-collapsed="true"` on TopBar; the echo rises in (`transform` on the fast movement clock, `opacity` on the fast fade; reduced motion keeps the fade). After a `Breadcrumb` path the echo is the path's last level (an `aria-hidden`, `inert` `Breadcrumb.Item`), so the path folds its levels into «…» for it (`data-collapsed` on the Breadcrumb) and the title stays readable; a path whose last level already reads as the title (the same words, however spaced or cased) gets no echo |
|
|
@@ -273,12 +290,14 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
273
290
|
| with actions | `Page.Actions` child | `data-has-actions="true"` on Header / the section heading row |
|
|
274
291
|
| section heading column | `layout="aside"` on Section | `data-layout="aside"` on Section (absent for `stack`) |
|
|
275
292
|
| tile span | `span` / `rows` on Cell | `data-span="1"…"12"` (always), `data-rows="2"…"4"` (absent for 1) |
|
|
293
|
+
| tile pair | a `span` 7–11 Cell next to a `span` 4–5 Cell in the Grid | `data-pair="wide" \| "narrow"` on both Cells (set by the Grid) |
|
|
294
|
+
| held | `Page.Guard` `busy` or `dirty` | a press on a `Sidebar.Item`, `Sidebar.Brand`, `BottomNav.Item` or `Breadcrumb.Item` link is stopped (capture phase, before the item's and the router link's handlers); `dirty` opens the `ConfirmDialog` and its confirm repeats the press once the dialog has closed (its texts are the `leave*` keys of `labels` on `Page.Root`); a `beforeunload` listener while either is on. A press that opens a new tab (a modifier key, a middle click, `target="_blank"`) and navigation from code are never held |
|
|
276
295
|
| detail open | `open` / `defaultOpen` on Split | `data-open="true"` on Split; narrow: the list is `display: none`, else the detail is |
|
|
277
296
|
| pane swap | `open` changes on a narrow split | the pane that comes into view enters once (`enterMotion.enterBase`); focus moves into the detail, or back to the list control |
|
|
278
297
|
|
|
279
298
|
## Layout & spacing
|
|
280
299
|
- The edge air comes from the host: `AppShell.Main` sets `--page-gutter` (the page gutter, 16 → 24 → 32) and `--page-top` (24 → 32 from 1024px); `Page.Root` pads itself with them. Outside a host the page brings no air. The page resets both for what it holds, so a page nested in it starts clean.
|
|
281
|
-
- `Page.TopBar` steps out of the gutter to the panel's edges and keeps the gutter inside; padding 12 above (or the top safe-area inset) and below; the
|
|
300
|
+
- `Page.TopBar` steps out of the gutter to the panel's edges and keeps the gutter inside; padding 12 above (or the top safe-area inset) and below; what follows starts 24 under its controls (header → body; the bar's own 12 counts in). The first line is as high as the Sidebar brand row (control m + 8), so the two read as one line. Groups 16 apart on a line, lines 12 apart; tools 8 apart; buttons 8 apart.
|
|
282
301
|
- In the bar only the heading's column gives (`minmax(0, 1fr)`): the tools and the actions keep their width. The heading keeps its natural width until every button step is taken; then a heading with a path stacks (the path over the title, the title and the description wrapping), so no level is cut to a fragment; a heading without a path truncates its title.
|
|
283
302
|
- The plan is measured, never chosen by breakpoints: the bar lays itself out from its own width.
|
|
284
303
|
- Header (flow) → Body 24; blocks inside Body 24; a `Page.Section` 40 from its neighbours (the body's 24 + 16); tile → tile in a Grid 16 on both axes; section heading → content 16, heading → description 4; flow Title → Description 8; heading → Actions 24 side by side, 16 once they wrap below.
|
|
@@ -303,6 +322,7 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
303
322
|
- The menu button carries `aria-expanded` and `aria-controls` → the Sidebar's `<nav>`. A Button that shows only its icon keeps its label as its accessible name; its tooltip repeats it. Folded Buttons are `hidden` in the bar and `menuitem`s in the «⋯» menu (named by `labels.more`), named by the Button's `aria-label` or its text, disabled with it; their `onClick` runs from the menu.
|
|
304
323
|
- `Page.TopBar` renders its groups in reading order — `Page.Heading`, `Page.Tools`, `Page.Actions`, `Page.Chips` — whatever the JSX order; the plan moves them only visually. A named `Page.Tools` is a `group`. The chevron between the path and the title is decorative (`aria-hidden`): the title is the `<h1>`, outside the path's `<nav>`.
|
|
305
324
|
- `Page.Section` is a `<section>` named by its own `<h2>` (`label`) through `aria-labelledby`; the heading levels go h1 (page) → h2 (section) → h3 (a card title inside it).
|
|
325
|
+
- `Page.Guard` holds a press made by pointer or keyboard alike (Enter on a link is a click). Its confirm is a `ConfirmDialog` (`alertdialog`): focus moves into it and, on «stay», returns to the item that was pressed.
|
|
306
326
|
- Name both panes of `Page.Split` with `aria-label`: a named pane is `role="region"`. On a narrow split opening the detail moves focus into it and going back returns focus to the list control; the hidden pane is `display: none`. `Page.BackButton` is named by `labels.back` and shown only while the split shows one pane.
|
|
307
327
|
|
|
308
328
|
### Labels
|
|
@@ -313,6 +333,10 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
313
333
|
| `menu` | `"Открыть меню"` | Name of the menu button `Page.TopBar` draws while the shell's Sidebar is off-canvas. |
|
|
314
334
|
| `more` | `"Ещё действия"` | Name of the «⋯» button the secondary Buttons of the top bar's `Page.Actions` fold into. |
|
|
315
335
|
| `back` | `"Назад к списку"` | Name of `Page.BackButton`. |
|
|
336
|
+
| `leaveTitle` | `"Уйти без сохранения?"` | Title of the confirm `Page.Guard` opens when a person leaves a `dirty` page. |
|
|
337
|
+
| `leaveDescription` | `"Изменения на странице пропадут."` | Its description. |
|
|
338
|
+
| `leaveConfirm` | `"Уйти"` | Its confirm button: leave and lose the changes. |
|
|
339
|
+
| `leaveCancel` | `"Остаться"` | Its cancel button: stay on the page. |
|
|
316
340
|
|
|
317
341
|
## Examples
|
|
318
342
|
| Example | Shows |
|
|
@@ -321,16 +345,19 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
321
345
|
| [variants.tsx](examples/variants.tsx) | The same page at three caps: the whole main, a wide dashboard column, a reading column — `maxWidth`. |
|
|
322
346
|
| [structure.tsx](examples/structure.tsx) | A document page in the flow: the path over the large title, its line of data and the page's buttons beside them, then the body with a section — `Page.Header`, `Page.Heading`, `Page.Description`, `Page.Section`. |
|
|
323
347
|
| [top-bar.tsx](examples/top-bar.tsx) | A nested record in the top bar: the path ending in the title, a line of data, the page's tools and its buttons — `Page.TopBar`, `Page.Heading`, `Page.Tools`, `Page.Actions`. |
|
|
348
|
+
| [loading.tsx](examples/loading.tsx) | A page whose figures load after it opens: the line of data keeps its place under the title while it loads, so nothing in the bar moves — `loading`. |
|
|
324
349
|
| [collapse.tsx](examples/collapse.tsx) | An overview whose large title scrolls away under the top bar: the bar repeats it with its line of data and keeps the period and the page's buttons in reach — `collapse`. |
|
|
325
350
|
| [tools.tsx](examples/tools.tsx) | The orders page's tools in its top bar: sections with counts, search and filter, the period, in the order written; the active filters on a line under everything — `Page.Tools`, `Page.Chips`. |
|
|
326
351
|
| [actions-collapse.tsx](examples/actions-collapse.tsx) | The buttons of an invoices page on a tablet-wide column: the line runs short, so the secondary buttons show only their icons with the label as the name and the tooltip, the primary keeps its label — `Page.Actions`, `Button.Icon`. |
|
|
327
352
|
| [sections.tsx](examples/sections.tsx) | A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading, a heading column beside a panel — `Page.Section`, `label`, `description`, `Page.Actions`, `layout`. |
|
|
328
353
|
| [grid.tsx](examples/grid.tsx) | A dashboard board: four KPI tiles, a chart two rows high beside two panels, one gap on both axes — `Page.Grid`, `Page.Cell`, `span`, `rows`. |
|
|
329
354
|
| [list-detail.tsx](examples/list-detail.tsx) | A ticket queue as list + detail on a full-height page: side by side on a wide panel, one pane at a time with a back button on a narrow one; the chosen ticket cross-fades in — `Page.Split`, `Page.List`, `Page.Detail`, `Page.BackButton`, `fill`. |
|
|
355
|
+
| [guard.tsx](examples/guard.tsx) | A client's card edited on its own page: after an edit a press on the navigation or the path asks before the changes are lost, and while it saves nothing leaves — `Page.Guard`, `dirty`, `busy`. |
|
|
330
356
|
| [narrow.tsx](examples/narrow.tsx) | On a phone the top bar leads with the menu button that opens the navigation by itself, the primary button gives way to its icon and the secondary ones fold into «⋯» — `offCanvas`, `Page.Actions`. |
|
|
331
357
|
| [screen.tsx](examples/screen.tsx) | A settings page as a document: the path in the top bar that repeats the title once it scrolls away, the large title in the flow, sections with a heading column and a panel, a form saved by its own button, switches that apply at once and a danger zone with a confirm — `collapse`, `layout`, `maxWidth`. |
|
|
332
358
|
|
|
333
359
|
## Mistakes
|
|
360
|
+
- `{ready ? <Page.Description>…</Page.Description> : null}` → the line appears after the load and the title jumps; keep it mounted with `loading` until the data comes.
|
|
334
361
|
- A global header above every page → the app is navigation and the page; each page renders its own `Page.TopBar` with only what it needs.
|
|
335
362
|
- Padding on `Page.Root` or around it to imitate gutters → the page takes them from `AppShell.Main`.
|
|
336
363
|
- Two titles — one in the bar, one in the flow → one `Page.Title`; a flow title with `collapse` is repeated in the bar by itself.
|
|
@@ -347,7 +374,9 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
|
|
|
347
374
|
- Each row of cards in its own grid → put every tile of the screen in one `Page.Grid`.
|
|
348
375
|
- Computing a height from `100vh` minus the bar for a full-height table or chat → `Page.Root fill` and a filling child.
|
|
349
376
|
- A list + detail built with window media queries → `Page.Split` follows its own width.
|
|
377
|
+
- A `beforeunload` listener and a ConfirmDialog on a crumb of your own for unsaved changes → `Page.Guard dirty`; it also holds the Sidebar and BottomNav items.
|
|
378
|
+
- Tiles of a row cut to 6 + 6 so a mid-width grid keeps no hole → write the 8 and the 4 next to each other; the grid pairs them as 2/3 + 1/3.
|
|
350
379
|
|
|
351
380
|
## Related
|
|
352
|
-
- **Built from:** [AppShell](../app-shell/COMPONENT.md) (the navigation the menu button opens), [Breadcrumb](../../components/breadcrumb/COMPONENT.md) (the path the heading places), [Button](../../components/button/COMPONENT.md) (the menu button, «⋯», `Page.BackButton`; the icon-only label hook), [Dropdown](../../components/dropdown/COMPONENT.md) (folded buttons), [Tooltip](../../components/tooltip/COMPONENT.md) (the label of an icon-only button), [SegmentedControl](../../components/segmented-control/COMPONENT.md) (`fullWidth` in `Page.Tools`), [SmartFilter](../../components/smart-filter/COMPONENT.md) and [Input](../../components/input/COMPONENT.md) (the search that grows first in `Page.Tools`), Icon (`nav.menu`, `nav.chevronRight`, `action.more`, `nav.chevronLeft`)
|
|
381
|
+
- **Built from:** [AppShell](../app-shell/COMPONENT.md) (the navigation the menu button opens), [Breadcrumb](../../components/breadcrumb/COMPONENT.md) (the path the heading places), [Button](../../components/button/COMPONENT.md) (the menu button, «⋯», `Page.BackButton`; the icon-only label hook), [Dropdown](../../components/dropdown/COMPONENT.md) (folded buttons), [Tooltip](../../components/tooltip/COMPONENT.md) (the label of an icon-only button), [SegmentedControl](../../components/segmented-control/COMPONENT.md) and [Datepicker](../../components/datepicker/COMPONENT.md) (`fullWidth` in `Page.Tools`), [SmartFilter](../../components/smart-filter/COMPONENT.md) and [Input](../../components/input/COMPONENT.md) (the search that grows first in `Page.Tools`), [Crossfade](../../components/crossfade/COMPONENT.md) (a `Page.Cell` child gets `fill`), [Skeleton](../../components/skeleton/COMPONENT.md) (the line of data while it loads), [ConfirmDialog](../../components/confirm-dialog/COMPONENT.md) (the confirm of `Page.Guard`), Icon (`nav.menu`, `nav.chevronRight`, `action.more`, `nav.chevronLeft`)
|
|
353
382
|
- **See also:** [Sidebar](../sidebar/COMPONENT.md), [BottomNav](../bottom-nav/COMPONENT.md), [Select](../../components/select/COMPONENT.md), [Card](../../components/card/COMPONENT.md), [ChartFrame](../../components/chart-frame/COMPONENT.md), [List](../../components/list/COMPONENT.md), [EmptyPage](../../components/empty-page/COMPONENT.md), [Drawer](../../components/drawer/COMPONENT.md), [VisuallyHidden](../../components/visually-hidden/COMPONENT.md)
|
|
@@ -77,6 +77,13 @@
|
|
|
77
77
|
padding: var(--prime-space-2) var(--prime-space-4);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/* guard.tsx: the fields of the form, one column of the form width. */
|
|
81
|
+
.fields {
|
|
82
|
+
display: grid;
|
|
83
|
+
gap: var(--prime-space-4);
|
|
84
|
+
max-width: var(--prime-layout-form-width-m);
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
/* actions-collapse.tsx: a tablet-width page column (600px). */
|
|
81
88
|
.tablet {
|
|
82
89
|
width: 100%;
|