@cloud-ru/ds-scroll 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 +179 -0
- package/dist/cjs/Scroll.d.ts +107 -0
- package/dist/cjs/Scroll.js +124 -0
- package/dist/cjs/constants.d.ts +22 -0
- package/dist/cjs/constants.js +28 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +290 -0
- package/dist/cjs/styles.module.css +290 -0
- package/dist/cjs/types.d.ts +6 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/Scroll.d.ts +107 -0
- package/dist/esm/Scroll.js +118 -0
- package/dist/esm/constants.d.ts +22 -0
- package/dist/esm/constants.js +25 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/style.css +290 -0
- package/dist/esm/styles.module.css +290 -0
- package/dist/esm/types.d.ts +6 -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 +45 -0
- package/src/Scroll.tsx +240 -0
- package/src/constants.ts +30 -0
- package/src/index.ts +11 -0
- package/src/styles.module.scss +163 -0
- package/src/types.ts +11 -0
package/src/Scroll.tsx
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import 'overlayscrollbars/styles/overlayscrollbars.css';
|
|
2
|
+
|
|
3
|
+
import { isBrowser, useLayoutEffect, WithSupportProps } from '@cloud-ru/ds-utils';
|
|
4
|
+
import cn from 'classnames';
|
|
5
|
+
import { OverlayScrollbars } from 'overlayscrollbars';
|
|
6
|
+
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from 'overlayscrollbars-react';
|
|
7
|
+
import { forwardRef, PropsWithChildren, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
AUTOSCROLL_ENABLE_LIMIT,
|
|
11
|
+
AUTOSCROLL_TO,
|
|
12
|
+
BAR_AUTO_HIDE_DELAY_MS,
|
|
13
|
+
BAR_HIDE_STRATEGY,
|
|
14
|
+
RESIZE,
|
|
15
|
+
SIZE,
|
|
16
|
+
} from './constants';
|
|
17
|
+
import styles from './styles.module.scss';
|
|
18
|
+
import { AutoscrollTo, BarHideStrategy, Resize, Size } from './types';
|
|
19
|
+
|
|
20
|
+
export type ScrollProps = WithSupportProps<
|
|
21
|
+
PropsWithChildren<{
|
|
22
|
+
className?: string;
|
|
23
|
+
/** Размер скролбаров */
|
|
24
|
+
size?: Size;
|
|
25
|
+
/** Скролить ли по клику в скроллбар. */
|
|
26
|
+
clickScrolling?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Включает автоскрол при маунте и изменении размера контента:
|
|
29
|
+
* <br /> - `bottom` - автоскрол вниз,
|
|
30
|
+
* <br /> - `right` - автоскрол вправо,
|
|
31
|
+
*/
|
|
32
|
+
autoscrollTo?: AutoscrollTo;
|
|
33
|
+
/**
|
|
34
|
+
* Управление скрытием скролл баров:
|
|
35
|
+
* <br /> - `Never` - показывать всегда
|
|
36
|
+
* <br /> - `Leave` - скрывать когда курсор покидает компонент
|
|
37
|
+
* <br /> - `Scroll` - показывать только когда происходит скроллинг
|
|
38
|
+
* <br /> - `Move` - показывать при движении курсора над компонентом
|
|
39
|
+
*/
|
|
40
|
+
barHideStrategy?: BarHideStrategy;
|
|
41
|
+
/** Колбек события скрола. */
|
|
42
|
+
onScroll?: (event?: Event) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Настройка возможности регулировать Scroll-контейнер:
|
|
45
|
+
* <br /> - `None` - нельзя изменять размер
|
|
46
|
+
* <br /> - `Horizontal` - можно изменять размер только по горизонтали
|
|
47
|
+
* <br /> - `Vertical` - можно изменять размер только по вертикали
|
|
48
|
+
* <br /> - `Both` - можно изменять размер в обеих координатах
|
|
49
|
+
*/
|
|
50
|
+
resize?: Resize;
|
|
51
|
+
/** Отключает возможность взаимодействовать со скролбарами мышью. */
|
|
52
|
+
untouchableScrollbars?: boolean;
|
|
53
|
+
/** Должны ли паддинги быть абсолютными */
|
|
54
|
+
paddingAbsolute?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Поведение overflow по осям. По умолчанию OverlayScrollbars выставляет `scroll`
|
|
57
|
+
* на обе оси; если контента по оси быть не должно — передавай `'hidden'`.
|
|
58
|
+
*/
|
|
59
|
+
overflow?: {
|
|
60
|
+
x?: 'hidden' | 'visible' | 'scroll' | 'visible-hidden' | 'visible-scroll';
|
|
61
|
+
y?: 'hidden' | 'visible' | 'scroll' | 'visible-hidden' | 'visible-scroll';
|
|
62
|
+
};
|
|
63
|
+
/** Коллбэк вызывающийся на инициализацию скролла */
|
|
64
|
+
onInitialized?(): void;
|
|
65
|
+
}>
|
|
66
|
+
>;
|
|
67
|
+
|
|
68
|
+
type PositionsRef = {
|
|
69
|
+
scrolledToRight: boolean;
|
|
70
|
+
scrolledToBottom: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Установить nonce атрибут для инлайн стилей.
|
|
75
|
+
* @function setNonce
|
|
76
|
+
*/
|
|
77
|
+
export const setNonce = OverlayScrollbars.nonce;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scroll — контейнер с кастомными скроллбарами (OverlayScrollbars).
|
|
81
|
+
* Стили из @cloud-ru/ds-figma-variables (anatomy container + slider + theme colors).
|
|
82
|
+
*/
|
|
83
|
+
export const Scroll = forwardRef<HTMLElement, ScrollProps>(function Scroll(
|
|
84
|
+
{
|
|
85
|
+
children,
|
|
86
|
+
className,
|
|
87
|
+
onScroll: onScrollProp,
|
|
88
|
+
onInitialized: onInitializedProp,
|
|
89
|
+
size = SIZE.M,
|
|
90
|
+
resize = RESIZE.None,
|
|
91
|
+
clickScrolling = true,
|
|
92
|
+
barHideStrategy = BAR_HIDE_STRATEGY.Leave,
|
|
93
|
+
autoscrollTo,
|
|
94
|
+
paddingAbsolute,
|
|
95
|
+
overflow,
|
|
96
|
+
untouchableScrollbars = false,
|
|
97
|
+
...rest
|
|
98
|
+
},
|
|
99
|
+
ref,
|
|
100
|
+
) {
|
|
101
|
+
const overlayScrollbarsRef = useRef<OverlayScrollbarsComponentRef | null>(null);
|
|
102
|
+
const [isOverlayScrollbarInited, setOverlayScrollbarInited] = useState(false);
|
|
103
|
+
const getOSInstance = useCallback(
|
|
104
|
+
(overlayScrollbars = overlayScrollbarsRef.current) => overlayScrollbars?.osInstance(),
|
|
105
|
+
[overlayScrollbarsRef],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
useImperativeHandle<HTMLElement | null, HTMLElement | null>(
|
|
109
|
+
ref,
|
|
110
|
+
() => (overlayScrollbarsRef.current?.osInstance()?.elements().viewport ?? null) as HTMLElement | null,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const lastPositionsRef = useRef<PositionsRef>({ scrolledToBottom: false, scrolledToRight: false });
|
|
114
|
+
|
|
115
|
+
const syncPositions = useCallback(
|
|
116
|
+
(scrollbars?: OverlayScrollbarsComponentRef | null) => {
|
|
117
|
+
const instance = getOSInstance(scrollbars);
|
|
118
|
+
if (instance) {
|
|
119
|
+
const { viewport } = instance.elements();
|
|
120
|
+
const gapToBottom = viewport.scrollHeight - (viewport.offsetHeight + viewport.scrollTop);
|
|
121
|
+
const gapToRight = viewport.scrollWidth - (viewport.offsetWidth + viewport.scrollLeft);
|
|
122
|
+
const hasYScroll = viewport.scrollHeight > viewport.offsetHeight;
|
|
123
|
+
const hasXScroll = viewport.scrollWidth > viewport.offsetWidth;
|
|
124
|
+
lastPositionsRef.current.scrolledToBottom = gapToBottom < AUTOSCROLL_ENABLE_LIMIT || !hasYScroll;
|
|
125
|
+
lastPositionsRef.current.scrolledToRight = gapToRight < AUTOSCROLL_ENABLE_LIMIT || !hasXScroll;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
[getOSInstance],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
useLayoutEffect(() => {
|
|
132
|
+
if (!autoscrollTo) {
|
|
133
|
+
onInitializedProp?.();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const instance = getOSInstance();
|
|
138
|
+
if (instance && isOverlayScrollbarInited) {
|
|
139
|
+
syncPositions();
|
|
140
|
+
|
|
141
|
+
const { content } = instance.elements();
|
|
142
|
+
|
|
143
|
+
autoscrollTo === AUTOSCROLL_TO.Bottom && content.scroll(0, content.scrollHeight);
|
|
144
|
+
autoscrollTo === AUTOSCROLL_TO.Right && content.scroll(content.scrollWidth, 0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
onInitializedProp?.();
|
|
148
|
+
|
|
149
|
+
/* autoscrollTo умышленно не в депсах — интересен только на момент маунта */
|
|
150
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
151
|
+
}, [getOSInstance, isOverlayScrollbarInited, syncPositions]);
|
|
152
|
+
|
|
153
|
+
const injectStateLayers = useCallback(() => {
|
|
154
|
+
const instance = overlayScrollbarsRef.current?.osInstance();
|
|
155
|
+
const host = instance?.elements().host;
|
|
156
|
+
if (!host) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
host.querySelectorAll('.os-scrollbar-handle').forEach((handle: Element) => {
|
|
161
|
+
const stateLayerClass = styles.stateLayer ?? '';
|
|
162
|
+
if (!stateLayerClass) return;
|
|
163
|
+
if (handle.querySelector(`.${CSS.escape(stateLayerClass)}`)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isBrowser()) {
|
|
168
|
+
const layer = document.createElement('div');
|
|
169
|
+
layer.classList.add(stateLayerClass);
|
|
170
|
+
layer.setAttribute('data-state', 'versionOnColor');
|
|
171
|
+
handle.appendChild(layer);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
const onInitialized = useCallback(() => {
|
|
177
|
+
injectStateLayers();
|
|
178
|
+
setOverlayScrollbarInited(true);
|
|
179
|
+
}, [injectStateLayers]);
|
|
180
|
+
|
|
181
|
+
const onScroll = useCallback(
|
|
182
|
+
(event?: Event) => {
|
|
183
|
+
onScrollProp?.(event);
|
|
184
|
+
|
|
185
|
+
if (autoscrollTo) {
|
|
186
|
+
syncPositions();
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
/* autoscrollTo не аффектит на расчеты в syncPositions */
|
|
190
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
+
[onScrollProp, syncPositions],
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const onContentSizeChanged = useCallback(() => {
|
|
195
|
+
if (!autoscrollTo) return;
|
|
196
|
+
|
|
197
|
+
const instance = getOSInstance()?.elements();
|
|
198
|
+
|
|
199
|
+
if (instance?.content) {
|
|
200
|
+
if (lastPositionsRef.current.scrolledToBottom && autoscrollTo === AUTOSCROLL_TO.Bottom) {
|
|
201
|
+
instance?.content.scroll(0, instance?.content.scrollHeight);
|
|
202
|
+
}
|
|
203
|
+
if (lastPositionsRef.current.scrolledToRight && autoscrollTo === AUTOSCROLL_TO.Right) {
|
|
204
|
+
instance?.content.scroll(instance?.content.scrollWidth, 0);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}, [getOSInstance, autoscrollTo]);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<OverlayScrollbarsComponent
|
|
211
|
+
{...rest}
|
|
212
|
+
ref={overlayScrollbarsRef}
|
|
213
|
+
data-size={size}
|
|
214
|
+
data-untouchable-scrollbars={untouchableScrollbars || undefined}
|
|
215
|
+
className={cn(className, styles.scroll, 'osThemeSnack')}
|
|
216
|
+
style={{ resize }}
|
|
217
|
+
options={{
|
|
218
|
+
paddingAbsolute,
|
|
219
|
+
overflow,
|
|
220
|
+
scrollbars: {
|
|
221
|
+
autoHide: barHideStrategy,
|
|
222
|
+
autoHideDelay: BAR_AUTO_HIDE_DELAY_MS,
|
|
223
|
+
clickScroll: clickScrolling,
|
|
224
|
+
},
|
|
225
|
+
}}
|
|
226
|
+
events={{
|
|
227
|
+
initialized: onInitialized,
|
|
228
|
+
scroll: (_instance, event) => {
|
|
229
|
+
onScroll(event);
|
|
230
|
+
},
|
|
231
|
+
updated: () => {
|
|
232
|
+
injectStateLayers();
|
|
233
|
+
onContentSizeChanged();
|
|
234
|
+
},
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
{autoscrollTo ? isOverlayScrollbarInited && children : children}
|
|
238
|
+
</OverlayScrollbarsComponent>
|
|
239
|
+
);
|
|
240
|
+
});
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const SIZE = {
|
|
2
|
+
S: 's',
|
|
3
|
+
M: 'm',
|
|
4
|
+
} as const;
|
|
5
|
+
|
|
6
|
+
export const BAR_HIDE_STRATEGY = {
|
|
7
|
+
Never: 'never',
|
|
8
|
+
Leave: 'leave',
|
|
9
|
+
Scroll: 'scroll',
|
|
10
|
+
Move: 'move',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const RESIZE = {
|
|
14
|
+
None: 'none',
|
|
15
|
+
Horizontal: 'horizontal',
|
|
16
|
+
Vertical: 'vertical',
|
|
17
|
+
Both: 'both',
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export const AUTOSCROLL_TO = {
|
|
21
|
+
Bottom: 'bottom',
|
|
22
|
+
Right: 'right',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export const BAR_AUTO_HIDE_DELAY_MS = 100;
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* Значение отступа до края контейнера при котором считается что контейнер проскролен до конца вниз или вправо, в пикселях.
|
|
29
|
+
*/
|
|
30
|
+
export const AUTOSCROLL_ENABLE_LIMIT = 2;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Scroll, setNonce } from './Scroll';
|
|
2
|
+
export type { ScrollProps } from './Scroll';
|
|
3
|
+
export {
|
|
4
|
+
SIZE,
|
|
5
|
+
BAR_HIDE_STRATEGY,
|
|
6
|
+
RESIZE,
|
|
7
|
+
AUTOSCROLL_TO,
|
|
8
|
+
BAR_AUTO_HIDE_DELAY_MS,
|
|
9
|
+
AUTOSCROLL_ENABLE_LIMIT,
|
|
10
|
+
} from './constants';
|
|
11
|
+
export type { Size, BarHideStrategy, Resize, AutoscrollTo } from './types';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Стили Scroll (OverlayScrollbars) в соответствии с Figma.
|
|
3
|
+
* Темизация через :global .osThemeSnack, токены из @cloud-ru/ds-figma-variables.
|
|
4
|
+
*/
|
|
5
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
|
|
6
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/scroll.module' as scroll;
|
|
7
|
+
@use '@cloud-ru/ds-materials' as m;
|
|
8
|
+
|
|
9
|
+
$orientations: 'vertical', 'horizontal';
|
|
10
|
+
$sizes: 's', 'm';
|
|
11
|
+
/* stylelint-disable-next-line */
|
|
12
|
+
$theme-name: osThemeSnack;
|
|
13
|
+
$scrollbar-slider-active-zone: 12px;
|
|
14
|
+
|
|
15
|
+
.scroll {
|
|
16
|
+
position: relative;
|
|
17
|
+
/* stylelint-disable-next-line declaration-property-value-allowed-list */
|
|
18
|
+
z-index: 0;
|
|
19
|
+
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.stateLayer {
|
|
26
|
+
visibility: initial;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Темизация OverlayScrollbars: селекторы из документации библиотеки.
|
|
31
|
+
* Размеры и цвета — из @cloud-ru/ds-figma-variables (scroll anatomy + theme).
|
|
32
|
+
*/
|
|
33
|
+
/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
|
|
34
|
+
:global {
|
|
35
|
+
/* ——— Базовые скроллбары ——— */
|
|
36
|
+
.#{$theme-name} .os-scrollbar {
|
|
37
|
+
padding: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@each $orientation in $orientations {
|
|
41
|
+
.#{$theme-name}[data-untouchable-scrollbars] > .os-scrollbar-#{$orientation} {
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
|
|
44
|
+
> .os-scrollbar-track {
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
|
|
47
|
+
> .os-scrollbar-handle {
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ——— Размеры (s/m) и resize-disabled ——— */
|
|
55
|
+
@each $size in $sizes {
|
|
56
|
+
@each $orientation in $orientations {
|
|
57
|
+
.#{$theme-name}[data-size='#{$size}'] > .os-scrollbar-#{$orientation} {
|
|
58
|
+
@if $orientation == 'vertical' {
|
|
59
|
+
width: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', $size, 'width');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@else {
|
|
63
|
+
height: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', $size, 'height');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
|
|
69
|
+
// По макету у скролла нет жёлоба-рельсы — виден только handle (thumb)
|
|
70
|
+
// c neutral-decorTransparent (стилизуется ниже). Трек остаётся прозрачным.
|
|
71
|
+
& > .os-scrollbar-track > .os-scrollbar-handle {
|
|
72
|
+
border-radius: scroll.simple-var(scroll.$scroll, 'anatomy', 'slider', 'size', $size, 'border-radius');
|
|
73
|
+
|
|
74
|
+
@if $orientation == 'vertical' {
|
|
75
|
+
width: scroll.simple-var(scroll.$scroll, 'anatomy', 'slider', 'size', $size, 'width');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@else {
|
|
79
|
+
height: scroll.simple-var(scroll.$scroll, 'anatomy', 'slider', 'size', $size, 'height');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.#{$theme-name}[data-size='#{$size}'].os-host-resize-disabled > .os-scrollbar-horizontal {
|
|
86
|
+
right: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', $size, 'width');
|
|
87
|
+
}
|
|
88
|
+
.#{$theme-name}[data-size='#{$size}'].os-host-resize-disabled.os-host-rtl > .os-scrollbar-horizontal {
|
|
89
|
+
right: 0;
|
|
90
|
+
left: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', $size, 'width');
|
|
91
|
+
}
|
|
92
|
+
.#{$theme-name}[data-size='#{$size}'].os-host-resize-disabled > .os-scrollbar-vertical {
|
|
93
|
+
bottom: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', $size, 'height');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// TODO: кажется, это не используется, и у нас применяется нативный ресайз
|
|
98
|
+
|
|
99
|
+
/* ——— Corner (resize grip) ——— */
|
|
100
|
+
|
|
101
|
+
/* .#{$theme-name} > .os-scrollbar-corner {
|
|
102
|
+
--bg-icon-mask: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iYmxhY2siIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTMuNTMwMyAzLjQ2OTY3QzEzLjgyMzIgMy43NjI1NiAxMy44MjMyIDQuMjM3NDQgMTMuNTMwMyA0LjUzMDMzTDQuNTMwMzMgMTMuNTMwM0M0LjIzNzQ0IDEzLjgyMzIgMy43NjI1NiAxMy44MjMyIDMuNDY5NjcgMTMuNTMwM0MzLjE3Njc4IDEzLjIzNzQgMy4xNzY3OCAxMi43NjI2IDMuNDY5NjcgMTIuNDY5N0wxMi40Njk3IDMuNDY5NjdDMTIuNzYyNiAzLjE3Njc4IDEzLjIzNzQgMy4xNzY3OCAxMy41MzAzIDMuNDY5NjdaTTEzLjUzMDMgNy40Njk2N0MxMy44MjMyIDcuNzYyNTYgMTMuODIzMiA4LjIzNzQ0IDEzLjUzMDMgOC41MzAzM0w4LjUzMDMzIDEzLjUzMDNDOC4yMzc0NCAxMy44MjMyIDcuNzYyNTYgMTMuODIzMiA3LjQ2OTY3IDEzLjUzMDNDNy4xNzY3OCAxMy4yMzc0IDcuMTc2NzggMTIuNzYyNiA3LjQ2OTY3IDEyLjQ2OTdMMTIuNDY5NyA3LjQ2OTY3QzEyLjc2MjYgNy4xNzY3OCAxMy4yMzc0IDcuMTc2NzggMTMuNTMwMyA3LjQ2OTY3Wk0xMy41MzAzIDEyLjUzMDNDMTMuODIzMiAxMi4yMzc0IDEzLjgyMzIgMTEuNzYyNiAxMy41MzAzIDExLjQ2OTdDMTMuMjM3NCAxMS4xNzY4IDEyLjc2MjYgMTEuMTc2OCAxMi40Njk3IDExLjQ2OTdMMTEuNDY5NyAxMi40Njk3QzExLjE3NjggMTIuNzYyNiAxMS4xNzY4IDEzLjIzNzQgMTEuNDY5NyAxMy41MzAzQzExLjc2MjYgMTMuODIzMiAxMi4yMzc0IDEzLjgyMzIgMTIuNTMwMyAxMy41MzAzTDEzLjUzMDMgMTIuNTMwM1oiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgo=');
|
|
103
|
+
z-index: 0;
|
|
104
|
+
|
|
105
|
+
width: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', 'm', 'width');
|
|
106
|
+
height: scroll.simple-var(scroll.$scroll, 'anatomy', 'container', 'size', 'm', 'height');
|
|
107
|
+
|
|
108
|
+
background-color: base.simple-var(base.$sn-theme, 'color', 'neutral', 'decor');
|
|
109
|
+
background-image: unset;
|
|
110
|
+
|
|
111
|
+
mask-image: var(--bg-icon-mask);
|
|
112
|
+
|
|
113
|
+
&:hover {
|
|
114
|
+
background-color: base.simple-var(base.$sn-theme, 'color', 'neutral', 'text');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.os-dragging .#{$theme-name} > .os-scrollbar-corner:hover {
|
|
119
|
+
background-color: base.simple-var(base.$sn-theme, 'color', 'primary', 'accent');
|
|
120
|
+
}
|
|
121
|
+
.#{$theme-name}.os-host-resize-disabled > .os-scrollbar-corner {
|
|
122
|
+
display: none;
|
|
123
|
+
} */
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* Handle + state layer: нельзя держать внутри `:global { }` — иначе класс из `#{stateLayer}`
|
|
128
|
+
* станет глобальным и не совпадёт с хэшем из JS. Родитель — только :global(цепочка).
|
|
129
|
+
*/
|
|
130
|
+
@each $orientation in $orientations {
|
|
131
|
+
:global(.#{$theme-name} > .os-scrollbar-#{$orientation} > .os-scrollbar-track > .os-scrollbar-handle) {
|
|
132
|
+
pointer-events: initial;
|
|
133
|
+
position: relative;
|
|
134
|
+
border: base.$sn-primitive-strokeWeight-strokeThin solid base.$sn-theme-color-invertNeutral-decorTransparent;
|
|
135
|
+
background: base.$sn-theme-color-neutral-decorTransparent;
|
|
136
|
+
|
|
137
|
+
.#{stateLayer} {
|
|
138
|
+
transition: background 0.15s;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@include m.has-state-layer-as-child(#{stateLayer});
|
|
142
|
+
|
|
143
|
+
@if $orientation == 'vertical' {
|
|
144
|
+
left: 50%;
|
|
145
|
+
translate: -50% 0;
|
|
146
|
+
|
|
147
|
+
&::before {
|
|
148
|
+
right: -$scrollbar-slider-active-zone;
|
|
149
|
+
left: -$scrollbar-slider-active-zone;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@else {
|
|
154
|
+
top: 50%;
|
|
155
|
+
translate: 0 -50%;
|
|
156
|
+
|
|
157
|
+
&::before {
|
|
158
|
+
top: -$scrollbar-slider-active-zone;
|
|
159
|
+
bottom: -$scrollbar-slider-active-zone;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ValueOf } from '@cloud-ru/ds-utils';
|
|
2
|
+
|
|
3
|
+
import { AUTOSCROLL_TO, BAR_HIDE_STRATEGY, RESIZE, SIZE } from './constants';
|
|
4
|
+
|
|
5
|
+
export type Size = ValueOf<typeof SIZE>;
|
|
6
|
+
|
|
7
|
+
export type AutoscrollTo = ValueOf<typeof AUTOSCROLL_TO>;
|
|
8
|
+
|
|
9
|
+
export type Resize = ValueOf<typeof RESIZE>;
|
|
10
|
+
|
|
11
|
+
export type BarHideStrategy = ValueOf<typeof BAR_HIDE_STRATEGY>;
|