@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.
Files changed (111) hide show
  1. package/fesm2022/rt-tools-ui-kit-v2.mjs +313 -205
  2. package/fesm2022/rt-tools-ui-kit-v2.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/rt-tools-ui-kit-v2-0.3.0.tgz +0 -0
  5. package/src/lib/components/CONTEXT.md +49 -0
  6. package/src/lib/components/aside/CONTEXT.md +51 -0
  7. package/src/lib/components/aside/_rt-aside-overlay.scss +88 -0
  8. package/src/lib/components/aside/rt-aside.component.scss +19 -94
  9. package/src/lib/components/aside-section/CONTEXT.md +26 -0
  10. package/src/lib/components/autocomplete/CONTEXT.md +58 -0
  11. package/src/lib/components/bar-list/CONTEXT.md +27 -0
  12. package/src/lib/components/bar-list/rt-bar-list.component.scss +5 -2
  13. package/src/lib/components/bottom-sheet/CONTEXT.md +54 -0
  14. package/src/lib/components/bottom-sheet/rt-bottom-sheet.component.scss +53 -53
  15. package/src/lib/components/button/CONTEXT.md +53 -0
  16. package/src/lib/components/button/rt-button.directive.scss +16 -0
  17. package/src/lib/components/calendar/CONTEXT.md +41 -0
  18. package/src/lib/components/calendar/rt-calendar.component.scss +96 -99
  19. package/src/lib/components/card/CONTEXT.md +34 -0
  20. package/src/lib/components/card/rt-card.component.scss +5 -2
  21. package/src/lib/components/chat/CONTEXT.md +41 -0
  22. package/src/lib/components/checkbox/CONTEXT.md +37 -0
  23. package/src/lib/components/checkbox/rt-checkbox.component.scss +7 -2
  24. package/src/lib/components/collapsible-text/CONTEXT.md +29 -0
  25. package/src/lib/components/confirm-popover/CONTEXT.md +47 -0
  26. package/src/lib/components/container/CONTEXT.md +42 -0
  27. package/src/lib/components/container/rt-container.component.scss +5 -69
  28. package/src/lib/components/counter/CONTEXT.md +40 -0
  29. package/src/lib/components/counter/rt-counter.component.scss +10 -9
  30. package/src/lib/components/counter-row/CONTEXT.md +24 -0
  31. package/src/lib/components/counter-row/rt-counter-row.component.scss +23 -22
  32. package/src/lib/components/date-picker/CONTEXT.md +41 -0
  33. package/src/lib/components/delta-view/CONTEXT.md +31 -0
  34. package/src/lib/components/detail-list/CONTEXT.md +29 -0
  35. package/src/lib/components/dialog/CONTEXT.md +50 -0
  36. package/src/lib/components/dialog/rt-dialog.component.scss +6 -2
  37. package/src/lib/components/download-link/CONTEXT.md +26 -0
  38. package/src/lib/components/empty-state/CONTEXT.md +35 -0
  39. package/src/lib/components/field/CONTEXT.md +72 -0
  40. package/src/lib/components/file-card/CONTEXT.md +28 -0
  41. package/src/lib/components/file-drop/CONTEXT.md +39 -0
  42. package/src/lib/components/file-input/CONTEXT.md +36 -0
  43. package/src/lib/components/file-list/CONTEXT.md +15 -0
  44. package/src/lib/components/filter-control/CONTEXT.md +33 -0
  45. package/src/lib/components/form-control/CONTEXT.md +50 -0
  46. package/src/lib/components/header/CONTEXT.md +36 -0
  47. package/src/lib/components/icon/CONTEXT.md +46 -0
  48. package/src/lib/components/icon/rt-icon.component.scss +3 -5
  49. package/src/lib/components/icon-button/CONTEXT.md +49 -0
  50. package/src/lib/components/icon-button/rt-icon-button.component.scss +14 -1
  51. package/src/lib/components/info-item/CONTEXT.md +30 -0
  52. package/src/lib/components/input/CONTEXT.md +47 -0
  53. package/src/lib/components/input-number/CONTEXT.md +52 -0
  54. package/src/lib/components/live-badge/CONTEXT.md +31 -0
  55. package/src/lib/components/live-badge/rt-live-badge.component.scss +5 -2
  56. package/src/lib/components/logo/CONTEXT.md +42 -0
  57. package/src/lib/components/logo/rt-logo.component.scss +4 -6
  58. package/src/lib/components/menu/CONTEXT.md +65 -0
  59. package/src/lib/components/message/CONTEXT.md +35 -0
  60. package/src/lib/components/message/rt-message.component.scss +5 -2
  61. package/src/lib/components/message-composer/CONTEXT.md +46 -0
  62. package/src/lib/components/money-list/CONTEXT.md +32 -0
  63. package/src/lib/components/multiselect/CONTEXT.md +43 -0
  64. package/src/lib/components/night-grid/CONTEXT.md +31 -0
  65. package/src/lib/components/night-grid/rt-night-grid.component.scss +5 -2
  66. package/src/lib/components/note/CONTEXT.md +27 -0
  67. package/src/lib/components/notifications-bell/CONTEXT.md +29 -0
  68. package/src/lib/components/page-header/CONTEXT.md +33 -0
  69. package/src/lib/components/pagination/CONTEXT.md +43 -0
  70. package/src/lib/components/photo-viewer/CONTEXT.md +30 -0
  71. package/src/lib/components/photo-viewer/rt-photo-viewer.component.scss +95 -96
  72. package/src/lib/components/popover/CONTEXT.md +54 -0
  73. package/src/lib/components/rich-editor/CONTEXT.md +36 -0
  74. package/src/lib/components/section-nav/CONTEXT.md +27 -0
  75. package/src/lib/components/section-nav/rt-section-nav.component.scss +7 -1
  76. package/src/lib/components/select/CONTEXT.md +54 -0
  77. package/src/lib/components/skeleton/CONTEXT.md +41 -0
  78. package/src/lib/components/skeleton-wrapper/CONTEXT.md +24 -0
  79. package/src/lib/components/skeleton-wrapper/rt-skeleton-wrapper.component.scss +5 -1
  80. package/src/lib/components/spinner/CONTEXT.md +39 -0
  81. package/src/lib/components/split-button/CONTEXT.md +39 -0
  82. package/src/lib/components/stat-tile/CONTEXT.md +35 -0
  83. package/src/lib/components/stat-tile/rt-stat-tile.component.scss +5 -2
  84. package/src/lib/components/stepper/CONTEXT.md +36 -0
  85. package/src/lib/components/stepper/rt-stepper.component.scss +5 -2
  86. package/src/lib/components/table/CONTEXT.md +53 -0
  87. package/src/lib/components/table/settings-aside/rt-table-settings-aside.component.scss +4 -2
  88. package/src/lib/components/table/settings-panel/rt-table-settings-panel.component.scss +1 -1
  89. package/src/lib/components/tabs/CONTEXT.md +56 -0
  90. package/src/lib/components/tag/CONTEXT.md +47 -0
  91. package/src/lib/components/tag/rt-tag.component.scss +5 -2
  92. package/src/lib/components/textarea/CONTEXT.md +38 -0
  93. package/src/lib/components/theme-toggle/CONTEXT.md +28 -0
  94. package/src/lib/components/theme-toggle/rt-theme-toggle.component.scss +5 -1
  95. package/src/lib/components/thread-list/CONTEXT.md +42 -0
  96. package/src/lib/components/timeline/CONTEXT.md +33 -0
  97. package/src/lib/components/timeline/rt-timeline.component.scss +5 -2
  98. package/src/lib/components/toast/CONTEXT.md +53 -0
  99. package/src/lib/components/toggle-button-group/CONTEXT.md +38 -0
  100. package/src/lib/components/toggle-button-group/rt-toggle-button-group.component.scss +12 -7
  101. package/src/lib/components/toggle-switch/CONTEXT.md +35 -0
  102. package/src/lib/components/toggle-switch/rt-toggle-switch.component.scss +7 -2
  103. package/src/lib/components/toolbar/CONTEXT.md +22 -0
  104. package/src/lib/components/tooltip/CONTEXT.md +34 -0
  105. package/src/lib/components/welcome-dialog/CONTEXT.md +20 -0
  106. package/src/lib/components/welcome-dialog/rt-welcome-dialog.component.scss +6 -2
  107. package/src/lib/components/workspace/CONTEXT.md +46 -0
  108. package/src/lib/components/workspace-details/CONTEXT.md +42 -0
  109. package/styles/tokens.css +14 -0
  110. package/types/rt-tools-ui-kit-v2.d.ts +50 -29
  111. package/rt-tools-ui-kit-v2-0.1.0.tgz +0 -0
@@ -0,0 +1,35 @@
1
+ # `rt-message`
2
+
3
+ Цветное сообщение внутри страницы: палитра, иконка, необязательное закрытие.
4
+
5
+ ```html
6
+ <rt-message severity="danger" [closable]="true" (closed)="dismiss()">Не удалось сохранить</rt-message>
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | ---------- | -------------------------------------------------------------------------- | ------------------- |
11
+ | `severity` | `'info' \| 'success' \| 'warning' \| 'danger' \| 'secondary' \| 'neutral'` | `'info'` |
12
+ | `icon` | `IRtIcon.Name \| null` | `null` → по палитре |
13
+ | `hideIcon` | `boolean` | `false` |
14
+ | `closable` | `boolean` | `false` |
15
+
16
+ Выход: `closed`. Текст приходит проекцией.
17
+
18
+ ## Главное, что нужно знать
19
+
20
+ **Это `role="alert"`** — скринридер читает сообщение сразу, перебивая. Тихая заметка, которая не
21
+ перебивает, — это [`rt-note`](../note/CONTEXT.md). Выбор между ними — выбор громкости.
22
+
23
+ **Крестик просит закрыть, но сам сообщение не убирает**: оно может быть частью формы, и решение
24
+ за потребителем.
25
+
26
+ ## Края
27
+
28
+ - Иконка подставляется по палитре (`info` → `ico-info`, `success` → `check`,
29
+ `warning` → `ico-warning`, `danger` → `ico-error`). У `secondary` и `neutral` иконки нет —
30
+ они не сообщают об исходе.
31
+ - Заданная иконка перебивает умолчание; `hideIcon` гасит любую.
32
+
33
+ ## Рядом
34
+
35
+ - [`rt-toaster`](../toast/CONTEXT.md) — всплывающее уведомление вместо встроенного.
@@ -1,6 +1,9 @@
1
- /* rt-message — alert/banner в common/ui (Emulated encapsulation; :host обязательно). */
1
+ /* rt-message — alert/banner в common/ui. ViewEncapsulation.None.
2
+ Хост адресуется по имени элемента, а не по классу блока: класс `.rt-message` висит
3
+ и на `<rt-message>`, и на корне шаблона, поэтому правила блока вложены в хост —
4
+ так они достаются только корню шаблона. */
2
5
 
3
- :host {
6
+ rt-message {
4
7
  display: block;
5
8
 
6
9
  .rt-message {
@@ -0,0 +1,46 @@
1
+ # `rt-message-composer`
2
+
3
+ Поле отправки сообщения: текст, вложения, необязательное форматирование.
4
+
5
+ ```html
6
+ <rt-message-composer
7
+ placeholder="Ваш вопрос"
8
+ [attachments]="true"
9
+ [sending]="sending()"
10
+ [droppedFiles]="dropped()"
11
+ (submitted)="send($event)" />
12
+ ```
13
+
14
+ | вход | тип | умолчание |
15
+ | ---------------------- | ---------------------- | ------------------------------ |
16
+ | `placeholder` | `string` | `''` → `rtKit.chatPlaceholder` |
17
+ | `attachments` | `boolean` | `false` |
18
+ | `accept` | `string` | `''` |
19
+ | `sending` / `disabled` | `boolean` | `false` |
20
+ | `formatting` | `boolean` | `false` |
21
+ | `toolbar` | `IRtRichEditorToolbar` | `'full'` |
22
+ | `minRows` / `maxRows` | `number` | 1 / 6 |
23
+ | `droppedFiles` | `File[] \| null` | `null` |
24
+
25
+ Выход: `submitted` — `{ text, delta?, files }`.
26
+
27
+ ## Главное, что нужно знать
28
+
29
+ **Отправка разблокируется только при непустом содержимом.** Пробелы значением не считаются; один
30
+ вложенный файл разблокирует отправку и без текста. Во время `sending` поле и кнопка заблокированы —
31
+ иначе второе сообщение ушло бы поверх ещё не доставленного.
32
+
33
+ **Enter отправляет, Shift+Enter переносит строку.** Набор во время IME-композиции игнорируется.
34
+
35
+ **`droppedFiles` — вход, а не выход.** Файлы, брошенные в область
36
+ [`rt-file-drop`](../file-drop/CONTEXT.md) снаружи, передаются сюда входом и добавляются к
37
+ вложениям; без `attachments` они игнорируются.
38
+
39
+ ## Края
40
+
41
+ - После отправки форма сбрасывается целиком — и текст, и файлы.
42
+ - В режиме `formatting` текст уезжает пустым, а содержимое едет в `delta` строкой JSON.
43
+
44
+ ## Рядом
45
+
46
+ - [`rt-chat`](../chat/CONTEXT.md) — использует это поле как своё.
@@ -0,0 +1,32 @@
1
+ # `rt-money-list` + `rt-money-row`
2
+
3
+ Перечень сумм с выделенной итоговой строкой.
4
+
5
+ ```html
6
+ <rt-money-list>
7
+ <rt-money-row label="Тариф">12 000 ₽</rt-money-row>
8
+ <rt-money-row label="Скидка" [loading]="!discount()">−1 200 ₽</rt-money-row>
9
+ <rt-money-row label="Итого" [total]="true">10 800 ₽</rt-money-row>
10
+ </rt-money-list>
11
+ ```
12
+
13
+ `rt-money-list` — чистая раскладка, входов нет.
14
+
15
+ | вход `rt-money-row` | тип | умолчание |
16
+ | ------------------- | --------- | --------- |
17
+ | `label` | `string` | `''` |
18
+ | `total` | `boolean` | `false` |
19
+ | `loading` | `boolean` | `false` |
20
+
21
+ Сумма приходит проекцией — форматирует её потребитель.
22
+
23
+ ## Главное, что нужно знать
24
+
25
+ **Считать компонент не умеет.** Итог — это просто строка с `total`, выделенная начертанием;
26
+ складывает суммы вызывающий код.
27
+
28
+ Во время загрузки сумма подменяется заглушкой, подпись остаётся на месте.
29
+
30
+ ## Рядом
31
+
32
+ - [`rt-detail-list`](../detail-list/CONTEXT.md) — то же для нечисловых значений.
@@ -0,0 +1,43 @@
1
+ # `rt-multiselect<TValue>`
2
+
3
+ ```html
4
+ <rt-field label="Города">
5
+ <rt-multiselect [formControl]="cities" [options]="options" [maxChips]="3" />
6
+ </rt-field>
7
+ ```
8
+
9
+ Свои входы поверх общих из [`RtFormControlBase`](../form-control/CONTEXT.md):
10
+
11
+ | вход | тип | умолчание |
12
+ | ------------- | ---------------------------------------------------------------------------- | ----------------------------- |
13
+ | `options` | `ReadonlyArray<{ label, value, disabled? }>` (тот же тип, что у `rt-select`) | `[]` |
14
+ | `placeholder` | `string` | `''` → `rtKit.uiSelectValues` |
15
+ | `maxChips` | `number` | `3` |
16
+
17
+ Значение — `ReadonlyArray<TValue>`. Отдельного выхода выбора нет: значение едет только формой.
18
+
19
+ ## Главное, что нужно знать
20
+
21
+ **Список не закрывается после выбора.** Иначе каждый следующий вариант приходилось бы выбирать,
22
+ открывая панель заново. Закрывают его Escape, клик мимо или повторный клик по триггеру.
23
+
24
+ **Выбранное рисуется фишками внутри триггера**, а сверх лимита появляется счётчик `+N`.
25
+ Крестик на фишке снимает выбор и **не раскрывает список** — хотя фишка и лежит внутри кнопки.
26
+
27
+ ## Края
28
+
29
+ - Значение всегда заменяется новым массивом, прежний не правится: форма видит новую ссылку.
30
+ - `null` в форме читается как пустой выбор.
31
+ - Закрытие панели помечает контрол тронутым; `FormControl.disable()` закрывает раскрытую панель.
32
+ - Подпись фишки берётся из `options` пайпом `rtMultiselectLabel`; для значения вне набора
33
+ подставляется его строковое представление.
34
+ - Отключённые опции не выбираются, пустой набор даёт `rtKit.uiNoOptions`.
35
+
36
+ ## Клавиатура
37
+
38
+ Как у [`rt-select`](../select/CONTEXT.md), с одним отличием: `Enter`/`Space` **переключают**
39
+ подсвеченную опцию, оставляя список открытым.
40
+
41
+ ## Рядом
42
+
43
+ - [`rt-select`](../select/CONTEXT.md) — одно значение.
@@ -0,0 +1,31 @@
1
+ # `rt-night-grid`
2
+
3
+ Плотная сетка ночей: одна клетка — одна ночь, цвет — её состояние.
4
+
5
+ ```html
6
+ <rt-night-grid ariaLabel="Ночи марта" [cells]="cells()" (cellClick)="toggle($event)" />
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | ----------- | ------------------------------------- | ------------------------------ |
11
+ | `cells` | `ReadonlyArray<{ id, state, title }>` | **обязателен** |
12
+ | `ariaLabel` | `string` | `''` → `rtKit.uiNightGridAria` |
13
+
14
+ Выход: `cellClick` — отдаёт **целую клетку**, а не идентификатор.
15
+
16
+ `state` — `'free' | 'primary' | 'secondary'`.
17
+
18
+ ## Главное, что нужно знать
19
+
20
+ **Клетка пустая — весь смысл несёт её подпись.** Внутри нет ни текста, ни иконки: сетка плотная,
21
+ и `title` существует только для всплывающей подсказки и скринридера. Значит подпись обязана быть
22
+ самодостаточной («3 марта — занято»), а не «занято».
23
+
24
+ ## Края
25
+
26
+ - Клетка — настоящая `<button>`, до неё доходят клавиатурой.
27
+ - Состояние и идентификатор продублированы атрибутами `data-state` и `data-id`.
28
+ - Сетка объявлена `role="group"`, а клетки роли не несут вовсе — они `<button>`, и родной роли
29
+ им достаточно. Списком сетка была бы неправдой: явная роль на кнопке вытесняет родную, и с
30
+ `role="listitem"` скринридер называл бы элемент списка, умалчивая, что по нему нажимают.
31
+ Обернуть каждую кнопку в `listitem` тоже нельзя — клетки прямые дети grid-контейнера.
@@ -1,9 +1,12 @@
1
- /* rt-night-grid — плотная сетка суток в common/ui (Emulated encapsulation; :host обязательно).
1
+ /* rt-night-grid — плотная сетка суток в common/ui. ViewEncapsulation.None.
2
+ Хост адресуется по имени элемента, а не по классу блока: класс `.rt-night-grid` висит
3
+ и на `<rt-night-grid>`, и на корне шаблона, поэтому правила блока вложены в хост —
4
+ так они достаются только корню шаблона.
2
5
 
3
6
  Ячейки мелкие и без подписей: сетка читается как индикатор занятости целиком, а
4
7
  не как календарь — конкретный день подсказывает нативный title. */
5
8
 
6
- :host {
9
+ rt-night-grid {
7
10
  display: block;
8
11
 
9
12
  .rt-night-grid {
@@ -0,0 +1,27 @@
1
+ # `rt-note`
2
+
3
+ ```html
4
+ <rt-note>
5
+ Тариф меняется
6
+ <strong>со следующего месяца</strong>
7
+ .
8
+ </rt-note>
9
+ ```
10
+
11
+ Входов и выходов нет — всё содержимое приходит проекцией.
12
+
13
+ ## Главное, что нужно знать
14
+
15
+ **Это не уведомление.** `role="note"` — вспомогательный контент: скринридер не перебивает им
16
+ чтение. Громкое цветное сообщение с палитрой, иконкой и закрытием — это
17
+ [`rt-message`](../message/CONTEXT.md). Выбор между ними — выбор громкости, а не оформления.
18
+
19
+ ## Как этим пользоваться
20
+
21
+ - Блок объявлен на `ng-container`, поэтому вокруг проекции не появляется лишней обёртки:
22
+ разметка потребителя остаётся прямым потомком host-а, и раскладка на нём (flex/grid) работает.
23
+ - Внутри допустимы строчные теги и управляющие конструкции шаблона.
24
+
25
+ ## Рядом
26
+
27
+ - [`rt-message`](../message/CONTEXT.md) — то же место на экране, но с severity и закрытием.
@@ -0,0 +1,29 @@
1
+ # `rt-notifications-bell`
2
+
3
+ Колокольчик уведомлений с меткой непрочитанного.
4
+
5
+ ```html
6
+ <rt-notifications-bell ariaLabel="Уведомления" unreadLabel="Есть непрочитанные" [unread]="hasUnread()" (clicked)="openPanel()" />
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | ------------- | --------- | --------- |
11
+ | `unread` | `boolean` | `false` |
12
+ | `ariaLabel` | `string` | `''` |
13
+ | `unreadLabel` | `string` | `''` |
14
+
15
+ Выход: `clicked`.
16
+
17
+ ## Главное, что нужно знать
18
+
19
+ **Компонент ничего не открывает** — он только сообщает о нажатии. Панель уведомлений рисует
20
+ приложение.
21
+
22
+ **У метки своя подпись.** Точка несёт смысл, а не оформление: она объявлена `role="img"` с
23
+ `aria-label`, иначе скринридер сказал бы только «Уведомления», не упомянув непрочитанное.
24
+
25
+ Обе подписи задаёт приложение — своих слов у кита здесь нет.
26
+
27
+ ## Рядом
28
+
29
+ - [`rt-header`](../header/CONTEXT.md) — слот `[rtHeaderBell]`, куда его обычно кладут.
@@ -0,0 +1,33 @@
1
+ # `rt-page-header`
2
+
3
+ Полоса разделов приложения: плоские пункты-ссылки и пункты с раскрывающейся панелью.
4
+
5
+ ```html
6
+ <rt-page-header ariaLabel="Разделы" [items]="sections()" [user]="user()" (itemClick)="go($event)" (userClick)="openProfile()" />
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | ----------- | --------------------------- | ------------------------ |
11
+ | `items` | `ReadonlyArray<Item>` | `[]` |
12
+ | `user` | `{ name, avatar? } \| null` | `null` |
13
+ | `userTitle` | `string` | `''` |
14
+ | `userMenu` | `TemplateRef \| null` | `null` |
15
+ | `ariaLabel` | `string` | `''` → `rtKit.uiMainNav` |
16
+
17
+ Выходы: `itemClick`, `userClick`.
18
+
19
+ ## Главное, что нужно знать
20
+
21
+ **Пункт с `route` — это ссылка, пункт с `columns` — кнопка.** Второму некуда вести: он
22
+ раскрывает панель, а переходят уже её пункты. Подсветку активного даёт `routerLinkActive`, то
23
+ есть маршрутизатор, а не входы компонента.
24
+
25
+ **Узкая разметка отдельная**, а не свёрнутая стилями: те же разделы доступны через кнопку-бургер
26
+ (`header-nav-burger`), и оба варианта живут в DOM одновременно.
27
+
28
+ ## Края
29
+
30
+ - `unread: true` у пункта рисует отдельную точку.
31
+ - Панель вложенных разделов раскрывается по наведению и живёт в оверлее CDK — до наведения её в
32
+ документе нет.
33
+ - Раскладку панели (колонки и группы) готовит потребитель: компонент их не считает.
@@ -0,0 +1,43 @@
1
+ # `rt-pagination`
2
+
3
+ ```html
4
+ <rt-pagination
5
+ [pageModel]="page()"
6
+ [perPageOptions]="[20, 50, 100]"
7
+ [loading]="loading()"
8
+ (pageChange)="load($event, page().pageSize)"
9
+ (perPageChange)="load(1, $event)" />
10
+ ```
11
+
12
+ | вход | тип | умолчание |
13
+ | ---------------- | ----------------------------------------------------- | --------------- |
14
+ | `pageModel` | `IPageModel` (`pageNumber`, `pageSize`, `totalCount`) | **обязателен** |
15
+ | `perPageOptions` | `ReadonlyArray<number>` | `[20, 50, 100]` |
16
+ | `loading` | `boolean` | `false` |
17
+
18
+ | выход | тип |
19
+ | --------------- | -------- |
20
+ | `pageChange` | `number` |
21
+ | `perPageChange` | `number` |
22
+
23
+ ## Главное, что нужно знать
24
+
25
+ **Компонент не листает — он просит.** Страницу меняет потребитель, передав новый `pageModel`.
26
+ Поэтому клик по уже открытой странице, выход за границы диапазона и выбор того же размера
27
+ страницы событий **не поднимают**: лишний запрос был бы за них.
28
+
29
+ **Полоса прячет себя целиком**, когда записей меньше самого мелкого варианта размера страницы
30
+ (`display: none` на host-е): листать нечего, а строка под таблицей всё равно занимала бы место.
31
+
32
+ ## Края
33
+
34
+ - Во время загрузки заблокированы и номера, и стрелки — иначе второй клик ушёл бы поверх ещё
35
+ не пришедшего ответа.
36
+ - Полоса номеров показывает первую, последнюю и по одному соседу открытой страницы; между ними —
37
+ «…». При единственной странице номеров нет вовсе.
38
+ - Узкая и широкая разметки живут в DOM **одновременно** (`pagination-compact` и
39
+ `pagination-nav`): какую показать, решает медиазапрос, а не условная отрисовка.
40
+ - **У пустого набора нет своего вида строки диапазона — бар при нём спрятан целиком.** Ветка на
41
+ `rtKit.uiRangeEmpty` тут была, но показать её не могло ничто: правило видимости прячет бар
42
+ раньше. Сам ключ в словаре остался (он часть публичного `rtKit`), но кит его больше не читает.
43
+ Про пустоту говорит [`rt-empty-state`](../empty-state/CONTEXT.md), а не пагинация.
@@ -0,0 +1,30 @@
1
+ # `rt-photo-viewer`
2
+
3
+ Полноэкранный просмотр кадров. Открывается через [`RtDialogService`](../dialog/CONTEXT.md).
4
+
5
+ ```typescript
6
+ const ref: RtDialogRef<number> = this.#dialog.open<RtPhotoViewerComponent, IRtPhotoViewer.Data, number>(RtPhotoViewerComponent, {
7
+ data: { photos, startIndex: 2, panel: this.panelTpl },
8
+ });
9
+ ref.afterClosed().subscribe((lastIndex?: number): void => scrollListTo(lastIndex));
10
+ ```
11
+
12
+ Данные приходят токеном `RT_DIALOG_DATA`: `photos`, `startIndex`, необязательный `panel`
13
+ (шаблон подвала, получает текущий кадр и его номер).
14
+
15
+ ## Главное, что нужно знать
16
+
17
+ **При закрытии отдаётся номер кадра, на котором закрыли.** Список за просмотрщиком
18
+ прокручивается к тому же кадру — иначе пользователь терял бы место.
19
+
20
+ **Лента замкнута**: с последнего кадра вперёд — на первый, с первого назад — на последний.
21
+
22
+ **Рисуются только текущий и соседние кадры.** Длинная лента разом не грузится; остальные
23
+ `<figure>` остаются пустыми до подхода.
24
+
25
+ ## Края
26
+
27
+ - Фокус ставит сам компонент (`role="dialog"`, `tabindex="-1"`), а `cdkTrapFocus` держит Tab
28
+ внутри без авто-захвата — иначе клавиши переставали бы листать.
29
+ - Стрелки-кнопки не рисуются, если кадр один.
30
+ - Управление кадром (удалить, сделать обложкой) приходит снаружи шаблоном `panel`.
@@ -1,112 +1,113 @@
1
- :host {
1
+ /* rt-photo-viewer — ViewEncapsulation.None: правила таргетируют BEM-класс блока.
2
+ `.rt-photo-viewer` — сам хост (шаблон начинается с ng-container, класс ставит host). */
3
+
4
+ .rt-photo-viewer {
2
5
  display: block;
3
6
  width: 100vw;
4
7
  height: 100vh;
5
8
  height: 100dvh;
6
9
  background: var(--rt-color-bg-inverse);
7
10
 
8
- .rt-photo-viewer {
9
- // Обёртка существует ради ловушки фокуса и повторяет размеры хоста:
10
- // display: contents ломает ловушку — ей нужен настоящий бокс
11
- &__surface {
12
- position: absolute;
13
- inset: 0;
14
- }
11
+ // Обёртка существует ради ловушки фокуса и повторяет размеры хоста:
12
+ // display: contents ломает ловушку — ей нужен настоящий бокс
13
+ &__surface {
14
+ position: absolute;
15
+ inset: 0;
16
+ }
15
17
 
16
- &__bar {
17
- position: absolute;
18
- z-index: 1;
19
- top: 0;
20
- right: 0;
21
- left: 0;
22
- display: flex;
23
- align-items: center;
24
- justify-content: space-between;
25
- padding: var(--rt-space-4) var(--rt-space-5);
26
- color: var(--rt-color-text-inverse);
27
- gap: var(--rt-space-4);
28
- }
18
+ &__bar {
19
+ position: absolute;
20
+ z-index: 1;
21
+ top: 0;
22
+ right: 0;
23
+ left: 0;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+ padding: var(--rt-space-4) var(--rt-space-5);
28
+ color: var(--rt-color-text-inverse);
29
+ gap: var(--rt-space-4);
30
+ }
29
31
 
30
- &__counter {
31
- font-size: var(--rt-text-sm);
32
- font-variant-numeric: tabular-nums;
33
- }
32
+ &__counter {
33
+ font-size: var(--rt-text-sm);
34
+ font-variant-numeric: tabular-nums;
35
+ }
34
36
 
35
- &__track {
36
- display: flex;
37
- overflow: auto hidden;
38
- width: 100%;
39
- height: 100%;
37
+ &__track {
38
+ display: flex;
39
+ overflow: auto hidden;
40
+ width: 100%;
41
+ height: 100%;
40
42
 
41
- // Свайп, инерцию и прилипание кадра даёт браузер
42
- scroll-snap-type: x mandatory;
43
- scrollbar-width: none;
44
- }
43
+ // Свайп, инерцию и прилипание кадра даёт браузер
44
+ scroll-snap-type: x mandatory;
45
+ scrollbar-width: none;
46
+ }
45
47
 
46
- &__slide {
48
+ &__slide {
49
+ display: flex;
50
+ flex: 0 0 100%;
51
+ align-items: center;
52
+ justify-content: center;
53
+ padding: var(--rt-space-10) var(--rt-space-4);
54
+ margin: 0;
55
+ scroll-snap-align: center;
56
+
57
+ picture {
47
58
  display: flex;
48
- flex: 0 0 100%;
59
+ width: 100%;
60
+ height: 100%;
49
61
  align-items: center;
50
62
  justify-content: center;
51
- padding: var(--rt-space-10) var(--rt-space-4);
52
- margin: 0;
53
- scroll-snap-align: center;
54
-
55
- picture {
56
- display: flex;
57
- width: 100%;
58
- height: 100%;
59
- align-items: center;
60
- justify-content: center;
61
- }
62
63
  }
64
+ }
63
65
 
64
- // Кадры вертикальные, поэтому ограничение по высоте важнее, чем по ширине
65
- &__image {
66
- display: block;
67
- max-width: 100%;
68
- max-height: 100%;
69
- object-fit: contain;
70
- }
66
+ // Кадры вертикальные, поэтому ограничение по высоте важнее, чем по ширине
67
+ &__image {
68
+ display: block;
69
+ max-width: 100%;
70
+ max-height: 100%;
71
+ object-fit: contain;
72
+ }
71
73
 
72
- // Панель правки: светлая полоса поверх тёмного просмотра — она рабочая
73
- // область, а не часть кадра
74
- &__panel {
75
- position: absolute;
76
- z-index: 1;
77
- right: 0;
78
- bottom: 0;
79
- left: 0;
80
- padding: var(--rt-space-4) var(--rt-space-5);
81
- background: var(--rt-color-bg-surface);
82
- color: var(--rt-color-text-primary);
83
- }
74
+ // Панель правки: светлая полоса поверх тёмного просмотра — она рабочая
75
+ // область, а не часть кадра
76
+ &__panel {
77
+ position: absolute;
78
+ z-index: 1;
79
+ right: 0;
80
+ bottom: 0;
81
+ left: 0;
82
+ padding: var(--rt-space-4) var(--rt-space-5);
83
+ background: var(--rt-color-bg-surface);
84
+ color: var(--rt-color-text-primary);
85
+ }
84
86
 
85
- /* TODO: отрефакторить это дерьмо */
86
- &__bar rt-icon-button,
87
- &__arrow-prev,
88
- &__arrow-next {
89
- --rt-icon-button-color: var(--rt-color-text-inverse);
90
- --rt-icon-button-color-bg-hover: color-mix(in srgb, var(--rt-color-text-inverse) 24%, transparent);
87
+ /* TODO: отрефакторить это дерьмо */
88
+ &__bar rt-icon-button,
89
+ &__arrow-prev,
90
+ &__arrow-next {
91
+ --rt-icon-button-color: var(--rt-color-text-inverse);
92
+ --rt-icon-button-color-bg-hover: color-mix(in srgb, var(--rt-color-text-inverse) 24%, transparent);
91
93
 
92
- border-radius: var(--rt-radius-md);
93
- background: color-mix(in srgb, var(--rt-color-bg-inverse) 55%, transparent);
94
- }
94
+ border-radius: var(--rt-radius-md);
95
+ background: color-mix(in srgb, var(--rt-color-bg-inverse) 55%, transparent);
96
+ }
95
97
 
96
- &__arrow-prev,
97
- &__arrow-next {
98
- position: absolute;
99
- top: 50%;
100
- transform: translateY(-50%);
101
- }
98
+ &__arrow-prev,
99
+ &__arrow-next {
100
+ position: absolute;
101
+ top: 50%;
102
+ transform: translateY(-50%);
103
+ }
102
104
 
103
- &__arrow-prev {
104
- left: var(--rt-space-3);
105
- }
105
+ &__arrow-prev {
106
+ left: var(--rt-space-3);
107
+ }
106
108
 
107
- &__arrow-next {
108
- right: var(--rt-space-3);
109
- }
109
+ &__arrow-next {
110
+ right: var(--rt-space-3);
110
111
  }
111
112
 
112
113
  // С панелью кадру нужно место под неё, иначе низ снимка уходит под полосу
@@ -117,15 +118,13 @@
117
118
  }
118
119
 
119
120
  @media (width <= 700px) {
120
- .rt-photo-viewer {
121
- &__arrow-prev,
122
- &__arrow-next {
123
- display: none;
124
- }
125
-
126
- &__slide {
127
- padding: var(--rt-space-12) 0;
128
- }
121
+ &__arrow-prev,
122
+ &__arrow-next {
123
+ display: none;
124
+ }
125
+
126
+ &__slide {
127
+ padding: var(--rt-space-12) 0;
129
128
  }
130
129
  }
131
130
  }