@cloud-ru/ds-list 1.1.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/cjs/components/Items/AccordionItem/AccordionItem.js +3 -9
- package/dist/cjs/components/Items/BaseItem/BaseItem.d.ts +6 -6
- package/dist/cjs/components/Items/BaseItem/BaseItem.js +22 -21
- package/dist/cjs/components/Items/BaseItem/styles.module.css +4 -60
- package/dist/cjs/components/Items/NextListItem/NextListItem.js +1 -1
- package/dist/cjs/style.css +4 -60
- package/dist/esm/components/Items/AccordionItem/AccordionItem.js +3 -9
- package/dist/esm/components/Items/BaseItem/BaseItem.d.ts +6 -6
- package/dist/esm/components/Items/BaseItem/BaseItem.js +22 -21
- package/dist/esm/components/Items/BaseItem/styles.module.css +4 -60
- package/dist/esm/components/Items/NextListItem/NextListItem.js +1 -1
- package/dist/esm/style.css +4 -60
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/components/Items/AccordionItem/AccordionItem.tsx +4 -11
- package/src/components/Items/BaseItem/BaseItem.tsx +33 -45
- package/src/components/Items/BaseItem/styles.module.scss +11 -68
- package/src/components/Items/NextListItem/NextListItem.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloud-ru/ds-list",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Списочный UI — компонент List для плоских/вложенных списков с выбором, группами и поиском и Droplist — тот же список в поповере.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -51,18 +51,18 @@
|
|
|
51
51
|
"@cloud-ru/ds-adaptive": "^1.0.1",
|
|
52
52
|
"@cloud-ru/ds-bottom-sheet": "^1.0.2",
|
|
53
53
|
"@cloud-ru/ds-button": "^1.0.1",
|
|
54
|
+
"@cloud-ru/ds-divider": "^1.0.1",
|
|
54
55
|
"@cloud-ru/ds-drag-and-drop": "^1.1.1",
|
|
55
56
|
"@cloud-ru/ds-dropdown": "^1.0.2",
|
|
56
57
|
"@cloud-ru/ds-icons": "^1.0.1",
|
|
57
|
-
"@cloud-ru/ds-divider": "^1.0.1",
|
|
58
|
-
"@cloud-ru/ds-info-block": "^1.0.1",
|
|
59
58
|
"@cloud-ru/ds-loader": "^1.0.1",
|
|
59
|
+
"@cloud-ru/ds-info-block": "^1.0.1",
|
|
60
60
|
"@cloud-ru/ds-materials": "^1.0.1",
|
|
61
61
|
"@cloud-ru/ds-portal-context": "^1.0.1",
|
|
62
|
-
"@cloud-ru/ds-scroll": "^1.0.1",
|
|
63
62
|
"@cloud-ru/ds-search-private": "^1.0.2",
|
|
64
|
-
"@cloud-ru/ds-
|
|
63
|
+
"@cloud-ru/ds-scroll": "^1.0.1",
|
|
65
64
|
"@cloud-ru/ds-toggles": "^1.1.1",
|
|
65
|
+
"@cloud-ru/ds-truncate-string": "^1.0.2",
|
|
66
66
|
"@cloud-ru/ds-utils": "^1.1.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
@@ -39,20 +39,13 @@ export function AccordionItem({ id, disabled, allChildIds, items, ...option }: A
|
|
|
39
39
|
(isSelectionMultiple && checkedProp),
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// Раскрытие переключает вся строка целиком: клик/Enter по телу и ArrowRight с клавиатуры.
|
|
43
|
+
const handleToggle = useCallback(() => {
|
|
43
44
|
toggleOpenCollapseItem?.(id ?? '');
|
|
44
45
|
}, [id, toggleOpenCollapseItem]);
|
|
45
46
|
|
|
46
47
|
const itemsJSX = useRenderItems(items);
|
|
47
48
|
|
|
48
|
-
// Раскрытие привязано к явной кнопке-триггеру `groupIndicator` (шеврон справа) и
|
|
49
|
-
// ТОЛЬКО переключает collapse — `option.onClick` сюда не зовём, иначе у потребителя,
|
|
50
|
-
// который вешает на onClick собственный toggle, выходит двойное переключение.
|
|
51
|
-
// Клик по телу строки (выбор/навигация) идёт через `option.onClick` в BaseItem.
|
|
52
|
-
const handleExpandIconClick = () => {
|
|
53
|
-
toggleOpenCollapseItem?.(id ?? '');
|
|
54
|
-
};
|
|
55
|
-
|
|
56
49
|
return (
|
|
57
50
|
<CollapseBlockPrivate
|
|
58
51
|
header={
|
|
@@ -62,9 +55,9 @@ export function AccordionItem({ id, disabled, allChildIds, items, ...option }: A
|
|
|
62
55
|
disabled={disabled}
|
|
63
56
|
open={isOpen}
|
|
64
57
|
expandIcon={isOpen ? <ChevronUpSVG /> : <ChevronDownSVG />}
|
|
65
|
-
|
|
58
|
+
onToggleExpand={handleToggle}
|
|
66
59
|
isParentNode
|
|
67
|
-
onOpenNestedList={
|
|
60
|
+
onOpenNestedList={handleToggle}
|
|
68
61
|
checked={checked}
|
|
69
62
|
indeterminate={indeterminate}
|
|
70
63
|
onSelect={!disabled ? handleOnSelect : undefined}
|
|
@@ -25,11 +25,11 @@ type AllBaseItemProps = FlattenBaseItem & {
|
|
|
25
25
|
isParentNode?: boolean;
|
|
26
26
|
onOpenNestedList?(e?: KeyboardEvent<HTMLElement>): void;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Переключение раскрытия вложенного списка. Триггер — вся строка целиком (клик или
|
|
29
|
+
* Enter/Space), шеврон `groupIndicator` при этом остаётся неинтерактивным индикатором
|
|
30
|
+
* состояния.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
onToggleExpand?(): void;
|
|
33
33
|
/**
|
|
34
34
|
* Слот ручки drag&drop (Figma `centeredWrapper`) — рендерится первым в строке, перед
|
|
35
35
|
* маркером/чекбоксом. Интерактивность (обработчики `@dnd-kit`) и содержимое (иконка)
|
|
@@ -57,7 +57,7 @@ export function BaseItem({
|
|
|
57
57
|
checked: checkedProp,
|
|
58
58
|
onSelect,
|
|
59
59
|
onOpenNestedList,
|
|
60
|
-
|
|
60
|
+
onToggleExpand,
|
|
61
61
|
isParentNode,
|
|
62
62
|
className,
|
|
63
63
|
inactive,
|
|
@@ -107,7 +107,11 @@ export function BaseItem({
|
|
|
107
107
|
// пункту меню), поэтому keyboard-событие пробрасывается под тем же типом.
|
|
108
108
|
onClick?.(e as MouseEvent<HTMLElement>);
|
|
109
109
|
|
|
110
|
-
if (
|
|
110
|
+
if (isParentNode) {
|
|
111
|
+
// Раскрытие вложенного списка триггерит вся строка целиком: шеврон `groupIndicator` —
|
|
112
|
+
// только индикатор состояния, собственной интерактивности у него нет.
|
|
113
|
+
onToggleExpand?.();
|
|
114
|
+
} else if (interactive) {
|
|
111
115
|
handleChange();
|
|
112
116
|
}
|
|
113
117
|
|
|
@@ -129,15 +133,20 @@ export function BaseItem({
|
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
132
|
-
if (
|
|
133
|
-
|
|
136
|
+
if (isParentNode) {
|
|
137
|
+
// В multiple-режиме Enter/Space на группе выбирает всю ветку, иначе — раскрывает её.
|
|
138
|
+
if (isSelectionMultiple && onSelect) {
|
|
139
|
+
onSelect();
|
|
140
|
+
} else {
|
|
141
|
+
onToggleExpand?.();
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// Повторяем клик по элементу: `handleItemClick` сам вызывает `handleChange` /
|
|
145
|
+
// `closeDroplist`. Отдельный `handleChange` здесь не нужен — иначе в multiple-режиме
|
|
146
|
+
// выбор переключался бы дважды (toggle + откат) и Enter не менял бы состояние.
|
|
147
|
+
handleItemClick(e);
|
|
134
148
|
}
|
|
135
149
|
|
|
136
|
-
// Повторяем клик по элементу: `handleItemClick` сам вызывает `handleChange` /
|
|
137
|
-
// `closeDroplist`. Отдельный `handleChange` здесь не нужен — иначе в multiple-режиме
|
|
138
|
-
// выбор переключался бы дважды (toggle + откат) и Enter не менял бы состояние.
|
|
139
|
-
!isParentNode && handleItemClick(e);
|
|
140
|
-
|
|
141
150
|
e.stopPropagation();
|
|
142
151
|
e.preventDefault();
|
|
143
152
|
}
|
|
@@ -149,18 +158,6 @@ export function BaseItem({
|
|
|
149
158
|
}
|
|
150
159
|
};
|
|
151
160
|
|
|
152
|
-
const handleExpandIconClick = (e: MouseEvent<HTMLElement>) => {
|
|
153
|
-
if (disabled) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Триггер раскрытия изолирован от клика по строке: всплытие останавливаем,
|
|
158
|
-
// чтобы клик по шеврону не активировал выбор/навигацию пункта.
|
|
159
|
-
e.stopPropagation();
|
|
160
|
-
e.preventDefault();
|
|
161
|
-
onExpandIconClick?.(e);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
161
|
const props = extractSupportProps(rest);
|
|
165
162
|
|
|
166
163
|
let contentNode: ReactNode;
|
|
@@ -201,6 +198,7 @@ export function BaseItem({
|
|
|
201
198
|
data-non-pointer={(inactive && !onClick) || undefined}
|
|
202
199
|
data-variant={mode || undefined}
|
|
203
200
|
data-open={open || undefined}
|
|
201
|
+
aria-expanded={isParentNode ? Boolean(open) : undefined}
|
|
204
202
|
onKeyDown={handleItemKeyDown}
|
|
205
203
|
onFocus={handleItemFocus}
|
|
206
204
|
style={{ '--level': level }}
|
|
@@ -243,26 +241,16 @@ export function BaseItem({
|
|
|
243
241
|
/>
|
|
244
242
|
)}
|
|
245
243
|
|
|
246
|
-
{!switchProp &&
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
disabled={disabled}
|
|
257
|
-
onClick={handleExpandIconClick}
|
|
258
|
-
onMouseDown={e => e.stopPropagation()}
|
|
259
|
-
>
|
|
260
|
-
<span className={styles.groupIndicatorState} aria-hidden />
|
|
261
|
-
{expandIcon}
|
|
262
|
-
</button>
|
|
263
|
-
) : (
|
|
264
|
-
<span className={styles.expandableIcon}>{expandIcon}</span>
|
|
265
|
-
))}
|
|
244
|
+
{!switchProp && expandIcon && (
|
|
245
|
+
<span
|
|
246
|
+
className={styles.groupIndicator}
|
|
247
|
+
data-open={open || undefined}
|
|
248
|
+
data-test-id={TEST_IDS.groupIndicator}
|
|
249
|
+
aria-hidden
|
|
250
|
+
>
|
|
251
|
+
{expandIcon}
|
|
252
|
+
</span>
|
|
253
|
+
)}
|
|
266
254
|
</li>
|
|
267
255
|
</div>
|
|
268
256
|
);
|
|
@@ -80,82 +80,35 @@ $sizes: 's', 'm', 'l';
|
|
|
80
80
|
color: base.$sn-theme-color-available-version-textSecondary;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
.expandableIcon {
|
|
84
|
-
display: flex;
|
|
85
|
-
align-items: center;
|
|
86
|
-
color: base.$sn-theme-color-available-version-textSecondary;
|
|
87
|
-
fill: currentcolor;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
83
|
.groupIndicator {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
// Шеврон — индикатор состояния, а не кнопка: раскрытие переключает клик по всей строке.
|
|
85
|
+
// Своих hover/pressed/focus-состояний у него нет.
|
|
86
|
+
pointer-events: none;
|
|
94
87
|
|
|
95
88
|
display: flex;
|
|
96
89
|
flex-shrink: 0;
|
|
97
90
|
align-items: center;
|
|
98
91
|
justify-content: center;
|
|
99
92
|
|
|
100
|
-
padding: 0;
|
|
101
|
-
border: none;
|
|
102
|
-
|
|
103
|
-
// Token bridge: list/groupIndicator/cornerRadius (8) ещё не выгружен scss-композитом
|
|
104
|
-
// из @cloud-ru/ds-figma-variables — берём примитив dimension-8 напрямую.
|
|
105
|
-
border-radius: base.$sn-primitive-dimension-8;
|
|
106
|
-
|
|
107
93
|
// Закрытым шеврон в макете приглушён (@0.80), сплошной `textMain` — только раскрытым.
|
|
108
94
|
color: base.$sn-theme-color-available-version-textSecondary;
|
|
109
95
|
|
|
110
|
-
|
|
111
|
-
background: transparent;
|
|
112
|
-
outline: none;
|
|
96
|
+
fill: currentcolor;
|
|
113
97
|
|
|
114
98
|
svg {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
width: base.$sn-density-size-icon-xs;
|
|
118
|
-
height: base.$sn-density-size-icon-xs;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
&:focus-visible {
|
|
122
|
-
outline: base.$sn-theme-color-available-complementary solid base.$sn-primitive-strokeWeight-strokeSemiBold;
|
|
123
|
-
outline-offset: calc(0px - base.$sn-primitive-strokeWeight-strokeSemiBold);
|
|
99
|
+
width: 100%;
|
|
100
|
+
height: 100%;
|
|
124
101
|
}
|
|
125
102
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
103
|
+
// Раскрытая группа держит постоянную подложку `neutral/decorTransparent`
|
|
104
|
+
// (варианты мастера `groupIndicatorS/Xs`, `open=true`).
|
|
128
105
|
&[data-open] {
|
|
129
106
|
border-radius: base.$sn-primitive-dimension-2;
|
|
130
107
|
color: base.$sn-theme-color-available-version-textMain;
|
|
108
|
+
background: base.$sn-theme-color-neutral-decorTransparent;
|
|
131
109
|
}
|
|
132
110
|
}
|
|
133
111
|
|
|
134
|
-
.groupIndicatorState {
|
|
135
|
-
pointer-events: none;
|
|
136
|
-
|
|
137
|
-
position: absolute;
|
|
138
|
-
inset: 0;
|
|
139
|
-
|
|
140
|
-
border-radius: inherit;
|
|
141
|
-
|
|
142
|
-
background: transparent;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.groupIndicator:hover > .groupIndicatorState {
|
|
146
|
-
background: base.$sn-theme-color-material-stateLayer-neutral-onBackground-hovered-filled;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.groupIndicator:active > .groupIndicatorState {
|
|
150
|
-
background: base.$sn-theme-color-material-stateLayer-neutral-onBackground-pressed-filled;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Раскрытая группа держит постоянную подложку `neutral/decorTransparent`,
|
|
154
|
-
// а не pressed-слой состояния.
|
|
155
|
-
.groupIndicator[data-open] > .groupIndicatorState {
|
|
156
|
-
background: base.$sn-theme-color-neutral-decorTransparent;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
112
|
.markerContainer {
|
|
160
113
|
pointer-events: none;
|
|
161
114
|
|
|
@@ -234,17 +187,12 @@ $level: var(--level, 0);
|
|
|
234
187
|
padding-bottom: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'centeredWrapper', 'padding-bottom');
|
|
235
188
|
}
|
|
236
189
|
|
|
237
|
-
.expandableIcon {
|
|
238
|
-
width: base.simple-var(icons.$iconSizeMap, $size);
|
|
239
|
-
height: base.simple-var(icons.$iconSizeMap, $size);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
190
|
.groupIndicator {
|
|
243
191
|
width: base.simple-var(icons.$iconSizeMap, $size);
|
|
244
192
|
height: base.simple-var(icons.$iconSizeMap, $size);
|
|
245
|
-
// Строка выровнена по flex-start; centeredWrapper.padding-top сдвигает
|
|
193
|
+
// Строка выровнена по flex-start; centeredWrapper.padding-top сдвигает шеврон на центр
|
|
246
194
|
// первой строки текста (как у afterContent/beforeContent). margin (а не padding) —
|
|
247
|
-
// чтобы
|
|
195
|
+
// чтобы подложка раскрытого состояния осталась icon-сеткой, а не растянулась по вертикали.
|
|
248
196
|
margin-top: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'centeredWrapper', 'padding-top');
|
|
249
197
|
}
|
|
250
198
|
|
|
@@ -280,12 +228,7 @@ $level: var(--level, 0);
|
|
|
280
228
|
|
|
281
229
|
.innerWrapper {
|
|
282
230
|
&[data-disabled] {
|
|
283
|
-
.expandableIcon {
|
|
284
|
-
color: base.$sn-theme-color-available-version-textDisabled;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
231
|
.groupIndicator {
|
|
288
|
-
cursor: not-allowed;
|
|
289
232
|
color: base.$sn-theme-color-available-version-textDisabled;
|
|
290
233
|
}
|
|
291
234
|
|