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,356 @@
|
|
|
1
|
+
/** A record editor: a plan's name, its specs, selling points and prices in one form — repeating records as FieldList rows with the headings once, a base price pinned above its changes, one value in three currencies in one row. */
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
ColorPresets,
|
|
6
|
+
FieldList,
|
|
7
|
+
type FieldListColumn,
|
|
8
|
+
Fieldset,
|
|
9
|
+
Icon,
|
|
10
|
+
type IconName,
|
|
11
|
+
Input,
|
|
12
|
+
PageContent,
|
|
13
|
+
Select,
|
|
14
|
+
Textarea,
|
|
15
|
+
} from "prime-ui-kit";
|
|
16
|
+
import * as React from "react";
|
|
17
|
+
|
|
18
|
+
import styles from "./record-editor.module.css";
|
|
19
|
+
|
|
20
|
+
type Spec = {
|
|
21
|
+
id: string;
|
|
22
|
+
icon: IconName;
|
|
23
|
+
name: string;
|
|
24
|
+
value: string;
|
|
25
|
+
color: string;
|
|
26
|
+
pinned?: boolean;
|
|
27
|
+
};
|
|
28
|
+
type Point = { id: string; text: string };
|
|
29
|
+
type Period = { id: string; from: string; price: string; base?: boolean };
|
|
30
|
+
|
|
31
|
+
const SPEC_COLUMNS: FieldListColumn[] = [
|
|
32
|
+
{ label: "Иконка", width: "compact" },
|
|
33
|
+
{ label: "Параметр" },
|
|
34
|
+
{ label: "Значение" },
|
|
35
|
+
{ label: "Цвет", width: "compact" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const SPEC_ICONS: { name: IconName; label: string }[] = [
|
|
39
|
+
{ name: "object.users", label: "Сотрудники" },
|
|
40
|
+
{ name: "object.storage", label: "Хранилище" },
|
|
41
|
+
{ name: "object.headset", label: "Поддержка" },
|
|
42
|
+
{ name: "object.chart", label: "Отчёты" },
|
|
43
|
+
{ name: "object.key", label: "Доступ" },
|
|
44
|
+
{ name: "object.globe", label: "Интеграции" },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const SPECS: Spec[] = [
|
|
48
|
+
{
|
|
49
|
+
id: "seats",
|
|
50
|
+
icon: "object.users",
|
|
51
|
+
name: "Сотрудники",
|
|
52
|
+
value: "до 50",
|
|
53
|
+
color: "#5068f5",
|
|
54
|
+
pinned: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "storage",
|
|
58
|
+
icon: "object.storage",
|
|
59
|
+
name: "Хранилище",
|
|
60
|
+
value: "200 ГБ",
|
|
61
|
+
color: "#22c55e",
|
|
62
|
+
pinned: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "support",
|
|
66
|
+
icon: "object.headset",
|
|
67
|
+
name: "Поддержка",
|
|
68
|
+
value: "8/5, ответ за 2 часа",
|
|
69
|
+
color: "#f97316",
|
|
70
|
+
pinned: true,
|
|
71
|
+
},
|
|
72
|
+
{ id: "reports", icon: "object.chart", name: "Отчёты", value: "12 шаблонов", color: "#a855f7" },
|
|
73
|
+
{ id: "sso", icon: "object.key", name: "Вход через SSO", value: "SAML, OIDC", color: "#14b8a6" },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const POINT_COLUMNS: FieldListColumn[] = [{ label: "Преимущество" }];
|
|
77
|
+
|
|
78
|
+
const POINTS: Point[] = [
|
|
79
|
+
{ id: "space", text: "Вся команда в одном пространстве" },
|
|
80
|
+
{ id: "files", text: "Документы и вложения без ограничений по типу" },
|
|
81
|
+
{ id: "api", text: "API и вебхуки для своих интеграций" },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const PRICE_COLUMNS: FieldListColumn[] = [{ label: "Действует" }, { label: "Цена", width: "m" }];
|
|
85
|
+
|
|
86
|
+
const MONTHS = [
|
|
87
|
+
{ value: "01", label: "С 1 января" },
|
|
88
|
+
{ value: "04", label: "С 1 апреля" },
|
|
89
|
+
{ value: "07", label: "С 1 июля" },
|
|
90
|
+
{ value: "10", label: "С 1 октября" },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const PERIODS: Period[] = [
|
|
94
|
+
{ id: "base", from: "", price: "4 900", base: true },
|
|
95
|
+
{ id: "autumn", from: "10", price: "5 400" },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const SETUP_FEE = [
|
|
99
|
+
{ currency: "₽", name: "setupRub", label: "Подключение в рублях", value: "15 000" },
|
|
100
|
+
{ currency: "$", name: "setupUsd", label: "Подключение в долларах", value: "170" },
|
|
101
|
+
{ currency: "€", name: "setupEur", label: "Подключение в евро", value: "" },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const SAVE_MS = 900;
|
|
105
|
+
const SAVED_MS = 2000;
|
|
106
|
+
|
|
107
|
+
export default function RecordEditorPattern() {
|
|
108
|
+
const formId = React.useId();
|
|
109
|
+
const [specs, setSpecs] = React.useState(SPECS);
|
|
110
|
+
const [points, setPoints] = React.useState(POINTS);
|
|
111
|
+
const [periods, setPeriods] = React.useState(PERIODS);
|
|
112
|
+
const [nameError, setNameError] = React.useState<string>();
|
|
113
|
+
const [saving, setSaving] = React.useState(false);
|
|
114
|
+
const [saved, setSaved] = React.useState(false);
|
|
115
|
+
|
|
116
|
+
const save = (event: React.FormEvent<HTMLFormElement>) => {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
const name = String(new FormData(event.currentTarget).get("name") ?? "").trim();
|
|
119
|
+
if (name === "") {
|
|
120
|
+
setNameError("Назовите тариф: так его увидят клиенты");
|
|
121
|
+
event.currentTarget.querySelector<HTMLInputElement>("[name='name']")?.focus();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setNameError(undefined);
|
|
125
|
+
setSaving(true);
|
|
126
|
+
window.setTimeout(() => {
|
|
127
|
+
setSaving(false);
|
|
128
|
+
setSaved(true);
|
|
129
|
+
window.setTimeout(() => setSaved(false), SAVED_MS);
|
|
130
|
+
}, SAVE_MS);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<PageContent.Root maxWidth="wide" aria-labelledby="plan-title">
|
|
135
|
+
<PageContent.Header>
|
|
136
|
+
<PageContent.Title id="plan-title">Тариф «Команда»</PageContent.Title>
|
|
137
|
+
<PageContent.Description>
|
|
138
|
+
Так тариф выглядит на странице цен и в счетах.
|
|
139
|
+
</PageContent.Description>
|
|
140
|
+
<PageContent.Actions>
|
|
141
|
+
<Button.Root type="submit" form={formId} loading={saving}>
|
|
142
|
+
{saved ? "Сохранено" : "Сохранить тариф"}
|
|
143
|
+
</Button.Root>
|
|
144
|
+
</PageContent.Actions>
|
|
145
|
+
</PageContent.Header>
|
|
146
|
+
<PageContent.Body>
|
|
147
|
+
<PageContent.Section layout="aside" label="Основное">
|
|
148
|
+
<form id={formId} noValidate onSubmit={save}>
|
|
149
|
+
<Card.Root variant="panel">
|
|
150
|
+
<Card.Body>
|
|
151
|
+
<Fieldset disabled={saving} aria-label="Основное">
|
|
152
|
+
<Input.Root label="Название" error={nameError}>
|
|
153
|
+
<Input.Wrapper>
|
|
154
|
+
<Input.Field name="name" defaultValue="Команда" />
|
|
155
|
+
</Input.Wrapper>
|
|
156
|
+
</Input.Root>
|
|
157
|
+
<Textarea.Root
|
|
158
|
+
name="summary"
|
|
159
|
+
label="Описание"
|
|
160
|
+
optional
|
|
161
|
+
rows={2}
|
|
162
|
+
defaultValue="Для отделов до 50 человек: задачи, документы и отчёты в одном месте."
|
|
163
|
+
/>
|
|
164
|
+
</Fieldset>
|
|
165
|
+
</Card.Body>
|
|
166
|
+
</Card.Root>
|
|
167
|
+
</form>
|
|
168
|
+
</PageContent.Section>
|
|
169
|
+
|
|
170
|
+
<PageContent.Section layout="aside" label="Характеристики">
|
|
171
|
+
<Card.Root variant="panel">
|
|
172
|
+
<Card.Body>
|
|
173
|
+
<FieldList
|
|
174
|
+
aria-label="Характеристики"
|
|
175
|
+
disabled={saving}
|
|
176
|
+
columns={SPEC_COLUMNS}
|
|
177
|
+
reorderable
|
|
178
|
+
value={specs}
|
|
179
|
+
onValueChange={setSpecs}
|
|
180
|
+
getId={(spec) => spec.id}
|
|
181
|
+
getLabel={(spec) => spec.name}
|
|
182
|
+
getLocked={(spec) => spec.pinned ?? false}
|
|
183
|
+
createItem={(): Spec => ({
|
|
184
|
+
id: crypto.randomUUID(),
|
|
185
|
+
icon: "object.globe",
|
|
186
|
+
name: "",
|
|
187
|
+
value: "",
|
|
188
|
+
color: "#14b8a6",
|
|
189
|
+
})}
|
|
190
|
+
addLabel="Добавить характеристику"
|
|
191
|
+
renderItem={(spec, row) => (
|
|
192
|
+
<>
|
|
193
|
+
<Select.Root
|
|
194
|
+
value={spec.icon}
|
|
195
|
+
onValueChange={(icon) => row.update({ icon: icon as IconName })}
|
|
196
|
+
>
|
|
197
|
+
<Select.Trigger>
|
|
198
|
+
<Select.TriggerIcon>
|
|
199
|
+
<Icon name={spec.icon} />
|
|
200
|
+
</Select.TriggerIcon>
|
|
201
|
+
</Select.Trigger>
|
|
202
|
+
<Select.Content>
|
|
203
|
+
{SPEC_ICONS.map((icon) => (
|
|
204
|
+
<Select.Item key={icon.name} value={icon.name}>
|
|
205
|
+
<Select.ItemIcon>
|
|
206
|
+
<Icon name={icon.name} />
|
|
207
|
+
</Select.ItemIcon>
|
|
208
|
+
{icon.label}
|
|
209
|
+
</Select.Item>
|
|
210
|
+
))}
|
|
211
|
+
</Select.Content>
|
|
212
|
+
</Select.Root>
|
|
213
|
+
<Input.Root>
|
|
214
|
+
<Input.Wrapper>
|
|
215
|
+
<Input.Field
|
|
216
|
+
value={spec.name}
|
|
217
|
+
onChange={(event) => row.update({ name: event.target.value })}
|
|
218
|
+
/>
|
|
219
|
+
</Input.Wrapper>
|
|
220
|
+
</Input.Root>
|
|
221
|
+
<Input.Root>
|
|
222
|
+
<Input.Wrapper>
|
|
223
|
+
<Input.Field
|
|
224
|
+
value={spec.value}
|
|
225
|
+
onChange={(event) => row.update({ value: event.target.value })}
|
|
226
|
+
/>
|
|
227
|
+
</Input.Wrapper>
|
|
228
|
+
</Input.Root>
|
|
229
|
+
<ColorPresets.Root
|
|
230
|
+
value={spec.color}
|
|
231
|
+
onValueChange={(color) => row.update({ color: color ?? spec.color })}
|
|
232
|
+
>
|
|
233
|
+
<ColorPresets.Trigger>
|
|
234
|
+
<ColorPresets.SwatchButton />
|
|
235
|
+
</ColorPresets.Trigger>
|
|
236
|
+
<ColorPresets.Content />
|
|
237
|
+
</ColorPresets.Root>
|
|
238
|
+
</>
|
|
239
|
+
)}
|
|
240
|
+
/>
|
|
241
|
+
</Card.Body>
|
|
242
|
+
</Card.Root>
|
|
243
|
+
</PageContent.Section>
|
|
244
|
+
|
|
245
|
+
<PageContent.Section layout="aside" label="Преимущества">
|
|
246
|
+
<Card.Root variant="panel">
|
|
247
|
+
<Card.Body>
|
|
248
|
+
<FieldList
|
|
249
|
+
aria-label="Преимущества"
|
|
250
|
+
disabled={saving}
|
|
251
|
+
columns={POINT_COLUMNS}
|
|
252
|
+
reorderable
|
|
253
|
+
max={6}
|
|
254
|
+
value={points}
|
|
255
|
+
onValueChange={setPoints}
|
|
256
|
+
getId={(point) => point.id}
|
|
257
|
+
getLabel={(point) => point.text}
|
|
258
|
+
createItem={() => ({ id: crypto.randomUUID(), text: "" })}
|
|
259
|
+
addLabel="Добавить преимущество"
|
|
260
|
+
renderItem={(point, row) => (
|
|
261
|
+
<Input.Root>
|
|
262
|
+
<Input.Wrapper>
|
|
263
|
+
<Input.Field
|
|
264
|
+
value={point.text}
|
|
265
|
+
onChange={(event) => row.update({ text: event.target.value })}
|
|
266
|
+
/>
|
|
267
|
+
</Input.Wrapper>
|
|
268
|
+
</Input.Root>
|
|
269
|
+
)}
|
|
270
|
+
/>
|
|
271
|
+
</Card.Body>
|
|
272
|
+
</Card.Root>
|
|
273
|
+
</PageContent.Section>
|
|
274
|
+
|
|
275
|
+
<PageContent.Section layout="aside" label="Стоимость">
|
|
276
|
+
<Card.Root variant="panel">
|
|
277
|
+
<Card.Body className={styles.groups}>
|
|
278
|
+
<Fieldset label="За месяц" disabled={saving}>
|
|
279
|
+
<FieldList
|
|
280
|
+
columns={PRICE_COLUMNS}
|
|
281
|
+
value={periods}
|
|
282
|
+
onValueChange={setPeriods}
|
|
283
|
+
getId={(period) => period.id}
|
|
284
|
+
getLocked={(period) => period.base ?? false}
|
|
285
|
+
createItem={() => ({ id: crypto.randomUUID(), from: "", price: "" })}
|
|
286
|
+
addLabel="Добавить изменение цены"
|
|
287
|
+
renderItem={(period, row) => (
|
|
288
|
+
<>
|
|
289
|
+
{period.base ? (
|
|
290
|
+
<Input.Root>
|
|
291
|
+
<Input.Wrapper>
|
|
292
|
+
<Input.Field value="Всегда, если нет изменений" readOnly />
|
|
293
|
+
</Input.Wrapper>
|
|
294
|
+
</Input.Root>
|
|
295
|
+
) : (
|
|
296
|
+
<Select.Root
|
|
297
|
+
value={period.from}
|
|
298
|
+
onValueChange={(from) => row.update({ from })}
|
|
299
|
+
placeholder="Выберите месяц"
|
|
300
|
+
>
|
|
301
|
+
<Select.Trigger>
|
|
302
|
+
<Select.Value />
|
|
303
|
+
</Select.Trigger>
|
|
304
|
+
<Select.Content>
|
|
305
|
+
{MONTHS.map((month) => (
|
|
306
|
+
<Select.Item key={month.value} value={month.value}>
|
|
307
|
+
{month.label}
|
|
308
|
+
</Select.Item>
|
|
309
|
+
))}
|
|
310
|
+
</Select.Content>
|
|
311
|
+
</Select.Root>
|
|
312
|
+
)}
|
|
313
|
+
<Input.Root>
|
|
314
|
+
<Input.Wrapper>
|
|
315
|
+
<Input.Field
|
|
316
|
+
value={period.price}
|
|
317
|
+
inputMode="decimal"
|
|
318
|
+
placeholder="0"
|
|
319
|
+
onChange={(event) => row.update({ price: event.target.value })}
|
|
320
|
+
/>
|
|
321
|
+
<Input.InlineAffix side="end">₽</Input.InlineAffix>
|
|
322
|
+
</Input.Wrapper>
|
|
323
|
+
</Input.Root>
|
|
324
|
+
</>
|
|
325
|
+
)}
|
|
326
|
+
/>
|
|
327
|
+
</Fieldset>
|
|
328
|
+
<Fieldset
|
|
329
|
+
label="Подключение"
|
|
330
|
+
description="Разовый платёж; пустая валюта не выставляется."
|
|
331
|
+
disabled={saving}
|
|
332
|
+
>
|
|
333
|
+
<div className={styles.currencies}>
|
|
334
|
+
{SETUP_FEE.map((fee) => (
|
|
335
|
+
<Input.Root key={fee.name}>
|
|
336
|
+
<Input.Wrapper>
|
|
337
|
+
<Input.Field
|
|
338
|
+
name={fee.name}
|
|
339
|
+
aria-label={fee.label}
|
|
340
|
+
inputMode="decimal"
|
|
341
|
+
placeholder="0"
|
|
342
|
+
defaultValue={fee.value}
|
|
343
|
+
/>
|
|
344
|
+
<Input.InlineAffix side="end">{fee.currency}</Input.InlineAffix>
|
|
345
|
+
</Input.Wrapper>
|
|
346
|
+
</Input.Root>
|
|
347
|
+
))}
|
|
348
|
+
</div>
|
|
349
|
+
</Fieldset>
|
|
350
|
+
</Card.Body>
|
|
351
|
+
</Card.Root>
|
|
352
|
+
</PageContent.Section>
|
|
353
|
+
</PageContent.Body>
|
|
354
|
+
</PageContent.Root>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
/* Screen states: the layout keeps its shape in every state, only the content of a region changes. */
|
|
2
2
|
|
|
3
|
-
/* The banner and the panels as one block with no gap of its own: the
|
|
4
|
-
slot inside the Crossfade, so it arrives and leaves with the banner. */
|
|
3
|
+
/* The banner and the panels as one block with no gap of its own: the 24 under the banner (the body's
|
|
4
|
+
block gap) lives in its slot inside the Crossfade, so it arrives and leaves with the banner. */
|
|
5
5
|
.lead {
|
|
6
6
|
display: grid;
|
|
7
7
|
min-width: 0;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.bannerSlot {
|
|
11
|
-
padding-block-end: var(--prime-space-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.panels {
|
|
15
|
-
display: grid;
|
|
16
|
-
grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--prime-space-20) * 4)), 1fr));
|
|
17
|
-
align-items: stretch;
|
|
18
|
-
gap: var(--prime-space-4);
|
|
11
|
+
padding-block-end: var(--prime-space-6);
|
|
19
12
|
}
|
|
20
13
|
|
|
21
14
|
/* Skeleton rows mirror the account rows, so the card keeps its height when data arrives. */
|
|
@@ -45,10 +38,3 @@
|
|
|
45
38
|
.amount {
|
|
46
39
|
font-variant-numeric: tabular-nums;
|
|
47
40
|
}
|
|
48
|
-
|
|
49
|
-
/* A section without a card: heading 16 above the table. */
|
|
50
|
-
.region {
|
|
51
|
-
display: grid;
|
|
52
|
-
gap: var(--prime-space-4);
|
|
53
|
-
min-width: 0;
|
|
54
|
-
}
|
|
@@ -66,7 +66,7 @@ export default function ScreenStatesPattern() {
|
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
return (
|
|
69
|
-
<PageContent.
|
|
69
|
+
<PageContent.Page aria-labelledby="reconcile-title">
|
|
70
70
|
<PageContent.Header>
|
|
71
71
|
<PageContent.Title id="reconcile-title">Сверка с банком</PageContent.Title>
|
|
72
72
|
<PageContent.Description>
|
|
@@ -100,75 +100,76 @@ export default function ScreenStatesPattern() {
|
|
|
100
100
|
) : null}
|
|
101
101
|
</Crossfade>
|
|
102
102
|
|
|
103
|
-
<
|
|
104
|
-
<
|
|
105
|
-
<Card.
|
|
106
|
-
<Card.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{status === "loading" ?
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
103
|
+
<PageContent.Grid>
|
|
104
|
+
<PageContent.Cell span={6}>
|
|
105
|
+
<Card.Root variant="panel" aria-busy={status === "loading"}>
|
|
106
|
+
<Card.Header>
|
|
107
|
+
<Card.Title as="h2">Остатки</Card.Title>
|
|
108
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
109
|
+
{status === "ready" ? "на 7 октября" : "на 6 октября"}
|
|
110
|
+
</Typography>
|
|
111
|
+
</Card.Header>
|
|
112
|
+
<Card.Body>
|
|
113
|
+
{/* Loading ↔ data cross-fade in place; the table below swaps its body the same way. */}
|
|
114
|
+
<Crossfade state={status === "loading" ? "loading" : "ready"}>
|
|
115
|
+
{status === "loading" ? (
|
|
116
|
+
<ul className={styles.accounts}>
|
|
117
|
+
{ACCOUNTS.map((account) => (
|
|
118
|
+
<li key={account.name} className={styles.account}>
|
|
119
|
+
<Skeleton className={styles.nameBar} />
|
|
120
|
+
{/* title-m is the l text tier: 16 / 24. */}
|
|
121
|
+
<Skeleton size="l" className={styles.balanceBar} />
|
|
122
|
+
</li>
|
|
123
|
+
))}
|
|
124
|
+
</ul>
|
|
125
|
+
) : (
|
|
126
|
+
<ul className={styles.accounts}>
|
|
127
|
+
{ACCOUNTS.map((account) => (
|
|
128
|
+
<li key={account.name} className={styles.account}>
|
|
129
|
+
<Typography as="span" variant="body-m" tone="secondary">
|
|
130
|
+
{account.name}
|
|
131
|
+
</Typography>
|
|
132
|
+
<Typography as="span" variant="title-m" className={styles.amount}>
|
|
133
|
+
{account.balance}
|
|
134
|
+
</Typography>
|
|
135
|
+
</li>
|
|
136
|
+
))}
|
|
137
|
+
</ul>
|
|
138
|
+
)}
|
|
139
|
+
</Crossfade>
|
|
140
|
+
</Card.Body>
|
|
141
|
+
</Card.Root>
|
|
142
|
+
</PageContent.Cell>
|
|
141
143
|
|
|
142
|
-
<
|
|
143
|
-
<Card.
|
|
144
|
-
<Card.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
<EmptyPage.
|
|
149
|
-
<Icon
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
144
|
+
<PageContent.Cell span={6}>
|
|
145
|
+
<Card.Root variant="panel">
|
|
146
|
+
<Card.Header>
|
|
147
|
+
<Card.Title as="h2">Правила разнесения</Card.Title>
|
|
148
|
+
</Card.Header>
|
|
149
|
+
<Card.Body>
|
|
150
|
+
<EmptyPage.Root size="s" aria-labelledby="rules-empty">
|
|
151
|
+
<EmptyPage.Icon tone="accent">
|
|
152
|
+
<Icon name="action.filter" />
|
|
153
|
+
</EmptyPage.Icon>
|
|
154
|
+
<EmptyPage.Title as="h3" id="rules-empty">
|
|
155
|
+
Правил пока нет
|
|
156
|
+
</EmptyPage.Title>
|
|
157
|
+
<EmptyPage.Description>
|
|
158
|
+
Правило свяжет платёж со счётом по ИНН или назначению.
|
|
159
|
+
</EmptyPage.Description>
|
|
160
|
+
<EmptyPage.Actions>
|
|
161
|
+
<Button.Root variant="soft" tone="neutral">
|
|
162
|
+
Создать правило
|
|
163
|
+
</Button.Root>
|
|
164
|
+
</EmptyPage.Actions>
|
|
165
|
+
</EmptyPage.Root>
|
|
166
|
+
</Card.Body>
|
|
167
|
+
</Card.Root>
|
|
168
|
+
</PageContent.Cell>
|
|
169
|
+
</PageContent.Grid>
|
|
166
170
|
</div>
|
|
167
171
|
|
|
168
|
-
<
|
|
169
|
-
<Typography as="h2" variant="title-m" id="unmatched-title">
|
|
170
|
-
Неразнесённые платежи
|
|
171
|
-
</Typography>
|
|
172
|
+
<PageContent.Section label="Неразнесённые платежи">
|
|
172
173
|
<DataTable
|
|
173
174
|
columns={COLUMNS}
|
|
174
175
|
rows={status === "ready" ? PAYMENTS : []}
|
|
@@ -179,8 +180,8 @@ export default function ScreenStatesPattern() {
|
|
|
179
180
|
error={status === "error" ? "Платежи появятся после загрузки выписки" : undefined}
|
|
180
181
|
empty="Все платежи разнесены"
|
|
181
182
|
/>
|
|
182
|
-
</
|
|
183
|
+
</PageContent.Section>
|
|
183
184
|
</PageContent.Body>
|
|
184
|
-
</PageContent.
|
|
185
|
+
</PageContent.Page>
|
|
185
186
|
);
|
|
186
187
|
}
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
/* Settings page:
|
|
2
|
-
|
|
3
|
-
.section {
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-wrap: wrap;
|
|
6
|
-
align-items: flex-start;
|
|
7
|
-
gap: var(--prime-space-4) var(--prime-space-10);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/* ~16rem of heading and lead text; drops above the panel once the panel would get < 30rem. */
|
|
11
|
-
.lead {
|
|
12
|
-
display: grid;
|
|
13
|
-
flex: 1 1 calc(var(--prime-space-16) * 4);
|
|
14
|
-
gap: var(--prime-space-1);
|
|
15
|
-
min-width: 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.panel {
|
|
19
|
-
flex: 999 1 calc(var(--prime-space-24) * 5);
|
|
20
|
-
min-width: 0;
|
|
21
|
-
}
|
|
1
|
+
/* Settings page: each section is a PageContent.Section layout="aside"; here only the panels' insides. */
|
|
22
2
|
|
|
23
3
|
/* Field → field 20. */
|
|
24
4
|
|