@cloud-ru/ds-list 1.1.4-preview-5ffe9ec9.0 → 1.1.4
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 +4 -0
- package/dist/cjs/components/Items/AccordionItem/AccordionItem.js +9 -3
- package/dist/cjs/components/Items/BaseItem/BaseItem.d.ts +6 -6
- package/dist/cjs/components/Items/BaseItem/BaseItem.js +21 -22
- package/dist/cjs/components/Items/BaseItem/styles.module.css +60 -4
- package/dist/cjs/components/Items/NextListItem/NextListItem.js +1 -1
- package/dist/cjs/style.css +60 -4
- package/dist/esm/components/Items/AccordionItem/AccordionItem.js +9 -3
- package/dist/esm/components/Items/BaseItem/BaseItem.d.ts +6 -6
- package/dist/esm/components/Items/BaseItem/BaseItem.js +21 -22
- package/dist/esm/components/Items/BaseItem/styles.module.css +60 -4
- package/dist/esm/components/Items/NextListItem/NextListItem.js +1 -1
- package/dist/esm/style.css +60 -4
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +18 -18
- package/src/components/Items/AccordionItem/AccordionItem.tsx +11 -4
- package/src/components/Items/BaseItem/BaseItem.tsx +45 -33
- package/src/components/Items/BaseItem/styles.module.scss +68 -11
- 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": "1.1.4
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Списочный UI — компонент List для плоских/вложенных списков с выбором, группами и поиском и Droplist — тот же список в поповере.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,24 +48,24 @@
|
|
|
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-
|
|
52
|
-
"@cloud-ru/ds-
|
|
53
|
-
"@cloud-ru/ds-button": "1.0.1",
|
|
54
|
-
"@cloud-ru/ds-
|
|
55
|
-
"@cloud-ru/ds-
|
|
56
|
-
"@cloud-ru/ds-
|
|
57
|
-
"@cloud-ru/ds-
|
|
58
|
-
"@cloud-ru/ds-
|
|
59
|
-
"@cloud-ru/ds-
|
|
60
|
-
"@cloud-ru/ds-
|
|
61
|
-
"@cloud-ru/ds-
|
|
62
|
-
"@cloud-ru/ds-
|
|
63
|
-
"@cloud-ru/ds-
|
|
64
|
-
"@cloud-ru/ds-
|
|
65
|
-
"@cloud-ru/ds-
|
|
66
|
-
"@cloud-ru/ds-utils": "1.1.0"
|
|
51
|
+
"@cloud-ru/ds-adaptive": "^1.0.1",
|
|
52
|
+
"@cloud-ru/ds-bottom-sheet": "^1.0.2",
|
|
53
|
+
"@cloud-ru/ds-button": "^1.0.1",
|
|
54
|
+
"@cloud-ru/ds-drag-and-drop": "^1.1.1",
|
|
55
|
+
"@cloud-ru/ds-dropdown": "^1.0.2",
|
|
56
|
+
"@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
|
+
"@cloud-ru/ds-loader": "^1.0.1",
|
|
60
|
+
"@cloud-ru/ds-materials": "^1.0.1",
|
|
61
|
+
"@cloud-ru/ds-portal-context": "^1.0.1",
|
|
62
|
+
"@cloud-ru/ds-scroll": "^1.0.1",
|
|
63
|
+
"@cloud-ru/ds-search-private": "^1.0.2",
|
|
64
|
+
"@cloud-ru/ds-truncate-string": "^1.0.2",
|
|
65
|
+
"@cloud-ru/ds-toggles": "^1.1.1",
|
|
66
|
+
"@cloud-ru/ds-utils": "^1.1.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@cloud-ru/ds-locale": "1.0.0"
|
|
69
|
+
"@cloud-ru/ds-locale": "^1.0.0"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -39,13 +39,20 @@ export function AccordionItem({ id, disabled, allChildIds, items, ...option }: A
|
|
|
39
39
|
(isSelectionMultiple && checkedProp),
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
const handleToggle = useCallback(() => {
|
|
42
|
+
const handleKeyDown = useCallback(() => {
|
|
44
43
|
toggleOpenCollapseItem?.(id ?? '');
|
|
45
44
|
}, [id, toggleOpenCollapseItem]);
|
|
46
45
|
|
|
47
46
|
const itemsJSX = useRenderItems(items);
|
|
48
47
|
|
|
48
|
+
// Раскрытие привязано к явной кнопке-триггеру `groupIndicator` (шеврон справа) и
|
|
49
|
+
// ТОЛЬКО переключает collapse — `option.onClick` сюда не зовём, иначе у потребителя,
|
|
50
|
+
// который вешает на onClick собственный toggle, выходит двойное переключение.
|
|
51
|
+
// Клик по телу строки (выбор/навигация) идёт через `option.onClick` в BaseItem.
|
|
52
|
+
const handleExpandIconClick = () => {
|
|
53
|
+
toggleOpenCollapseItem?.(id ?? '');
|
|
54
|
+
};
|
|
55
|
+
|
|
49
56
|
return (
|
|
50
57
|
<CollapseBlockPrivate
|
|
51
58
|
header={
|
|
@@ -55,9 +62,9 @@ export function AccordionItem({ id, disabled, allChildIds, items, ...option }: A
|
|
|
55
62
|
disabled={disabled}
|
|
56
63
|
open={isOpen}
|
|
57
64
|
expandIcon={isOpen ? <ChevronUpSVG /> : <ChevronDownSVG />}
|
|
58
|
-
|
|
65
|
+
onExpandIconClick={handleExpandIconClick}
|
|
59
66
|
isParentNode
|
|
60
|
-
onOpenNestedList={
|
|
67
|
+
onOpenNestedList={handleKeyDown}
|
|
61
68
|
checked={checked}
|
|
62
69
|
indeterminate={indeterminate}
|
|
63
70
|
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
|
+
* Клик по явной кнопке-триггеру `groupIndicator` (шеврон справа), открывающей/закрывающей
|
|
29
|
+
* вложенный список. Если передан — шеврон рендерится как отдельная интерактивная кнопка
|
|
30
|
+
* со своим state-layer'ом, а клик по телу строки больше не переключает раскрытие.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
onExpandIconClick?(e: MouseEvent<HTMLElement>): 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
|
+
onExpandIconClick,
|
|
61
61
|
isParentNode,
|
|
62
62
|
className,
|
|
63
63
|
inactive,
|
|
@@ -107,11 +107,7 @@ export function BaseItem({
|
|
|
107
107
|
// пункту меню), поэтому keyboard-событие пробрасывается под тем же типом.
|
|
108
108
|
onClick?.(e as MouseEvent<HTMLElement>);
|
|
109
109
|
|
|
110
|
-
if (isParentNode) {
|
|
111
|
-
// Раскрытие вложенного списка триггерит вся строка целиком: шеврон `groupIndicator` —
|
|
112
|
-
// только индикатор состояния, собственной интерактивности у него нет.
|
|
113
|
-
onToggleExpand?.();
|
|
114
|
-
} else if (interactive) {
|
|
110
|
+
if (interactive && !isParentNode) {
|
|
115
111
|
handleChange();
|
|
116
112
|
}
|
|
117
113
|
|
|
@@ -133,20 +129,15 @@ export function BaseItem({
|
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
136
|
-
if (isParentNode) {
|
|
137
|
-
|
|
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);
|
|
132
|
+
if (isSelectionMultiple && isParentNode && onSelect) {
|
|
133
|
+
onSelect();
|
|
148
134
|
}
|
|
149
135
|
|
|
136
|
+
// Повторяем клик по элементу: `handleItemClick` сам вызывает `handleChange` /
|
|
137
|
+
// `closeDroplist`. Отдельный `handleChange` здесь не нужен — иначе в multiple-режиме
|
|
138
|
+
// выбор переключался бы дважды (toggle + откат) и Enter не менял бы состояние.
|
|
139
|
+
!isParentNode && handleItemClick(e);
|
|
140
|
+
|
|
150
141
|
e.stopPropagation();
|
|
151
142
|
e.preventDefault();
|
|
152
143
|
}
|
|
@@ -158,6 +149,18 @@ export function BaseItem({
|
|
|
158
149
|
}
|
|
159
150
|
};
|
|
160
151
|
|
|
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
|
+
|
|
161
164
|
const props = extractSupportProps(rest);
|
|
162
165
|
|
|
163
166
|
let contentNode: ReactNode;
|
|
@@ -198,7 +201,6 @@ export function BaseItem({
|
|
|
198
201
|
data-non-pointer={(inactive && !onClick) || undefined}
|
|
199
202
|
data-variant={mode || undefined}
|
|
200
203
|
data-open={open || undefined}
|
|
201
|
-
aria-expanded={isParentNode ? Boolean(open) : undefined}
|
|
202
204
|
onKeyDown={handleItemKeyDown}
|
|
203
205
|
onFocus={handleItemFocus}
|
|
204
206
|
style={{ '--level': level }}
|
|
@@ -241,16 +243,26 @@ export function BaseItem({
|
|
|
241
243
|
/>
|
|
242
244
|
)}
|
|
243
245
|
|
|
244
|
-
{!switchProp &&
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
{!switchProp &&
|
|
247
|
+
expandIcon &&
|
|
248
|
+
(onExpandIconClick ? (
|
|
249
|
+
<button
|
|
250
|
+
type='button'
|
|
251
|
+
className={styles.groupIndicator}
|
|
252
|
+
data-size={size}
|
|
253
|
+
data-open={open || undefined}
|
|
254
|
+
data-test-id={TEST_IDS.groupIndicator}
|
|
255
|
+
tabIndex={-1}
|
|
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
|
+
))}
|
|
254
266
|
</li>
|
|
255
267
|
</div>
|
|
256
268
|
);
|
|
@@ -80,35 +80,82 @@ $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
|
+
|
|
83
90
|
.groupIndicator {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
|
|
93
|
+
position: relative;
|
|
87
94
|
|
|
88
95
|
display: flex;
|
|
89
96
|
flex-shrink: 0;
|
|
90
97
|
align-items: center;
|
|
91
98
|
justify-content: center;
|
|
92
99
|
|
|
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
|
+
|
|
93
107
|
// Закрытым шеврон в макете приглушён (@0.80), сплошной `textMain` — только раскрытым.
|
|
94
108
|
color: base.$sn-theme-color-available-version-textSecondary;
|
|
95
109
|
|
|
96
|
-
|
|
110
|
+
appearance: none;
|
|
111
|
+
background: transparent;
|
|
112
|
+
outline: none;
|
|
97
113
|
|
|
98
114
|
svg {
|
|
99
|
-
|
|
100
|
-
|
|
115
|
+
position: relative;
|
|
116
|
+
z-index: 1;
|
|
117
|
+
width: base.$sn-density-size-icon-xs;
|
|
118
|
+
height: base.$sn-density-size-icon-xs;
|
|
101
119
|
}
|
|
102
120
|
|
|
103
|
-
|
|
104
|
-
|
|
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);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// В раскрытом состоянии мастер меняет скругление с 8 на 2 — кнопка становится
|
|
127
|
+
// «квадратнее» под подложкой нажатия (варианты `groupIndicatorS/Xs`, `open=true`).
|
|
105
128
|
&[data-open] {
|
|
106
129
|
border-radius: base.$sn-primitive-dimension-2;
|
|
107
130
|
color: base.$sn-theme-color-available-version-textMain;
|
|
108
|
-
background: base.$sn-theme-color-neutral-decorTransparent;
|
|
109
131
|
}
|
|
110
132
|
}
|
|
111
133
|
|
|
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
|
+
|
|
112
159
|
.markerContainer {
|
|
113
160
|
pointer-events: none;
|
|
114
161
|
|
|
@@ -187,12 +234,17 @@ $level: var(--level, 0);
|
|
|
187
234
|
padding-bottom: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'centeredWrapper', 'padding-bottom');
|
|
188
235
|
}
|
|
189
236
|
|
|
237
|
+
.expandableIcon {
|
|
238
|
+
width: base.simple-var(icons.$iconSizeMap, $size);
|
|
239
|
+
height: base.simple-var(icons.$iconSizeMap, $size);
|
|
240
|
+
}
|
|
241
|
+
|
|
190
242
|
.groupIndicator {
|
|
191
243
|
width: base.simple-var(icons.$iconSizeMap, $size);
|
|
192
244
|
height: base.simple-var(icons.$iconSizeMap, $size);
|
|
193
|
-
// Строка выровнена по flex-start; centeredWrapper.padding-top сдвигает
|
|
245
|
+
// Строка выровнена по flex-start; centeredWrapper.padding-top сдвигает чеврон на центр
|
|
194
246
|
// первой строки текста (как у afterContent/beforeContent). margin (а не padding) —
|
|
195
|
-
// чтобы
|
|
247
|
+
// чтобы квадрат state-layer'а остался icon-сеткой, а не растянулся по вертикали.
|
|
196
248
|
margin-top: base.simple-var(list.$list, 'anatomy', 'size', $size, 'listItem', 'centeredWrapper', 'padding-top');
|
|
197
249
|
}
|
|
198
250
|
|
|
@@ -228,7 +280,12 @@ $level: var(--level, 0);
|
|
|
228
280
|
|
|
229
281
|
.innerWrapper {
|
|
230
282
|
&[data-disabled] {
|
|
283
|
+
.expandableIcon {
|
|
284
|
+
color: base.$sn-theme-color-available-version-textDisabled;
|
|
285
|
+
}
|
|
286
|
+
|
|
231
287
|
.groupIndicator {
|
|
288
|
+
cursor: not-allowed;
|
|
232
289
|
color: base.$sn-theme-color-available-version-textDisabled;
|
|
233
290
|
}
|
|
234
291
|
|