@cloud-ru/ds-accordion 1.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 +8 -0
- package/LICENSE +201 -0
- package/README.md +484 -0
- package/dist/cjs/Accordion.d.ts +160 -0
- package/dist/cjs/Accordion.js +21 -0
- package/dist/cjs/components/CollapseBlock/CollapseBlock.d.ts +45 -0
- package/dist/cjs/components/CollapseBlock/CollapseBlock.js +47 -0
- package/dist/cjs/components/CollapseBlock/hooks.d.ts +12 -0
- package/dist/cjs/components/CollapseBlock/hooks.js +43 -0
- package/dist/cjs/components/CollapseBlock/index.d.ts +1 -0
- package/dist/cjs/components/CollapseBlock/index.js +17 -0
- package/dist/cjs/components/CollapseBlock/styles.module.css +480 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.js +17 -0
- package/dist/cjs/constants.d.ts +22 -0
- package/dist/cjs/constants.js +25 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +480 -0
- package/dist/cjs/types.d.ts +5 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/Accordion.d.ts +160 -0
- package/dist/esm/Accordion.js +18 -0
- package/dist/esm/components/CollapseBlock/CollapseBlock.d.ts +45 -0
- package/dist/esm/components/CollapseBlock/CollapseBlock.js +40 -0
- package/dist/esm/components/CollapseBlock/hooks.d.ts +12 -0
- package/dist/esm/components/CollapseBlock/hooks.js +40 -0
- package/dist/esm/components/CollapseBlock/index.d.ts +1 -0
- package/dist/esm/components/CollapseBlock/index.js +1 -0
- package/dist/esm/components/CollapseBlock/styles.module.css +480 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/constants.d.ts +22 -0
- package/dist/esm/constants.js +22 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/style.css +480 -0
- package/dist/esm/types.d.ts +5 -0
- package/dist/esm/types.js +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +49 -0
- package/src/Accordion.tsx +51 -0
- package/src/components/CollapseBlock/CollapseBlock.tsx +194 -0
- package/src/components/CollapseBlock/hooks.ts +49 -0
- package/src/components/CollapseBlock/index.ts +1 -0
- package/src/components/CollapseBlock/styles.module.scss +207 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +26 -0
- package/src/index.ts +4 -0
- package/src/types.ts +9 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useToggleGroup } from '@cloud-ru/ds-toggles';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { ANIMATION_DURATION } from '../../constants';
|
|
5
|
+
|
|
6
|
+
type UseCollapseStateProps = {
|
|
7
|
+
id: string;
|
|
8
|
+
keepMounted?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function useCollapseState({ id, keepMounted = false }: UseCollapseStateProps) {
|
|
12
|
+
const { isChecked: isOpen, handleClick: toggleOpen } = useToggleGroup({ value: id });
|
|
13
|
+
// Контент доживает до конца закрывающей анимации, но при открытии появляется синхронно с
|
|
14
|
+
// `isOpen` — если монтировать его эффектом, первый кадр раскрытия считается по пустому телу
|
|
15
|
+
// и высота стартует рывком.
|
|
16
|
+
const [keepMountedWhileClosing, setKeepMountedWhileClosing] = useState(false);
|
|
17
|
+
const isMounted = isOpen || keepMounted || keepMountedWhileClosing;
|
|
18
|
+
const [isCompletelyOpen, setIsCompletelyOpen] = useState(isOpen);
|
|
19
|
+
const [isCompletelyClose, setIsCompletelyClose] = useState(!isOpen);
|
|
20
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isOpen) {
|
|
24
|
+
setKeepMountedWhileClosing(true);
|
|
25
|
+
setIsCompletelyClose(false);
|
|
26
|
+
timeoutRef.current = setTimeout(() => {
|
|
27
|
+
setIsCompletelyOpen(true);
|
|
28
|
+
}, ANIMATION_DURATION);
|
|
29
|
+
} else {
|
|
30
|
+
setIsCompletelyOpen(false);
|
|
31
|
+
timeoutRef.current = setTimeout(() => {
|
|
32
|
+
setIsCompletelyClose(true);
|
|
33
|
+
setKeepMountedWhileClosing(false);
|
|
34
|
+
}, ANIMATION_DURATION);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return () => {
|
|
38
|
+
timeoutRef.current && clearTimeout(timeoutRef.current);
|
|
39
|
+
};
|
|
40
|
+
}, [isOpen, keepMounted]);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
isOpen,
|
|
44
|
+
isMounted,
|
|
45
|
+
toggleOpen,
|
|
46
|
+
isCompletelyOpen,
|
|
47
|
+
isCompletelyClose,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CollapseBlock';
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
|
|
2
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/accordion.module' as accordion;
|
|
3
|
+
@use '@cloud-ru/ds-materials' as m;
|
|
4
|
+
|
|
5
|
+
$components: 'accordionPrimary', 'accordionSecondary', 'accordionTertiary';
|
|
6
|
+
|
|
7
|
+
.acrylic {
|
|
8
|
+
pointer-events: none;
|
|
9
|
+
position: absolute;
|
|
10
|
+
inset: 0;
|
|
11
|
+
border-radius: inherit;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.stateLayer {
|
|
15
|
+
visibility: initial;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.wrapper {
|
|
19
|
+
@each $component in $components {
|
|
20
|
+
&[data-component='#{$component}'] {
|
|
21
|
+
@include base.composite-var(accordion.$accordion, 'anatomy', $component, 'container');
|
|
22
|
+
|
|
23
|
+
// Нижний отступ карточки одинаков в закрытом и открытом состоянии (в Figma закрытому его
|
|
24
|
+
// даёт titleContent, открытому — bodyContent, и это один и тот же токен). Держим его на
|
|
25
|
+
// обёртке: если переключать между заголовком и телом, два padding'а анимируются навстречу
|
|
26
|
+
// друг другу и суммарная высота блока идёт не монотонно — на старте проседает, в конце прыгает.
|
|
27
|
+
padding-bottom: base.simple-var(
|
|
28
|
+
accordion.$accordion,
|
|
29
|
+
'anatomy',
|
|
30
|
+
$component,
|
|
31
|
+
'container',
|
|
32
|
+
'bodyContent',
|
|
33
|
+
'padding-bottom'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
position: relative;
|
|
39
|
+
display: block;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
align-self: stretch;
|
|
42
|
+
|
|
43
|
+
&[data-component='accordionPrimary'],
|
|
44
|
+
&[data-component='accordionSecondary'] {
|
|
45
|
+
@include m.with-material('acrylic', #{acrylic});
|
|
46
|
+
|
|
47
|
+
&[data-view='elevated'] {
|
|
48
|
+
box-shadow: base.$sn-boxShadow-elevation-level2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&[data-view='simple'],
|
|
52
|
+
&[data-view='elevated'] {
|
|
53
|
+
&:hover,
|
|
54
|
+
&:focus-within {
|
|
55
|
+
box-shadow: base.$sn-boxShadow-elevation-level3;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&[data-view='outline'] {
|
|
60
|
+
@include m.has-state-layer-as-child(#{stateLayer});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.titleWrapper {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
|
|
70
|
+
width: 100%;
|
|
71
|
+
min-width: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Цвет заголовка (типографику даёт Typography variant='title').
|
|
75
|
+
.title {
|
|
76
|
+
color: base.$sn-theme-color-available-version-textMain;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Подзаголовок — обычный div: задаём и типографику body-m, и цвет.
|
|
80
|
+
.subTitle {
|
|
81
|
+
@include base.composite-var(base.$sn-regular, 'body', 'm');
|
|
82
|
+
|
|
83
|
+
color: base.$sn-theme-color-available-version-textSecondary;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.titleContent {
|
|
87
|
+
@each $component in $components {
|
|
88
|
+
[data-component='#{$component}'] & {
|
|
89
|
+
@include base.composite-var(accordion.$accordion, 'anatomy', $component, 'container', 'titleContent');
|
|
90
|
+
|
|
91
|
+
// Нижний отступ живёт на `.wrapper` — заголовок его не дублирует.
|
|
92
|
+
padding-bottom: 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
|
|
98
|
+
position: relative;
|
|
99
|
+
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: stretch;
|
|
102
|
+
justify-content: space-between;
|
|
103
|
+
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
|
|
106
|
+
&[data-chevron-position='before'] {
|
|
107
|
+
flex-direction: row-reverse;
|
|
108
|
+
justify-content: flex-end;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.afterTitle {
|
|
113
|
+
display: flex;
|
|
114
|
+
flex: 1 0 0;
|
|
115
|
+
align-items: center;
|
|
116
|
+
min-width: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.chevronWrapper {
|
|
120
|
+
@each $component in $components {
|
|
121
|
+
[data-component='#{$component}'] & {
|
|
122
|
+
@include base.composite-var(
|
|
123
|
+
accordion.$accordion,
|
|
124
|
+
'anatomy',
|
|
125
|
+
$component,
|
|
126
|
+
'container',
|
|
127
|
+
'titleContent',
|
|
128
|
+
'chevronWrapper'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.titleLine {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
|
|
142
|
+
@each $component in $components {
|
|
143
|
+
[data-component='#{$component}'] & {
|
|
144
|
+
@include base.composite-var(
|
|
145
|
+
accordion.$accordion,
|
|
146
|
+
'anatomy',
|
|
147
|
+
$component,
|
|
148
|
+
'container',
|
|
149
|
+
'titleContent',
|
|
150
|
+
'titleWrapper',
|
|
151
|
+
'titleLine'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.collapse {
|
|
158
|
+
position: relative;
|
|
159
|
+
|
|
160
|
+
display: grid;
|
|
161
|
+
grid-template-rows: 0fr;
|
|
162
|
+
|
|
163
|
+
box-sizing: border-box;
|
|
164
|
+
|
|
165
|
+
transition: grid-template-rows var(--sn-collapse-block-animation-duration, 300ms) ease-out;
|
|
166
|
+
|
|
167
|
+
&[data-expanded] {
|
|
168
|
+
grid-template-rows: 1fr;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.container {
|
|
173
|
+
@each $component in $components {
|
|
174
|
+
[data-component='#{$component}'] & {
|
|
175
|
+
box-sizing: border-box;
|
|
176
|
+
|
|
177
|
+
@include base.composite-var(accordion.$accordion, 'anatomy', $component, 'container', 'bodyContent');
|
|
178
|
+
|
|
179
|
+
// Вертикальные отступы тела схлопываются вместе с ним: `grid-template-rows: 0fr` обнуляет
|
|
180
|
+
// только content-box, padding остаётся и держит лишнюю высоту до размонтирования контента —
|
|
181
|
+
// в конце закрытия блок проседал ступенькой.
|
|
182
|
+
&:not([data-expanded]) {
|
|
183
|
+
padding-top: 0;
|
|
184
|
+
padding-bottom: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Нижний отступ карточки уже на `.wrapper` — тело его не дублирует.
|
|
188
|
+
&[data-expanded] {
|
|
189
|
+
padding-bottom: 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
overflow: hidden;
|
|
195
|
+
display: flex;
|
|
196
|
+
flex-direction: column;
|
|
197
|
+
|
|
198
|
+
min-height: 0;
|
|
199
|
+
|
|
200
|
+
transition:
|
|
201
|
+
padding-top var(--sn-collapse-block-animation-duration, 300ms) ease-out,
|
|
202
|
+
padding-bottom var(--sn-collapse-block-animation-duration, 300ms) ease-out;
|
|
203
|
+
|
|
204
|
+
&.containerCompletelyOpen {
|
|
205
|
+
overflow: visible;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CollapseBlock';
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const VIEW = {
|
|
2
|
+
Simple: 'simple',
|
|
3
|
+
Outline: 'outline',
|
|
4
|
+
Elevated: 'elevated',
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
export const CHEVRON_POSITION = {
|
|
8
|
+
Before: 'before',
|
|
9
|
+
After: 'after',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export const SELECTION_MODE = {
|
|
13
|
+
Single: 'single',
|
|
14
|
+
Multiple: 'multiple',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export const TEST_IDS = {
|
|
18
|
+
collapseBlock: 'accordion__collapse-block',
|
|
19
|
+
chevron: 'accordion__collapse-block__chevron',
|
|
20
|
+
title: 'accordion__collapse-block__title',
|
|
21
|
+
subTitle: 'accordion__collapse-block__sub-title',
|
|
22
|
+
content: 'accordion__collapse-block__content',
|
|
23
|
+
afterTitle: 'accordion__collapse-block__after-title',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const ANIMATION_DURATION = 300;
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ValueOf } from '@cloud-ru/ds-utils';
|
|
2
|
+
|
|
3
|
+
import { CHEVRON_POSITION, SELECTION_MODE, VIEW } from './constants';
|
|
4
|
+
|
|
5
|
+
export type View = ValueOf<typeof VIEW>;
|
|
6
|
+
|
|
7
|
+
export type ChevronPosition = ValueOf<typeof CHEVRON_POSITION>;
|
|
8
|
+
|
|
9
|
+
export type SelectionMode = ValueOf<typeof SELECTION_MODE>;
|