Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/plasma-core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 3 additions & 17 deletions packages/plasma-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@
"main": "index.js",
"module": "es/index.js",
"types": "index.d.ts",
"files": [
"components",
"hocs",
"hooks",
"mixins",
"tokens",
"types",
"utils",
"index.d.ts",
"index.js",
"es"
],
"files": ["components", "hocs", "hooks", "mixins", "tokens", "types", "utils", "index.d.ts", "index.js", "es"],
"peerDependencies": {
"react": ">=16.13.1",
"react-dom": ">=16.13.1",
Expand Down Expand Up @@ -63,13 +52,10 @@
"test": "NODE_ICU_DATA=node_modules/full-icu jest",
"test:watch": "NODE_ICU_DATA=node_modules/full-icu jest --watch"
},
"contributors": [
"Vasiliy Loginevskiy",
"Виноградов Антон Александрович",
"Зубаиров Фаниль Асхатович"
],
"contributors": ["Vasiliy Loginevskiy", "Виноградов Антон Александрович", "Зубаиров Фаниль Асхатович"],
"sideEffects": false,
"dependencies": {
"@sberdevices/use-virtual": "0.7.1",
"focus-visible": "5.2.0",
"lodash.throttle": "4.1.1"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import styled, { css } from 'styled-components';

import type { VirtualCarouselProps } from './types';

/**
* Компонент применяется, если требуется компенсировать отступы контейнера в сетке.
* При обертывании вокруг ``Carousel``, добавляет карусели и ее прокрутке дополнительные отступы.
* Стилизованный компонент, обладающий всеми свойствами ``div``.
*/
export const VirtualCarouselGridWrapper = styled.div`
overflow: hidden;
margin-left: calc(var(--plasma-grid-margin) * -1);
margin-right: calc(var(--plasma-grid-margin) * -1);
`;

/**
* Корневой элемент - ограничивающая обертка карусели.
*/
export const VirtualCarousel = styled.div<Pick<VirtualCarouselProps, 'carouselHeight' | 'axis' | 'scrollSnapType'>>`
position: relative;
${({ carouselHeight, axis }) => css`
${axis === 'y' ? 'height' : 'width'}: ${carouselHeight}px;
`}
/* stylelint-disable-next-line selector-max-empty-lines, selector-nested-pattern, selector-type-no-unknown */
::-webkit-scrollbar {
display: none;
}

${({ axis }) =>
axis === 'x'
? css`
overflow-x: auto;
overflow-y: hidden;
`
: css`
overflow-x: hidden;
overflow-y: auto;
`}

${({ scrollSnapType, axis }) =>
scrollSnapType &&
scrollSnapType !== 'none' &&
css`
//scroll-behavior: smooth;
//scroll-snap-type: ${axis} ${scrollSnapType};
`}

/* stylelint-disable-next-line */
${VirtualCarouselGridWrapper} & {
scroll-padding: 0 var(--plasma-grid-margin);
padding-left: var(--plasma-grid-margin);
}
`;

/**
* Списковый (трековый) элемент карусели для непосредственного вложения айтемов в него.
*/
export const VirtualCarouselTrack = styled.div<
Pick<VirtualCarouselProps, 'carouselHeight' | 'axis' | 'paddingStart' | 'paddingEnd'>
>`
position: relative;
${({ carouselHeight, axis }) => css`
${axis === 'x' ? 'width' : 'height'}: ${carouselHeight}px;
`}
${({ axis, paddingStart, paddingEnd }) =>
axis === 'x'
? css`
//display: inline-flex;
//flex-direction: row;

${paddingStart &&
css`
padding-left: ${paddingStart};
`}
${paddingEnd
? css`
padding-right: ${paddingEnd};
`
: css`
/* stylelint-disable-next-line selector-nested-pattern */
${VirtualCarouselGridWrapper} & {
padding-right: var(--plasma-grid-margin);
}
`}
`
: css`
display: flex;
flex-direction: column;
width: 100%;

${paddingStart &&
css`
padding-top: ${paddingStart};
`}
${paddingEnd &&
css`
padding-bottom: ${paddingEnd};
`}
`}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext } from 'react';

import { ScrollAxis } from './types';

export interface VirtualCarouselState {
axis: ScrollAxis;
}

const initialValue: VirtualCarouselState = {
axis: 'x',
};

export const VirtualCarouselContext = createContext<VirtualCarouselState>(initialValue);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import styled, { css } from 'styled-components';

import { applyScrollSnap, ScrollSnapProps } from '../../mixins';
import type { AsProps } from '../../types';

export interface VirtualCarouselItemProps extends ScrollSnapProps, AsProps, React.HTMLAttributes<HTMLDivElement> {
/**
* Смещение по оси
*/
start: number;
/**
* Ось
*/
axis: string;
}

const StyledItem = styled.div<VirtualCarouselItemProps>`
position: absolute;
top: 0;
left: 0;
${applyScrollSnap}
${({ start, axis }) => css`
transform: ${axis === 'x' ? 'translateX' : 'translateY'} (${start}px);
`}
`;

export const VirtualCarouselItem: React.FC<VirtualCarouselItemProps> = ({
scrollSnapAlign = 'center',
children,
...rest
}) => {
return (
<StyledItem scrollSnapAlign={scrollSnapAlign} role="group" aria-roledescription="slide" {...rest}>
{children}
</StyledItem>
);
};
10 changes: 10 additions & 0 deletions packages/plasma-core/src/components/VirtualCarousel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export { VirtualCarouselGridWrapper } from './VirtualCarousel';
export { VirtualCarousel } from './VirtualCarousel';
export { VirtualCarouselTrack } from './VirtualCarousel';

export { VirtualCarouselContext } from './VirtualCarouselContext';

export { VirtualCarouselItem } from './VirtualCarouselItem';
export type { VirtualCarouselItemProps } from './VirtualCarouselItem';

export type { VirtualCarouselProps } from './types';
72 changes: 72 additions & 0 deletions packages/plasma-core/src/components/VirtualCarousel/types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { HTMLAttributes } from 'react';

import type { AsProps, SnapType } from '../../types';

export type ScrollAxis = 'x' | 'y';
export type ScrollAlign = 'start' | 'center' | 'end' | 'activeDirection';

export type ToIndex = (i: number) => void;
export type ToPrev = () => void;
export type ToNext = () => void;

export interface BasicProps extends AsProps, HTMLAttributes<HTMLDivElement> {
/**
* Ось прокрутки
*/
axis: ScrollAxis;
/**
* Тип CSS Scroll Snap
*/
scrollSnapType?: SnapType;
/**
* Отступ в начале, используется при центрировании крайних элементов
*/
paddingStart?: string;
/**
* Отступ в конце, используется при центрировании крайних элементов
*/
paddingEnd?: string;
/**
* Обработчик события скролла
*/
onScroll?: HTMLAttributes<HTMLDivElement>['onScroll'];

/**
* Количество всех элементов в списке.
*/
itemCount: number;
/**
* Вычисление размера элемента в зависимости от индекса.
* По умолчанию размер = 50px
*/
estimateSize: (index: number) => number;
/**
* количество элементов для рендера за видимой областью
* при скролле
*/
overscan?: number;
/**
* Функция для отрисовки элементов
* @param visibleItems - текущие элементы
* @param currentIndex - текущий выбранный индекс
*/
renderItems: (visibleItems: { index: number; start: number }[], currentIndex: number) => React.FC;
/**
* Высота карусели
*/
carouselHeight: number;
}
export interface DetectionProps {
/**
* Коллбек изменения индекса
*/
onIndexChange?: (index: number) => void;
}
export interface NoDetectionProps {
detectActive?: false;
detectThreshold?: never;
onIndexChange?: never;
scaleCallback?: never;
scaleResetCallback?: never;
}
export type VirtualCarouselProps = BasicProps & (DetectionProps | NoDetectionProps);
57 changes: 57 additions & 0 deletions packages/plasma-core/src/components/VirtualCarousel/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ScrollAxis, ScrollAlign } from './types';

/**
* Подсчет смещения из-за паддингов.
*/
export const getCalculatedOffset = (scrollEl: Element, trackEl: Element, axis: ScrollAxis) => {
const paddingProp = axis === 'x' ? 'paddingLeft' : 'paddingTop';
return parseInt(getComputedStyle(scrollEl)[paddingProp], 10) + parseInt(getComputedStyle(trackEl)[paddingProp], 10);
};

const round = (n: number) => Math.round(n * 100) / 100;

/**
* Получить позицию (слот) айтема в каруселе.
* Каждый айтем имеет свой слот относительно вьюпорта карусели.
*/
export const getItemSlot = (
itemIndex: number,
itemEnd: number,
itemSize: number,
scrollStart: number,
scrollSize: number,
scrollAlign: ScrollAlign,
prevIndex = 0,
offset = 0,
) => {
/**
* Граница и центр скролла (видимой части).
* Смещение + размер.
*/
const scrollEnd = scrollStart + scrollSize;
const scrollCenter = scrollStart + scrollSize / 2;
const itemCenter = itemEnd - itemSize / 2;

if (scrollAlign === 'center') {
return round((itemCenter - scrollCenter) / itemSize);
}
if (scrollAlign === 'start') {
return round((itemEnd - itemSize - scrollStart) / itemSize);
}
if (scrollAlign === 'end') {
return round((itemEnd - (scrollSize + scrollStart)) / itemSize);
}
if (scrollAlign === 'activeDirection') {
const prevStart = offset + itemSize * prevIndex;
const prevEnd = prevStart + itemSize;
const prevVisible = prevEnd > scrollStart && prevStart < scrollEnd;

if (!prevVisible) {
if (prevIndex < itemIndex) {
return round((itemEnd - (scrollSize + scrollStart)) / itemSize);
}
return round((itemEnd - itemSize - scrollStart) / itemSize);
}
}
return null;
};
1 change: 1 addition & 0 deletions packages/plasma-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './components/Price';
export * from './components/Image';
export * from './components/Toast';
export * from './components/Typography';
export * from './components/VirtualCarousel';
export * from './types';
export * from './tokens';
export * from './utils';
Expand Down
5 changes: 5 additions & 0 deletions packages/plasma-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading