prime-ui-kit 0.17.0 → 0.19.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-2ODXLPW3.js → chunk-QFHE5GKW.js} +1 -0
- package/dist/chunks/{chunk-7WWZYGRC.js → chunk-RZNGLELR.js} +4497 -3896
- package/dist/chunks/{chunk-A4WE3UNC.js → chunk-WYCTXD63.js} +18399 -17624
- package/dist/chunks/{chunk-SVFWPLNW.js → chunk-ZTZ2ML2B.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/ColumnsMenu.d.ts +16 -20
- package/dist/components/data-table/DataTable.d.ts +16 -11
- package/dist/components/data-table/Head.d.ts +10 -1
- package/dist/components/data-table/Row.d.ts +7 -1
- package/dist/components/data-table/StateRows.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 +24 -12
- package/dist/components/hint/Hint.d.ts +8 -2
- package/dist/components/index.d.ts +7 -4
- 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 +8 -9
- package/dist/components/list/List.d.ts +25 -6
- 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 +7 -1
- package/dist/internal/formatClock.d.ts +23 -2
- package/dist/internal/leaveGuard.d.ts +27 -0
- package/dist/internal/messageList.d.ts +2 -0
- package/dist/internal/overlay/DialogGuard.d.ts +3 -2
- package/dist/internal/overlay/DialogParts.d.ts +40 -6
- 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 +48 -15
- package/dist/skill/anti-slop.md +28 -0
- package/dist/skill/api-contract.md +14 -7
- package/dist/skill/cheatsheet.md +28 -9
- package/dist/skill/checklist.md +9 -0
- package/dist/skill/choosing.md +25 -12
- package/dist/skill/components.md +1 -0
- package/dist/skill/composition.md +88 -16
- package/dist/skill/layouts.md +6 -4
- 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/confirm-dialog/COMPONENT.md +1 -1
- package/dist/skill/reference/components/crossfade/COMPONENT.md +1 -1
- package/dist/skill/reference/components/data-table/COMPONENT.md +46 -41
- package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
- package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +3 -12
- package/dist/skill/reference/components/data-table/examples/controlled.tsx +1 -1
- package/dist/skill/reference/components/data-table/examples/narrow.tsx +4 -11
- package/dist/skill/reference/components/data-table/examples/numeric.tsx +9 -2
- package/dist/skill/reference/components/data-table/examples/row-link.tsx +18 -22
- package/dist/skill/reference/components/data-table/examples/screen.tsx +12 -1
- package/dist/skill/reference/components/data-table/examples/toolbar.tsx +26 -32
- 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/dnd/COMPONENT.md +4 -3
- package/dist/skill/reference/components/dnd/examples/examples.module.css +4 -3
- package/dist/skill/reference/components/dnd/examples/handle.tsx +5 -5
- package/dist/skill/reference/components/dnd/examples/locked.tsx +1 -1
- package/dist/skill/reference/components/dnd/examples/table.tsx +6 -6
- package/dist/skill/reference/components/drawer/COMPONENT.md +38 -9
- package/dist/skill/reference/components/drawer/examples/client-card.tsx +32 -8
- package/dist/skill/reference/components/drawer/examples/header-actions.tsx +1 -1
- package/dist/skill/reference/components/drawer/examples/loading.tsx +58 -0
- package/dist/skill/reference/components/field-list/COMPONENT.md +40 -24
- package/dist/skill/reference/components/field-list/examples/base-row.tsx +2 -1
- package/dist/skill/reference/components/field-list/examples/locked.tsx +11 -11
- package/dist/skill/reference/components/field-list/examples/reorder.tsx +1 -1
- package/dist/skill/reference/components/field-list/examples/saving.tsx +83 -0
- package/dist/skill/reference/components/field-list/examples/screen.module.css +1 -1
- package/dist/skill/reference/components/field-list/examples/screen.tsx +168 -162
- package/dist/skill/reference/components/field-list/examples/totals.tsx +83 -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 +12 -8
- package/dist/skill/reference/components/kanban/examples/columns.tsx +4 -2
- package/dist/skill/reference/components/kanban/examples/states.tsx +7 -5
- package/dist/skill/reference/components/list/COMPONENT.md +39 -13
- package/dist/skill/reference/components/list/examples/reorder.tsx +42 -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 +35 -10
- 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 +4 -2
- 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/sheet/COMPONENT.md +18 -7
- 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 +42 -20
- 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/page/examples/screen.module.css +1 -1
- package/dist/skill/reference/layout/page/examples/screen.tsx +93 -104
- package/dist/skill/reference/layout/page/examples/sections.tsx +2 -15
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +1 -0
- package/dist/skill/responsive.md +19 -12
- package/dist/skill/ux.md +5 -3
- package/dist/tokens/semantic.d.ts +3 -1
- package/dist/tokens/themes/dark.d.ts +1 -1
- 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/confirm-dialog/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/data-table/COMPONENT.md +46 -41
- package/src/components/data-table/examples/actions-column.tsx +154 -0
- package/src/components/data-table/examples/columns-visibility.tsx +3 -12
- package/src/components/data-table/examples/controlled.tsx +1 -1
- package/src/components/data-table/examples/narrow.tsx +4 -11
- package/src/components/data-table/examples/numeric.tsx +9 -2
- package/src/components/data-table/examples/row-link.tsx +18 -22
- package/src/components/data-table/examples/screen.tsx +12 -1
- package/src/components/data-table/examples/toolbar.tsx +26 -32
- 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/dnd/COMPONENT.md +4 -3
- package/src/components/dnd/examples/examples.module.css +4 -3
- package/src/components/dnd/examples/handle.tsx +5 -5
- package/src/components/dnd/examples/locked.tsx +1 -1
- package/src/components/dnd/examples/table.tsx +6 -6
- package/src/components/drawer/COMPONENT.md +38 -9
- package/src/components/drawer/examples/client-card.tsx +32 -8
- package/src/components/drawer/examples/header-actions.tsx +1 -1
- package/src/components/drawer/examples/loading.tsx +58 -0
- package/src/components/field-list/COMPONENT.md +40 -24
- package/src/components/field-list/examples/base-row.tsx +2 -1
- package/src/components/field-list/examples/locked.tsx +11 -11
- package/src/components/field-list/examples/reorder.tsx +1 -1
- package/src/components/field-list/examples/saving.tsx +83 -0
- package/src/components/field-list/examples/screen.module.css +1 -1
- package/src/components/field-list/examples/screen.tsx +168 -162
- package/src/components/field-list/examples/totals.tsx +83 -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 +12 -8
- package/src/components/kanban/examples/columns.tsx +4 -2
- package/src/components/kanban/examples/states.tsx +7 -5
- package/src/components/list/COMPONENT.md +39 -13
- package/src/components/list/examples/reorder.tsx +42 -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 +35 -10
- 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 +4 -2
- package/src/components/segmented-control/examples/in-form.tsx +1 -1
- package/src/components/select/COMPONENT.md +7 -3
- package/src/components/sheet/COMPONENT.md +18 -7
- 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 +42 -20
- 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/page/examples/screen.module.css +1 -1
- package/src/layout/page/examples/screen.tsx +93 -104
- package/src/layout/page/examples/sections.tsx +2 -15
- package/src/layout/sidebar/COMPONENT.md +1 -0
- package/src/styles/theme-dark.css +1 -1
- package/src/styles/theme-light.css +2 -1
- package/dist/components/data-table/context.d.ts +0 -14
- package/dist/components/scroll-container/useWheelToInline.d.ts +0 -10
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
/** Funnel stages in the team's order: a stage moves by its handle or Alt+arrows and is saved on the server — `onReorder`, `value`. */
|
|
2
|
-
import { Card, List, moveBefore } from "prime-ui-kit";
|
|
1
|
+
/** Funnel stages in the team's order: a stage moves by its handle or Alt+arrows and is saved on the server; its switch leads the row, the grip ends it — `onReorder`, `value`, `side`. */
|
|
2
|
+
import { Card, List, moveBefore, Switch } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
import styles from "./examples.module.css";
|
|
6
6
|
|
|
7
7
|
const STAGES = [
|
|
8
|
-
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
{
|
|
9
|
+
id: "lead",
|
|
10
|
+
name: "Новый лид",
|
|
11
|
+
description: "Заявка с сайта или от партнёра",
|
|
12
|
+
deals: 42,
|
|
13
|
+
shown: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "qualified",
|
|
17
|
+
name: "Квалификация",
|
|
18
|
+
description: "Бюджет и сроки подтверждены",
|
|
19
|
+
deals: 18,
|
|
20
|
+
shown: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "proposal",
|
|
24
|
+
name: "Коммерческое предложение",
|
|
25
|
+
description: "КП отправлено",
|
|
26
|
+
deals: 11,
|
|
27
|
+
shown: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "contract",
|
|
31
|
+
name: "Договор",
|
|
32
|
+
description: "Согласование с юристами",
|
|
33
|
+
deals: 6,
|
|
34
|
+
shown: true,
|
|
35
|
+
},
|
|
36
|
+
{ id: "won", name: "Оплата", description: "Счёт выставлен", deals: 3, shown: false },
|
|
13
37
|
];
|
|
14
38
|
|
|
15
39
|
const SAVE_DELAY_MS = 600;
|
|
@@ -36,6 +60,17 @@ export default function ListReorderExample() {
|
|
|
36
60
|
>
|
|
37
61
|
{stages.map((stage) => (
|
|
38
62
|
<List.Item key={stage.id} value={stage.id}>
|
|
63
|
+
<List.ItemActions side="start">
|
|
64
|
+
<Switch.Root
|
|
65
|
+
aria-label={`Показывать «${stage.name}» на доске`}
|
|
66
|
+
checked={stage.shown}
|
|
67
|
+
onCheckedChange={(shown) =>
|
|
68
|
+
setStages((current) =>
|
|
69
|
+
current.map((item) => (item.id === stage.id ? { ...item, shown } : item)),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
/>
|
|
73
|
+
</List.ItemActions>
|
|
39
74
|
<List.ItemText>{stage.name}</List.ItemText>
|
|
40
75
|
<List.ItemDescription>{stage.description}</List.ItemDescription>
|
|
41
76
|
<List.ItemCount>{stage.deals}</List.ItemCount>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Funnel stages with their own controls: a colour dot names the stage's hue, a switch shows it on the board and a menu edits it, all beside the row that opens the stage — `List.ItemActions`, `Badge.Dot`. */
|
|
2
|
+
import { Badge, Button, Card, Dropdown, Icon, List, type PaletteColor, Switch } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import styles from "./examples.module.css";
|
|
6
|
+
|
|
7
|
+
type Stage = { id: string; name: string; color: PaletteColor; deals: number; shown: boolean };
|
|
8
|
+
|
|
9
|
+
const STAGES: Stage[] = [
|
|
10
|
+
{ id: "lead", name: "Новый лид", color: "blue", deals: 42, shown: true },
|
|
11
|
+
{ id: "qualified", name: "Квалификация", color: "purple", deals: 18, shown: true },
|
|
12
|
+
{ id: "proposal", name: "Коммерческое предложение", color: "orange", deals: 11, shown: true },
|
|
13
|
+
{ id: "paused", name: "Отложено", color: "gray", deals: 7, shown: false },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export default function ListRowControlsExample() {
|
|
17
|
+
const [stages, setStages] = React.useState(STAGES);
|
|
18
|
+
const [current, setCurrent] = React.useState("lead");
|
|
19
|
+
|
|
20
|
+
const patch = (id: string, next: Partial<Stage>) =>
|
|
21
|
+
setStages((list) => list.map((stage) => (stage.id === id ? { ...stage, ...next } : stage)));
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Card.Root className={styles.pane}>
|
|
25
|
+
<Card.Body>
|
|
26
|
+
<List.Root aria-label="Этапы воронки">
|
|
27
|
+
{stages.map((stage) => (
|
|
28
|
+
<List.Item
|
|
29
|
+
key={stage.id}
|
|
30
|
+
value={stage.id}
|
|
31
|
+
current={current === stage.id}
|
|
32
|
+
onClick={() => setCurrent(stage.id)}
|
|
33
|
+
>
|
|
34
|
+
<List.ItemIcon>
|
|
35
|
+
<Badge.Dot color={stage.color} />
|
|
36
|
+
</List.ItemIcon>
|
|
37
|
+
<List.ItemText>{stage.name}</List.ItemText>
|
|
38
|
+
<List.ItemCount>{stage.deals}</List.ItemCount>
|
|
39
|
+
<List.ItemActions>
|
|
40
|
+
<Switch.Root
|
|
41
|
+
aria-label={`Показывать «${stage.name}» на доске`}
|
|
42
|
+
checked={stage.shown}
|
|
43
|
+
onCheckedChange={(shown) => patch(stage.id, { shown })}
|
|
44
|
+
/>
|
|
45
|
+
<Dropdown.Root>
|
|
46
|
+
<Dropdown.Trigger>
|
|
47
|
+
<Button.Root
|
|
48
|
+
variant="ghost"
|
|
49
|
+
tone="neutral"
|
|
50
|
+
aria-label={`Действия с этапом «${stage.name}»`}
|
|
51
|
+
>
|
|
52
|
+
<Button.Icon>
|
|
53
|
+
<Icon name="action.more" />
|
|
54
|
+
</Button.Icon>
|
|
55
|
+
</Button.Root>
|
|
56
|
+
</Dropdown.Trigger>
|
|
57
|
+
<Dropdown.Content>
|
|
58
|
+
<Dropdown.Item onSelect={() => setCurrent(stage.id)}>Открыть</Dropdown.Item>
|
|
59
|
+
<Dropdown.Item onSelect={() => patch(stage.id, { color: "green" })}>
|
|
60
|
+
Отметить зелёным
|
|
61
|
+
</Dropdown.Item>
|
|
62
|
+
<Dropdown.Separator />
|
|
63
|
+
<Dropdown.Item
|
|
64
|
+
tone="danger"
|
|
65
|
+
onSelect={() =>
|
|
66
|
+
setStages((list) => list.filter((item) => item.id !== stage.id))
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
Удалить этап
|
|
70
|
+
</Dropdown.Item>
|
|
71
|
+
</Dropdown.Content>
|
|
72
|
+
</Dropdown.Root>
|
|
73
|
+
</List.ItemActions>
|
|
74
|
+
</List.Item>
|
|
75
|
+
))}
|
|
76
|
+
</List.Root>
|
|
77
|
+
</Card.Body>
|
|
78
|
+
</Card.Root>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** Leading avatars of clients and
|
|
2
|
-
import { Avatar, Icon, List, Typography } from "prime-ui-kit";
|
|
1
|
+
/** Leading avatars of clients, icons of queues and colour dots of projects; an avatar takes the row tier, a dot stands in an icon-wide box — `List.ItemIcon`, `Badge.Dot`. */
|
|
2
|
+
import { Avatar, Badge, Icon, List, Typography } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
const CLIENTS = [
|
|
5
5
|
{ id: "belova", name: "Ирина Белова", company: "ООО «Гранит»", initials: "ИБ", color: "blue" },
|
|
@@ -7,6 +7,12 @@ const CLIENTS = [
|
|
|
7
7
|
{ id: "kozlov", name: "Сергей Козлов", company: "ИП Козлов", initials: "СК", color: "orange" },
|
|
8
8
|
] as const;
|
|
9
9
|
|
|
10
|
+
const PROJECTS = [
|
|
11
|
+
{ id: "crm", name: "Внедрение CRM", color: "purple" },
|
|
12
|
+
{ id: "portal", name: "Клиентский портал", color: "teal" },
|
|
13
|
+
{ id: "billing", name: "Биллинг", color: "orange" },
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
10
16
|
export default function ListWithIconExample() {
|
|
11
17
|
return (
|
|
12
18
|
<div>
|
|
@@ -56,6 +62,21 @@ export default function ListWithIconExample() {
|
|
|
56
62
|
Icon
|
|
57
63
|
</Typography>
|
|
58
64
|
</div>
|
|
65
|
+
<div>
|
|
66
|
+
<List.Root aria-label="Проекты">
|
|
67
|
+
{PROJECTS.map((project, index) => (
|
|
68
|
+
<List.Item key={project.id} current={index === 0}>
|
|
69
|
+
<List.ItemIcon>
|
|
70
|
+
<Badge.Dot color={project.color} />
|
|
71
|
+
</List.ItemIcon>
|
|
72
|
+
<List.ItemText>{project.name}</List.ItemText>
|
|
73
|
+
</List.Item>
|
|
74
|
+
))}
|
|
75
|
+
</List.Root>
|
|
76
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
77
|
+
Badge.Dot
|
|
78
|
+
</Typography>
|
|
79
|
+
</div>
|
|
59
80
|
</div>
|
|
60
81
|
</div>
|
|
61
82
|
);
|
|
@@ -34,7 +34,7 @@ LoginForm.Root width cap; a Card inside: card fill, radius,
|
|
|
34
34
|
│ └─ LoginForm.Actions primary button + ghost back action
|
|
35
35
|
└─ LoginForm.Footer «Нет аккаунта? <LinkButton>»
|
|
36
36
|
```
|
|
37
|
-
Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, dividers
|
|
37
|
+
Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, dividers and links are the regular kit components; LoginForm owns only the layout and the rhythm.
|
|
38
38
|
|
|
39
39
|
## API
|
|
40
40
|
|
|
@@ -149,8 +149,8 @@ Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, divid
|
|
|
149
149
|
| `flat` | card fill, no shadow | inside a Modal or any other surface | `false` |
|
|
150
150
|
|
|
151
151
|
**Combinations**
|
|
152
|
-
- Recommended: one `size` on Root only (fields and buttons follow it); `hint` / `error` on fields for validation; a
|
|
153
|
-
- Avoid: more than one primary button; provider buttons in `solid` (they compete with the submit); a Card around LoginForm.
|
|
152
|
+
- Recommended: one `size` on Root only (fields and buttons follow it); `hint` / `error` on fields for validation; a refused sign-in as `invalid` on the fields it concerns plus a [VisuallyHidden](../visually-hidden/COMPONENT.md) live region with the reason.
|
|
153
|
+
- Avoid: a [Banner](../banner/COMPONENT.md) in the form; more than one primary button; provider buttons in `solid` (they compete with the submit); a Card around LoginForm.
|
|
154
154
|
|
|
155
155
|
## States
|
|
156
156
|
LoginForm has no state of its own. Compose them:
|
|
@@ -159,7 +159,7 @@ LoginForm has no state of its own. Compose them:
|
|
|
159
159
|
|---|---|---|
|
|
160
160
|
| submitting | `loading` on the submit Button; the fields in a `Fieldset disabled`, the submit Button outside it | `aria-busy` on the button; native `disabled` on the `<fieldset>`, the fields in their disabled look |
|
|
161
161
|
| field error | `error` / `invalid` on `Input.Root`, `error` on `DigitInput` | `aria-invalid`, the error under the field |
|
|
162
|
-
|
|
|
162
|
+
| refused sign-in | `invalid` on the fields it concerns (they shake once); `VisuallyHidden live="assertive"`, mounted from the start in `LoginForm.Form`, gets the reason | `aria-invalid` on the fields; `role="alert"` on the hidden region |
|
|
163
163
|
|
|
164
164
|
Root carries `data-size`, `data-align` and `data-flat`.
|
|
165
165
|
|
|
@@ -185,7 +185,7 @@ Root carries `data-size`, `data-align` and `data-flat`.
|
|
|
185
185
|
- The logo is decorative; with an `aria-label` it is the product mark image (`role="img"`).
|
|
186
186
|
- Give `LoginForm.Form` an `aria-label` (or `aria-labelledby` pointing to the title id) so the form is a landmark.
|
|
187
187
|
- Fields use `autoComplete` (`email`, `current-password`, `new-password`, `one-time-code`) so password managers and OTP autofill work.
|
|
188
|
-
-
|
|
188
|
+
- Field errors come from the fields (`aria-invalid`, described by the error). A refused sign-in has no Banner: the fields it concerns take `invalid` and an always-mounted `VisuallyHidden live="assertive"` says why.
|
|
189
189
|
|
|
190
190
|
### Labels
|
|
191
191
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
@@ -202,16 +202,17 @@ No `labels`.
|
|
|
202
202
|
| [forgot-password.tsx](examples/forgot-password.tsx) | Password reset request: one field, the primary action and a quiet way back — `LoginForm.Actions`. |
|
|
203
203
|
| [reset-password.tsx](examples/reset-password.tsx) | A new password after the e-mail link: a strength meter on the first field and a mismatch error under the second one — `strength`, `error`. |
|
|
204
204
|
| [verification-code.tsx](examples/verification-code.tsx) | The second step with a one-time code: a rejected code shows its error under the cells, resend is a quiet action — `DigitInput`, `LoginForm.Actions`. |
|
|
205
|
-
| [states.tsx](examples/states.tsx) | The submit cycle: the button is busy while the request runs, a
|
|
205
|
+
| [states.tsx](examples/states.tsx) | The submit cycle: the button is busy while the request runs, a refused sign-in marks both fields, which shake once, and a hidden live region says why — `loading`, `invalid`, `VisuallyHidden`. |
|
|
206
206
|
| [narrow.tsx](examples/narrow.tsx) | On a phone-width screen the card keeps its padding, the header wraps and the buttons stay full width — `LoginForm.Form`. |
|
|
207
207
|
|
|
208
208
|
## Mistakes
|
|
209
209
|
- A separate block of your own for provider buttons → put them in `LoginForm.Actions`.
|
|
210
210
|
- `disabled` on every field while the request runs → one [Fieldset](../fieldset/COMPONENT.md) `disabled` around the fields; keep the submit Button outside it so it shows `loading`.
|
|
211
|
+
- A danger Banner above the fields for a refused sign-in → `invalid` on the fields and the reason in a `VisuallyHidden live="assertive"`; a sign-in or sign-up form has no banners.
|
|
211
212
|
- A placeholder instead of a field label → pass `label` to the field.
|
|
212
213
|
- A Card around LoginForm → the root already is the card; use `flat` inside another surface.
|
|
213
214
|
- Repeating the card `size` on every field and button → set it once on Root; fields and buttons follow it.
|
|
214
215
|
|
|
215
216
|
## Related
|
|
216
217
|
- **Built from:** [Card](../card/COMPONENT.md) (the surface), [Typography](../typography/COMPONENT.md) (`Title`, `Description`, `Footer`), the icon tile (`iconBox` `.tile`, the `start` logo)
|
|
217
|
-
- **See also:** [Input](../input/COMPONENT.md), [Fieldset](../fieldset/COMPONENT.md) (disables the fields while signing in), [Button](../button/COMPONENT.md), [LinkButton](../link-button/COMPONENT.md), [Divider](../divider/COMPONENT.md), [DigitInput](../digit-input/COMPONENT.md), [
|
|
218
|
+
- **See also:** [Input](../input/COMPONENT.md), [Fieldset](../fieldset/COMPONENT.md) (disables the fields while signing in), [Button](../button/COMPONENT.md), [LinkButton](../link-button/COMPONENT.md), [Divider](../divider/COMPONENT.md), [DigitInput](../digit-input/COMPONENT.md), [VisuallyHidden](../visually-hidden/COMPONENT.md), [Modal](../modal/COMPONENT.md)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** The submit cycle: the button is busy while the request runs, a
|
|
2
|
-
import {
|
|
1
|
+
/** The submit cycle: the button is busy while the request runs, a refused sign-in marks both fields, which shake once, and a hidden live region says why — `loading`, `invalid`, `VisuallyHidden`. */
|
|
2
|
+
import { Button, Fieldset, Icon, Input, LoginForm, VisuallyHidden } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
export default function LoginFormStatesExample() {
|
|
@@ -30,20 +30,18 @@ export default function LoginFormStatesExample() {
|
|
|
30
30
|
}, 1200);
|
|
31
31
|
}}
|
|
32
32
|
>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<Banner.Icon>
|
|
37
|
-
<Icon name="status.danger" />
|
|
38
|
-
</Banner.Icon>
|
|
39
|
-
<Banner.Title>Неверный email или пароль</Banner.Title>
|
|
40
|
-
</Banner.Content>
|
|
41
|
-
</Banner.Root>
|
|
42
|
-
) : null}
|
|
33
|
+
<VisuallyHidden live="assertive">
|
|
34
|
+
{failed ? "Неверный email или пароль" : ""}
|
|
35
|
+
</VisuallyHidden>
|
|
43
36
|
<Fieldset disabled={pending}>
|
|
44
|
-
<Input.Root label="Email" required>
|
|
37
|
+
<Input.Root label="Email" required invalid={failed}>
|
|
45
38
|
<Input.Wrapper>
|
|
46
|
-
<Input.Field
|
|
39
|
+
<Input.Field
|
|
40
|
+
type="email"
|
|
41
|
+
autoComplete="email"
|
|
42
|
+
placeholder="name@company.ru"
|
|
43
|
+
onValueChange={() => setFailed(false)}
|
|
44
|
+
/>
|
|
47
45
|
</Input.Wrapper>
|
|
48
46
|
</Input.Root>
|
|
49
47
|
<Input.Root label="Пароль" required invalid={failed}>
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
## When to use
|
|
9
9
|
- A message of any kind: text with formatting, a reply, a forwarded message, reactions, photos and albums (`Message.Media`, a `Gallery`), a round video message (`VideoNote` inside), a voice message (`VoicePlayer` inside), a file, a location, a contact, a link preview.
|
|
10
10
|
- The rows of a conversation in [Chat](../chat/COMPONENT.md) (`Chat.Messages`): the list, dates, search, selection and jumps live there; the message is one row.
|
|
11
|
-
- A message on its own: a quoted reply in a ticket, the last message in a card, a preview of what will be sent.
|
|
11
|
+
- A message on its own: a quoted reply in a ticket, the last message in a card, a preview of what will be sent (`preview` — a bot's welcome message on its settings page).
|
|
12
|
+
- A sticker (`Message.Sticker`): a webp or gif by `src`, or your own Lottie / video player as children.
|
|
12
13
|
- «Печатает…» (`Message.Typing`) as the last row of a list.
|
|
13
14
|
|
|
14
15
|
## When not to use
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
- A feed of events with a time axis (an order history, an audit log) → [Timeline](../timeline/COMPONENT.md).
|
|
18
19
|
- One system message on a page (saved, failed) → [Banner](../banner/COMPONENT.md) or a [Notification](../notification/COMPONENT.md).
|
|
19
20
|
- Photos outside a message → [Gallery](../gallery/COMPONENT.md); a recording outside a chat → [VoicePlayer](../voice-player/COMPONENT.md).
|
|
21
|
+
- Formatted text outside a message, with no bubble → [RichText](../rich-text/COMPONENT.md).
|
|
20
22
|
|
|
21
23
|
## Import
|
|
22
24
|
```tsx
|
|
@@ -35,7 +37,8 @@ Message.Root role="article" row «{author}, {time}» (an op
|
|
|
35
37
|
│ ├─ Message.Forwarded «Переслано от …»
|
|
36
38
|
│ ├─ Message.Reply quote of the answered message (button with target / onClick)
|
|
37
39
|
│ ├─ content Message.Text · Message.Media (+ caption Text) · VoicePlayer · Message.File ·
|
|
38
|
-
│ │ Message.Location · Message.Contact · Message.LinkPreview · VideoNote
|
|
40
|
+
│ │ Message.Location · Message.Contact · Message.LinkPreview · VideoNote ·
|
|
41
|
+
│ │ Message.Sticker
|
|
39
42
|
│ │ (a voice message or a circle may open its transcript under it)
|
|
40
43
|
│ ├─ Message.Reactions a row held closed in every message; chips closing a bubble
|
|
41
44
|
│ │ (under a picture or a circle), the meta's spacer at its end
|
|
@@ -52,9 +55,9 @@ The part that ends a bubble closes its last line with the meta's spacer — an i
|
|
|
52
55
|
|
|
53
56
|
In a list (`Chat.Messages`) a message reads the list through `internal/messageList`: its place in a run (`position`) and the chat's kind decide the author's header (first of a run in a group chat) and the avatar (last of a run); the list registers it for new-message entry and read receipts, selects it, jumps to it and holds its size changes still. On its own a message given an author or an avatar does the same by its `position`: the header on `single` / `first`, the avatar (and the gutter) on `single` / `last`.
|
|
54
57
|
|
|
55
|
-
Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md) the message shapes (the messenger album layout from the items' ratios, every box final before load, the shared transfer disk over a tile, the meta overlay; an album's gaps stand on the incoming surface in both directions). `VideoNote` and `VoicePlayer` are components of their own that declare themselves parts (the round shell, and a fill bubble) and carry the same transfer props as `Message.File`. `Message.File` — a round format tile (or the preview) that is also its one action (download, cancel inside the progress ring, retry), the name, and the size line under it ending with the spacer. `Message.Location` — the map edge to edge with an accent pin (breathing when `live`), the place and the address ending with the spacer. `Message.Text` renders the Markdown subset the Composer writes — **bold**, _italic_, ~~strike~~, `code`, fenced code, Markdown links, bare URLs, `> ` quotes — as elements; links take only http(s), mailto and tel addresses (anything else stays text) and open in a new tab. `Message.Menu` holds the `Dropdown.Item`s you write (Ответить, Скопировать, Закрепить, Переслать, Пожаловаться, Удалить with `tone="danger"`); in a chat with a selection the kit appends a separator and «Выбрать».
|
|
58
|
+
Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md) the message shapes (the messenger album layout from the items' ratios, every box final before load, the shared transfer disk over a tile, the meta overlay; an album's gaps stand on the incoming surface in both directions). `VideoNote` and `VoicePlayer` are components of their own that declare themselves parts (the round shell, and a fill bubble) and carry the same transfer props as `Message.File`. `Message.File` — a round format tile (or the preview) that is also its one action (download, cancel inside the progress ring, retry), the name, and the size line under it ending with the spacer. `Message.Location` — the map edge to edge with an accent pin (breathing when `live`), the place and the address ending with the spacer. `Message.Sticker` — a square picture with no bubble (`media` shell, the meta on its pill): the kit draws a webp, gif or apng from `src`; a Lottie (.tgs) or video sticker is your player as children (the kit has no runtime dependencies), with `loading` while it prepares. `Message.Text` renders the Markdown subset the Composer writes — **bold**, _italic_, ~~strike~~, `code`, fenced code, Markdown links, bare URLs, `> ` quotes — as elements; links take only http(s), mailto and tel addresses (anything else stays text) and open in a new tab. `Message.Menu` holds the `Dropdown.Item`s you write (Ответить, Скопировать, Закрепить, Переслать, Пожаловаться, Удалить with `tone="danger"`); in a chat with a selection the kit appends a separator and «Выбрать».
|
|
56
59
|
|
|
57
|
-
`formatMessageTime(date)` gives «09:05», the default `formatTime
|
|
60
|
+
`formatMessageTime(date, timeZone?)` gives «09:05», the default `formatTime`; the time is read in `timeZone`, else the list's (`Chat.Messages timeZone`), else the browser's zone. `hueOf(id)` gives a palette hue stable per id for `Message.Author color` and the author's `Avatar`.
|
|
58
61
|
|
|
59
62
|
## API
|
|
60
63
|
|
|
@@ -68,9 +71,11 @@ Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md)
|
|
|
68
71
|
| `direction` | `"incoming" \| "outgoing"` | `"incoming"` | `incoming` — on the start side, a surface one step off the chat; `outgoing` — on the end side, in the accent hue (`--prime-color-message-outgoing-*`), everything inside recolors with it. |
|
|
69
72
|
| `position` | `"single" \| "first" \| "middle" \| "last"` | `"single"` | The place in a run of one author (`Chat.Messages items` sets it): `single` and `last` end a run with the tail (and a group chat's avatar); `single` and `first` show the author's header. |
|
|
70
73
|
| `time` | `Date \| string` | — | When it was sent: a `Date` shown with `formatTime` (and put in `<time dateTime>`), or the text to show. |
|
|
71
|
-
| `
|
|
74
|
+
| `timeZone` | `string` | the list's `timeZone`, else the browser's zone | The IANA zone the time is read in (`"Asia/Vladivostok"`): a profile's zone, not the browser's. |
|
|
75
|
+
| `formatTime` | `(date: Date, timeZone?: string) => string` | «09:05» | Display of a `Date` time; gets the `timeZone` in force. |
|
|
72
76
|
| `edited` | `boolean` | `false` | The text was edited: `labels.edited` before the time. |
|
|
73
77
|
| `status` | `"sending" \| "sent" \| "read" \| "failed"` | — | Delivery of an outgoing message: a clock, one check, two checks (accent), or a danger mark; the glyphs cross-fade from one state to the next. Named for assistive tech by `labels`. |
|
|
78
|
+
| `preview` | `boolean` | `false` | A picture of the message outside a chat (a bot's settings): no menu, reactions, retry or row focus, `Message.Actions` inert; links and players still work. |
|
|
74
79
|
| `onRetry` | `() => void \| Promise<void>` | — | Sends a failed message again: a ghost danger button beside the bubble. A returned promise shows `sending` until it settles; a rejection leaves it failed. |
|
|
75
80
|
| `onReactionToggle` | `(emoji: string) => void \| Promise<unknown>` | — | Puts or takes back the reader's reaction (a chip, a double tap, `L`, the reaction bar). Shown at once; a returned promise keeps it pending, a rejection drops it back. Without it reactions are read-only. |
|
|
76
81
|
| `value` | `string` | — | The message id for selection, jumps, read receipts, search, pins and `Message.Reply target` (`Chat.Messages items` sets the item's key). Keep it when the server confirms a sent message. |
|
|
@@ -206,6 +211,16 @@ Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md)
|
|
|
206
211
|
| `image` | `string` | — | The page's picture, full width under the text. |
|
|
207
212
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `className` and the other attributes of the link. |
|
|
208
213
|
|
|
214
|
+
### Message.Sticker
|
|
215
|
+
`ref` → `HTMLDivElement` (`role="img"`). A sticker: a square picture with no bubble, the meta on a pill over its corner; a `Skeleton` holds the box until it is ready.
|
|
216
|
+
|
|
217
|
+
| Prop | Type | Default | Description |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| `aria-label` | `string` | — (required) | The sticker's name («Стикер «Класс»», or its emoji); shown in its box when `src` fails. |
|
|
220
|
+
| `src` | `string` | — | A still or animated picture (webp, gif, apng), decoded before it shows. Without it the children are the sticker. |
|
|
221
|
+
| `loading` | `boolean` | `false` | Your player in the children is not ready yet: the `Skeleton` stays over the box. |
|
|
222
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "role" \| "aria-label">` | — | `children` — your player for a Lottie (.tgs) or a video sticker, sized to the box; `className`. |
|
|
223
|
+
|
|
209
224
|
### Message.Reactions
|
|
210
225
|
`ref` → `HTMLDivElement`. Reaction chips at the bottom of a bubble, or under a picture or a circle: a `role="group"` of toggle chips — the emoji, then who reacted (`users`) or the count. A press goes to `Message.Root onReactionToggle`.
|
|
211
226
|
|
|
@@ -250,7 +265,7 @@ Decided by the content parts, not a prop (`data-shell` on the bubble): a circle
|
|
|
250
265
|
| Value | Looks like | Use when | Default |
|
|
251
266
|
|---|---|---|---|
|
|
252
267
|
| `bubble` | fill (surface or accent), padding, the meta in the bottom-end corner over the last line's reserve | text, voice, file, contact, location, link preview, a picture **with** a caption | yes |
|
|
253
|
-
| `media` | the picture itself, clipped to the radius — no backing, no padding; the meta on a dark pill over its bottom-end corner; reactions in a row of their own **under** it, on the message's side (chips in their own fill); an album's gaps show the incoming surface in both directions, never the outgoing fill | a photo, a video or an album **without** a caption | |
|
|
268
|
+
| `media` | the picture itself, clipped to the radius — no backing, no padding; the meta on a dark pill over its bottom-end corner; reactions in a row of their own **under** it, on the message's side (chips in their own fill); an album's gaps show the incoming surface in both directions, never the outgoing fill | a photo, a video or an album **without** a caption; a sticker | |
|
|
254
269
|
| `round` | `VideoNote`: a circle standing on the chat — no backing in either direction; the duration and the meta float on their own pills over its bottom; reactions under it as for `media` | a round video message | |
|
|
255
270
|
|
|
256
271
|
With a caption a picture sits at the top of a normal bubble, edge to edge, and its reactions stay inside it, under the caption. The row of reactions is held closed in every message and opens with the first reaction (see States).
|
|
@@ -294,6 +309,8 @@ The fill moves between the two in a color transition (`fast`, both ways); the av
|
|
|
294
309
|
| Prop | Effect |
|
|
295
310
|
|---|---|
|
|
296
311
|
| `edited` | «изменено» before the time |
|
|
312
|
+
| `preview` | a picture of the message outside a chat: no menu, reaction bar, reactions toggle, retry or row focus; `Message.Actions` rows `inert` (seen, never pressed); links, players and spoilers still work |
|
|
313
|
+
| `Message.Sticker loading` | the Skeleton stays over the sticker's box while your player prepares |
|
|
297
314
|
| `Message.Location live` | a breathing pin and a «Трансляция» badge |
|
|
298
315
|
| `Message.Text plain` | a string without Markdown |
|
|
299
316
|
| `Message.Typing open` | the typing bubble shows; off, it leaves closing its height |
|
|
@@ -313,6 +330,8 @@ The list-level settings a message reads — the group chat's author header and a
|
|
|
313
330
|
| retrying | `onRetry` returns a promise | `sending` ticks and a `loading` retry button until it settles |
|
|
314
331
|
| inline keyboard | `Message.Actions` children | `data-actions` on the message column: the rows under the bubble, the bubble at least as wide as them, no tail |
|
|
315
332
|
| failed | `status="failed"` | `data-failed` on the row; danger mark, retry button |
|
|
333
|
+
| preview | `preview` | `data-preview` on the row, no `tabindex`; `inert` on every `Message.Actions` row |
|
|
334
|
+
| sticker loading | `Message.Sticker` until its `src` is decoded, or while `loading` | `data-status="loading"`, `aria-busy`: a `Skeleton` covers the box, then fades out over the picture (`fade-base`); a `src` that fails shows the sticker's name, `data-status="error"` |
|
|
316
335
|
| reaction toggled | a chip, a double tap, `L`, the reaction bar | the chip changes in place (never remounts) in the same frame: the fill is a color transition (`fast`); a reactor who joins fades and scales in from 0.9 while the stack widens (its width follows its circles), one who leaves the reverse; a count that only changes value rolls its digits and the chip does not move — its width follows the number of digits (tabular) and moves (`fast`) only when that changes; the reader (`Chat.Messages reader`) joins or leaves the avatar stack; while a returned promise runs `aria-busy`, `data-pending` — update `items` before it resolves; a rejection drops it back. Without `onReactionToggle` the chips are read-only and there is no reaction bar |
|
|
317
336
|
| size change | a reaction row that opens or closes, a chip that comes or goes, a transcript, an edit, an upload state | one stroke (`bubbleMorph`, `--prime-message-morph-duration` 300 ms): the new layout is laid out at once, in the same frame, and the bubble's visible box glides from the old rectangle to the new one — width and height together, one `clip-path: inset(… round …)` animation on the compositor, the fill a layer under the content that reaches over both boxes. Every part that moved (the text, the meta, the cards, the chips, the tail, the retry) glides from where it was by a `transform` on the same clock; a part that came fades (a chip also scales from 0.9) in where it stands once the box has room for it; a part that went fades out where it stood, out of flow. A change mid-flight retargets from what is on screen; a commit that moves nothing touches nothing. The meta and the tail stay put on a reaction: the message grows upward, the chat scrolling by the change in the same frame while the rows above glide as one block on the same clock (the rows below never move). A disclosed block (a transcript) fades over the whole stroke while the box opens or closes over it. The text glides straight with the bubble's top-start corner, keeping its padding all the way. Nothing travels across text: a meta that stays while text passes through its place dips — it fades out at the same spot before the text reaches it and back once the text has left — and a meta that changes line (a transcript's last line ↔ the player row) cross-fades — out at its old place in the first 40 % of the stroke, in at its new place in the last 40 %. Easing `standard`. Reduced motion: instant (the scroll still keeps the view) |
|
|
318
337
|
| new reaction | an emoji not on the message yet (the reader's or anyone's) | part of the size change: its room is there at once and the chip fades and scales in from 0.9 where it stands, on the bubble's clock, once the parts moving away have left its place. Never on the first render of a message: opening a chat or loading history plays nothing |
|
|
@@ -337,7 +356,7 @@ The list-level settings a message reads — the group chat's author header and a
|
|
|
337
356
|
- **The author's header** (a group chat, the first of a run): one line `body-s` — the name semibold in the author's hue, an `xs` icon, the tag muted after them; the name ellipsizes first, then the tag. The bubble hugs the whole line, so a long name or tag widens it up to its cap. Over a picture or a circle without a caption it stands on the media pill at `space-2` from the top-start corner, in the pill's colors.
|
|
338
357
|
- **A bubble hugs its text** (`bubbleHug`): a bubble of text — text, author, reply, forwarded, reactions — is as wide as its widest laid-out line plus the padding, never the width its lines wrapped in, and never wider than the cap; the meta's room counts on the last line only, where it sits, the reactions row as one line of chips with the meta after them, and the author's header at its full width (`data-message-fit="row"`, an ellipsized name counted whole). Quote and code blocks stretch to the bubble's width; long code lines wrap. Plain wrapping, never `pretty`. The width is measured once per text and row width — cleared, measured and written in one pass in the commit that changed the text, before paint, so a size change's morph runs to the hugged width — and cached by both: a row the window renders again takes its width with nothing measured. Row widths come from one shared `ResizeObserver`, batched per frame; hover, the menu, the reaction bar and selection never measure; a morph reads boxes only, never the hug width; fonts that finish loading measure once more. A bubble with any other part (a card, a player, a picture) keeps the width its parts give it.
|
|
339
358
|
- No minimum width beyond the corners: «Ок 10:16» is a compact pill — the text, the meta on the same line and the padding. With reactions the meta moves to the end of the row's last line and the text gives its room back.
|
|
340
|
-
- Fixed kinds stay one width: a file, a contact and a location are `--prime-message-card-width` (16rem, at most 75% of the chat), a voice message is the player's width, a round video is the VideoNote circle (268, at most the row), a link preview the media width (360) or what the row allows.
|
|
359
|
+
- Fixed kinds stay one width: a file, a contact and a location are `--prime-message-card-width` (16rem, at most 75% of the chat), a voice message is the player's width, a round video is the VideoNote circle (268, at most the row), a sticker the square `--prime-message-sticker` (160, at most 85% of the row), a link preview the media width (360) or what the row allows.
|
|
341
360
|
- Embedded blocks (a link preview, a reply quote, a forwarded header, the cards) span the bubble's content width; long unbreakable text (URLs) wraps instead of widening the bubble.
|
|
342
361
|
- Pictures follow the album layout of their Gallery, at most `--prime-gallery-max-size` (360) wide and tall and within 85% of the row; a bubble with a picture or a card is exactly that part's width and a caption wraps under it.
|
|
343
362
|
- A file's name stands on one line without the extension its format tile shows; the full name is its tooltip and the download label. The tile downloads by an `href` link, or with `transfer="remote"` through `onDownload` in the app.
|
|
@@ -488,6 +507,8 @@ The list-level settings a message reads — the group chat's author header and a
|
|
|
488
507
|
| [contact.tsx](examples/contact.tsx) | A shared contact: initials or a photo, name and phone, with the actions to write or save — `Message.Contact`, `phone`. |
|
|
489
508
|
| [link-preview.tsx](examples/link-preview.tsx) | A message with a link and the card of the page under it: site, title, description and picture — `Message.LinkPreview`, `site`, `image`. |
|
|
490
509
|
| [bot-actions.tsx](examples/bot-actions.tsx) | A bot's message with an inline keyboard: rows of buttons under the bubble at its width, sharing each row equally, one of them a link — `Message.Actions`, `asChild`. |
|
|
510
|
+
| [sticker.tsx](examples/sticker.tsx) | Stickers in a conversation: a picture by its address and a video sticker in your own player, no bubble, the time on a pill over the corner — `Message.Sticker`, `src`, `loading`. |
|
|
511
|
+
| [preview.tsx](examples/preview.tsx) | A bot's welcome message as its settings page shows it: the same bubble with its keyboard, nothing in it pressed or opened, the time in the zone of the profile — `preview`, `timeZone`. |
|
|
491
512
|
| [narrow.tsx](examples/narrow.tsx) | At phone width bubbles take up to 85% of the chat, long words and links wrap inside, an album keeps its rows — `Message.Root`, `Message.Media`. |
|
|
492
513
|
|
|
493
514
|
## Mistakes
|
|
@@ -513,5 +534,5 @@ The list-level settings a message reads — the group chat's author header and a
|
|
|
513
534
|
- A bot's buttons placed inside `Message.Text`, or a row of buttons of your own under a message → `Message.Actions` with `Button.Root` children: one row per part, at the message's width.
|
|
514
535
|
|
|
515
536
|
## Related
|
|
516
|
-
- **Built from:** [Dropdown](../dropdown/COMPONENT.md) (`Message.Menu`), [ScrollContainer](../scroll-container/COMPONENT.md) (the floating panel of the reaction bar), [Gallery](../gallery/COMPONENT.md) (`Message.Media`), [Avatar](../avatar/COMPONENT.md) (`Avatar.Group` + `Avatar.Overflow` in reaction chips), [Badge](../badge/COMPONENT.md) (the «Трансляция» mark of a live location), [Thumbnail](../thumbnail/COMPONENT.md) (the map, the link preview picture, a file's preview), [Button](../button/COMPONENT.md) (retry, the transfer disk, `shape="circle"`, the buttons of `Message.Actions`), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in selection mode), [Spinner](../spinner/COMPONENT.md) (pending transfers and transcripts), Icon (status ticks, file glyphs, `action.forward`, `action.refresh`); `internal/messagePart` (the contract of content parts), `internal/messageList` (the contract with a chat's list), `internal/palette` (`hueOf`), `internal/Disclosure` (the reactions row, transcripts), `bubbleMorph` (every size change as one stroke), `bubbleHug` (a bubble of text as wide as its widest line), `internal/transferDisk` (the transfer of a file), `internal/mediaPill` (the meta, the author's pill), `internal/HighlightMatch` (search marks), `internal/richText` (Markdown), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
|
517
|
-
- **See also:** [Chat](../chat/COMPONENT.md) — the conversation the messages stand in; [Gallery](../gallery/COMPONENT.md) — the album of `Message.Media`; [VideoNote](../video-note/COMPONENT.md) — the round video message; [VoicePlayer](../voice-player/COMPONENT.md) — the content of a voice message; [Composer](../composer/COMPONENT.md) — the input under the list; [Timeline](../timeline/COMPONENT.md)
|
|
537
|
+
- **Built from:** [Dropdown](../dropdown/COMPONENT.md) (`Message.Menu`), [ScrollContainer](../scroll-container/COMPONENT.md) (the floating panel of the reaction bar), [Gallery](../gallery/COMPONENT.md) (`Message.Media`), [Avatar](../avatar/COMPONENT.md) (`Avatar.Group` + `Avatar.Overflow` in reaction chips), [Badge](../badge/COMPONENT.md) (the «Трансляция» mark of a live location), [Thumbnail](../thumbnail/COMPONENT.md) (the map, the link preview picture, a file's preview), [Button](../button/COMPONENT.md) (retry, the transfer disk, `shape="circle"`, the buttons of `Message.Actions`), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in selection mode), [Spinner](../spinner/COMPONENT.md) (pending transfers and transcripts), [Skeleton](../skeleton/COMPONENT.md) (a sticker while it loads), Icon (status ticks, file glyphs, `action.forward`, `action.refresh`); `internal/messagePart` (the contract of content parts), `internal/messageList` (the contract with a chat's list), `internal/palette` (`hueOf`), `internal/Disclosure` (the reactions row, transcripts), `bubbleMorph` (every size change as one stroke), `bubbleHug` (a bubble of text as wide as its widest line), `internal/transferDisk` (the transfer of a file), `internal/mediaPill` (the meta, the author's pill), `internal/HighlightMatch` (search marks), `internal/richText` (Markdown), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
|
538
|
+
- **See also:** [Chat](../chat/COMPONENT.md) — the conversation the messages stand in; [Gallery](../gallery/COMPONENT.md) — the album of `Message.Media`; [VideoNote](../video-note/COMPONENT.md) — the round video message; [VoicePlayer](../voice-player/COMPONENT.md) — the content of a voice message; [Composer](../composer/COMPONENT.md) — the input under the list; [RichText](../rich-text/COMPONENT.md) — the text of `Message.Text` outside a message; [Timeline](../timeline/COMPONENT.md)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** A bot's welcome message as its settings page shows it: the same bubble with its keyboard, nothing in it pressed or opened, the time in the zone of the profile — `preview`, `timeZone`. */
|
|
2
|
+
import { Button, Message } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const SENT = new Date(Date.UTC(2026, 9, 14, 23, 30));
|
|
7
|
+
|
|
8
|
+
export default function MessagePreviewExample() {
|
|
9
|
+
return (
|
|
10
|
+
<div className={styles.thread}>
|
|
11
|
+
<Message.Root preview time={SENT} timeZone="Asia/Vladivostok">
|
|
12
|
+
<Message.Text>
|
|
13
|
+
{
|
|
14
|
+
"**Здравствуйте!** Я бот склада «Прайм»: подскажу статус заказа и пришлю счёт. Выберите, с чего начать."
|
|
15
|
+
}
|
|
16
|
+
</Message.Text>
|
|
17
|
+
<Message.Actions>
|
|
18
|
+
<Button.Root variant="soft" tone="neutral">
|
|
19
|
+
Статус заказа
|
|
20
|
+
</Button.Root>
|
|
21
|
+
<Button.Root variant="soft" tone="neutral">
|
|
22
|
+
Прислать счёт
|
|
23
|
+
</Button.Root>
|
|
24
|
+
</Message.Actions>
|
|
25
|
+
</Message.Root>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Stickers in a conversation: a picture by its address and a video sticker in your own player, no bubble, the time on a pill over the corner — `Message.Sticker`, `src`, `loading`. */
|
|
2
|
+
import { Message } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import styles from "./examples.module.css";
|
|
6
|
+
|
|
7
|
+
const THUMBS_UP = "https://fonts.gstatic.com/s/e/notoemoji/latest/1f44d/512.webp";
|
|
8
|
+
const CLIP = "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4";
|
|
9
|
+
|
|
10
|
+
export default function MessageStickerExample() {
|
|
11
|
+
const [clipReady, setClipReady] = React.useState(false);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className={styles.thread}>
|
|
15
|
+
<Message.Root value="sticker-text" time="10:42">
|
|
16
|
+
<Message.Text>Отгрузку подтвердили, машина выезжает в 14:00.</Message.Text>
|
|
17
|
+
</Message.Root>
|
|
18
|
+
<Message.Root value="sticker-image" direction="outgoing" time="10:43" status="read">
|
|
19
|
+
<Message.Sticker src={THUMBS_UP} aria-label="Стикер «Класс»" />
|
|
20
|
+
</Message.Root>
|
|
21
|
+
<Message.Root value="sticker-video" time="10:44">
|
|
22
|
+
<Message.Sticker aria-label="Видеостикер «Цветок»" loading={!clipReady}>
|
|
23
|
+
<video
|
|
24
|
+
src={CLIP}
|
|
25
|
+
autoPlay
|
|
26
|
+
loop
|
|
27
|
+
muted
|
|
28
|
+
playsInline
|
|
29
|
+
onLoadedData={() => setClipReady(true)}
|
|
30
|
+
/>
|
|
31
|
+
</Message.Sticker>
|
|
32
|
+
</Message.Root>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|