@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,54 @@
1
+ # `[rtPopover]`
2
+
3
+ Примитив плавающей панели поверх CDK Overlay. На нём стоят
4
+ [`rt-select`](../select/CONTEXT.md), [`rt-multiselect`](../multiselect/CONTEXT.md),
5
+ [`rt-autocomplete`](../autocomplete/CONTEXT.md) и пояснение в [`rt-field`](../field/CONTEXT.md).
6
+
7
+ ```html
8
+ <button type="button" rtPopoverTrigger="click" rtPopoverWidth="trigger" [rtPopover]="tpl">Открыть</button>
9
+ <ng-template #tpl><div>Содержимое панели</div></ng-template>
10
+ ```
11
+
12
+ | вход (алиас) | тип | умолчание |
13
+ | --------------------------------------- | -------------------------------- | --------- |
14
+ | `rtPopover` | `TemplateRef \| null` | `null` |
15
+ | `rtPopoverTrigger` | `'click' \| 'hover' \| 'manual'` | `'click'` |
16
+ | `rtPopoverWidth` | `'trigger' \| 'auto'` | `'auto'` |
17
+ | `rtPopoverAlign` | `'start' \| 'end'` | `'start'` |
18
+ | `rtPopoverFitViewport` | `boolean` | `false` |
19
+ | `rtPopoverOffsetY` / `rtPopoverOffsetX` | `number` | `4` / `0` |
20
+ | `rtPopoverPanelClass` | `string` | `''` |
21
+ | `rtPopoverContext` | `unknown` | `null` |
22
+ | `rtPopoverDisabled` | `boolean` | `false` |
23
+
24
+ Выходы: `rtPopoverOpened`, `rtPopoverClosed`. Публичное API: `open()`, `close()`, `toggle()`,
25
+ `isOpen()` — работают **во всех режимах**; `trigger` определяет только автоматические слушатели.
26
+
27
+ ## Главное, что нужно знать
28
+
29
+ **Панель живёт в оверлее CDK, а не внутри host-элемента.** Поэтому её не режет `overflow: hidden`
30
+ предков (таблицы, асайды, карточки) — но и искать её в разметке компонента бесполезно: в тестах
31
+ она ищется по документу.
32
+
33
+ **Вложенная панель не считается «кликом снаружи».** Панель, открытая из нашей (список
34
+ `rt-select`, календарь, вложенное меню), лежит в DOM рядом, отдельным оверлеем. Без этой проверки
35
+ меню профиля закрывалось бы, стоило потянуться к списку языков, а выбранное значение не доходило
36
+ бы до обработчика. Вложенность определяется порядком панелей в контейнере оверлеев: открытая позже
37
+ считается вложенной.
38
+
39
+ ## Как этим пользоваться
40
+
41
+ - `manual` — для контролов со своим жестом (поле с подсказками открывает панель по набору текста).
42
+ - `hover` держит панель открытой при переходе курсора на неё: закрытие отложено на 100 мс, и
43
+ наведение на панель отсрочку гасит.
44
+ - `fitViewport` **выключен по умолчанию**: панель стоит там, куда её ставит привязка к триггеру,
45
+ даже если часть ушла за экран. Включать для широких и высоких панелей, иначе их содержимое
46
+ остаётся недостижимым.
47
+ - `rtPopoverPanelClass` добавляется **к** базовому `rt-popover-panel`, а не вместо него.
48
+ - Escape и клик мимо закрывают панель во всех режимах.
49
+
50
+ ## Рядом
51
+
52
+ - [`[rtTooltip]`](../tooltip/CONTEXT.md) — та же механика, но для короткой подсказки.
53
+ - [`[rtConfirm]`](../confirm-popover/CONTEXT.md) — панель подтверждения.
54
+ - [`rt-menu`](../menu/CONTEXT.md) — меню действий (на CDK-оверлее напрямую).
@@ -0,0 +1,36 @@
1
+ # `rt-rich-editor`
2
+
3
+ Поле ввода с форматированием поверх Quill. Значение — модель Quill (`IQuillDelta`), а не HTML.
4
+
5
+ ```html
6
+ <rt-field label="Описание">
7
+ <rt-rich-editor placeholder="Опишите задачу" toolbar="full" [formControl]="description" />
8
+ </rt-field>
9
+ ```
10
+
11
+ Свои входы поверх общих из [`RtFormControlBase`](../form-control/CONTEXT.md):
12
+
13
+ | вход | тип | умолчание |
14
+ | ------------- | ---------------------- | --------- |
15
+ | `placeholder` | `string` | `''` |
16
+ | `toolbar` | `IRtRichEditorToolbar` | `'full'` |
17
+
18
+ Выход: `enterPressed` — Enter без Shift (перенос при этом гасится).
19
+
20
+ ## Главное, что нужно знать
21
+
22
+ **Quill грузится динамическим импортом** и создаётся только после первой отрисовки — чтобы не
23
+ тянуть его в основную сборку приложения. Отсюда два следствия:
24
+
25
+ - значение, пришедшее из формы **до** готовности редактора, откладывается и применяется при его
26
+ создании — оно не теряется;
27
+ - в тестах модуль подменяется (`jest.mock('quill', …)`): сам Quill в jsdom не поднимается.
28
+
29
+ **Пустой текст пишет в форму `null`, а не пустой набор операций.** Так «пусто» отличимо от
30
+ «есть форматирование без текста» — на этом стоит проверка обязательности поля.
31
+
32
+ ## Рядом
33
+
34
+ - [`rt-delta-view`](../delta-view/CONTEXT.md) — показ той же модели без редактирования.
35
+ - [`rt-message-composer`](../message-composer/CONTEXT.md) — использует редактор в режиме
36
+ форматирования.
@@ -0,0 +1,27 @@
1
+ # `rt-section-nav`
2
+
3
+ Плитки разделов: иконка, подпись, подсветка текущего.
4
+
5
+ ```html
6
+ <rt-section-nav [items]="sections()" (itemSelect)="goTo($event)" />
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | ------- | ---------------------------------------- | -------------- |
11
+ | `items` | `readonly { id, icon, label, active }[]` | **обязателен** |
12
+
13
+ | выход | тип |
14
+ | ------------ | -------------------------------- |
15
+ | `itemSelect` | `string` — идентификатор раздела |
16
+
17
+ ## Главное, что нужно знать
18
+
19
+ **Активность приходит снаружи, в самом наборе.** Нажатие только просит открыть раздел; подсветку
20
+ двигает уже новый набор, пришедший после перехода. Компонент состояния не держит.
21
+
22
+ ## Края
23
+
24
+ - Плитка — настоящая `<button>`: до неё доходят клавиатурой.
25
+ - Активная плитка помечена тремя способами: модификатором, `data-active` и `aria-current="page"`.
26
+ - Идентификатор дублируется атрибутом `data-key`.
27
+ - Пустой набор рисует пустую навигацию.
@@ -1,3 +1,6 @@
1
+ /* rt-section-nav — ViewEncapsulation.None: правила таргетируют BEM-класс блока.
2
+ `.rt-section-nav` — сам хост (шаблон начинается с ng-container, класс ставит host). */
3
+
1
4
  .rt-section-nav {
2
5
  display: flex;
3
6
  gap: var(--rt-space-md);
@@ -47,6 +50,9 @@
47
50
  }
48
51
  }
49
52
 
50
- :host-context([data-theme='dark']) .rt-section-nav__tile--active {
53
+ /* Тёмная тема: активная плитка на тёмной поверхности берёт свой фон.
54
+ Раньше правило было записано через `:host-context`, который под None не
55
+ совпадает ни с чем, — тема задаётся атрибутом на предке, и им же адресуется. */
56
+ [data-theme='dark'] .rt-section-nav__tile--active {
51
57
  background: var(--rt-blue-900);
52
58
  }
@@ -0,0 +1,54 @@
1
+ # `rt-select<TValue>`
2
+
3
+ ```html
4
+ <rt-field label="Город">
5
+ <rt-select placeholder="Выберите" [formControl]="city" [options]="cities" [filter]="true" (selectionChange)="onPicked($event)" />
6
+ </rt-field>
7
+ ```
8
+
9
+ Свои входы поверх общих из [`RtFormControlBase`](../form-control/CONTEXT.md):
10
+
11
+ | вход | тип | умолчание |
12
+ | ------------------- | -------------------------------------------- | ----------------------- |
13
+ | `options` | `ReadonlyArray<{ label, value, disabled? }>` | `[]` |
14
+ | `placeholder` | `string` | `''` |
15
+ | `iconLeft` | `IRtIcon.Name \| null` | `null` |
16
+ | `filter` | `boolean` | `false` |
17
+ | `filterPlaceholder` | `string` | `''` → `rtKit.uiSearch` |
18
+
19
+ | выход | тип |
20
+ | ----------------- | ---------------- |
21
+ | `selectionChange` | `TValue \| null` |
22
+
23
+ ## Главное, что нужно знать
24
+
25
+ **Подпись выбранного берётся из `options`, а не хранится.** В значении лежит только `value`;
26
+ если набор опций пришёл позже или сменился, подпись перерисуется сама. Значение, которого нет в
27
+ наборе, покажется пустой строкой.
28
+
29
+ **`selectionChange` — это не дубль формы.** Форма получает значение через
30
+ `ControlValueAccessor`; отдельный выход нужен там, где на выбор надо среагировать (перезагрузить
31
+ зависимый список), не подписываясь на `valueChanges` всей формы. Очистка крестиком тоже
32
+ поднимает его — со значением `null`.
33
+
34
+ ## Клавиатура
35
+
36
+ - Закрытый список раскрывают `ArrowDown`, `ArrowUp`, `Enter`, `Space` — и сразу подсвечивают
37
+ первую доступную опцию.
38
+ - Стрелки **перешагивают отключённые опции** и заворачиваются по кругу.
39
+ - `Enter`/`Space` выбирают подсвеченную, `Escape` закрывает без выбора.
40
+ - Опции не фокусируются поодиночке: подсвеченная называется скринридеру через
41
+ `aria-activedescendant` на триггере.
42
+
43
+ ## Края
44
+
45
+ - Закрытие списка помечает контрол тронутым и сбрасывает строку фильтра.
46
+ - `FormControl.disable()` **закрывает уже раскрытый** список — иначе панель висела бы над
47
+ контролом, которым уже нельзя пользоваться.
48
+ - Пустой набор опций рисует строку `rtKit.uiNoOptions`.
49
+ - Ширина панели равна ширине триггера (`rtPopoverWidth="trigger"`).
50
+
51
+ ## Рядом
52
+
53
+ - [`rt-multiselect`](../multiselect/CONTEXT.md) — тот же список, но с несколькими значениями.
54
+ - [`rt-autocomplete`](../autocomplete/CONTEXT.md) — когда набор ищут на сервере.
@@ -0,0 +1,41 @@
1
+ # `rt-skeleton`
2
+
3
+ ```html
4
+ <rt-skeleton shape="rectangle" size="md" width="240px" height="16px" borderRadius="xl" [animation]="true" />
5
+ ```
6
+
7
+ | вход | тип | умолчание |
8
+ | -------------- | ---------------------------------------------- | ------------------ |
9
+ | `shape` | `'rectangle' \| 'circle' \| 'square'` | `'rectangle'` |
10
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
11
+ | `width` | `string` (любая CSS-длина) | `'100%'` |
12
+ | `height` | `string` | `'10px'` |
13
+ | `borderRadius` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| null` | `null` (по фигуре) |
14
+ | `animation` | `boolean` | `true` |
15
+
16
+ Шкала размеров: `sm` 10px, `md` 15px, `lg` 20px. Шкала скруглений: `xs` 2, `sm` 4, `md` 6,
17
+ `lg` 10, `xl` 999px.
18
+
19
+ ## Главное, что нужно знать
20
+
21
+ **Напрямую его почти не ставят.** Условную отрисовку делает
22
+ [`rt-skeleton-wrapper`](../skeleton-wrapper/CONTEXT.md): он решает, что показать — заглушку или
23
+ проекцию, — и пробрасывает сюда все эти входы. Прямой `rt-skeleton` уместен там, где заглушка
24
+ рисуется без пары «загрузка/контент»: сетка карточек-призраков, макет страницы.
25
+
26
+ ## Края
27
+
28
+ - **Умолчание высоты — 10px, хотя умолчание размера — `md` (15px).** Расхождение намеренное:
29
+ полоска текста тоньше шага размера. Прямоугольник слушается `height`, круг и квадрат — нет.
30
+ - Круг и квадрат берут сторону из `size` и **игнорируют** `width`/`height`.
31
+ - **`borderRadius: null` значит «по фигуре», а не «без скругления»:** прямоугольник берёт `xl`,
32
+ квадрат — `sm` (пилюля из квадрата не квадрат). Заданное значение перебивает умолчание у
33
+ обоих. Умолчанием нельзя держать сам `xl` — тогда явный `xl` неотличим от невыставленного, и
34
+ квадрату приходится прибивать `sm` жёстко, молча теряя вход.
35
+ - Круг — единственное исключение: он всегда `50%`, потому что круг и есть это скругление.
36
+ - `animation` принимает голый атрибут (`animation` без значения = `true`).
37
+ - Мерцание снимается у пользователей с `prefers-reduced-motion` — правило в SCSS.
38
+
39
+ ## Рядом
40
+
41
+ - [`rt-skeleton-wrapper`](../skeleton-wrapper/CONTEXT.md) — обёртка «загрузка ↔ контент».
@@ -0,0 +1,24 @@
1
+ # `rt-skeleton-wrapper`
2
+
3
+ Обёртка «загрузка ↔ содержимое»: показывает заглушку или проекцию.
4
+
5
+ ```html
6
+ <rt-skeleton-wrapper width="240px" height="16px" [isLoading]="!tour()">{{ tour()?.title }}</rt-skeleton-wrapper>
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | ----------------------------------------------- | --------------------------------------------- | --------- |
11
+ | `isLoading` | `boolean` | `false` |
12
+ | `width` | `string` | `'100%'` |
13
+ | `height` | `string` | `'15px'` |
14
+ | `shape` / `size` / `borderRadius` / `animation` | как у [`rt-skeleton`](../skeleton/CONTEXT.md) | |
15
+
16
+ ## Главное, что нужно знать
17
+
18
+ **Умолчание — «загрузка закончилась».** Обёртку часто ставят на путь, где данные уже есть,
19
+ поэтому `isLoading` по умолчанию `false`.
20
+
21
+ **Подмена полная**: пока идёт загрузка, проекция не отрисована вовсе — не спрятана, а
22
+ отсутствует. Значит внутрь можно смело класть выражения по ещё не пришедшим данным.
23
+
24
+ Высота по умолчанию здесь **15px**, а у самого `rt-skeleton` — 10px.
@@ -1,4 +1,8 @@
1
- :host {
1
+ /* rt-skeleton-wrapper — ViewEncapsulation.None: правило таргетирует BEM-класс блока.
2
+ `.rt-skeleton-wrapper` — сам хост: своей разметки блока нет, внутри либо
3
+ rt-skeleton, либо спроецированное содержимое. */
4
+
5
+ .rt-skeleton-wrapper {
2
6
  display: inline-flex;
3
7
  width: 100%;
4
8
  }
@@ -0,0 +1,39 @@
1
+ # `rt-spinner`
2
+
3
+ ```html
4
+ <rt-spinner color="primary" [diameter]="32" />
5
+ ```
6
+
7
+ | вход | тип | умолчание |
8
+ | ---------- | ---------------------------------------- | ----------- |
9
+ | `diameter` | `number \| string` (px) | `32` |
10
+ | `color` | `'primary' \| 'neutral' \| 'on-primary'` | `'primary'` |
11
+
12
+ Выходов нет. Собственной разметки нет — кольцо рисуется на host-е.
13
+
14
+ ## Главное, что нужно знать
15
+
16
+ **Спиннер не решает, показывать ли себя.** Ни условной отрисовки, ни подложки внутри нет: и то,
17
+ и другое — дело вмещающего. Таблица рисует свой sticky-overlay и кладёт спиннер внутрь, кнопка
18
+ подменяет им иконку.
19
+
20
+ Диаметр едет свойством оформления `--rt-spinner-diameter`, а не классом, — поэтому размер
21
+ задаётся произвольным числом, а не шкалой.
22
+
23
+ ## Как этим пользоваться
24
+
25
+ - `on-primary` — для спиннера поверх залитой основной палитрой поверхности (кнопка, баннер).
26
+ - `neutral` — для тонких строчных индикаторов рядом с текстом.
27
+ - Host объявлен `role="status"` с `aria-live="polite"`: скринридер сообщит о появлении, не
28
+ перебивая чтение. Отдельной подписи компонент не даёт — если состояние важно назвать словами,
29
+ положи текст рядом.
30
+
31
+ ## Края
32
+
33
+ - Вращение выключается у пользователей с `prefers-reduced-motion`. Проверять это в спеке нечем —
34
+ правило живёт в SCSS.
35
+
36
+ ## Рядом
37
+
38
+ - [`rt-skeleton`](../skeleton/CONTEXT.md) — заглушка формы контента вместо кольца; для списков
39
+ и карточек она честнее спиннера.
@@ -0,0 +1,39 @@
1
+ # `rt-split-button`
2
+
3
+ Кнопка основного действия с прикреплённым меню дополнительных.
4
+
5
+ ```html
6
+ <rt-split-button
7
+ label="Сохранить"
8
+ [menuItems]="[{ value: 'draft', label: 'Сохранить черновик' }]"
9
+ [loading]="saving()"
10
+ (faceClick)="save()"
11
+ (itemSelect)="run($event)" />
12
+ ```
13
+
14
+ | вход | тип | умолчание |
15
+ | ---------------------- | ----------------------------------------------- | ---------------------------- |
16
+ | `label` | `string` | **обязателен** |
17
+ | `menuItems` | `readonly { value, label, icon?, disabled? }[]` | **обязателен** |
18
+ | `theme` / `size` | как у [`[rtButton]`](../button/CONTEXT.md) | `'primary'` / `'md'` |
19
+ | `menuAriaLabel` | `string` | `''` → `rtKit.uiMoreActions` |
20
+ | `loading` / `disabled` | `boolean` | `false` |
21
+
22
+ Выходы: `faceClick`, `itemSelect` (значение пункта).
23
+
24
+ ## Главное, что нужно знать
25
+
26
+ **Поповер висит на всём контроле, а не на узкой каретке.** Так ширина меню равна ширине кнопки, а
27
+ левый край совпадает — иначе панель уезжала бы за экран.
28
+
29
+ **Нажатие основной кнопки меню не открывает**, и наоборот: это два независимых действия.
30
+
31
+ ## Края
32
+
33
+ - `loading` и `disabled` блокируют обе части и запрещают открытие меню.
34
+ - Отключённый пункт не выбирается и меню не закрывает.
35
+ - Значение пункта продублировано атрибутом `data-value`.
36
+
37
+ ## Рядом
38
+
39
+ - [`rt-menu`](../menu/CONTEXT.md) — меню без основного действия.
@@ -0,0 +1,35 @@
1
+ # `rt-stat-tile`
2
+
3
+ Плитка показателя: значение, изменение к периоду, база сравнения.
4
+
5
+ ```html
6
+ <rt-stat-tile
7
+ label="Визиты"
8
+ value="1 240"
9
+ secondary="из них 300 новых"
10
+ hint="Считается по уникальным"
11
+ [deltaPrimary]="{ percent: 12.5, label: 'к прошлой неделе', baseline: '1 100' }"
12
+ [deltaSecondary]="{ percent: 8, label: 'к прошлому году' }" />
13
+ ```
14
+
15
+ | вход | тип | умолчание |
16
+ | --------------------------------- | --------------------------------------- | --------------- |
17
+ | `label` / `value` | `string` | **обязательны** |
18
+ | `secondary` | `string \| null` | `null` |
19
+ | `hint` | `string \| null` | `null` |
20
+ | `deltaPrimary` / `deltaSecondary` | `{ percent, label, baseline? } \| null` | `null` |
21
+
22
+ Выходов нет.
23
+
24
+ ## Главное, что нужно знать
25
+
26
+ **`percent: null` означает «сравнить не с чем» — и тогда изменение не рисуется вовсе.**
27
+ Пустая плашка на его месте читалась бы как ноль. Ноль же — это `flat`: не рост и не падение.
28
+
29
+ Знак и стрелка выводятся из числа: `> 0` — «+» и стрелка вверх, `< 0` — стрелка вниз. Дробная
30
+ часть отделяется запятой.
31
+
32
+ **Значение базы уходит параметром перевода, а не подставляется в готовую строку.** Transloco
33
+ интерполирует `{{value}}` в момент перевода, и к возвращённой строке подставлять уже нечего:
34
+ `String.replace` после `translateSignal` не находил плейсхолдера, и от подписи оставалась одна
35
+ приставка («было», «was»). Параметр передан сигналом — смена базы переписывает строку.
@@ -1,9 +1,12 @@
1
- /* rt-stat-tile — KPI-плитка в common/ui (Emulated encapsulation; :host обязательно).
1
+ /* rt-stat-tile — KPI-плитка в common/ui. ViewEncapsulation.None.
2
+ Хост адресуется по имени элемента, а не по классу блока: класс `.rt-stat-tile` висит
3
+ и на `<rt-stat-tile>`, и на корне шаблона, поэтому правила блока вложены в хост —
4
+ так они достаются только корню шаблона.
2
5
 
3
6
  Рамку и фон плитка не рисует: плитки стоят рядом внутри одной карточки, а
4
7
  разделители между ними — забота контейнера. */
5
8
 
6
- :host {
9
+ rt-stat-tile {
7
10
  display: block;
8
11
 
9
12
  .rt-stat-tile {
@@ -0,0 +1,36 @@
1
+ # `rt-stepper`
2
+
3
+ Полоса прогресса по шагам: точки, подпись текущего шага и его описание.
4
+
5
+ ```html
6
+ <rt-stepper [steps]="[{ label: 'Заявка', description: 'Заполните данные' }, …]" [currentIndex]="1" />
7
+ ```
8
+
9
+ | вход | тип | умолчание |
10
+ | -------------- | ----------------------------------- | --------- |
11
+ | `steps` | `readonly { label, description }[]` | `[]` |
12
+ | `currentIndex` | `number \| string` | `0` |
13
+
14
+ Выходов нет — шаги переключает потребитель.
15
+
16
+ ## Главное, что нужно знать
17
+
18
+ **Заполнение считается по промежуткам, а не по шагам.** Первый шаг даёт 0 %, последний — 100 %:
19
+ при трёх шагах середина это 50 %. Единственный шаг всегда даёт 0 % — делить не на что.
20
+
21
+ Номер шага за пределами набора зажимается **один раз, до всего остального**: и полоса, и якорь
22
+ подписи, и объявление для скринридера читают уже зажатый индекс. Иначе показ и объявление
23
+ расходятся — полоса упирается в 100 %, а на прогрессбаре оказывается `aria-valuenow` больше
24
+ `aria-valuemax`, и подпись шага пропадает. Пустой набор объявляется нулём.
25
+
26
+ ## Края
27
+
28
+ - Описание разбивается на абзацы по пустой строке (`\n\n`); пустое описание не создаёт абзаца.
29
+ - Точки спрятаны от скринридера — прогресс он читает с самой полосы: `role="progressbar"`,
30
+ `aria-valuemin/max/now` и `aria-valuetext` с подписью текущего шага.
31
+ - Подпись текущего шага прижимается к началу, центру или концу полосы в зависимости от того,
32
+ где стоит шаг.
33
+
34
+ ## Рядом
35
+
36
+ - [`rt-timeline`](../timeline/CONTEXT.md) — журнал событий, а не линейный прогресс.
@@ -1,6 +1,9 @@
1
- /* rt-stepper — линейный прогресс-степпер в common/ui (Emulated; :host обязателен). */
1
+ /* rt-stepper — линейный прогресс-степпер в common/ui. ViewEncapsulation.None.
2
+ Хост адресуется по имени элемента, а не по классу блока: класс `.rt-stepper` висит
3
+ и на `<rt-stepper>`, и на корне шаблона, поэтому правила блока вложены в хост —
4
+ так они достаются только корню шаблона. */
2
5
 
3
- :host {
6
+ rt-stepper {
4
7
  display: block;
5
8
 
6
9
  .rt-stepper {
@@ -0,0 +1,53 @@
1
+ # `rt-table`
2
+
3
+ Таблица поверх CDK Table: колонки объявляются директивами CDK, а кит добавляет состояния,
4
+ карточки для узкого экрана, меню строки и настройку колонок.
5
+
6
+ ```html
7
+ <table rt-table tableId="tours" ariaLabel="Туры" [dataSource]="rows()" [columns]="columns" [loading]="loading()" [columnsConfig]="config">
8
+ <ng-container cdkColumnDef="title">
9
+ <th *cdkHeaderCellDef cdk-header-cell>Название</th>
10
+ <td *cdkCellDef="let row" cdk-cell>{{ row.title }}</td>
11
+ </ng-container>
12
+ <tr *cdkHeaderRowDef="columns" cdk-header-row></tr>
13
+ <tr *cdkRowDef="let row; columns: columns" cdk-row></tr>
14
+ </table>
15
+ ```
16
+
17
+ | вход | тип | умолчание |
18
+ | ----------------------------------- | ----------------------------- | ------------- |
19
+ | `columns` | `ReadonlyArray<string>` | `[]` |
20
+ | `columnsConfig` | `ReadonlyArray<ColumnConfig>` | `[]` |
21
+ | `tableId` | `string \| null` | `null` |
22
+ | `density` | `'default' \| 'compact'` | `'default'` |
23
+ | `cards` | `boolean` | `true` |
24
+ | `clickable` | `boolean` | `false` |
25
+ | `loading` / `fetching` | `boolean` | `false` |
26
+ | `skeletonRows` | `number` | 5 |
27
+ | `emptyMessage` / `emptyDescription` | `string` / `string \| null` | `''` / `null` |
28
+ | `emptyIcon` | `IRtIcon.Name \| null` | `'inbox'` |
29
+ | `showRowActions` | `boolean` | `false` |
30
+ | `sort` | `ISortModel<string> \| null` | `null` |
31
+
32
+ Выход: `sortChange`.
33
+
34
+ ## Главное, что нужно знать
35
+
36
+ **`loading` и `fetching` — разные состояния.** Первая загрузка (`loading` при пустых данных)
37
+ подменяет строки заглушками; догрузка (`fetching`) оставляет уже показанное на месте — иначе
38
+ таблица мигала бы при каждой смене страницы.
39
+
40
+ **Карточки — это другая разметка, а не перестроенная стилями таблица.** Компонент сам решает по
41
+ ширине вьюпорта (`BreakpointsService.narrow`), что показать. Подписью в карточке служит **ключ
42
+ колонки**: заголовки живут в шаблонах ячеек CDK, куда карточка не дотягивается. Осмысленные
43
+ подписи даёт `columnsConfig`.
44
+
45
+ **Настройка колонок требует и `columnsConfig`, и `tableId`.** Выбор хранится в IndexedDB, и без
46
+ идентификатора таблицы его некуда положить — поэтому `canConfigure()` останется `false`.
47
+
48
+ ## Края
49
+
50
+ - Пустой набор рисует заглушку с переведённым `rtKit.uiNoData`; свой текст перебивает её.
51
+ - Колонка `rtRowActions` добавляется китом сама, когда включён `showRowActions`, и рисует
52
+ [`rt-menu`](../menu/CONTEXT.md) в строке.
53
+ - Спеки логики лежат рядом: сортировка и предикат действий строки проверены отдельно.
@@ -1,5 +1,7 @@
1
1
  /* rt-table-settings-aside — ViewEncapsulation.None (common/ui). Своей раскладки нет:
2
- chrome даёт rt-aside, тело — rt-table-settings-panel. */
3
- :host {
2
+ chrome даёт rt-aside, тело — rt-table-settings-panel. `.rt-table-settings-aside` —
3
+ сам хост, и он не должен добавлять бокс между асайдом и его местом в overlay. */
4
+
5
+ .rt-table-settings-aside {
4
6
  display: contents;
5
7
  }
@@ -1,5 +1,5 @@
1
1
  /* rt-table-settings-panel — ViewEncapsulation.None (common/ui): правила таргетируют
2
- BEM-класс глобально, без :host-обёртки (как rt-icon / rt-dialog-footer). */
2
+ BEM-класс блока, как во всём ките. */
3
3
  .rt-table-settings-panel {
4
4
  &__list {
5
5
  display: flex;
@@ -0,0 +1,56 @@
1
+ # `rt-tabs` + `[rtTab]`
2
+
3
+ Вкладки объявляются директивой на `<ng-template>` — содержимое неактивной вкладки не создаётся.
4
+
5
+ ```html
6
+ <rt-tabs stretch [activeId]="tab()" (activeIdChange)="tab.set($event)">
7
+ <ng-template rtTab="main" label="Основное">…</ng-template>
8
+ <ng-template rtTab="extra" label="Дополнительно" [badge]="3">…</ng-template>
9
+ <ng-template rtTab="docs" label="Документы" icon="file" [invalid]="hasErrors()">…</ng-template>
10
+ </rt-tabs>
11
+ ```
12
+
13
+ ## `rt-tabs`
14
+
15
+ | вход | тип | умолчание |
16
+ | ------------------- | ---------------------------- | -------------- |
17
+ | `activeId` | `IRtTabs.Id \| null` | `null` |
18
+ | `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` |
19
+ | `stretch` | `boolean` | `false` |
20
+ | `contentScrollable` | `boolean` | `true` |
21
+
22
+ Выход: `activeIdChange: IRtTabs.Id`.
23
+
24
+ ## `[rtTab]`
25
+
26
+ | вход | тип | умолчание |
27
+ | --------------------------------- | --------------------------------------------- | --------------------------- |
28
+ | `rtTab` | `IRtTabs.Id` — идентификатор вкладки | `''` |
29
+ | `label` | `string` | `''` |
30
+ | `titleTemplate` | `TemplateRef \| null` | `null` |
31
+ | `icon` / `iconColor` | `IRtIcon.Name \| null` / `IRtTabs.TitleColor` | `null` / `'current'` |
32
+ | `badge` | `string \| number \| null` | `null` |
33
+ | `disabled` / `hidden` / `invalid` | `boolean` | `false` |
34
+ | `invalidMessage` | `string` | `''` → `rtKit.uiTabInvalid` |
35
+
36
+ ## Главное, что нужно знать
37
+
38
+ **Вход `activeId` необязателен.** Без него компонент помнит выбор сам — управляемый режим нужен
39
+ только когда вкладка лежит в адресе страницы.
40
+
41
+ **Идентификатор, которого нет, откатывается на первую доступную вкладку.** То же с отключённой:
42
+ активной она стать не может. Так вкладка из старой ссылки не оставляет пустой экран.
43
+
44
+ ## Клавиатура и доступность
45
+
46
+ - Стрелки (влево/вправо, а в вертикальной раскладке вверх/вниз) перешагивают отключённые вкладки
47
+ и заворачиваются по кругу; `Home`/`End` — к первой и последней.
48
+ - В таб-порядке стоит **только активная** вкладка (`tabindex="0"`), остальные достаются стрелками.
49
+ - Панель и вкладка связаны в обе стороны: `aria-controls` ↔ `aria-labelledby`.
50
+
51
+ ## Края
52
+
53
+ - `hidden` убирает вкладку из шапки целиком; `disabled` оставляет её видимой, но некликабельной.
54
+ - У невалидной вкладки появляется подсказка с текстом `invalidMessage`.
55
+ - Стрелки прокрутки шапки есть в разметке всегда — показывает их модификатор `--visible`,
56
+ который выставляется по замеру ширины после отрисовки.