Skip to content

Commit

Permalink
feat(table): компоненты для построения таблиц (#911)
Browse files Browse the repository at this point in the history
* feat(table): add component

* chore(table): docs

* chore(table): update examples

* feat(table): move wrapper inside table, add pagination slot

* feat(pagination): move to separated package

* feat(pagination): add dots

* feat(pagination): add views

* feat(table): add scroll into view

* chore: update table & pagination examples

* fix(table): popover lag

* fix(table): imports & types

* fix(table): fix styles
  • Loading branch information
reme3d2y committed Dec 29, 2021
1 parent f81d7d8 commit 4ac648a
Show file tree
Hide file tree
Showing 58 changed files with 2,365 additions and 0 deletions.
Empty file.
24 changes: 24 additions & 0 deletions packages/pagination/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@alfalab/core-components-pagination",
"version": "1.0.0",
"description": "",
"keywords": [],
"license": "MIT",
"main": "dist/index.js",
"module": "./dist/esm/index.js",
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.1",
"react-dom": "^16.9.0 || ^17.0.1"
},
"dependencies": {
"@alfalab/core-components-button": "^5.0.0",
"@alfalab/core-components-tag": "^4.0.0",
"classnames": "^2.2.6"
}
}
122 changes: 122 additions & 0 deletions packages/pagination/src/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { FC } from 'react';
import cn from 'classnames';

import { ChevronBackMIcon } from '@alfalab/icons-glyph/ChevronBackMIcon';
import { ChevronForwardMIcon } from '@alfalab/icons-glyph/ChevronForwardMIcon';

import { Tag } from './components/tag';
import { DefaultView } from './components/default-view';
import { PerPageView } from './components/per-page-view';

import styles from './index.module.css';

export type PaginationProps = {
/**
* Текущая страница (с нуля)
*/
currentPageIndex: number;

/**
* Количество страниц
*/
pagesCount: number;

/**
* Дополнительный класс
*/
className?: string;

/**
* Скрывает стрелки, если выбрана первая или последняя страница
*/
hideArrows?: boolean;

/**
* Количество видимых страниц по бокам
*/
sidePadding?: number;

/**
* Количество видимых страниц вокруг выбранной
*/
activePadding?: number;

/**
* Режим пагинации
*/
view?: 'default' | 'per-page';

/**
* Обработчик переключения страницы
*/
onPageChange?: (pageIndex: number) => void;

/**
* Идентификатор для систем автоматизированного тестирования
*/
dataTestId?: string;
};

export const Pagination: FC<PaginationProps> = ({
currentPageIndex = 0,
pagesCount,
className,
sidePadding = 1,
activePadding = 2,
hideArrows = true,
view = 'default',
onPageChange = () => null,
dataTestId,
}) => {
const handlePageClick = (pageIndex: number) => {
onPageChange(pageIndex);
};

const handleNextPageClick = () => {
handlePageClick(Math.min(pagesCount - 1, currentPageIndex + 1));
};

const handlePrevPageClick = () => {
handlePageClick(Math.max(0, currentPageIndex - 1));
};

const shouldRenderPrevArrow = view === 'per-page' || !hideArrows || currentPageIndex > 0;
const shouldRenderNextArrow =
view === 'per-page' || !hideArrows || currentPageIndex < pagesCount - 1;

return (
<div className={cn(styles.component, className, styles[view])} data-test-id={dataTestId}>
{shouldRenderPrevArrow && (
<Tag
className={styles.arrow}
disabled={currentPageIndex <= 0}
onClick={handlePrevPageClick}
rightAddons={<ChevronBackMIcon width={16} height={16} />}
/>
)}

{view === 'default' && (
<DefaultView
activePadding={activePadding}
sidePadding={sidePadding}
currentPageIndex={currentPageIndex}
pagesCount={pagesCount}
onPageChange={handlePageClick}
/>
)}

{view === 'per-page' && (
<PerPageView currentPageIndex={currentPageIndex} pagesCount={pagesCount} />
)}

{shouldRenderNextArrow && (
<Tag
className={styles.arrow}
disabled={currentPageIndex >= pagesCount - 1}
onClick={handleNextPageClick}
rightAddons={<ChevronForwardMIcon width={16} height={16} />}
/>
)}
</div>
);
};
12 changes: 12 additions & 0 deletions packages/pagination/src/components/default-view/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import '../../../../themes/src/default.css';

.dots {
width: 32px;
height: 32px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-light-text-secondary);
cursor: default;
}
87 changes: 87 additions & 0 deletions packages/pagination/src/components/default-view/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { FC, useCallback } from 'react';

import { Tag } from '../tag';
import { PaginationProps } from '../../Component';

import styles from './index.module.css';

type DefaultViewProps = Pick<
PaginationProps,
'sidePadding' | 'activePadding' | 'pagesCount' | 'currentPageIndex' | 'onPageChange'
>;

export const DefaultView: FC<DefaultViewProps> = ({
sidePadding = 2,
activePadding = 1,
pagesCount,
currentPageIndex,
onPageChange = () => null,
}) => {
const maxHalfCount = sidePadding + activePadding + 1;
const maxElementsCount = maxHalfCount * 2 + 1;
const itemsFit = pagesCount <= maxElementsCount;
const elementsCount = itemsFit ? pagesCount : maxElementsCount;

const getPageIndex = useCallback(
(elementIndex: number) => {
const lastIndex = pagesCount - 1;
const reverseIndex = lastIndex - currentPageIndex;
const lastElementIndex = elementsCount - 1;
const reverseElementIndex = lastElementIndex - elementIndex;

const hasCollapsedItems = (index: number) => !itemsFit && index >= maxHalfCount;

if (elementIndex < sidePadding) {
return elementIndex;
}

if (elementIndex === sidePadding && hasCollapsedItems(currentPageIndex)) {
return null;
}

if (reverseElementIndex === sidePadding && hasCollapsedItems(reverseIndex)) {
return null;
}

if (reverseElementIndex < sidePadding) {
return lastIndex - reverseElementIndex;
}

const computedIndex = currentPageIndex - maxHalfCount + elementIndex;

return Math.min(lastIndex - reverseElementIndex, Math.max(elementIndex, computedIndex));
},
[currentPageIndex, elementsCount, itemsFit, maxHalfCount, pagesCount, sidePadding],
);

return (
<React.Fragment>
{Array(elementsCount)
.fill('')
.map((_, i) => {
const pageIndex = getPageIndex(i);

if (pageIndex === null) {
return (
<div key={i.toString()} className={styles.dots}>
...
</div>
);
}

const active = currentPageIndex === pageIndex;

return (
<Tag
key={i.toString()}
checked={active}
disabled={active}
onClick={() => onPageChange(pageIndex)}
>
{pageIndex + 1}
</Tag>
);
})}
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import '../../../../themes/src/default.css';

.component {
display: block;
margin: 0 var(--gap-m);
}
13 changes: 13 additions & 0 deletions packages/pagination/src/components/per-page-view/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { FC } from 'react';

import { PaginationProps } from '../../Component';

import styles from './index.module.css';

type PerPageViewProps = Pick<PaginationProps, 'pagesCount' | 'currentPageIndex'>;

export const PerPageView: FC<PerPageViewProps> = ({ pagesCount, currentPageIndex }) => (
<span className={styles.component}>
{currentPageIndex + 1} из {pagesCount} страниц
</span>
);
40 changes: 40 additions & 0 deletions packages/pagination/src/components/tag/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@import '../../../../themes/src/default.css';

.tag.tag {
color: var(--color-light-text-secondary);
border-radius: var(--border-radius-m);
border: none;
flex-shrink: 0;
min-width: auto;
min-height: auto;
width: 32px;
height: 32px;
padding: 0;

&:disabled:not(.checked) {
opacity: 0.3;
}

&:hover:not(:disabled) {
background-color: var(--color-light-bg-tertiary);
color: var(--color-light-text-primary);
}

&:active:not(:disabled) {
background-color: var(--color-light-bg-neutral);
}

&.checked {
cursor: default;
background-color: var(--color-light-bg-secondary-inverted);
color: var(--color-light-text-primary-inverted);
}

&.checked:hover:not(:disabled) {
color: var(--color-light-text-primary-inverted);
}

&.checked:active:not(:disabled) {
color: var(--color-light-text-primary);
}
}
15 changes: 15 additions & 0 deletions packages/pagination/src/components/tag/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { FC } from 'react';
import cn from 'classnames';

import { Tag as CoreTag, TagProps } from '@alfalab/core-components-tag';

import styles from './index.module.css';

export const Tag: FC<TagProps> = ({ className, checked, ...restProps }) => (
<CoreTag
{...restProps}
checked={checked}
size='xxs'
className={cn(className, styles.tag, { [styles.checked]: checked })}
/>
);
55 changes: 55 additions & 0 deletions packages/pagination/src/docs/Component.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Meta, Story, Props } from '@storybook/addon-docs/blocks';
import { text, number, select, boolean } from '@storybook/addon-knobs';
import { ComponentHeader, Tabs } from 'storybook/blocks';


import { Pagination } from '../index';
import { version } from '../../package.json';
import Description from './description.mdx';
import Changelog from '../../CHANGELOG.md';


<Meta
title='Компоненты/Pagination'
component={Pagination}
/>

<!-- Canvas -->

<Story name='Pagination'>
{React.createElement(() => {
const [page, setPage] = React.useState(0);
const handlePageChange = pageIndex => setPage(pageIndex);
return (
<Pagination
view={select('view', ['default', 'per-page'], 'default')}
pagesCount={number('pagesCount', 10)}
hideArrows={boolean('hideArrows', true)}
activePadding={number('activePadding', 2)}
sidePadding={number('sidePadding', 1)}
currentPageIndex={page}
onPageChange={handlePageChange}
/>
);
})}
</Story>

<!-- Docs -->

<ComponentHeader
name='Pagination'
version={version}
package='@alfalab/core-components/pagination'
stage={1}
design=''
/>

```tsx
import { Pagination } from '@alfalab/core-components/pagination';
```

<Tabs
description={<Description />}
changelog={<Changelog />}
props={<Props of={Pagination} />}
/>

0 comments on commit 4ac648a

Please sign in to comment.