@cloud-ru/ds-list 2.0.1 → 2.0.2

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 (61) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/cjs/components/Items/BaseItem/BaseItem.js +1 -1
  4. package/dist/cjs/components/Items/BaseItem/styles.module.css +42 -0
  5. package/dist/cjs/components/Items/SearchItem/SearchItem.js +1 -1
  6. package/dist/cjs/components/Items/SimpleItem/utils.d.ts +1 -1
  7. package/dist/cjs/components/Items/SimpleItem/utils.js +1 -1
  8. package/dist/cjs/components/Items/styles.module.css +9 -0
  9. package/dist/cjs/components/Items/types.d.ts +2 -2
  10. package/dist/cjs/components/Items/utils.js +1 -1
  11. package/dist/cjs/components/Lists/ListPrivate/ListPrivate.js +1 -1
  12. package/dist/cjs/components/Lists/ListPrivate/styles.module.css +2 -3
  13. package/dist/cjs/components/Lists/hooks.d.ts +1 -1
  14. package/dist/cjs/components/Lists/hooks.js +2 -2
  15. package/dist/cjs/helperComponents/DesktopDroplist/DesktopDroplist.js +1 -1
  16. package/dist/cjs/helperComponents/ItemContent/ItemContent.js +1 -1
  17. package/dist/cjs/helperComponents/ItemContent/styles.module.css +45 -24
  18. package/dist/cjs/helperComponents/Separator/Separator.js +3 -4
  19. package/dist/cjs/helperComponents/Separator/styles.module.css +69 -49
  20. package/dist/cjs/style.css +167 -76
  21. package/dist/esm/components/Items/BaseItem/BaseItem.js +1 -1
  22. package/dist/esm/components/Items/BaseItem/styles.module.css +42 -0
  23. package/dist/esm/components/Items/SearchItem/SearchItem.js +1 -1
  24. package/dist/esm/components/Items/SimpleItem/utils.d.ts +1 -1
  25. package/dist/esm/components/Items/SimpleItem/utils.js +1 -1
  26. package/dist/esm/components/Items/styles.module.css +9 -0
  27. package/dist/esm/components/Items/types.d.ts +2 -2
  28. package/dist/esm/components/Items/utils.js +1 -1
  29. package/dist/esm/components/Lists/ListPrivate/ListPrivate.js +1 -1
  30. package/dist/esm/components/Lists/ListPrivate/styles.module.css +2 -3
  31. package/dist/esm/components/Lists/hooks.d.ts +1 -1
  32. package/dist/esm/components/Lists/hooks.js +2 -2
  33. package/dist/esm/helperComponents/DesktopDroplist/DesktopDroplist.js +1 -1
  34. package/dist/esm/helperComponents/ItemContent/ItemContent.js +1 -1
  35. package/dist/esm/helperComponents/ItemContent/styles.module.css +45 -24
  36. package/dist/esm/helperComponents/Separator/Separator.js +3 -4
  37. package/dist/esm/helperComponents/Separator/styles.module.css +69 -49
  38. package/dist/esm/style.css +167 -76
  39. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  40. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  41. package/package.json +10 -10
  42. package/src/components/Items/BaseItem/BaseItem.tsx +17 -10
  43. package/src/components/Items/BaseItem/styles.module.scss +57 -4
  44. package/src/components/Items/SearchItem/SearchItem.tsx +1 -1
  45. package/src/components/Items/SimpleItem/utils.ts +1 -1
  46. package/src/components/Items/styles.module.scss +10 -2
  47. package/src/components/Items/types.ts +3 -7
  48. package/src/components/Items/utils.ts +1 -1
  49. package/src/components/Lists/ListPrivate/ListPrivate.tsx +1 -1
  50. package/src/components/Lists/ListPrivate/styles.module.scss +5 -4
  51. package/src/components/Lists/hooks.ts +2 -2
  52. package/src/helperComponents/DesktopDroplist/DesktopDroplist.tsx +1 -1
  53. package/src/helperComponents/ItemContent/ItemContent.tsx +22 -20
  54. package/src/helperComponents/ItemContent/styles.module.scss +25 -15
  55. package/src/helperComponents/Separator/Separator.tsx +23 -22
  56. package/src/helperComponents/Separator/styles.module.scss +132 -101
  57. package/dist/cjs/helperComponents/Separator/constants.d.ts +0 -3
  58. package/dist/cjs/helperComponents/Separator/constants.js +0 -10
  59. package/dist/esm/helperComponents/Separator/constants.d.ts +0 -3
  60. package/dist/esm/helperComponents/Separator/constants.js +0 -7
  61. package/src/helperComponents/Separator/constants.ts +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-ru/ds-list",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Списочный UI — компонент List для плоских/вложенных списков с выбором, группами и поиском и Droplist — тот же список в поповере.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,22 +48,22 @@
48
48
  "@tanstack/react-virtual": "^3.14.8",
49
49
  "classnames": "^2.5.1",
50
50
  "merge-refs": "^2.0.0",
51
- "@cloud-ru/ds-drag-and-drop": "^1.1.1",
51
+ "@cloud-ru/ds-button": "^1.0.1",
52
52
  "@cloud-ru/ds-divider": "^1.0.1",
53
- "@cloud-ru/ds-adaptive": "^1.0.1",
53
+ "@cloud-ru/ds-drag-and-drop": "^1.1.1",
54
+ "@cloud-ru/ds-dropdown": "^1.0.4",
54
55
  "@cloud-ru/ds-info-block": "^1.0.1",
55
- "@cloud-ru/ds-materials": "^1.0.1",
56
- "@cloud-ru/ds-dropdown": "^1.0.3",
57
56
  "@cloud-ru/ds-loader": "^1.0.1",
57
+ "@cloud-ru/ds-bottom-sheet": "^1.0.3",
58
+ "@cloud-ru/ds-adaptive": "^1.0.1",
58
59
  "@cloud-ru/ds-portal-context": "^1.0.1",
60
+ "@cloud-ru/ds-materials": "^1.0.1",
59
61
  "@cloud-ru/ds-icons": "^1.0.2",
60
- "@cloud-ru/ds-bottom-sheet": "^1.0.3",
61
- "@cloud-ru/ds-scroll": "^1.0.2",
62
- "@cloud-ru/ds-search-private": "^1.0.3",
63
- "@cloud-ru/ds-toggles": "^1.1.2",
64
62
  "@cloud-ru/ds-truncate-string": "^1.0.3",
63
+ "@cloud-ru/ds-scroll": "^1.0.2",
65
64
  "@cloud-ru/ds-utils": "^1.1.0",
66
- "@cloud-ru/ds-button": "^1.0.1"
65
+ "@cloud-ru/ds-search-private": "^1.0.3",
66
+ "@cloud-ru/ds-toggles": "^1.1.2"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@cloud-ru/ds-locale": "^1.0.0"
@@ -179,6 +179,7 @@ export function BaseItem({
179
179
  const itemJSX = (
180
180
  <div
181
181
  className={cn(commonStyles.itemWrapper, styles.innerWrapper, className)}
182
+ data-size={size}
182
183
  data-inactive={inactive || undefined}
183
184
  data-disabled={disabled || undefined}
184
185
  data-variant={mode || undefined}
@@ -227,18 +228,24 @@ export function BaseItem({
227
228
  </div>
228
229
  )}
229
230
 
230
- {beforeContent && <div className={styles.beforeContent}>{beforeContent}</div>}
231
- {contentNode}
232
- {afterContent && <div className={styles.afterContent}>{afterContent}</div>}
231
+ {/* contentWrapper мастера: иконочные слоты и текст лежат в одном узле без gap —
232
+ их разделяют только горизонтальные поля textContent. */}
233
+ <div className={styles.contentWrapper}>
234
+ {beforeContent && <div className={styles.beforeContent}>{beforeContent}</div>}
235
+ {contentNode}
236
+ {afterContent && <div className={styles.afterContent}>{afterContent}</div>}
237
+ </div>
233
238
 
234
239
  {switchProp && interactive && (
235
- <Switch
236
- size={TOGGLE_SIZE_MAP[size]}
237
- disabled={disabled}
238
- checked={isChecked}
239
- data-test-id={TEST_IDS.baseItemSwitch}
240
- showIcon={showSwitchIcon}
241
- />
240
+ <span className={styles.switchWrapper}>
241
+ <Switch
242
+ size={TOGGLE_SIZE_MAP[size]}
243
+ disabled={disabled}
244
+ checked={isChecked}
245
+ data-test-id={TEST_IDS.baseItemSwitch}
246
+ showIcon={showSwitchIcon}
247
+ />
248
+ </span>
242
249
  )}
243
250
 
244
251
  {!switchProp && expandIcon && (
@@ -185,6 +185,37 @@ $level: var(--level, 0);
185
185
  min-height: base.simple-var(icons.$iconSizeMap, $size);
186
186
  padding-top: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'centeredWrapper', 'padding-top');
187
187
  padding-bottom: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'centeredWrapper', 'padding-bottom');
188
+
189
+ // Иконка в слоте ограничена icon-сеткой размера. Через max-*, потому что у svg
190
+ // из @cloud-ru/ds-icons размер приходит инлайновым style и обычный width его не перебивает.
191
+ // Произвольный контент (counter, tag) правило не задевает — адресуется только прямой svg.
192
+ > svg {
193
+ max-width: base.simple-var(icons.$iconSizeMap, $size);
194
+ max-height: base.simple-var(icons.$iconSizeMap, $size);
195
+ }
196
+ }
197
+
198
+ // Switch выравнивается по центру первой строки текста тем же centeredWrapper-паддингом,
199
+ // что иконочные слоты: строка выровнена по flex-start, иначе контрол липнет к верхней кромке.
200
+ .switchWrapper {
201
+ padding-top: base.simple-var(
202
+ list.$list,
203
+ 'anatomy',
204
+ 'size',
205
+ $size,
206
+ 'listItem',
207
+ 'centeredWrapper',
208
+ 'padding-top'
209
+ );
210
+ padding-bottom: base.simple-var(
211
+ list.$list,
212
+ 'anatomy',
213
+ 'size',
214
+ $size,
215
+ 'listItem',
216
+ 'centeredWrapper',
217
+ 'padding-bottom'
218
+ );
188
219
  }
189
220
 
190
221
  .groupIndicator {
@@ -256,10 +287,14 @@ $level: var(--level, 0);
256
287
  }
257
288
 
258
289
  &[data-disabled] {
259
- .markerContainer::before {
260
- // В мастере выключенный маркер — сплошной `neutral/accent`, а не полупрозрачный
261
- // текстовый токен: полоса остаётся читаемой на любом фоне.
262
- background-color: base.$sn-theme-color-neutral-accent;
290
+ .markerContainer {
291
+ opacity: base.$sn-theme-effect-opacity-disabled;
292
+
293
+ &::before {
294
+ // В мастере выключенный маркер — сплошной `neutral/accent`, а не полупрозрачный
295
+ // текстовый токен: полоса остаётся читаемой на любом фоне.
296
+ background-color: base.$sn-theme-color-neutral-accent;
297
+ }
263
298
  }
264
299
  }
265
300
  }
@@ -271,3 +306,21 @@ $level: var(--level, 0);
271
306
  flex-shrink: 1;
272
307
  box-sizing: border-box;
273
308
  }
309
+
310
+ // contentWrapper мастера: собственного зазора не имеет — иконочные слоты отделены от текста
311
+ // горизонтальными полями textContent, поэтому gap здесь не задаётся.
312
+ .switchWrapper {
313
+ display: flex;
314
+ flex-shrink: 0;
315
+ align-items: center;
316
+ box-sizing: content-box;
317
+ }
318
+
319
+ .contentWrapper {
320
+ display: flex;
321
+ flex-grow: 1;
322
+ flex-shrink: 1;
323
+ align-items: flex-start;
324
+
325
+ min-width: 0;
326
+ }
@@ -28,7 +28,7 @@ export function SearchItem({ search, itemRef, onNavKeyDown, onFocus, inTopBar }:
28
28
  const { firstItemId, size = 'm' } = useNewListContext();
29
29
 
30
30
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
31
- // Поиск — текстовый input: гасим только Up/Down (навигация по списку), горизонтальные
31
+ // Поиск — текстовый input: перехватываются только Up/Down (навигация по списку), горизонтальные
32
32
  // стрелки оставляем каретке.
33
33
  preventScrollOnVerticalArrows(e);
34
34
 
@@ -2,7 +2,7 @@
2
2
  * Отключает FLIP-анимацию перестановки после отпускания (общий контракт `SimpleItem` и `SimpleGroupBlock`).
3
3
  * Порядок применяется синхронно через `onItemsReorder`; без флага `@dnd-kit` доигрывает `transform 200ms`
4
4
  * из старого слота в новый — отпущенная строка дёргается обратно и уезжает на место. Расступание соседей
5
- * на время переноса флаг не трогает: его ведёт стратегия `SortableContext`.
5
+ * на время переноса флаг не изменяется: его ведёт стратегия `SortableContext`.
6
6
  */
7
7
  export function animateLayoutChanges(): boolean {
8
8
  return false;
@@ -24,6 +24,14 @@ $sizes: 's', 'm', 'l';
24
24
  // data-state переключается в BaseItem.tsx: emptyNeutralOnBackground ↔ activatedOnBackground.
25
25
  @include m.has-state-layer-as-child(#{stateLayer});
26
26
 
27
+ // Радиус дублируется здесь, а не задан только на `.listItem`: подложка state-layer
28
+ // отрисовывается на этом узле и без радиуса выходит за скругление строки.
29
+ @each $size in $sizes {
30
+ &[data-size='#{$size}'] {
31
+ border-radius: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'border-radius');
32
+ }
33
+ }
34
+
27
35
  &[data-disabled],
28
36
  &:disabled {
29
37
  cursor: not-allowed;
@@ -54,9 +62,9 @@ $sizes: 's', 'm', 'l';
54
62
  }
55
63
  }
56
64
 
57
- // Миксин has-state-layer-as-child гасит hover/active state-layer только для
65
+ // Миксин has-state-layer-as-child отключает hover/active state-layer только для
58
66
  // data-disabled/data-loading. data-inactive — list-специфичный «неинтерактивный» флаг,
59
- // миксин про него не знает → глушим state-layer и для него (перебиваем по специфичности).
67
+ // миксин про него не знает → state-layer отключается и для него (перебивается по специфичности).
60
68
  &[data-inactive] {
61
69
  &:hover,
62
70
  &:active {
@@ -40,7 +40,7 @@ export type BaseItem = WithSupportProps<{
40
40
  id?: ItemId;
41
41
  /** Флаг неактивности элемента */
42
42
  disabled?: boolean;
43
- /** Скрыть элемент из списка (не рендерится и выпадает из навигации) */
43
+ /** Скрыть элемент из списка (не рендерится и исключается из навигации) */
44
44
  hidden?: boolean;
45
45
  /** Ссылка на DOM-элемент айтема */
46
46
  itemRef?: RefObject<HTMLElement>;
@@ -49,7 +49,7 @@ export type BaseItem = WithSupportProps<{
49
49
  /**
50
50
  * Флаг отображения отключения реакции на любое css состояние (hover/focus и тд)
51
51
  * <br>
52
- * Так же элемент пропадает из навигации с клавиатуры, и не может быть выбран (selection)
52
+ * Также элемент исключается из навигации с клавиатуры и не может быть выбран (selection)
53
53
  */
54
54
  inactive?: boolean;
55
55
  /**
@@ -174,11 +174,7 @@ export type FlattenSimpleItem = FlattenBaseItem & CommonFlattenProps;
174
174
  export type FlattenItem =
175
175
  | FlattenBaseItem
176
176
  | ((
177
- | FlattenNextListItem
178
- | FlattenGroupListItem
179
- | FlattenAccordionItem
180
- | FlattenGroupSelectListItem
181
- | FlattenSimpleItem
177
+ FlattenNextListItem | FlattenGroupListItem | FlattenAccordionItem | FlattenGroupSelectListItem | FlattenSimpleItem
182
178
  ) &
183
179
  CommonFlattenProps);
184
180
 
@@ -134,7 +134,7 @@ export function kindFlattenItems({ items, prefix, parentId, sortable }: KindFlat
134
134
  key: itemId,
135
135
  originalId: itemId,
136
136
  id: autoId,
137
- // `inactive` элемент выпадает из клавиатурной навигации наравне с `disabled`
137
+ // `inactive` элемент исключается из клавиатурной навигации наравне с `disabled`
138
138
  // (см. JSDoc `BaseItem.inactive`). Гейтит только nav/bulk-select через
139
139
  // `extractActiveItems`; рендерный `disabled` в `flattenItems` остаётся исходным.
140
140
  disabled: item.disabled || item.inactive,
@@ -127,7 +127,7 @@ export const ListPrivate = forwardRef(
127
127
  useSensor(TouchSensor, {}),
128
128
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
129
129
  );
130
- // Id перетаскиваемой строки — для рендера её копии в `DragOverlay`. Копия живёт в портале
130
+ // Id перетаскиваемой строки — для рендера её копии в `DragOverlay`. Копия рендерится в портале
131
131
  // над страницей и не режется `overflow: hidden` контейнера `List` (сама строка на время
132
132
  // переноса уступает место — её слот пустеет). `null`, когда перетаскивание не идёт.
133
133
  const [activeDragId, setActiveDragId] = useState<ItemId | null>(null);
@@ -62,12 +62,13 @@ $loader-height: (
62
62
  gap: popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-headlineWrapper-gap;
63
63
 
64
64
  box-sizing: border-box;
65
- padding: 0 popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-headlineWrapper-paddingHorizontal
66
- popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-gap;
65
+ padding: 0 popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-headlineWrapper-paddingHorizontal;
67
66
 
67
+ // В мастере (popupDropdownHeader) шапка — вертикальный стек, в котором divider такой же
68
+ // элемент, как titleWrapper: `header-gap` отделяет заголовок от линии, а под линией отступа
69
+ // нет — контейнер dropdown размещает шапку и список без зазора.
68
70
  &[data-divider] {
69
- margin-bottom: popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-gap;
70
- padding-bottom: popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-headlineWrapper-paddingHorizontal;
71
+ padding-bottom: popupPrivate.$sn-popupPrivate-anatomy-dropdown-header-gap;
71
72
  border-bottom: base.$sn-primitive-strokeWeight-strokeRegular solid base.$sn-theme-color-available-borderColor;
72
73
  }
73
74
  }
@@ -54,7 +54,7 @@ type UseListItemsModelProps = {
54
54
  * Единая модель данных списка для `List` и `Droplist`: collapse-состояние,
55
55
  * плоские карты айтемов (`flattenItems` / `focusFlattenItems`) и производный
56
56
  * порядок навигации (`ids` / `expandedIds`). Оба компонента собирают одну и ту же
57
- * структуру, поэтому она живёт в одном хуке — расхождение между списком и дроплистом
57
+ * структуру, поэтому она собрана в одном хуке — расхождение между списком и дроплистом
58
58
  * физически невозможно.
59
59
  */
60
60
  export function useListItemsModel({
@@ -217,7 +217,7 @@ export function useNewKeyboardNavigation<T extends HTMLElement>({
217
217
 
218
218
  const handleListKeyDownFactory = useCallback(
219
219
  (ids: ItemId[], expandedIds: ItemId[]) => (e: KeyboardEvent<T>) => {
220
- // Стрелки навигации по списку гасим централизованно — иначе нативное поведение скроллит
220
+ // Стрелки навигации по списку перехватываются централизованно — иначе нативное поведение скроллит
221
221
  // страницу (элементы — кнопки/li, не текст, поэтому безопасны все четыре стрелки).
222
222
  preventScrollOnArrowKeys(e);
223
223
 
@@ -145,7 +145,7 @@ export function DesktopDroplist({
145
145
 
146
146
  const onKeyDown = useCallback(
147
147
  (e: KeyboardEvent<HTMLElement>, cb?: (e: KeyboardEvent<HTMLElement>) => void) => {
148
- // Up/Down открывают/закрывают дроплист — гасим их нативный скролл страницы. Только
148
+ // Up/Down открывают/закрывают дроплист — их нативный скролл страницы подавляется. Только
149
149
  // вертикальные: триггером может быть текстовое поле, где Left/Right двигают каретку.
150
150
  preventScrollOnVerticalArrows(e);
151
151
 
@@ -49,32 +49,34 @@ export function ItemContent({
49
49
 
50
50
  return (
51
51
  <div
52
- className={cn(styles.content, className)}
52
+ className={cn(styles.textWrapper, className)}
53
53
  {...extractSupportProps(rest)}
54
54
  data-size={size ?? 's'}
55
55
  data-disabled={disabled || undefined}
56
56
  >
57
- <div className={styles.headline}>
58
- <div className={styles.label}>
59
- <TruncateString
60
- variant={truncate.variant}
61
- text={String(label)}
62
- maxLines={truncate.label}
63
- data-test-id={TEST_IDS.baseItemLabel}
64
- />
57
+ <div className={styles.textContent}>
58
+ <div className={styles.headline}>
59
+ <div className={styles.label}>
60
+ <TruncateString
61
+ variant={truncate.variant}
62
+ text={String(label)}
63
+ maxLines={truncate.label}
64
+ data-test-id={TEST_IDS.baseItemLabel}
65
+ />
66
+ </div>
67
+ {caption && <span className={styles.caption}>{caption}</span>}
65
68
  </div>
66
- {caption && <span className={styles.caption}>{caption}</span>}
67
- </div>
68
69
 
69
- {description && (
70
- <div className={styles.description}>
71
- <TruncateString
72
- text={description}
73
- maxLines={truncate.description}
74
- data-test-id={TEST_IDS.baseItemDescription}
75
- />
76
- </div>
77
- )}
70
+ {description && (
71
+ <div className={styles.description}>
72
+ <TruncateString
73
+ text={description}
74
+ maxLines={truncate.description}
75
+ data-test-id={TEST_IDS.baseItemDescription}
76
+ />
77
+ </div>
78
+ )}
79
+ </div>
78
80
  </div>
79
81
  );
80
82
  }
@@ -48,10 +48,11 @@ $description-size-map: (
48
48
  color: base.$sn-theme-color-available-version-textSecondary;
49
49
  }
50
50
 
51
- .content {
51
+ // Структура повторяет мастер: contentWrapper → textWrapper (вертикальные поля) →
52
+ // textContent (горизонтальные поля и зазор headline ↔ description).
53
+ .textWrapper {
52
54
  overflow: hidden;
53
55
  display: flex;
54
- flex-direction: column;
55
56
  flex-grow: 1;
56
57
  flex-shrink: 1;
57
58
 
@@ -59,14 +60,15 @@ $description-size-map: (
59
60
 
60
61
  @each $size in $sizes {
61
62
  &[data-size='#{$size}'] {
62
-
63
- // textContent.gap вертикальный зазор headline description. s/m = 0, l = 2.
64
- gap: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'gap');
65
- // textWrapper: vertical padding вокруг строки контента, textContent: horizontal padding.
66
- padding: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'padding-top')
67
- base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'padding-right')
68
- base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'padding-bottom')
69
- base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'padding-left');
63
+ padding-top: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'padding-top');
64
+ padding-bottom: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'padding-bottom');
65
+
66
+ .textContent {
67
+ // textContent.gap вертикальный зазор headline description. s/m = 0, l = 2.
68
+ gap: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'gap');
69
+ padding-right: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'padding-right');
70
+ padding-left: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'padding-left');
71
+ }
70
72
 
71
73
  .headline {
72
74
  gap: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'contentWrapper', 'textWrapper', 'textContent', 'headline', 'gap');
@@ -90,11 +92,19 @@ $description-size-map: (
90
92
  }
91
93
  }
92
94
 
95
+ // В мастере выключенное состояние — общая opacity на contentWrapper, без смены цвета текста:
96
+ // так различие label/caption/description сохраняется и в disabled.
93
97
  &[data-disabled] {
94
- .label,
95
- .description,
96
- .caption {
97
- color: base.$sn-theme-color-available-version-textDisabled;
98
- }
98
+ opacity: base.$sn-theme-effect-opacity-disabled;
99
99
  }
100
100
  }
101
+
102
+ .textContent {
103
+ overflow: hidden;
104
+ display: flex;
105
+ flex-direction: column;
106
+ flex-grow: 1;
107
+ flex-shrink: 1;
108
+
109
+ box-sizing: border-box;
110
+ }
@@ -7,7 +7,6 @@ import { useCollapseLevelContext, useNewListContext } from '../../components/Lis
7
7
  import { TEST_IDS } from '../../constants';
8
8
  import { listLocale } from '../../locale';
9
9
  import { stopPropagation } from '../../utils';
10
- import { SELECT_BUTTON_SIZE_MAP } from './constants';
11
10
  import styles from './styles.module.scss';
12
11
 
13
12
  export type SeparatorProps = {
@@ -49,11 +48,11 @@ export function Separator({
49
48
  const { onClick, checked, itemRef, label } = selectButton;
50
49
 
51
50
  return (
52
- <span className={styles.selectButton} data-size={size} data-weight={(divider && groupVariant) || undefined}>
51
+ <span className={styles.selectButton} data-size={size}>
53
52
  <Button
54
53
  view='function'
55
54
  appearance='neutral'
56
- size={SELECT_BUTTON_SIZE_MAP[size]}
55
+ size={size}
57
56
  tabIndex={0}
58
57
  data-test-id={TEST_IDS.bulkSelectButton}
59
58
  onClick={(e: MouseEvent<HTMLElement>) => {
@@ -67,7 +66,7 @@ export function Separator({
67
66
  />
68
67
  </span>
69
68
  );
70
- }, [divider, groupVariant, selectButton, size, t]);
69
+ }, [selectButton, size, t]);
71
70
 
72
71
  if (label) {
73
72
  return (
@@ -78,27 +77,29 @@ export function Separator({
78
77
  data-level-more-one={level > 1 || undefined}
79
78
  style={{ '--level': level } as CSSProperties}
80
79
  >
81
- {beforeContent && (
82
- <span className={styles.beforeContent} aria-hidden>
83
- {beforeContent}
84
- </span>
85
- )}
86
-
87
- <span className={styles.label} data-group-variant={groupVariant}>
88
- <TruncateString variant={truncate?.variant} text={label} maxLines={1} />
89
- </span>
80
+ <div className={styles.contentWrapper}>
81
+ {beforeContent && (
82
+ <span className={styles.centeredWrapper} aria-hidden>
83
+ {beforeContent}
84
+ </span>
85
+ )}
90
86
 
91
- <div className={styles.labelEnd}>
92
- {selectButtonJSX}
87
+ <div className={styles.textWrapper}>
88
+ <div className={styles.textContent}>
89
+ <span className={styles.headline} data-group-variant={groupVariant}>
90
+ <TruncateString variant={truncate?.variant} text={label} maxLines={1} />
91
+ </span>
92
+ </div>
93
93
 
94
- {/* Figma weight-ось дивайдера = @cloud-ru/ds-divider `variant`: subtitle → regular, subtitleTertiary → thin (легаси light). */}
95
- {divider && (
96
- <Divider
97
- className={styles.divider}
98
- variant={groupVariant === 'subtitle' ? VARIANT.Regular : VARIANT.Thin}
99
- />
100
- )}
94
+ {selectButtonJSX}
95
+ </div>
101
96
  </div>
97
+
98
+ {/* Figma weight-ось дивайдера = @cloud-ru/ds-divider `variant`: subtitle → regular, subtitleTertiary → thin (легаси light).
99
+ Линия подчёркивает строку заголовка целиком, поэтому вынесена из потока. */}
100
+ {divider && (
101
+ <Divider className={styles.divider} variant={groupVariant === 'subtitle' ? VARIANT.Regular : VARIANT.Thin} />
102
+ )}
102
103
  </div>
103
104
  );
104
105
  }