@rt-tools/ui-kit-v2 0.1.0 → 0.3.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/fesm2022/rt-tools-ui-kit-v2.mjs +313 -205
- package/fesm2022/rt-tools-ui-kit-v2.mjs.map +1 -1
- package/package.json +1 -1
- package/rt-tools-ui-kit-v2-0.3.0.tgz +0 -0
- package/src/lib/components/CONTEXT.md +49 -0
- package/src/lib/components/aside/CONTEXT.md +51 -0
- package/src/lib/components/aside/_rt-aside-overlay.scss +88 -0
- package/src/lib/components/aside/rt-aside.component.scss +19 -94
- package/src/lib/components/aside-section/CONTEXT.md +26 -0
- package/src/lib/components/autocomplete/CONTEXT.md +58 -0
- package/src/lib/components/bar-list/CONTEXT.md +27 -0
- package/src/lib/components/bar-list/rt-bar-list.component.scss +5 -2
- package/src/lib/components/bottom-sheet/CONTEXT.md +54 -0
- package/src/lib/components/bottom-sheet/rt-bottom-sheet.component.scss +53 -53
- package/src/lib/components/button/CONTEXT.md +53 -0
- package/src/lib/components/button/rt-button.directive.scss +16 -0
- package/src/lib/components/calendar/CONTEXT.md +41 -0
- package/src/lib/components/calendar/rt-calendar.component.scss +96 -99
- package/src/lib/components/card/CONTEXT.md +34 -0
- package/src/lib/components/card/rt-card.component.scss +5 -2
- package/src/lib/components/chat/CONTEXT.md +41 -0
- package/src/lib/components/checkbox/CONTEXT.md +37 -0
- package/src/lib/components/checkbox/rt-checkbox.component.scss +7 -2
- package/src/lib/components/collapsible-text/CONTEXT.md +29 -0
- package/src/lib/components/confirm-popover/CONTEXT.md +47 -0
- package/src/lib/components/container/CONTEXT.md +42 -0
- package/src/lib/components/container/rt-container.component.scss +5 -69
- package/src/lib/components/counter/CONTEXT.md +40 -0
- package/src/lib/components/counter/rt-counter.component.scss +10 -9
- package/src/lib/components/counter-row/CONTEXT.md +24 -0
- package/src/lib/components/counter-row/rt-counter-row.component.scss +23 -22
- package/src/lib/components/date-picker/CONTEXT.md +41 -0
- package/src/lib/components/delta-view/CONTEXT.md +31 -0
- package/src/lib/components/detail-list/CONTEXT.md +29 -0
- package/src/lib/components/dialog/CONTEXT.md +50 -0
- package/src/lib/components/dialog/rt-dialog.component.scss +6 -2
- package/src/lib/components/download-link/CONTEXT.md +26 -0
- package/src/lib/components/empty-state/CONTEXT.md +35 -0
- package/src/lib/components/field/CONTEXT.md +72 -0
- package/src/lib/components/file-card/CONTEXT.md +28 -0
- package/src/lib/components/file-drop/CONTEXT.md +39 -0
- package/src/lib/components/file-input/CONTEXT.md +36 -0
- package/src/lib/components/file-list/CONTEXT.md +15 -0
- package/src/lib/components/filter-control/CONTEXT.md +33 -0
- package/src/lib/components/form-control/CONTEXT.md +50 -0
- package/src/lib/components/header/CONTEXT.md +36 -0
- package/src/lib/components/icon/CONTEXT.md +46 -0
- package/src/lib/components/icon/rt-icon.component.scss +3 -5
- package/src/lib/components/icon-button/CONTEXT.md +49 -0
- package/src/lib/components/icon-button/rt-icon-button.component.scss +14 -1
- package/src/lib/components/info-item/CONTEXT.md +30 -0
- package/src/lib/components/input/CONTEXT.md +47 -0
- package/src/lib/components/input-number/CONTEXT.md +52 -0
- package/src/lib/components/live-badge/CONTEXT.md +31 -0
- package/src/lib/components/live-badge/rt-live-badge.component.scss +5 -2
- package/src/lib/components/logo/CONTEXT.md +42 -0
- package/src/lib/components/logo/rt-logo.component.scss +4 -6
- package/src/lib/components/menu/CONTEXT.md +65 -0
- package/src/lib/components/message/CONTEXT.md +35 -0
- package/src/lib/components/message/rt-message.component.scss +5 -2
- package/src/lib/components/message-composer/CONTEXT.md +46 -0
- package/src/lib/components/money-list/CONTEXT.md +32 -0
- package/src/lib/components/multiselect/CONTEXT.md +43 -0
- package/src/lib/components/night-grid/CONTEXT.md +31 -0
- package/src/lib/components/night-grid/rt-night-grid.component.scss +5 -2
- package/src/lib/components/note/CONTEXT.md +27 -0
- package/src/lib/components/notifications-bell/CONTEXT.md +29 -0
- package/src/lib/components/page-header/CONTEXT.md +33 -0
- package/src/lib/components/pagination/CONTEXT.md +43 -0
- package/src/lib/components/photo-viewer/CONTEXT.md +30 -0
- package/src/lib/components/photo-viewer/rt-photo-viewer.component.scss +95 -96
- package/src/lib/components/popover/CONTEXT.md +54 -0
- package/src/lib/components/rich-editor/CONTEXT.md +36 -0
- package/src/lib/components/section-nav/CONTEXT.md +27 -0
- package/src/lib/components/section-nav/rt-section-nav.component.scss +7 -1
- package/src/lib/components/select/CONTEXT.md +54 -0
- package/src/lib/components/skeleton/CONTEXT.md +41 -0
- package/src/lib/components/skeleton-wrapper/CONTEXT.md +24 -0
- package/src/lib/components/skeleton-wrapper/rt-skeleton-wrapper.component.scss +5 -1
- package/src/lib/components/spinner/CONTEXT.md +39 -0
- package/src/lib/components/split-button/CONTEXT.md +39 -0
- package/src/lib/components/stat-tile/CONTEXT.md +35 -0
- package/src/lib/components/stat-tile/rt-stat-tile.component.scss +5 -2
- package/src/lib/components/stepper/CONTEXT.md +36 -0
- package/src/lib/components/stepper/rt-stepper.component.scss +5 -2
- package/src/lib/components/table/CONTEXT.md +53 -0
- package/src/lib/components/table/settings-aside/rt-table-settings-aside.component.scss +4 -2
- package/src/lib/components/table/settings-panel/rt-table-settings-panel.component.scss +1 -1
- package/src/lib/components/tabs/CONTEXT.md +56 -0
- package/src/lib/components/tag/CONTEXT.md +47 -0
- package/src/lib/components/tag/rt-tag.component.scss +5 -2
- package/src/lib/components/textarea/CONTEXT.md +38 -0
- package/src/lib/components/theme-toggle/CONTEXT.md +28 -0
- package/src/lib/components/theme-toggle/rt-theme-toggle.component.scss +5 -1
- package/src/lib/components/thread-list/CONTEXT.md +42 -0
- package/src/lib/components/timeline/CONTEXT.md +33 -0
- package/src/lib/components/timeline/rt-timeline.component.scss +5 -2
- package/src/lib/components/toast/CONTEXT.md +53 -0
- package/src/lib/components/toggle-button-group/CONTEXT.md +38 -0
- package/src/lib/components/toggle-button-group/rt-toggle-button-group.component.scss +12 -7
- package/src/lib/components/toggle-switch/CONTEXT.md +35 -0
- package/src/lib/components/toggle-switch/rt-toggle-switch.component.scss +7 -2
- package/src/lib/components/toolbar/CONTEXT.md +22 -0
- package/src/lib/components/tooltip/CONTEXT.md +34 -0
- package/src/lib/components/welcome-dialog/CONTEXT.md +20 -0
- package/src/lib/components/welcome-dialog/rt-welcome-dialog.component.scss +6 -2
- package/src/lib/components/workspace/CONTEXT.md +46 -0
- package/src/lib/components/workspace-details/CONTEXT.md +42 -0
- package/styles/tokens.css +14 -0
- package/types/rt-tools-ui-kit-v2.d.ts +50 -29
- package/rt-tools-ui-kit-v2-0.1.0.tgz +0 -0
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Компоненты `@rt-tools/ui-kit-v2`
|
|
2
|
+
|
|
3
|
+
72 семейства, каждое — своя директория с одинаковым устройством:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
tag/
|
|
7
|
+
index.ts # баррель семейства
|
|
8
|
+
rt-tag.component.ts # компонент
|
|
9
|
+
rt-tag.component.html
|
|
10
|
+
rt-tag.component.scss
|
|
11
|
+
rt-tag.model.ts # неймспейс IRtTag с union-типами входов
|
|
12
|
+
rt-tag.component.spec.ts # проверки
|
|
13
|
+
CONTEXT.md # контракт, края, чем отличается от соседей
|
|
14
|
+
stories/ # витрина
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**У каждого семейства есть свой CONTEXT.md** — читать надо его, а не этот файл: здесь только
|
|
18
|
+
карта и то, что верно для всех.
|
|
19
|
+
|
|
20
|
+
## Что верно для всех компонентов
|
|
21
|
+
|
|
22
|
+
- **Селектор `rt-*`**, `OnPush`, только сигнальные входы и выходы (`input()` / `output()`), без
|
|
23
|
+
`@Input`/`@Output`.
|
|
24
|
+
- **Разметка размечена BEM-директивами** `rtBlock` / `rtElem` / `[rtMod]` из `@rt-tools/core`.
|
|
25
|
+
Модификатор `{ severity: 'info' }` даёт класс `rt-tag--severity--info`, булев `{ closable: true }` —
|
|
26
|
+
`rt-tag--closable`, камелкейс превращается в дефис (`hasIndicator` → `--has-indicator`).
|
|
27
|
+
- **Точки для проверок помечены `qa-dataid`** — по нему компонент находят и спеки, и e2e.
|
|
28
|
+
- **Подписи, которые кит рисует сам**, живут в неймспейсе `rtKit` и приходят через
|
|
29
|
+
`provideRtKitTranslations()`. Свой текст почти везде перебивает переведённый; пустая строка
|
|
30
|
+
равна отсутствию входа.
|
|
31
|
+
- **Цвета и размеры — только токены `--rt-*`**, ни одного HEX в компонентных стилях.
|
|
32
|
+
|
|
33
|
+
## Карта
|
|
34
|
+
|
|
35
|
+
| задача | компонент |
|
|
36
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| иконка, кольцо, заглушка | `icon`, `spinner`, `skeleton`, `skeleton-wrapper` |
|
|
38
|
+
| статус, счётчик, метка | `tag`, `live-badge`, `info-item`, `stat-tile`, `delta-view` |
|
|
39
|
+
| кнопки | `button` (директива), `icon-button`, `split-button`, `toggle-button-group` |
|
|
40
|
+
| поля ввода | `input`, `textarea`, `input-number`, `counter`, `checkbox`, `toggle-switch`, `date-picker`, `file-input`, `rich-editor` |
|
|
41
|
+
| списки выбора | `select`, `multiselect`, `autocomplete`, `filter-control` |
|
|
42
|
+
| обёртка поля | `field`, `form-control` (основа) |
|
|
43
|
+
| плавающие панели | `popover`, `tooltip`, `confirm-popover`, `menu`, `toast` |
|
|
44
|
+
| окна и панели | `dialog`, `aside`, `aside-section`, `bottom-sheet`, `welcome-dialog`, `photo-viewer` |
|
|
45
|
+
| каркас страницы | `container`, `workspace`, `workspace-details`, `header`, `page-header`, `toolbar`, `section-nav` |
|
|
46
|
+
| данные | `table`, `pagination`, `thread-list`, `detail-list`, `money-list`, `bar-list`, `timeline`, `night-grid`, `calendar` |
|
|
47
|
+
| переписка | `chat`, `message`, `message-composer` |
|
|
48
|
+
| файлы | `file-card`, `file-list`, `file-drop`, `download-link` |
|
|
49
|
+
| прочее | `card`, `note`, `empty-state`, `tabs`, `stepper`, `logo`, `theme-toggle`, `notifications-bell`, `collapsible-text`, `counter-row` |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# `rt-aside` + `RtAsideService`
|
|
2
|
+
|
|
3
|
+
Панель, выезжающая сбоку поверх страницы. Открывается **сервисом**, а не разметкой.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
readonly #aside: RtAsideService = inject(RtAsideService);
|
|
7
|
+
|
|
8
|
+
const ref: RtAsideRef<ITour> = this.#aside.open(TourCardComponent, { data: tour, position: 'right' });
|
|
9
|
+
ref.afterClosed().subscribe((result?: ITour): void => …);
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<!-- внутри поднятого компонента -->
|
|
14
|
+
<rt-aside size="md" ariaLabel="Карточка тура">
|
|
15
|
+
<rt-aside-header title="Тур в Сочи" />
|
|
16
|
+
<rt-aside-section heading="Клиент">…</rt-aside-section>
|
|
17
|
+
<rt-aside-footer>…</rt-aside-footer>
|
|
18
|
+
</rt-aside>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| вход `rt-aside` | тип | умолчание |
|
|
22
|
+
| --------------- | ---------------------- | ----------- |
|
|
23
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
|
|
24
|
+
| `contentLayout` | `'default' \| 'tabs'` | `'default'` |
|
|
25
|
+
| `width` | `string \| null` | `null` |
|
|
26
|
+
| `ariaLabel` | `string \| null` | `null` |
|
|
27
|
+
|
|
28
|
+
| настройка `open()` | умолчание |
|
|
29
|
+
| ---------------------------------------- | ----------------------------------------- |
|
|
30
|
+
| `data` | `null` — доезжает токеном `RT_ASIDE_DATA` |
|
|
31
|
+
| `position` | `'right'` |
|
|
32
|
+
| `closeOnBackdropClick` / `closeOnEscape` | `true` |
|
|
33
|
+
| `backdropClass` / `panelClass` | `rt-aside-backdrop` / `rt-aside-overlay` |
|
|
34
|
+
|
|
35
|
+
## Главное, что нужно знать
|
|
36
|
+
|
|
37
|
+
**Асайд объявлен `role="complementary"`, а не диалогом.** Он не модален: страница за ним
|
|
38
|
+
остаётся видна, и это осознанный выбор — карточку смотрят, не теряя список.
|
|
39
|
+
|
|
40
|
+
**Закрытие двухтактное.** `close(result)` отдаёт результат **сразу**, а панель убирает через
|
|
41
|
+
200 мс — чтобы доиграла анимация ухода. В тестах это значит: подписчик получил значение, а
|
|
42
|
+
`.rt-aside-overlay` ещё в документе. Повторный `close()` игнорируется.
|
|
43
|
+
|
|
44
|
+
**`disableClose`** на `RtAsideRef` держит панель под кликом по подложке и под Escape — ставится
|
|
45
|
+
самим содержимым, когда в форме есть несохранённое.
|
|
46
|
+
|
|
47
|
+
## Рядом
|
|
48
|
+
|
|
49
|
+
- [`rt-aside-section`](../aside-section/CONTEXT.md) — раздел внутри.
|
|
50
|
+
- [`rt-bottom-sheet`](../bottom-sheet/CONTEXT.md) — то же на узком экране.
|
|
51
|
+
- [`rt-dialog`](../dialog/CONTEXT.md) — когда нужна именно модальность.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Панель и backdrop CDK Overlay, в котором живёт rt-aside.
|
|
2
|
+
|
|
3
|
+
Оверлей-контейнер CDK — sibling от <body>, поэтому эти правила глобальны по
|
|
4
|
+
природе: инкапсуляции здесь нет ни при какой настройке компонента.
|
|
5
|
+
|
|
6
|
+
Овладеть ими может не только сам rt-aside: rt-container держит ту же панель в
|
|
7
|
+
persistent-режиме и должен рисовать её, даже когда rt-aside в сборку не попал.
|
|
8
|
+
Поэтому правила вынесены сюда и подключаются обоими — один текст, один набор
|
|
9
|
+
значений, и разойтись им негде. */
|
|
10
|
+
|
|
11
|
+
/* Backdrop / длительность анимации — переопределяемые CSS-properties. */
|
|
12
|
+
:root {
|
|
13
|
+
--rt-aside-animation-duration: 200ms;
|
|
14
|
+
--rt-aside-backdrop-bg: rgb(0 0 0 / 45%);
|
|
15
|
+
--rt-aside-backdrop-blur: 5px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Двойной селектор `.cdk-overlay-backdrop.rt-aside-backdrop` нужен для specificity:
|
|
19
|
+
CDK Overlay добавляет `.cdk-overlay-backdrop-showing { opacity: 1 }` сразу при attach;
|
|
20
|
+
при persistent overlay (rt-container auto-attach) backdrop должен оставаться скрытым
|
|
21
|
+
пока aside не открыт. Два класса (.cdk-overlay-backdrop + .rt-aside-backdrop) — выше
|
|
22
|
+
specificity чем одиночный (.cdk-overlay-backdrop-showing), поэтому наш opacity:0
|
|
23
|
+
побеждает. */
|
|
24
|
+
.cdk-overlay-backdrop.rt-aside-backdrop {
|
|
25
|
+
/* stylelint-disable-next-line property-no-vendor-prefix -- Safari < 18 */
|
|
26
|
+
-webkit-backdrop-filter: blur(var(--rt-aside-backdrop-blur));
|
|
27
|
+
backdrop-filter: blur(var(--rt-aside-backdrop-blur));
|
|
28
|
+
background: var(--rt-aside-backdrop-bg);
|
|
29
|
+
opacity: 0;
|
|
30
|
+
pointer-events: none;
|
|
31
|
+
transition: opacity var(--rt-aside-animation-duration) ease;
|
|
32
|
+
|
|
33
|
+
&.rt-aside-backdrop--visible {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
pointer-events: auto;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.rt-aside-overlay {
|
|
40
|
+
height: 100dvh;
|
|
41
|
+
transition: transform var(--rt-aside-animation-duration) ease;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Закрытая панель нажатий не ловит. На узком экране она шириной во весь вьюпорт,
|
|
45
|
+
и пока её сдвиг за край экрана не досчитан, она перекрывает страницу целиком:
|
|
46
|
+
выглядит это нормально, а нажатия уходят в пустоту. Селектор из двух классов —
|
|
47
|
+
чтобы правило не зависело от порядка, в котором браузер получил стили панели и
|
|
48
|
+
стили контейнера. */
|
|
49
|
+
.rt-aside-overlay:not(.rt-aside-overlay--open) {
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Стартовое положение за краем экрана не анимируется: переход из него начинается,
|
|
54
|
+
как только вьюпорт задаёт панели ширину, и всю анимацию держит панель на месте. */
|
|
55
|
+
.rt-aside-overlay.rt-aside-overlay--entering {
|
|
56
|
+
transition: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Slide-in / slide-out states — переключаются сервисом через Renderer2 на host.
|
|
60
|
+
--entering: initial off-screen position; --open: in place; --leaving: off-screen exit. */
|
|
61
|
+
.rt-aside-overlay--position-right.rt-aside-overlay--entering,
|
|
62
|
+
.rt-aside-overlay--position-right.rt-aside-overlay--leaving {
|
|
63
|
+
transform: translateX(100%);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.rt-aside-overlay--position-left.rt-aside-overlay--entering,
|
|
67
|
+
.rt-aside-overlay--position-left.rt-aside-overlay--leaving {
|
|
68
|
+
transform: translateX(-100%);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.rt-aside-overlay--position-right.rt-aside-overlay--open,
|
|
72
|
+
.rt-aside-overlay--position-left.rt-aside-overlay--open {
|
|
73
|
+
transform: translateX(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (width <= 768px) {
|
|
77
|
+
.rt-aside-overlay {
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
width: 100dvw;
|
|
80
|
+
max-width: 100dvw;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (prefers-reduced-motion: reduce) {
|
|
85
|
+
.rt-aside-overlay {
|
|
86
|
+
transition: none;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/* rt-aside — styled frame side-sheet (mirror rt-dialog с заменой токенов).
|
|
2
2
|
ViewEncapsulation.None — селекторы префиксованы `.rt-aside`.
|
|
3
|
-
Видимость / backdrop / slide-in анимацию обеспечивает CDK Overlay через RtAsideService.
|
|
3
|
+
Видимость / backdrop / slide-in анимацию обеспечивает CDK Overlay через RtAsideService.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
5
|
+
Панель и backdrop самого overlay вынесены в отдельный кусок: тот же набор
|
|
6
|
+
правил нужен rt-container, который держит панель в persistent-режиме. */
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
@use './rt-aside-overlay';
|
|
9
|
+
|
|
10
|
+
/* Size tokens — sm/md/lg (aside уже модалки чем rt-dialog). */
|
|
11
11
|
:root {
|
|
12
12
|
--rt-aside-width-sm: 400px;
|
|
13
13
|
--rt-aside-width-md: 540px;
|
|
14
14
|
--rt-aside-width-lg: 720px;
|
|
15
|
-
--rt-aside-animation-duration: 200ms;
|
|
16
|
-
--rt-aside-backdrop-bg: rgb(0 0 0 / 45%);
|
|
17
|
-
--rt-aside-backdrop-blur: 5px;
|
|
18
15
|
|
|
19
16
|
/* Инсет панели: шапка, содержимое и футер отбиты от края одним и тем же
|
|
20
17
|
значением, поэтому поля во всех панелях одной ширины. */
|
|
@@ -24,7 +21,15 @@
|
|
|
24
21
|
--rt-aside-content-gap: var(--rt-space-md);
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
/* Хост адресуется по имени элемента, а не по классу блока: класс `.rt-aside`
|
|
25
|
+
висит и на `<rt-aside>`, и на рамке внутри него, поэтому правила рамки
|
|
26
|
+
вложены в хост. Сам хост боксом не становится: рамка должна быть прямым
|
|
27
|
+
flex-ребёнком overlay-панели. */
|
|
28
|
+
rt-aside {
|
|
29
|
+
display: contents;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
rt-aside .rt-aside {
|
|
28
33
|
display: flex;
|
|
29
34
|
width: var(--rt-aside-width, var(--rt-aside-width-md));
|
|
30
35
|
|
|
@@ -117,92 +122,12 @@
|
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
/* CDK Overlay backdrop + panel классы, выставляемые RtAsideService.
|
|
121
|
-
Live в global scope — overlay-container — sibling от <body>,
|
|
122
|
-
encapsulation не работает. */
|
|
123
|
-
|
|
124
|
-
/* Двойной селектор `.cdk-overlay-backdrop.rt-aside-backdrop` нужен для specificity:
|
|
125
|
-
CDK Overlay добавляет `.cdk-overlay-backdrop-showing { opacity: 1 }` сразу при attach;
|
|
126
|
-
при persistent overlay (rt-container auto-attach) backdrop должен оставаться скрытым
|
|
127
|
-
пока aside не открыт. Два класса (.cdk-overlay-backdrop + .rt-aside-backdrop) — выше
|
|
128
|
-
specificity чем одиночный (.cdk-overlay-backdrop-showing), поэтому наш opacity:0
|
|
129
|
-
побеждает. */
|
|
130
|
-
.cdk-overlay-backdrop.rt-aside-backdrop {
|
|
131
|
-
/* stylelint-disable-next-line property-no-vendor-prefix -- Safari < 18 */
|
|
132
|
-
-webkit-backdrop-filter: blur(var(--rt-aside-backdrop-blur));
|
|
133
|
-
backdrop-filter: blur(var(--rt-aside-backdrop-blur));
|
|
134
|
-
background: var(--rt-aside-backdrop-bg);
|
|
135
|
-
opacity: 0;
|
|
136
|
-
pointer-events: none;
|
|
137
|
-
transition: opacity var(--rt-aside-animation-duration) ease;
|
|
138
|
-
|
|
139
|
-
&.rt-aside-backdrop--visible {
|
|
140
|
-
opacity: 1;
|
|
141
|
-
pointer-events: auto;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.rt-aside-overlay {
|
|
146
|
-
height: 100dvh;
|
|
147
|
-
transition: transform var(--rt-aside-animation-duration) ease;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/* Закрытая панель нажатий не ловит. На узком экране она шириной во весь вьюпорт,
|
|
151
|
-
и пока её сдвиг за край экрана не досчитан, она перекрывает страницу целиком:
|
|
152
|
-
выглядит это нормально, а нажатия уходят в пустоту. Селектор из двух классов —
|
|
153
|
-
чтобы правило не зависело от порядка, в котором браузер получил стили панели и
|
|
154
|
-
стили контейнера. */
|
|
155
|
-
.rt-aside-overlay:not(.rt-aside-overlay--open) {
|
|
156
|
-
pointer-events: none;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/* Стартовое положение за краем экрана не анимируется: переход из него начинается,
|
|
160
|
-
как только вьюпорт задаёт панели ширину, и всю анимацию держит панель на месте. */
|
|
161
|
-
.rt-aside-overlay.rt-aside-overlay--entering {
|
|
162
|
-
transition: none;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* Slide-in / slide-out states — переключаются сервисом через Renderer2 на host.
|
|
166
|
-
--entering: initial off-screen position; --open: in place; --leaving: off-screen exit. */
|
|
167
|
-
.rt-aside-overlay--position-right.rt-aside-overlay--entering {
|
|
168
|
-
transform: translateX(100%);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.rt-aside-overlay--position-left.rt-aside-overlay--entering {
|
|
172
|
-
transform: translateX(-100%);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.rt-aside-overlay--position-right.rt-aside-overlay--open,
|
|
176
|
-
.rt-aside-overlay--position-left.rt-aside-overlay--open {
|
|
177
|
-
transform: translateX(0);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.rt-aside-overlay--position-right.rt-aside-overlay--leaving {
|
|
181
|
-
transform: translateX(100%);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.rt-aside-overlay--position-left.rt-aside-overlay--leaving {
|
|
185
|
-
transform: translateX(-100%);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
125
|
@media (width <= 768px) {
|
|
189
|
-
.rt-aside
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.rt-aside,
|
|
196
|
-
.rt-aside--size--sm,
|
|
197
|
-
.rt-aside--size--md,
|
|
198
|
-
.rt-aside--size--lg {
|
|
126
|
+
rt-aside .rt-aside,
|
|
127
|
+
rt-aside .rt-aside--size--sm,
|
|
128
|
+
rt-aside .rt-aside--size--md,
|
|
129
|
+
rt-aside .rt-aside--size--lg {
|
|
199
130
|
width: 100dvw;
|
|
200
131
|
max-width: 100dvw;
|
|
201
132
|
}
|
|
202
133
|
}
|
|
203
|
-
|
|
204
|
-
@media (prefers-reduced-motion: reduce) {
|
|
205
|
-
.rt-aside-overlay {
|
|
206
|
-
transition: none;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# `rt-aside-section`
|
|
2
|
+
|
|
3
|
+
Раздел внутри асайда: необязательный заголовок и тело.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<rt-aside-section heading="Контакты">
|
|
7
|
+
<rt-detail-list>…</rt-detail-list>
|
|
8
|
+
</rt-aside-section>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| вход | тип | умолчание |
|
|
12
|
+
| --------- | ---------------- | --------- |
|
|
13
|
+
| `heading` | `string \| null` | `null` |
|
|
14
|
+
|
|
15
|
+
Выходов нет; содержимое приходит проекцией.
|
|
16
|
+
|
|
17
|
+
## Главное, что нужно знать
|
|
18
|
+
|
|
19
|
+
**Без заголовка пустой строки не появляется.** Разделы асайда часто идут подряд без подписей, и
|
|
20
|
+
пустой заголовок добавлял бы им лишний отступ.
|
|
21
|
+
|
|
22
|
+
Тело раздела рисуется всегда, даже пустым: прячут его стили.
|
|
23
|
+
|
|
24
|
+
## Рядом
|
|
25
|
+
|
|
26
|
+
- [`rt-aside`](../aside/CONTEXT.md) — сам асайд.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# `rt-autocomplete<TItem>`
|
|
2
|
+
|
|
3
|
+
Поле с подсказками. Ищет **потребитель** — компонент только сообщает, что набрано.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<rt-autocomplete
|
|
7
|
+
[formControl]="city"
|
|
8
|
+
[suggestions]="found()"
|
|
9
|
+
[displayWith]="cityLabel"
|
|
10
|
+
[minLength]="2"
|
|
11
|
+
(complete)="search($event.query)"
|
|
12
|
+
(itemSelect)="onPicked($event)" />
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Свои входы поверх общих из [`RtFormControlBase`](../form-control/CONTEXT.md):
|
|
16
|
+
|
|
17
|
+
| вход | тип | умолчание |
|
|
18
|
+
| -------------- | ------------------------------------------- | -------------------- |
|
|
19
|
+
| `suggestions` | `ReadonlyArray<TItem>` | `[]` |
|
|
20
|
+
| `displayWith` | `(item: TItem \| null) => string` | `String(item ?? '')` |
|
|
21
|
+
| `placeholder` | `string` | `''` |
|
|
22
|
+
| `minLength` | `number` | `1` |
|
|
23
|
+
| `openOnFocus` | `boolean` | `false` |
|
|
24
|
+
| `iconLeft` | `IRtIcon.Name \| null` | `null` |
|
|
25
|
+
| `itemTemplate` | `TemplateRef<{ $implicit: TItem }> \| null` | `null` |
|
|
26
|
+
|
|
27
|
+
| выход | тип |
|
|
28
|
+
| ------------ | -------------------------- |
|
|
29
|
+
| `complete` | `{ query, originalEvent }` |
|
|
30
|
+
| `itemSelect` | `TItem` |
|
|
31
|
+
|
|
32
|
+
Значение — `TItem | null` (объект, а не строка).
|
|
33
|
+
|
|
34
|
+
## Главное, что нужно знать
|
|
35
|
+
|
|
36
|
+
**Набор текста не пишет значение в форму.** В форме лежит объект, а набранная строка — это ещё
|
|
37
|
+
не объект. Значение появляется только при выборе подсказки. Отсюда же следует, что «свободный
|
|
38
|
+
ввод» этим компонентом не поддерживается: если пользователь набрал текст и ушёл, значение
|
|
39
|
+
осталось прежним.
|
|
40
|
+
|
|
41
|
+
**Задайте `displayWith`.** Без него подписи подсказок и текст в поле собираются через
|
|
42
|
+
`String(item)`, и объект превращается в `[object Object]`. Функция нужна и для отрисовки
|
|
43
|
+
подсказки, и для подстановки текста после выбора, и для плоского режима чтения.
|
|
44
|
+
|
|
45
|
+
## Как этим пользоваться
|
|
46
|
+
|
|
47
|
+
- Панель открывается, когда набрано не меньше `minLength` символов; более короткая строка
|
|
48
|
+
закрывает её и события не поднимает.
|
|
49
|
+
- `openOnFocus` дополнительно запрашивает подсказки при фокусе — для списков «последнее
|
|
50
|
+
выбранное».
|
|
51
|
+
- `itemTemplate` рисует свою разметку подсказки (иконка, две строки); контекст — `$implicit`.
|
|
52
|
+
- Крестик очистки стирает и текст, и значение.
|
|
53
|
+
- Клавиатура: `ArrowDown`/`ArrowUp` водят по подсказкам, `Enter` выбирает, `Escape` закрывает.
|
|
54
|
+
При закрытой панели клавиши ничего не делают.
|
|
55
|
+
|
|
56
|
+
## Рядом
|
|
57
|
+
|
|
58
|
+
- [`rt-select`](../select/CONTEXT.md) — когда набор известен заранее.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# `rt-bar-list`
|
|
2
|
+
|
|
3
|
+
Список долей с горизонтальными полосами: источники трафика, распределение по статусам.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<rt-bar-list title="Источники" [rows]="[{ id: 'direct', title: 'Прямые заходы', meta: '1 240 визитов', value: '52%', sharePercent: 52 }]" />
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
| вход | тип | умолчание |
|
|
10
|
+
| ----------- | ---------------------------------------------------------- | ----------------------- |
|
|
11
|
+
| `rows` | `ReadonlyArray<{ id, title, meta?, value, sharePercent }>` | **обязателен** |
|
|
12
|
+
| `title` | `string` | `''` |
|
|
13
|
+
| `emptyText` | `string` | `''` → `rtKit.uiNoData` |
|
|
14
|
+
|
|
15
|
+
Выходов нет.
|
|
16
|
+
|
|
17
|
+
## Главное, что нужно знать
|
|
18
|
+
|
|
19
|
+
**`value` и `sharePercent` — разные вещи.** Первое рисуется текстом справа (это может быть
|
|
20
|
+
«52%», «1 240 ₽», «12 шт.»), второе задаёт ширину полосы. Считать проценты компонент не умеет —
|
|
21
|
+
он их только показывает.
|
|
22
|
+
|
|
23
|
+
## Края
|
|
24
|
+
|
|
25
|
+
- Пустой набор подменяет список заглушкой с иконкой; свой текст перебивает переведённый.
|
|
26
|
+
- Идентификатор строки дублируется атрибутом `data-id`.
|
|
27
|
+
- Приписка `meta` рисуется, только когда передана.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
/* rt-bar-list — топ-N со шкалой доли в common/ui
|
|
1
|
+
/* rt-bar-list — топ-N со шкалой доли в common/ui. ViewEncapsulation.None.
|
|
2
|
+
Хост адресуется по имени элемента, а не по классу блока: класс `.rt-bar-list` висит
|
|
3
|
+
и на `<rt-bar-list>`, и на корне шаблона, поэтому правила блока вложены в хост —
|
|
4
|
+
так они достаются только корню шаблона. */
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
rt-bar-list {
|
|
4
7
|
display: block;
|
|
5
8
|
|
|
6
9
|
.rt-bar-list {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# `rt-bottom-sheet`
|
|
2
|
+
|
|
3
|
+
Лист, выезжающий снизу. Мобильная замена асайду и диалогу.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<rt-bottom-sheet [open]="filtersOpen()" (openChange)="filtersOpen.set($event)">
|
|
7
|
+
<h2 sheetHeader>Фильтры</h2>
|
|
8
|
+
<rt-filter-control … />
|
|
9
|
+
</rt-bottom-sheet>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| вход | тип | умолчание |
|
|
13
|
+
| ------ | --------- | -------------- |
|
|
14
|
+
| `open` | `boolean` | **обязателен** |
|
|
15
|
+
|
|
16
|
+
| выход | тип |
|
|
17
|
+
| ------------ | -------------------------------------------- |
|
|
18
|
+
| `openChange` | `boolean` (всегда `false` — просьба закрыть) |
|
|
19
|
+
|
|
20
|
+
Слоты: `[sheetHeader]` — шапка; слот по умолчанию — содержимое.
|
|
21
|
+
|
|
22
|
+
## Главное, что нужно знать
|
|
23
|
+
|
|
24
|
+
**Лист не закрывает себя сам.** Состоянием владеет потребитель: клик по подложке и свайп вниз
|
|
25
|
+
поднимают `openChange` со значением `false`, а закрыть должен тот, кто держит сигнал. Так лист
|
|
26
|
+
можно не закрывать, если что-то не сохранено.
|
|
27
|
+
|
|
28
|
+
**Закрытый лист остаётся в разметке.** Прячут его стили, а не `@if`: анимацию выезда и ухода
|
|
29
|
+
рисует CSS, и исчезнувший из DOM узел анимировать было бы нечем. В тестах это значит, что
|
|
30
|
+
`rt-bottom-sheet__panel` найдётся и у закрытого листа — смотреть надо на модификатор
|
|
31
|
+
`rt-bottom-sheet--open` на host-е.
|
|
32
|
+
|
|
33
|
+
**Раз лист остаётся в разметке, закрытым он помечается `inert`.** Прозрачность и
|
|
34
|
+
`pointer-events` прячут его от глаза и мыши, но не убирают ни из порядка табуляции, ни из дерева
|
|
35
|
+
доступности: без `inert` страница с закрытым листом держала бы в табуляции невидимую кнопку
|
|
36
|
+
«Закрыть», а скринридер объявлял бы модальный диалог, которого не видно.
|
|
37
|
+
|
|
38
|
+
## Свайп
|
|
39
|
+
|
|
40
|
+
Порог — **80 px** (`DRAG_DISMISS_THRESHOLD_PX` в
|
|
41
|
+
`rt-bottom-sheet.logic.ts`). Ровно на пороге лист остаётся
|
|
42
|
+
открытым, закрывается только за ним. Вверх лист не тянется: отрицательный сдвиг обнуляется.
|
|
43
|
+
После отпускания сдвиг сбрасывается независимо от исхода.
|
|
44
|
+
|
|
45
|
+
## Края
|
|
46
|
+
|
|
47
|
+
- Подложка — настоящая `<button>` с переведённой подписью, а не немой `div`: до неё доходят
|
|
48
|
+
клавиатурой и её называет скринридер.
|
|
49
|
+
- Панель объявлена `role="dialog"` с `aria-modal="true"`.
|
|
50
|
+
|
|
51
|
+
## Рядом
|
|
52
|
+
|
|
53
|
+
- [`rt-aside`](../aside/CONTEXT.md) — то же на широком экране.
|
|
54
|
+
- [`rt-dialog`](../dialog/CONTEXT.md) — модальное окно по центру.
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
:
|
|
1
|
+
/* rt-bottom-sheet — ViewEncapsulation.None: правила таргетируют BEM-класс блока.
|
|
2
|
+
`.rt-bottom-sheet` — сам хост (шаблон начинается с ng-container, класс и
|
|
3
|
+
модификатор `--open` ставит host). */
|
|
4
|
+
|
|
5
|
+
.rt-bottom-sheet {
|
|
2
6
|
position: fixed;
|
|
3
7
|
z-index: 1000;
|
|
4
8
|
inset: 0;
|
|
5
9
|
pointer-events: none;
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
11
|
+
&__backdrop {
|
|
12
|
+
position: absolute;
|
|
13
|
+
padding: 0;
|
|
14
|
+
border: none;
|
|
15
|
+
background: var(--rt-overlay-black-50);
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
inset: 0;
|
|
18
|
+
opacity: 0;
|
|
19
|
+
transition: opacity var(--rt-duration-base) ease;
|
|
20
|
+
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
&__panel {
|
|
23
|
+
position: absolute;
|
|
24
|
+
right: 0;
|
|
25
|
+
bottom: 0;
|
|
26
|
+
left: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
max-height: 80vh;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
border-radius: var(--rt-radius-2xl) var(--rt-radius-2xl) 0 0;
|
|
31
|
+
background: var(--rt-color-bg-surface);
|
|
32
|
+
box-shadow: var(--rt-shadow-card-soft);
|
|
33
|
+
transform: translateY(100%);
|
|
34
|
+
transition: transform var(--rt-duration-base) ease;
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
+
&--dragging {
|
|
37
|
+
transition: none;
|
|
36
38
|
}
|
|
39
|
+
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
&__handle {
|
|
42
|
+
width: 40px;
|
|
43
|
+
height: 4px;
|
|
44
|
+
align-self: center;
|
|
45
|
+
border-radius: var(--rt-radius-xs);
|
|
46
|
+
margin: var(--rt-space-3) 0;
|
|
47
|
+
background: var(--rt-color-border-default);
|
|
48
|
+
cursor: grab;
|
|
49
|
+
touch-action: none;
|
|
50
|
+
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
52
|
+
&__header {
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
padding: 0 var(--rt-space-5) var(--rt-space-3);
|
|
56
|
+
color: var(--rt-color-text-primary);
|
|
57
|
+
font-weight: var(--rt-font-weight-semibold);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
&--open {
|
|
59
61
|
pointer-events: auto;
|
|
60
62
|
|
|
61
|
-
.rt-bottom-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
63
|
+
.rt-bottom-sheet__backdrop {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
67
|
+
.rt-bottom-sheet__panel {
|
|
68
|
+
transform: translateY(0);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
}
|