@rt-tools/ui-kit-v2 0.1.0 → 0.4.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 (117) hide show
  1. package/README.md +25 -16
  2. package/fesm2022/rt-tools-ui-kit-v2.mjs +786 -1504
  3. package/fesm2022/rt-tools-ui-kit-v2.mjs.map +1 -1
  4. package/package.json +1 -2
  5. package/rt-tools-ui-kit-v2-0.4.0.tgz +0 -0
  6. package/src/lib/components/CONTEXT.md +49 -0
  7. package/src/lib/components/aside/CONTEXT.md +51 -0
  8. package/src/lib/components/aside/_rt-aside-overlay.scss +88 -0
  9. package/src/lib/components/aside/rt-aside.component.scss +19 -94
  10. package/src/lib/components/aside-section/CONTEXT.md +26 -0
  11. package/src/lib/components/autocomplete/CONTEXT.md +58 -0
  12. package/src/lib/components/bar-list/CONTEXT.md +27 -0
  13. package/src/lib/components/bar-list/rt-bar-list.component.scss +5 -2
  14. package/src/lib/components/bottom-sheet/CONTEXT.md +54 -0
  15. package/src/lib/components/bottom-sheet/rt-bottom-sheet.component.scss +53 -53
  16. package/src/lib/components/button/CONTEXT.md +53 -0
  17. package/src/lib/components/button/rt-button.directive.scss +20 -0
  18. package/src/lib/components/calendar/CONTEXT.md +41 -0
  19. package/src/lib/components/calendar/rt-calendar.component.scss +96 -99
  20. package/src/lib/components/card/CONTEXT.md +34 -0
  21. package/src/lib/components/card/rt-card.component.scss +5 -2
  22. package/src/lib/components/chat/CONTEXT.md +41 -0
  23. package/src/lib/components/checkbox/CONTEXT.md +37 -0
  24. package/src/lib/components/checkbox/rt-checkbox.component.scss +13 -4
  25. package/src/lib/components/collapsible-text/CONTEXT.md +29 -0
  26. package/src/lib/components/confirm-popover/CONTEXT.md +47 -0
  27. package/src/lib/components/container/CONTEXT.md +42 -0
  28. package/src/lib/components/container/rt-container.component.scss +5 -69
  29. package/src/lib/components/counter/CONTEXT.md +40 -0
  30. package/src/lib/components/counter/rt-counter.component.scss +10 -9
  31. package/src/lib/components/counter-row/CONTEXT.md +24 -0
  32. package/src/lib/components/counter-row/rt-counter-row.component.scss +23 -22
  33. package/src/lib/components/date-picker/CONTEXT.md +41 -0
  34. package/src/lib/components/delta-view/CONTEXT.md +31 -0
  35. package/src/lib/components/detail-list/CONTEXT.md +29 -0
  36. package/src/lib/components/dialog/CONTEXT.md +50 -0
  37. package/src/lib/components/dialog/footer/rt-dialog-footer.component.scss +20 -9
  38. package/src/lib/components/dialog/header/rt-dialog-header.component.scss +27 -15
  39. package/src/lib/components/dialog/rt-dialog.component.scss +6 -2
  40. package/src/lib/components/download-link/CONTEXT.md +26 -0
  41. package/src/lib/components/download-link/rt-download-link.component.scss +13 -0
  42. package/src/lib/components/empty-state/CONTEXT.md +35 -0
  43. package/src/lib/components/field/CONTEXT.md +72 -0
  44. package/src/lib/components/file-card/CONTEXT.md +28 -0
  45. package/src/lib/components/file-drop/CONTEXT.md +39 -0
  46. package/src/lib/components/file-input/CONTEXT.md +36 -0
  47. package/src/lib/components/file-list/CONTEXT.md +15 -0
  48. package/src/lib/components/filter-control/CONTEXT.md +33 -0
  49. package/src/lib/components/form-control/CONTEXT.md +50 -0
  50. package/src/lib/components/header/CONTEXT.md +36 -0
  51. package/src/lib/components/icon/CONTEXT.md +46 -0
  52. package/src/lib/components/icon/rt-icon.component.scss +3 -5
  53. package/src/lib/components/icon-button/CONTEXT.md +49 -0
  54. package/src/lib/components/icon-button/rt-icon-button.component.scss +14 -1
  55. package/src/lib/components/info-item/CONTEXT.md +30 -0
  56. package/src/lib/components/input/CONTEXT.md +47 -0
  57. package/src/lib/components/input-number/CONTEXT.md +52 -0
  58. package/src/lib/components/live-badge/CONTEXT.md +31 -0
  59. package/src/lib/components/live-badge/rt-live-badge.component.scss +5 -2
  60. package/src/lib/components/logo/CONTEXT.md +42 -0
  61. package/src/lib/components/logo/rt-logo.component.scss +4 -6
  62. package/src/lib/components/menu/CONTEXT.md +65 -0
  63. package/src/lib/components/message/CONTEXT.md +35 -0
  64. package/src/lib/components/message/rt-message.component.scss +5 -2
  65. package/src/lib/components/message-composer/CONTEXT.md +46 -0
  66. package/src/lib/components/money-list/CONTEXT.md +32 -0
  67. package/src/lib/components/multiselect/CONTEXT.md +43 -0
  68. package/src/lib/components/night-grid/CONTEXT.md +31 -0
  69. package/src/lib/components/night-grid/rt-night-grid.component.scss +5 -2
  70. package/src/lib/components/note/CONTEXT.md +27 -0
  71. package/src/lib/components/notifications-bell/CONTEXT.md +29 -0
  72. package/src/lib/components/page-header/CONTEXT.md +33 -0
  73. package/src/lib/components/pagination/CONTEXT.md +43 -0
  74. package/src/lib/components/photo-viewer/CONTEXT.md +30 -0
  75. package/src/lib/components/photo-viewer/rt-photo-viewer.component.scss +95 -96
  76. package/src/lib/components/popover/CONTEXT.md +54 -0
  77. package/src/lib/components/rich-editor/CONTEXT.md +36 -0
  78. package/src/lib/components/section-nav/CONTEXT.md +27 -0
  79. package/src/lib/components/section-nav/rt-section-nav.component.scss +7 -1
  80. package/src/lib/components/select/CONTEXT.md +54 -0
  81. package/src/lib/components/skeleton/CONTEXT.md +41 -0
  82. package/src/lib/components/skeleton-wrapper/CONTEXT.md +24 -0
  83. package/src/lib/components/skeleton-wrapper/rt-skeleton-wrapper.component.scss +5 -1
  84. package/src/lib/components/spinner/CONTEXT.md +39 -0
  85. package/src/lib/components/split-button/CONTEXT.md +39 -0
  86. package/src/lib/components/split-button/rt-split-button.component.scss +13 -0
  87. package/src/lib/components/stat-tile/CONTEXT.md +35 -0
  88. package/src/lib/components/stat-tile/rt-stat-tile.component.scss +5 -2
  89. package/src/lib/components/stepper/CONTEXT.md +36 -0
  90. package/src/lib/components/stepper/rt-stepper.component.scss +5 -2
  91. package/src/lib/components/table/CONTEXT.md +53 -0
  92. package/src/lib/components/table/settings-aside/rt-table-settings-aside.component.scss +4 -2
  93. package/src/lib/components/table/settings-panel/rt-table-settings-panel.component.scss +1 -1
  94. package/src/lib/components/tabs/CONTEXT.md +56 -0
  95. package/src/lib/components/tag/CONTEXT.md +47 -0
  96. package/src/lib/components/tag/rt-tag.component.scss +5 -2
  97. package/src/lib/components/textarea/CONTEXT.md +38 -0
  98. package/src/lib/components/theme-toggle/CONTEXT.md +28 -0
  99. package/src/lib/components/theme-toggle/rt-theme-toggle.component.scss +5 -1
  100. package/src/lib/components/thread-list/CONTEXT.md +42 -0
  101. package/src/lib/components/timeline/CONTEXT.md +33 -0
  102. package/src/lib/components/timeline/rt-timeline.component.scss +5 -2
  103. package/src/lib/components/toast/CONTEXT.md +53 -0
  104. package/src/lib/components/toggle-button-group/CONTEXT.md +38 -0
  105. package/src/lib/components/toggle-button-group/rt-toggle-button-group.component.scss +12 -7
  106. package/src/lib/components/toggle-switch/CONTEXT.md +35 -0
  107. package/src/lib/components/toggle-switch/rt-toggle-switch.component.scss +13 -4
  108. package/src/lib/components/toolbar/CONTEXT.md +22 -0
  109. package/src/lib/components/tooltip/CONTEXT.md +34 -0
  110. package/src/lib/components/welcome-dialog/CONTEXT.md +20 -0
  111. package/src/lib/components/welcome-dialog/rt-welcome-dialog.component.scss +6 -2
  112. package/src/lib/components/workspace/CONTEXT.md +46 -0
  113. package/src/lib/components/workspace-details/CONTEXT.md +42 -0
  114. package/src/styles/_semantic.scss +11 -2
  115. package/styles/tokens.css +26 -1
  116. package/types/rt-tools-ui-kit-v2.d.ts +365 -87
  117. package/rt-tools-ui-kit-v2-0.1.0.tgz +0 -0
@@ -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
  }
@@ -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` | `''` → `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
+ - Пустой набор опций рисует строку `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` | `''` → `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) — меню без основного действия.
@@ -57,8 +57,21 @@
57
57
  list-style: none;
58
58
  }
59
59
 
60
+ /* Ширину пункту задаёт панель, а не его текст: `width="trigger"` держит её равной
61
+ кнопке. Пункт длиннее обязан обрезаться, иначе лейбл вылезает за скруглённую
62
+ рамку панели и висит в воздухе. Многоточие требует блочного лейбла: у
63
+ `inline-flex` `text-overflow` не работает — текст внутри становится анонимным
64
+ flex-элементом, и обрезка проходит без знака. */
60
65
  &__menu-item.rt-button {
66
+ overflow: hidden;
61
67
  width: 100%;
62
68
  justify-content: flex-start;
69
+
70
+ .rt-button__label {
71
+ display: block;
72
+ overflow: hidden;
73
+ min-width: 0;
74
+ text-overflow: ellipsis;
75
+ }
63
76
  }
64
77
  }
@@ -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
+ **Значение базы уходит параметром подписи, а не подставляется в готовую строку.** Переводчик приложения
33
+ интерполирует `{{value}}` в момент перевода, и к возвращённой строке подставлять уже нечего:
34
+ `String.replace` после `translateSignal` не находил плейсхолдера, и от подписи оставалась одна
35
+ приставка («было», «was»). Параметр передан сигналом — смена базы переписывает строку.