prime-ui-kit 0.13.1 → 0.14.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 +7 -6
- package/dist/chunks/{chunk-XNOJEX55.js → chunk-P6ZO37JM.js} +1 -1
- package/dist/chunks/{chunk-JV2KB5JZ.js → chunk-SYEX7OVT.js} +3572 -2865
- package/dist/chunks/{chunk-PK2GC77E.js → chunk-WXKRUVSE.js} +164 -140
- package/dist/color-picker.js +2 -2
- package/dist/components/banner/Banner.d.ts +5 -2
- package/dist/components/chart-frame/ChartFrame.d.ts +19 -6
- package/dist/components/chat/Chat.d.ts +2 -2
- package/dist/components/chat/ChatPinned.d.ts +2 -2
- package/dist/components/chat/MessageList.d.ts +1 -1
- package/dist/components/chat/chatContext.d.ts +1 -1
- package/dist/components/field-list/FieldList.d.ts +90 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/input/Input.d.ts +2 -2
- package/dist/components/kanban/Kanban.d.ts +6 -3
- package/dist/components/page-content/PageContent.d.ts +55 -4
- package/dist/components/sparkline/Sparkline.d.ts +64 -15
- package/dist/components/sparkline/geometry.d.ts +26 -0
- package/dist/components/tooltip/Tooltip.d.ts +5 -2
- package/dist/hooks/usePosition.d.ts +15 -4
- package/dist/index.css +1 -1
- package/dist/index.js +4 -2
- package/dist/internal/ChartTooltip.d.ts +26 -0
- package/dist/internal/FieldAction.d.ts +6 -7
- package/dist/internal/fieldCell.d.ts +21 -0
- package/dist/internal/messageList.d.ts +2 -2
- package/dist/internal/palette.d.ts +7 -0
- package/dist/skill/SKILL.md +24 -14
- package/dist/skill/anti-slop.md +77 -4
- package/dist/skill/cheatsheet.md +24 -5
- package/dist/skill/checklist.md +19 -1
- package/dist/skill/choosing.md +10 -5
- package/dist/skill/components.md +4 -3
- package/dist/skill/composition.md +28 -15
- package/dist/skill/foundations.md +8 -6
- package/dist/skill/layouts.md +6 -6
- package/dist/skill/patterns/app-shell.module.css +1 -8
- package/dist/skill/patterns/app-shell.tsx +11 -9
- package/dist/skill/patterns/chat.tsx +23 -5
- package/dist/skill/patterns/dashboard.module.css +1 -31
- package/dist/skill/patterns/dashboard.tsx +150 -136
- package/dist/skill/patterns/detail-page.module.css +0 -3
- package/dist/skill/patterns/detail-page.tsx +4 -7
- package/dist/skill/patterns/form-drawer.tsx +2 -2
- package/dist/skill/patterns/kanban-board.tsx +4 -2
- package/dist/skill/patterns/list-page.tsx +2 -2
- package/dist/skill/patterns/media-library.module.css +0 -22
- package/dist/skill/patterns/media-library.tsx +17 -18
- package/dist/skill/patterns/record-editor.module.css +18 -0
- package/dist/skill/patterns/record-editor.tsx +356 -0
- package/dist/skill/patterns/screen-states.module.css +3 -17
- package/dist/skill/patterns/screen-states.tsx +70 -69
- package/dist/skill/patterns/settings-page.module.css +1 -21
- package/dist/skill/patterns/settings-page.tsx +58 -79
- package/dist/skill/reference/components/avatar/COMPONENT.md +4 -4
- package/dist/skill/reference/components/avatar/examples/presence.tsx +1 -1
- package/dist/skill/reference/components/avatar/examples/states.tsx +1 -1
- package/dist/skill/reference/components/banner/COMPONENT.md +6 -6
- package/dist/skill/reference/components/banner/examples/dismissible.tsx +22 -11
- package/dist/skill/reference/components/banner/examples/examples.module.css +3 -3
- package/dist/skill/reference/components/banner/examples/narrow.tsx +1 -1
- package/dist/skill/reference/components/breadcrumb/COMPONENT.md +1 -1
- package/dist/skill/reference/components/breadcrumb/examples/overflow.tsx +1 -1
- package/dist/skill/reference/components/card/COMPONENT.md +5 -4
- package/dist/skill/reference/components/card/examples/examples.module.css +0 -28
- package/dist/skill/reference/components/card/examples/kpi-media.tsx +13 -11
- package/dist/skill/reference/components/card/examples/narrow.tsx +1 -1
- package/dist/skill/reference/components/card/examples/panel-chart.tsx +45 -22
- package/dist/skill/reference/components/chart-frame/COMPONENT.md +10 -7
- package/dist/skill/reference/components/chart-frame/examples/tooltip.tsx +20 -24
- package/dist/skill/reference/components/chat/COMPONENT.md +3 -3
- package/dist/skill/reference/components/chat/examples/search.tsx +1 -1
- package/dist/skill/reference/components/chat-list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/checkbox/examples/indicator.tsx +24 -2
- package/dist/skill/reference/components/color-picker/COMPONENT.md +2 -2
- package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
- package/dist/skill/reference/components/datepicker/COMPONENT.md +1 -0
- package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
- package/dist/skill/reference/components/field-list/COMPONENT.md +164 -0
- package/dist/skill/reference/components/field-list/examples/base-row.tsx +76 -0
- package/dist/skill/reference/components/field-list/examples/controlled.tsx +72 -0
- package/dist/skill/reference/components/field-list/examples/errors.tsx +85 -0
- package/dist/skill/reference/components/field-list/examples/examples.module.css +34 -0
- package/dist/skill/reference/components/field-list/examples/limits.tsx +55 -0
- package/dist/skill/reference/components/field-list/examples/locked.tsx +139 -0
- package/dist/skill/reference/components/field-list/examples/narrow.tsx +68 -0
- package/dist/skill/reference/components/field-list/examples/overview.tsx +66 -0
- package/dist/skill/reference/components/field-list/examples/reorder.tsx +42 -0
- package/dist/skill/reference/components/field-list/examples/sizes.tsx +42 -0
- package/dist/skill/reference/components/field-list/examples/states.tsx +51 -0
- package/dist/skill/reference/components/input/examples/examples.module.css +2 -7
- package/dist/skill/reference/components/kanban/COMPONENT.md +13 -9
- package/dist/skill/reference/components/kanban/examples/limits.tsx +2 -1
- package/dist/skill/reference/components/kanban/examples/variants.tsx +16 -8
- package/dist/skill/reference/components/list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/media-viewer/COMPONENT.md +2 -2
- package/dist/skill/reference/components/message/COMPONENT.md +2 -2
- package/dist/skill/reference/components/notification/COMPONENT.md +1 -0
- package/dist/skill/reference/components/page-content/COMPONENT.md +70 -14
- package/dist/skill/reference/components/page-content/examples/grid.tsx +107 -0
- package/dist/skill/reference/components/page-content/examples/overview.tsx +2 -2
- package/dist/skill/reference/components/page-content/examples/sections.tsx +81 -0
- package/dist/skill/reference/components/page-toolbar/COMPONENT.md +1 -1
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
- package/dist/skill/reference/components/select/COMPONENT.md +4 -2
- package/dist/skill/reference/components/select/examples/icon-only.tsx +38 -0
- package/dist/skill/reference/components/sparkline/COMPONENT.md +83 -30
- package/dist/skill/reference/components/sparkline/examples/compact.tsx +1 -1
- package/dist/skill/reference/components/sparkline/examples/curve.tsx +28 -0
- package/dist/skill/reference/components/sparkline/examples/gaps.tsx +33 -0
- package/dist/skill/reference/components/sparkline/examples/overview.tsx +11 -2
- package/dist/skill/reference/components/sparkline/examples/period.tsx +7 -2
- package/dist/skill/reference/components/sparkline/examples/reference.tsx +37 -0
- package/dist/skill/reference/components/sparkline/examples/variants.tsx +2 -2
- package/dist/skill/reference/components/tabs/examples/examples.module.css +0 -6
- package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +11 -13
- package/dist/skill/reference/components/timeline/examples/examples.module.css +0 -8
- package/dist/skill/reference/components/timeline/examples/narrow.tsx +2 -2
- package/dist/skill/reference/components/timeline/examples/sizes.tsx +3 -3
- package/dist/skill/reference/components/timeline/examples/variants.tsx +4 -4
- package/dist/skill/reference/components/tooltip/COMPONENT.md +6 -4
- package/dist/skill/reference/components/tooltip/examples/placement.tsx +2 -1
- package/dist/skill/reference/components/video-note/COMPONENT.md +1 -1
- package/dist/skill/reference/components/voice-player/COMPONENT.md +1 -1
- package/dist/skill/reference/layout/app-header/examples/in-app-shell.tsx +2 -2
- package/dist/skill/reference/layout/app-shell/COMPONENT.md +1 -1
- package/dist/skill/reference/layout/app-shell/examples/footer.tsx +2 -2
- package/dist/skill/reference/layout/app-shell/examples/overview.tsx +2 -2
- package/dist/skill/reference/layout/app-shell/examples/template.tsx +2 -2
- package/dist/skill/reference/layout/bottom-nav/examples/floating-action.tsx +2 -2
- package/dist/skill/reference/layout/bottom-nav/examples/floating.tsx +2 -2
- package/dist/skill/reference/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
- package/dist/skill/responsive.md +5 -3
- package/dist/skill/ux.md +145 -0
- package/dist/tokens/semantic.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/avatar/COMPONENT.md +4 -4
- package/src/components/avatar/examples/presence.tsx +1 -1
- package/src/components/avatar/examples/states.tsx +1 -1
- package/src/components/banner/COMPONENT.md +6 -6
- package/src/components/banner/examples/dismissible.tsx +22 -11
- package/src/components/banner/examples/examples.module.css +3 -3
- package/src/components/banner/examples/narrow.tsx +1 -1
- package/src/components/breadcrumb/COMPONENT.md +1 -1
- package/src/components/breadcrumb/examples/overflow.tsx +1 -1
- package/src/components/card/COMPONENT.md +5 -4
- package/src/components/card/examples/examples.module.css +0 -28
- package/src/components/card/examples/kpi-media.tsx +13 -11
- package/src/components/card/examples/narrow.tsx +1 -1
- package/src/components/card/examples/panel-chart.tsx +45 -22
- package/src/components/chart-frame/COMPONENT.md +10 -7
- package/src/components/chart-frame/examples/tooltip.tsx +20 -24
- package/src/components/chat/COMPONENT.md +3 -3
- package/src/components/chat/examples/search.tsx +1 -1
- package/src/components/chat-list/COMPONENT.md +1 -1
- package/src/components/checkbox/examples/indicator.tsx +24 -2
- package/src/components/color-picker/COMPONENT.md +2 -2
- package/src/components/color-swatches/COMPONENT.md +1 -0
- package/src/components/datepicker/COMPONENT.md +1 -0
- package/src/components/dnd/COMPONENT.md +3 -3
- package/src/components/field-list/COMPONENT.md +164 -0
- package/src/components/field-list/examples/base-row.tsx +76 -0
- package/src/components/field-list/examples/controlled.tsx +72 -0
- package/src/components/field-list/examples/errors.tsx +85 -0
- package/src/components/field-list/examples/examples.module.css +34 -0
- package/src/components/field-list/examples/limits.tsx +55 -0
- package/src/components/field-list/examples/locked.tsx +139 -0
- package/src/components/field-list/examples/narrow.tsx +68 -0
- package/src/components/field-list/examples/overview.tsx +66 -0
- package/src/components/field-list/examples/reorder.tsx +42 -0
- package/src/components/field-list/examples/sizes.tsx +42 -0
- package/src/components/field-list/examples/states.tsx +51 -0
- package/src/components/input/examples/examples.module.css +2 -7
- package/src/components/kanban/COMPONENT.md +13 -9
- package/src/components/kanban/examples/limits.tsx +2 -1
- package/src/components/kanban/examples/variants.tsx +16 -8
- package/src/components/list/COMPONENT.md +1 -1
- package/src/components/media-viewer/COMPONENT.md +2 -2
- package/src/components/message/COMPONENT.md +2 -2
- package/src/components/notification/COMPONENT.md +1 -0
- package/src/components/page-content/COMPONENT.md +70 -14
- package/src/components/page-content/examples/grid.tsx +107 -0
- package/src/components/page-content/examples/overview.tsx +2 -2
- package/src/components/page-content/examples/sections.tsx +81 -0
- package/src/components/page-toolbar/COMPONENT.md +1 -1
- package/src/components/segmented-control/COMPONENT.md +1 -0
- package/src/components/select/COMPONENT.md +4 -2
- package/src/components/select/examples/icon-only.tsx +38 -0
- package/src/components/sparkline/COMPONENT.md +83 -30
- package/src/components/sparkline/examples/compact.tsx +1 -1
- package/src/components/sparkline/examples/curve.tsx +28 -0
- package/src/components/sparkline/examples/gaps.tsx +33 -0
- package/src/components/sparkline/examples/overview.tsx +11 -2
- package/src/components/sparkline/examples/period.tsx +7 -2
- package/src/components/sparkline/examples/reference.tsx +37 -0
- package/src/components/sparkline/examples/variants.tsx +2 -2
- package/src/components/tabs/examples/examples.module.css +0 -6
- package/src/components/tabs/examples/in-drawer.tsx +11 -13
- package/src/components/timeline/examples/examples.module.css +0 -8
- package/src/components/timeline/examples/narrow.tsx +2 -2
- package/src/components/timeline/examples/sizes.tsx +3 -3
- package/src/components/timeline/examples/variants.tsx +4 -4
- package/src/components/tooltip/COMPONENT.md +6 -4
- package/src/components/tooltip/examples/placement.tsx +2 -1
- package/src/components/video-note/COMPONENT.md +1 -1
- package/src/components/voice-player/COMPONENT.md +1 -1
- package/src/layout/app-header/examples/in-app-shell.tsx +2 -2
- package/src/layout/app-shell/COMPONENT.md +1 -1
- package/src/layout/app-shell/examples/footer.tsx +2 -2
- package/src/layout/app-shell/examples/overview.tsx +2 -2
- package/src/layout/app-shell/examples/template.tsx +2 -2
- package/src/layout/bottom-nav/examples/floating-action.tsx +2 -2
- package/src/layout/bottom-nav/examples/floating.tsx +2 -2
- package/src/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
- package/src/styles/theme-light.css +1 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** The invoice lines in a 320 px column: the headings give way, each row stacks its fields with their own labels and rows stand a group apart — `columns`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import styles from "./examples.module.css";
|
|
6
|
+
|
|
7
|
+
type Line = { id: string; service: string; quantity: string; price: string };
|
|
8
|
+
|
|
9
|
+
const COLUMNS: FieldListColumn[] = [
|
|
10
|
+
{ label: "Услуга" },
|
|
11
|
+
{ label: "Кол-во", width: "s" },
|
|
12
|
+
{ label: "Цена", width: "m" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const LINES: Line[] = [
|
|
16
|
+
{ id: "setup", service: "Внедрение CRM", quantity: "1", price: "180 000" },
|
|
17
|
+
{ id: "training", service: "Обучение менеджеров", quantity: "3", price: "15 000" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default function FieldListNarrowExample() {
|
|
21
|
+
const [lines, setLines] = React.useState(LINES);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className={styles.narrow}>
|
|
25
|
+
<FieldList
|
|
26
|
+
label="Позиции счёта"
|
|
27
|
+
columns={COLUMNS}
|
|
28
|
+
value={lines}
|
|
29
|
+
onValueChange={setLines}
|
|
30
|
+
getId={(line) => line.id}
|
|
31
|
+
getLabel={(line) => line.service}
|
|
32
|
+
createItem={() => ({ id: crypto.randomUUID(), service: "", quantity: "1", price: "" })}
|
|
33
|
+
addLabel="Добавить позицию"
|
|
34
|
+
renderItem={(line, row) => (
|
|
35
|
+
<>
|
|
36
|
+
<Input.Root>
|
|
37
|
+
<Input.Wrapper>
|
|
38
|
+
<Input.Field
|
|
39
|
+
value={line.service}
|
|
40
|
+
onChange={(event) => row.update({ service: event.target.value })}
|
|
41
|
+
/>
|
|
42
|
+
</Input.Wrapper>
|
|
43
|
+
</Input.Root>
|
|
44
|
+
<Input.Root>
|
|
45
|
+
<Input.Wrapper>
|
|
46
|
+
<Input.Field
|
|
47
|
+
value={line.quantity}
|
|
48
|
+
inputMode="numeric"
|
|
49
|
+
onChange={(event) => row.update({ quantity: event.target.value })}
|
|
50
|
+
/>
|
|
51
|
+
</Input.Wrapper>
|
|
52
|
+
</Input.Root>
|
|
53
|
+
<Input.Root>
|
|
54
|
+
<Input.Wrapper>
|
|
55
|
+
<Input.Field
|
|
56
|
+
value={line.price}
|
|
57
|
+
inputMode="decimal"
|
|
58
|
+
onChange={(event) => row.update({ price: event.target.value })}
|
|
59
|
+
/>
|
|
60
|
+
<Input.InlineAffix side="end">₽</Input.InlineAffix>
|
|
61
|
+
</Input.Wrapper>
|
|
62
|
+
</Input.Root>
|
|
63
|
+
</>
|
|
64
|
+
)}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Lines of an invoice: the column headings stand once above the rows, each row has a remove button and the add button sits under them — `columns`, `renderItem`, `createItem`, `addLabel`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
type Line = { id: string; service: string; quantity: string; price: string };
|
|
6
|
+
|
|
7
|
+
const COLUMNS: FieldListColumn[] = [
|
|
8
|
+
{ label: "Услуга" },
|
|
9
|
+
{ label: "Кол-во", width: "s" },
|
|
10
|
+
{ label: "Цена", width: "m" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const LINES: Line[] = [
|
|
14
|
+
{ id: "setup", service: "Внедрение CRM", quantity: "1", price: "180 000" },
|
|
15
|
+
{ id: "training", service: "Обучение менеджеров", quantity: "3", price: "15 000" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export default function FieldListOverviewExample() {
|
|
19
|
+
const [lines, setLines] = React.useState(LINES);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<FieldList
|
|
23
|
+
label="Позиции счёта"
|
|
24
|
+
columns={COLUMNS}
|
|
25
|
+
value={lines}
|
|
26
|
+
onValueChange={setLines}
|
|
27
|
+
getId={(line) => line.id}
|
|
28
|
+
getLabel={(line) => line.service}
|
|
29
|
+
createItem={() => ({ id: crypto.randomUUID(), service: "", quantity: "1", price: "" })}
|
|
30
|
+
addLabel="Добавить позицию"
|
|
31
|
+
renderItem={(line, row) => (
|
|
32
|
+
<>
|
|
33
|
+
<Input.Root>
|
|
34
|
+
<Input.Wrapper>
|
|
35
|
+
<Input.Field
|
|
36
|
+
value={line.service}
|
|
37
|
+
placeholder="Поддержка, 1 месяц"
|
|
38
|
+
onChange={(event) => row.update({ service: event.target.value })}
|
|
39
|
+
/>
|
|
40
|
+
</Input.Wrapper>
|
|
41
|
+
</Input.Root>
|
|
42
|
+
<Input.Root>
|
|
43
|
+
<Input.Wrapper>
|
|
44
|
+
<Input.Field
|
|
45
|
+
value={line.quantity}
|
|
46
|
+
inputMode="numeric"
|
|
47
|
+
onChange={(event) => row.update({ quantity: event.target.value })}
|
|
48
|
+
/>
|
|
49
|
+
</Input.Wrapper>
|
|
50
|
+
</Input.Root>
|
|
51
|
+
<Input.Root>
|
|
52
|
+
<Input.Wrapper>
|
|
53
|
+
<Input.Field
|
|
54
|
+
value={line.price}
|
|
55
|
+
inputMode="decimal"
|
|
56
|
+
placeholder="0"
|
|
57
|
+
onChange={(event) => row.update({ price: event.target.value })}
|
|
58
|
+
/>
|
|
59
|
+
<Input.InlineAffix side="end">₽</Input.InlineAffix>
|
|
60
|
+
</Input.Wrapper>
|
|
61
|
+
</Input.Root>
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, a point moves by its handle or Alt+arrows — `reorderable`, `getLabel`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
type Point = { id: string; text: string };
|
|
6
|
+
|
|
7
|
+
const COLUMNS: FieldListColumn[] = [{ label: "Преимущество" }];
|
|
8
|
+
|
|
9
|
+
const POINTS: Point[] = [
|
|
10
|
+
{ id: "users", text: "До 50 сотрудников в одном пространстве" },
|
|
11
|
+
{ id: "storage", text: "200 ГБ для документов и вложений" },
|
|
12
|
+
{ id: "support", text: "Поддержка в рабочие часы, ответ за 2 часа" },
|
|
13
|
+
{ id: "api", text: "Доступ к API и вебхукам" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export default function FieldListReorderExample() {
|
|
17
|
+
const [points, setPoints] = React.useState(POINTS);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<FieldList
|
|
21
|
+
label="Преимущества тарифа"
|
|
22
|
+
columns={COLUMNS}
|
|
23
|
+
reorderable
|
|
24
|
+
value={points}
|
|
25
|
+
onValueChange={setPoints}
|
|
26
|
+
getId={(point) => point.id}
|
|
27
|
+
getLabel={(point) => point.text}
|
|
28
|
+
createItem={() => ({ id: crypto.randomUUID(), text: "" })}
|
|
29
|
+
addLabel="Добавить преимущество"
|
|
30
|
+
renderItem={(point, row) => (
|
|
31
|
+
<Input.Root>
|
|
32
|
+
<Input.Wrapper>
|
|
33
|
+
<Input.Field
|
|
34
|
+
value={point.text}
|
|
35
|
+
onChange={(event) => row.update({ text: event.target.value })}
|
|
36
|
+
/>
|
|
37
|
+
</Input.Wrapper>
|
|
38
|
+
</Input.Root>
|
|
39
|
+
)}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Every tier: the headings, the fields, the handle and the buttons follow the list's size — `size`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
type Contact = { id: string; name: string; phone: string };
|
|
5
|
+
|
|
6
|
+
const SIZES = ["xs", "s", "m", "l", "xl"] as const;
|
|
7
|
+
|
|
8
|
+
const COLUMNS: FieldListColumn[] = [{ label: "Контакт" }, { label: "Телефон", width: "m" }];
|
|
9
|
+
|
|
10
|
+
const CONTACTS: Contact[] = [{ id: "buyer", name: "Анна Лебедева", phone: "+7 912 404-18-20" }];
|
|
11
|
+
|
|
12
|
+
export default function FieldListSizesExample() {
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
{SIZES.map((size) => (
|
|
16
|
+
<FieldList
|
|
17
|
+
key={size}
|
|
18
|
+
size={size}
|
|
19
|
+
label={size}
|
|
20
|
+
columns={COLUMNS}
|
|
21
|
+
defaultValue={CONTACTS}
|
|
22
|
+
getId={(contact) => contact.id}
|
|
23
|
+
getLabel={(contact) => contact.name}
|
|
24
|
+
renderItem={(contact) => (
|
|
25
|
+
<>
|
|
26
|
+
<Input.Root>
|
|
27
|
+
<Input.Wrapper>
|
|
28
|
+
<Input.Field defaultValue={contact.name} />
|
|
29
|
+
</Input.Wrapper>
|
|
30
|
+
</Input.Root>
|
|
31
|
+
<Input.Root>
|
|
32
|
+
<Input.Wrapper>
|
|
33
|
+
<Input.Field type="tel" defaultValue={contact.phone} />
|
|
34
|
+
</Input.Wrapper>
|
|
35
|
+
</Input.Root>
|
|
36
|
+
</>
|
|
37
|
+
)}
|
|
38
|
+
/>
|
|
39
|
+
))}
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The same list editable and disabled while the request saves it: fields, handles and buttons turn off together — `disabled`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
type Stage = { id: string; name: string; days: string };
|
|
5
|
+
|
|
6
|
+
const COLUMNS: FieldListColumn[] = [{ label: "Этап" }, { label: "Дней", width: "s" }];
|
|
7
|
+
|
|
8
|
+
const STAGES: Stage[] = [
|
|
9
|
+
{ id: "brief", name: "Бриф и смета", days: "3" },
|
|
10
|
+
{ id: "design", name: "Дизайн", days: "10" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const STATES = [
|
|
14
|
+
{ label: "Этапы проекта", disabled: false },
|
|
15
|
+
{ label: "disabled", disabled: true },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export default function FieldListStatesExample() {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{STATES.map((state) => (
|
|
22
|
+
<FieldList
|
|
23
|
+
key={state.label}
|
|
24
|
+
label={state.label}
|
|
25
|
+
disabled={state.disabled}
|
|
26
|
+
reorderable
|
|
27
|
+
columns={COLUMNS}
|
|
28
|
+
defaultValue={STAGES}
|
|
29
|
+
getId={(stage) => stage.id}
|
|
30
|
+
getLabel={(stage) => stage.name}
|
|
31
|
+
createItem={() => ({ id: crypto.randomUUID(), name: "", days: "" })}
|
|
32
|
+
addLabel="Добавить этап"
|
|
33
|
+
renderItem={(stage) => (
|
|
34
|
+
<>
|
|
35
|
+
<Input.Root>
|
|
36
|
+
<Input.Wrapper>
|
|
37
|
+
<Input.Field defaultValue={stage.name} />
|
|
38
|
+
</Input.Wrapper>
|
|
39
|
+
</Input.Root>
|
|
40
|
+
<Input.Root>
|
|
41
|
+
<Input.Wrapper>
|
|
42
|
+
<Input.Field defaultValue={stage.days} inputMode="numeric" />
|
|
43
|
+
</Input.Wrapper>
|
|
44
|
+
</Input.Root>
|
|
45
|
+
</>
|
|
46
|
+
)}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
.formFields {
|
|
18
18
|
display: grid;
|
|
19
|
-
|
|
19
|
+
/* Two columns while each keeps 12rem, one column in a narrower form — from its own width. */
|
|
20
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--prime-space-16) * 3)), 1fr));
|
|
20
21
|
align-items: start;
|
|
21
22
|
gap: var(--prime-space-5) var(--prime-space-4);
|
|
22
23
|
}
|
|
@@ -30,9 +31,3 @@
|
|
|
30
31
|
justify-content: flex-end;
|
|
31
32
|
gap: var(--prime-space-2);
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
@media (max-width: 639px) {
|
|
35
|
-
.formFields {
|
|
36
|
-
grid-template-columns: minmax(0, 1fr);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
```
|
|
33
33
|
Kanban.Root board: horizontal strip of columns, its own Dnd.Root if none above
|
|
34
34
|
└─ <ul> (Dnd.Sortable, axis x) the columns in `columnOrder`
|
|
35
|
-
└─ <li> column (from `columns`) `
|
|
35
|
+
└─ <li> column (from `columns`) no well with a header plate (`plain`) or a header line (`line`), or a `fill-muted` well (`well`)
|
|
36
36
|
├─ header title (h3) · count Badge (the stage color) · renderColumnActions(column) · [grip Dnd.Handle, a lock when `locked`]
|
|
37
37
|
│ ├─ description muted line under the title (`column.description`)
|
|
38
38
|
│ └─ meta the stage's figures (`renderColumnMeta(column)`)
|
|
@@ -73,7 +73,7 @@ Generic `Kanban.Root<T>`; `ref` → `HTMLDivElement`. The board: a strip of colu
|
|
|
73
73
|
| `renderColumnActions` | `(column: KanbanColumn) => ReactNode` | — | Trailing actions of a column header (add a card, a menu); kit controls inside take the `s` tier. |
|
|
74
74
|
| `renderColumnMeta` | `(column: KanbanColumn) => ReactNode` | — | The meta line under a column's title and description: the stage's figures («12 сделок · 4,8 млн ₽»). |
|
|
75
75
|
| `canDrop` | `(id: string, columnId: string) => boolean \| string` | — | A workflow rule: refuses card `id` in column `columnId` — `false`, or a string that says why (spoken and shown under the carried card). |
|
|
76
|
-
| `variant` | `"
|
|
76
|
+
| `variant` | `"plain" \| "line" \| "well"` | `"plain"` | No wells: `plain` — a filled header plate over the cards, `line` — a bare header over a faint line; cards stand on the host. `well`: every column is a filled well holding its cards (bounded queues). |
|
|
77
77
|
| `loading` | `boolean` | `false` | Columns show skeleton cards in the card geometry; the cards cross-fade in when it turns off. |
|
|
78
78
|
| `disabled` | `boolean` | `false` | Read-only board: no drag, no keyboard moves, no column grips; `onItemClick` and `getItemHref` still open cards. |
|
|
79
79
|
| `labels` | `Partial<KanbanLabels>` | — | Built-in strings, see Labels. |
|
|
@@ -126,8 +126,11 @@ Generic `Kanban.Root<T>`; `ref` → `HTMLDivElement`. The board: a strip of colu
|
|
|
126
126
|
|
|
127
127
|
| Value | Looks like | Use when | Default |
|
|
128
128
|
|---|---|---|---|
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
129
|
+
| `plain` | no wells: the column is its header — a filled plate as wide as the cards (title, count) — and its cards, which stand on the board's host, each a surface one ladder step off it (white on the light page), as a Card on the page | the board is the page's main region (a pipeline, a task board, a queue screen) or sits in a Card or Drawer — one level of nesting, cards are the only boxes | yes |
|
|
130
|
+
| `line` | no wells, like `plain`, but the header is bare: title and count over a faint inset hairline as wide as the cards | the same places as `plain` when the headers should stay quiet — many columns, a calm board, the column headers are not the focus | |
|
|
131
|
+
| `well` | every column is a filled well (`fill-muted`, radius `l`) holding its cards (the selected step of the well) | columns must read as bounded queues: WIP limits and capacity, many sparse or empty columns whose drop area must show, a dense board of many narrow columns | |
|
|
132
|
+
|
|
133
|
+
Do not wrap a `plain` board's columns in your own boxes, and do not put a `well` board inside a filled box of your own — both are the double nesting the variants exist to avoid.
|
|
131
134
|
|
|
132
135
|
### Flags
|
|
133
136
|
| Value | Looks like | Use when | Default |
|
|
@@ -167,11 +170,11 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
|
|
|
167
170
|
- Give the root a height (`className`): columns span it and every column body scrolls on its own; without one the columns take the height of the tallest.
|
|
168
171
|
- The board is a size container. From 40rem of its own width columns keep a fixed 18rem; below it a column takes 85% of the board and snaps (`scroll-snap-type: x mandatory`), so the next column peeks. Snapping is off while a drag auto-scrolls the strip.
|
|
169
172
|
- The strip scrolls sideways inside the board, never the page. Cards sit at the top of their column; the drop area runs to the bottom of the column, so a card can be released anywhere in it.
|
|
170
|
-
- `
|
|
173
|
+
- `plain` / `line`: column gap `--prime-space-3`, cards inset `--prime-space-1` (room for the focus ring); a card sets `data-depth` one above the board's host and paints `--prime-color-layer-current`, so it keeps its color in flight too. `well`: column gap `--prime-space-3`, cards inset `--prime-space-2` from the column edge, column radius `l`. Card gap `--prime-space-2`, card padding `--prime-space-3`, card radius `m`.
|
|
171
174
|
- The column title is `title-m`; the header is at least a control `m` tall, its text starts where the card text does.
|
|
172
|
-
-
|
|
175
|
+
- `well` and `line`: a faint inset hairline (`--prime-color-border-faint`, as in a dialog header) separates the header from the cards. `plain`: the header is a filled plate (`--prime-color-fill-muted`, `--prime-radius-l`) as wide as the cards, in place of the hairline.
|
|
173
176
|
- The header is a grid: the title, the count, the actions and the grip at the end on the first row; the description under the title on the second, the meta line under it (`--prime-space-1` above, caption, `text-secondary`).
|
|
174
|
-
- A card is `--prime-color-control-selected` (two ladder steps off its
|
|
177
|
+
- A card is on `--prime-shadow-raised`: in `plain` / `line` the next layer of the board's host (`--prime-color-layer-current` of its own depth), in `well` `--prime-color-control-selected` (two ladder steps off its well; white in light).
|
|
175
178
|
- `Kanban.ItemHeader` is a muted caption row over a faint hairline (`--prime-color-border-faint`); a card without it starts with its title or a full-width `Thumbnail` cover.
|
|
176
179
|
- Header actions get the `s` tier; inside a card, kit controls take the `m` host tier (a `Badge` is `s`). Pick a small `Avatar` (`size="s"`).
|
|
177
180
|
|
|
@@ -215,10 +218,10 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
|
|
|
215
218
|
| Example | Shows |
|
|
216
219
|
|---|---|
|
|
217
220
|
| [overview.tsx](examples/overview.tsx) | A release board: cards move within and between columns by drag, a touch hold or Alt + arrows; a card shows its meta line, labels, comments and assignee — `Kanban.Root`, `Kanban.Item`, `renderItem`, `renderColumnActions`. |
|
|
218
|
-
| [variants.tsx](examples/variants.tsx) | The same release board in
|
|
221
|
+
| [variants.tsx](examples/variants.tsx) | The same release board in three looks: no wells with a header plate, no wells with a header over a line, and columns as filled wells holding their cards — `variant`. |
|
|
219
222
|
| [structure.tsx](examples/structure.tsx) | Cards take only the parts they need: a title alone, a meta line, labels, a footer with counts and an assignee — `Kanban.ItemTitle`, `Kanban.ItemDescription`, `Kanban.ItemBadges`, `Kanban.ItemFooter`, `Kanban.ItemCount`. |
|
|
220
223
|
| [cards.tsx](examples/cards.tsx) | Three kinds of cards on one board: a plain note, a ticket with a header row of key and priority, a design task with a cover — `Kanban.ItemHeader`, `Thumbnail`, `Kanban.ItemFooter`. |
|
|
221
|
-
| [limits.tsx](examples/limits.tsx) | A support queue with a work-in-progress limit and a workflow rule: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`. |
|
|
224
|
+
| [limits.tsx](examples/limits.tsx) | A support queue with a work-in-progress limit and a workflow rule, its columns drawn as wells so each reads as a bounded queue: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`, `variant`. |
|
|
222
225
|
| [columns.tsx](examples/columns.tsx) | A sales pipeline whose stages move by the grip in their header while the first and the last stay locked; each stage carries its color on the count, a rule and its figures under the title, and is edited in a popover — `reorderableColumns`, `onColumnOrderChange`, `locked`, `color`, `description`, `renderColumnMeta`, `renderColumnActions`. |
|
|
223
226
|
| [saving.tsx](examples/saving.tsx) | Every move is saved on the server: the card waits busy in its new place, and an invoice the server refuses to close glides back to its column — `onValueChange`. |
|
|
224
227
|
| [item-link.tsx](examples/item-link.tsx) | Tickets that open: each card is a link to its page, so ⌘-click or a middle click opens a new tab, a plain click, Enter or Space opens it in place, and a drag only moves it — `getItemHref`, `onItemClick`. |
|
|
@@ -234,6 +237,7 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
|
|
|
234
237
|
- A hover-only action on a card → touch has no hover; put the action in the card or open the card on click (`onItemClick`).
|
|
235
238
|
- `onClick` / `onKeyDown` on `Kanban.Item` to open a card → `onItemClick` (click, Enter, Space, never after a drag) or `getItemHref` for a page of its own.
|
|
236
239
|
- `getItemHref` without `getLabel` → the link is named by the card id; pass a human name.
|
|
240
|
+
- Drawing your own box (a Card, a filled `div`) around each column, or around a `well` board → pick the `variant`: `plain` or `line` for one level of nesting, `well` when columns must read as bounded queues.
|
|
237
241
|
- Styling a card from an ancestor (`.board .card`) → the flying clone is portalled to `<body>` and loses it; put the class on `Kanban.Item`.
|
|
238
242
|
- Keeping the column order in `columns` and reordering that array → pass `columnOrder` / `onColumnOrderChange`; `columns` stays the data.
|
|
239
243
|
- Putting a system stage back first after every move → `locked: true` on that column.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A support queue with a work-in-progress limit and a workflow rule: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`. */
|
|
1
|
+
/** A support queue with a work-in-progress limit and a workflow rule, its columns drawn as wells so each reads as a bounded queue: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`, `variant`. */
|
|
2
2
|
import { Badge, Kanban } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
@@ -33,6 +33,7 @@ export default function KanbanLimitsExample() {
|
|
|
33
33
|
<Kanban.Root
|
|
34
34
|
aria-label="Очередь поддержки"
|
|
35
35
|
className={styles.board}
|
|
36
|
+
variant="well"
|
|
36
37
|
columns={COLUMNS}
|
|
37
38
|
items={TICKETS}
|
|
38
39
|
getId={(ticket) => ticket.id}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** The same release board in
|
|
2
|
-
import { Kanban, type KanbanVariant, Typography } from "prime-ui-kit";
|
|
1
|
+
/** The same release board in three looks: no wells with a header plate, no wells with a header over a line, and columns as filled wells holding their cards — `variant`. */
|
|
2
|
+
import { Badge, Kanban, type KanbanVariant, Typography } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
5
5
|
|
|
6
|
-
type Task = { id: string; title: string; due: string };
|
|
6
|
+
type Task = { id: string; title: string; due: string; label: string };
|
|
7
7
|
|
|
8
|
-
const VARIANTS: KanbanVariant[] = ["
|
|
8
|
+
const VARIANTS: KanbanVariant[] = ["plain", "line", "well"];
|
|
9
9
|
|
|
10
10
|
const COLUMNS = [
|
|
11
11
|
{ id: "todo", title: "К выполнению", description: "Берём после планирования" },
|
|
@@ -14,10 +14,15 @@ const COLUMNS = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
const TASKS: Task[] = [
|
|
17
|
-
{ id: "PRJ-214", title: "Экспорт счетов в CSV для бухгалтерии", due: "14 окт" },
|
|
18
|
-
{ id: "PRJ-221", title: "Фильтр заказов по складу", due: "17 окт" },
|
|
19
|
-
{
|
|
20
|
-
|
|
17
|
+
{ id: "PRJ-214", title: "Экспорт счетов в CSV для бухгалтерии", due: "14 окт", label: "Финансы" },
|
|
18
|
+
{ id: "PRJ-221", title: "Фильтр заказов по складу", due: "17 окт", label: "Заказы" },
|
|
19
|
+
{
|
|
20
|
+
id: "PRJ-198",
|
|
21
|
+
title: "Повторная отправка приглашений в команду",
|
|
22
|
+
due: "10 окт",
|
|
23
|
+
label: "Команда",
|
|
24
|
+
},
|
|
25
|
+
{ id: "PRJ-176", title: "Онбординг нового проекта", due: "3 окт", label: "Проекты" },
|
|
21
26
|
];
|
|
22
27
|
|
|
23
28
|
const PLACEMENT = {
|
|
@@ -45,6 +50,9 @@ export default function KanbanVariantsExample() {
|
|
|
45
50
|
<Kanban.ItemDescription>
|
|
46
51
|
{task.id} · до {task.due}
|
|
47
52
|
</Kanban.ItemDescription>
|
|
53
|
+
<Kanban.ItemBadges>
|
|
54
|
+
<Badge.Root>{task.label}</Badge.Root>
|
|
55
|
+
</Kanban.ItemBadges>
|
|
48
56
|
</Kanban.Item>
|
|
49
57
|
)}
|
|
50
58
|
/>
|
|
@@ -134,7 +134,7 @@ A second line or an avatar makes a row taller than the one-line height; the vert
|
|
|
134
134
|
| current | `current` | `aria-current="true"` (button) or `"page"` (link); `fill-muted-hover` fill, primary text |
|
|
135
135
|
| focus-visible | keyboard | the focus ring inside the row edge |
|
|
136
136
|
| disabled | `disabled` | `disabled` on a button; `aria-disabled="true"`, no `href` on a link, `tabindex="-1"` on an `asChild` element; `data-disabled="true"`; disabled text; in a reorderable list the handle is disabled and the row holds its place |
|
|
137
|
-
| reorderable | `onReorder` | `data-reorderable="true"` on the root (a `Dnd.Sortable` `<ul>`); each `<li>` is a sortable item (`data-dnd-item` = `value`) with a leading `Dnd.Handle`; drag by the handle (a touch hold on touch), Alt + ↑ / ↓ from the handle or the row; a lifted row leaves a dashed gap, its neighbours glide, the copy follows the pointer and sets down in its new place |
|
|
137
|
+
| reorderable | `onReorder` | `data-reorderable="true"` on the root (a `Dnd.Sortable` `<ul>`); each `<li>` is a sortable item (`data-dnd-item` = `value`) with a leading `Dnd.Handle`; drag by the handle (a touch hold on touch), Alt + ↑ / ↓ from the handle or the row; a lifted row leaves a dashed gap in the row's radius, its neighbours glide, the copy (handle and row, in the row's padding and radius) follows the pointer and sets down in its new place without a jump |
|
|
138
138
|
| saving a move | `onReorder` returned a pending promise | the row stays in its new place, `aria-busy="true"`, a `Spinner` stands in for the handle, the row content dims to 0.6 (`--prime-motion-duration-fast` both ways); a rejection glides the row back |
|
|
139
139
|
|
|
140
140
|
Root attribute: `data-size`. The fill changes on `background-color` with `--prime-motion-duration-xfast` both ways (0 under reduced motion).
|
|
@@ -93,8 +93,8 @@ MediaViewer has no `variant`, `size` or `tone`: the stage is always near black,
|
|
|
93
93
|
| gesture | a finger or the mouse dragging | `data-gesture="page \| pan \| pinch \| dismiss"` on the dialog |
|
|
94
94
|
| chrome hidden | a tap, a pinch or a double tap (Tab brings it back); a playing video after 2.5 s without the pointer moving | `data-chrome="hidden"`: the bars fade and drift aside, take no presses; while a video plays the pointer hides too (`data-playing`). A video standing still always keeps its bar |
|
|
95
95
|
| flight | open and close | `data-phase="opening \| open \| closing"`: only the picture moves; the play disk and the bars come after it lands and go before it takes off |
|
|
96
|
-
| loading | the full picture is on its way or decoding | `data-status="loading"` on the page; a picture shows only once it is decoded, and the `thumbnail` stands in, blurred, until the picture has fully shown over it, so no frame is blank; without one a Spinner |
|
|
97
|
-
| error | the picture or the video failed | `data-status="error"`: a «Не удалось загрузить» panel with
|
|
96
|
+
| loading | the full picture is on its way or decoding | `data-status="loading"` on the page; a picture shows only once it is decoded, and the `thumbnail` stands in, blurred, until the picture has fully shown over it, so no frame is blank; without one a Spinner that fades out as the picture arrives |
|
|
97
|
+
| error | the picture or the video failed | `data-status="error"`: a «Не удалось загрузить» panel with «Повторить»; it fades in, and on a retry cross-fades with the Spinner in the same place |
|
|
98
98
|
| sound | the sound button, its level panel (hover, focus, a tap), ↑ / ↓, M | one `volume` and mute for every video of the viewer; `data-open` on the sound control while the level shows; `autoPlay` refused with sound starts muted |
|
|
99
99
|
|
|
100
100
|
Motion: it opens out of the thumbnail it was opened from — the picture grows from the thumbnail's shape to its fit over `base` on the `emphasized` curve while the stage fades in — and closes back into the thumbnail of the item on screen (`getOrigin`; without it the opener for the item it opened at, and for any other item the picture on the page that shows its `thumbnail`, `src` or `poster`, nearest the opener — paging on in a grid still closes into the right tile); without one, or with less than a fifth of it in view, it rises from a slight shrink and fades. The shape is what the page shows of the thumbnail: its media box, cut by every clipping ancestor (a scroller, a rounded album, a bubble) and the viewport, and the radius of each corner taken from the frame whose corner it is — an album's inner tiles fly square, its corner tiles round only on the outer side, and the corners glide all the way, never snap. During the flight only the picture moves; a video flies as the frame of the thumbnail it opened from (drawn before the first paint), starts only after it lands (`autoPlay`) and pauses before it takes off. Released gestures settle with a spring: a page turns past half its width or on a flick, and any other item — a thumbnail far along the strip, `Home` / `End` — arrives by the same slide, never a cut or a fade; the filmstrip follows the track on every frame, so its glide and the emphasis of its thumbnails move with the finger and settle with the page; a zoomed picture coasts with iOS momentum (deceleration 0.998 / ms) and springs back from its edges, and at its edge a sideways drag turns the page; a pinch past its range resists like a rubber band and settles inside it, a swipe away shrinks the picture and thins the stage and closes past 12% of the height or on a flick. Under `prefers-reduced-motion` the open and close are instant and the springs land at once; the picture still follows the finger.
|
|
@@ -116,7 +116,7 @@ Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md)
|
|
|
116
116
|
|---|---|---|---|
|
|
117
117
|
| `author` | `ReactNode` | — (required) | Who wrote the quoted message. |
|
|
118
118
|
| `color` | `"gray" \| "blue" \| "green" \| "orange" \| "red" \| "yellow" \| "purple" \| "sky" \| "pink" \| "teal"` | `"blue"` | The author's hue (the same as their `Message.Author`). |
|
|
119
|
-
| `target` | `string` | — | The `value` of the quoted message: a press brings the chat to it (rendering it first in a window), the
|
|
119
|
+
| `target` | `string` | — | The `value` of the quoted message: a press brings the chat to it (rendering it first in a window), the message blinks, and the focus moves to it. Makes the quote a button. |
|
|
120
120
|
| `onClick` | `(event: MouseEvent<HTMLButtonElement>) => void` | — | A press on the quote, after `target` is shown: load the original when it is not in the list yet. Makes the quote a button. |
|
|
121
121
|
| `labels` | `Partial<MessageReplyLabels>` | — | `reply` — the hidden prefix read before the quote. |
|
|
122
122
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "color" \| "onClick">` | — | `children` (the quoted text, one line), `className`. |
|
|
@@ -302,7 +302,7 @@ The list-level settings a message reads — the group chat's author header and a
|
|
|
302
302
|
| author header | `Message.Author` in a group chat (`Chat.Messages type="group"`) on `single` / `first`; any message on its own | the header line at the top of the bubble; over a picture or a circle on the media pill at the top-start corner; hidden on `middle` / `last` and in a direct chat (the author still names the message) |
|
|
303
303
|
| avatar | `Message.Avatar` in a group chat on `single` / `last` | the avatar in the gutter at the bubble's bottom; the gutter stays empty on the others |
|
|
304
304
|
| new at the end | the chat's list decided it (`data-enter="incoming"` / `"outgoing"`) | the message column floats up `--prime-space-3` from below and fades in (`fast`, enter easing, `transform` + `opacity` only; the row box clips it); never on a first render, history or a status change. See Chat |
|
|
305
|
-
| pointed at / stepping back | the chat's jump, search or selection (`data-pointed`, `data-selected` on the message; `data-pointing`, `data-selecting` on the list) | nothing is drawn on the message: it stays at full strength while the others — the bubble, picture or circle with its tail and reactions, the avatar, the retry — step back to `--prime-message-dim-opacity` with a slight desaturation (selection: `fast`, both ways; a jump
|
|
305
|
+
| pointed at / stepping back | the chat's jump, search or selection (`data-pointed`, `data-selected` on the message; `data-pointing`, `data-selecting` on the list) | nothing is drawn on the message: it stays at full strength while the others — the bubble, picture or circle with its tail and reactions, the avatar, the retry — step back to `--prime-message-dim-opacity` with a slight desaturation (selection: `fast`, both ways; the current search match: while it is current); a jump instead blinks the message itself three times, `base` each, the others stay still. Reduced motion: no blink |
|
|
306
306
|
| search match | the chat's search query | every case-insensitive match in `Message.Text` and the transcripts under a soft accent wash (`<mark>`) |
|
|
307
307
|
| typing | `Message.Typing active` | an incoming bubble of three dots rising and fading in turn on the loading loop (reduced motion slows it); enters floating up while its height opens (`fast`), leaves closing its height (`fast`) — or at once when a reply arrives at the end of the list in the same update; its block axis clips, so a closed row leaves nothing under the list |
|
|
308
308
|
| status change | `status` | the ticks cross-fade and scale (`base`, `emphasized`), interruptible |
|
|
@@ -152,6 +152,7 @@ Collapsed stacks show up to 3 cards peeking behind each other. Hover or keyboard
|
|
|
152
152
|
### ARIA
|
|
153
153
|
- Each stack is an `<ol>` named by the region label of its position; each card is an `<article>`: `role="status"` + `aria-live="polite"`, or `role="alert"` + `aria-live="assertive"` for `warning` / `danger`.
|
|
154
154
|
- The icon and the countdown are `aria-hidden`; the close button is named by `labels.close`.
|
|
155
|
+
- A toast is a non-modal live region, not a layer of the overlay stack: Escape and a press elsewhere belong to the page and to the topmost open layer, so a toast takes no `closeOnEscape` / `closeOnOutsideClick` — it leaves by its timer, the close button, a swipe or `dismiss()`.
|
|
155
156
|
- Do not keep the only copy of important information in a toast with a timer — use `persistent` or a Banner.
|
|
156
157
|
|
|
157
158
|
### Labels
|