Skip to content

Commit

Permalink
feat(core-components-tabs): add tabs (#149)
Browse files Browse the repository at this point in the history
* feat(core-components-tabs): add PrimaryTabsHeader

* feat(core-components-tabs): add secondary header. some refactor

* feat(core-components-tabs): add gaps props to PrimaryTabsHeader

* refactor(core-components-tabs): selected -> selectedId

* feat(core-components-tabs): add Tabs and useTabs

* feat(core-components-tabs): use PrimaryTablist as default

* refactor(core-components-tabs): move Tabs to components folder

* fix(core-components-tabs): pr fixes

* Feat/tabs desktop mobile split (#175)

* feat(core-components-tabs): split mobile and desktop versions

* feat(core-components-tabs): add responsive vartiant

* docs: ✏️ update stories

* refactor: 💡 fix responsive styles

* docs: ✏️ replace importd

* fix: 🐛 add mixins import

* refactor(core-components-tabs): pr refactor

* refactor(core-components-tabs): rename tablist

* Feat/tabs tests (#200)

* feat(core-components-tabs): add tests

* test(core-components-tabs): update tests

* fix(core-components-tabs): fix deps

* fix(core-components-tabs): use correct font mixin
  • Loading branch information
reme3d2y committed Aug 10, 2020
1 parent 7df831f commit b08b668
Show file tree
Hide file tree
Showing 43 changed files with 1,990 additions and 0 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
"dependencies": {
"@alfalab/icons-classic": "^1.7.0",
"@popperjs/core": "^2.3.3",
"compute-scroll-into-view": "^1.0.13",
"alfa-ui-primitives": "^2.29.0",
"downshift": "5.2.7",
"lodash.debounce": "^4.0.8",
"react-node-resolver": "^2.0.1",
"react-popper": "^2.2.2",
"react-transition-group": "^4.3.0",
Expand All @@ -79,6 +81,7 @@
"@testing-library/user-event": "^12.0.2",
"@types/classnames": "^2.2.9",
"@types/jest": "^25.1.2",
"@types/lodash.debounce": "^4.0.6",
"@types/node": "^13.5.0",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
Expand Down Expand Up @@ -132,6 +135,7 @@
"shelljs": "^0.8.3",
"standard-version": "^8.0.0",
"string-hash": "^1.1.3",
"style-loader": "^1.0.0",
"stylelint": "^12.0.0",
"stylelint-config-standard": "^20.0.0",
"ts-jest": "^25.4.0",
Expand Down
28 changes: 28 additions & 0 deletions packages/tabs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@alfalab/core-components-tabs",
"version": "1.0.0",
"description": "Tabs components",
"keywords": [],
"license": "ISC",
"main": "dist/index.js",
"types": "dist/typings.d.ts",
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.6"
},
"dependencies": {
"@alfalab/core-components-tag": "^1.3.0",
"compute-scroll-into-view": "^1.0.13",
"lodash.debounce": "^4.0.8"
},
"peerDependencies": {
"classnames": "^2.2.6",
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
}
138 changes: 138 additions & 0 deletions packages/tabs/src/Component.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Meta, Story, Props, Preview, Title } from '@storybook/addon-docs/blocks';
import { boolean, select } from '@storybook/addon-knobs';

import {
TabsDesktop,
TabsMobile,
TabsResponsive,
PrimaryTabListDesktop,
PrimaryTabListMobile,
SecondaryTabListDesktop,
SecondaryTabListMobile,
Tab,
} from './index';

import { name, version } from '../package.json';

export const titles = Array(7).fill(0).map((_, i) => ({ title: `Таб ${i + 1}`, id: i + 1 }));


<Meta title='Компоненты|Tabs' />

<Title>
Tabs ({name}@{version})
</Title>

## Описание

Компоненты для построения табов.

### TabsDesktop

<Story name='TabsDesktop'>
{React.createElement(() => {
const [selectedId, setSelectedId] = React.useState('tab-1');
const handleChange = (event, { selectedId }) => setSelectedId(selectedId);
return (
<TabsDesktop
view={select('view', ['primary', 'secondary'], 'primary')}
selectedId={selectedId}
keepMounted={boolean('keepMounted')}
onChange={handleChange}
scrollable={boolean('scrollable')}
gaps={select('gaps', ['default', 'wide'], 'default')}
>
<Tab title='Таб 1' id='tab-1'>
Таб 1
</Tab>
<Tab title='Таб 2' id='tab-2'>
Таб 2
</Tab>
<Tab title='Таб 3' id='tab-3'>
Таб 3
</Tab>
<Tab title='Таб 4' id='tab-4'>
Таб 4
</Tab>
<Tab title='Таб 5' id='tab-5'>
Таб 5
</Tab>
</TabsDesktop>
);
})}
</Story>

### TabsMobile

<Story name='TabsMobile'>
{React.createElement(() => {
const [selectedId, setSelectedId] = React.useState('tab-1');
const handleChange = (event, { selectedId }) => setSelectedId(selectedId);
return (
<TabsMobile
view={select('view', ['primary', 'secondary'], 'primary')}
selectedId={selectedId}
keepMounted={boolean('keepMounted')}
onChange={handleChange}
scrollable={boolean('scrollable')}
>
<Tab title='Таб 1' id='tab-1'>
Таб 1
</Tab>
<Tab title='Таб 2' id='tab-2'>
Таб 2
</Tab>
<Tab title='Таб 3' id='tab-3'>
Таб 3
</Tab>
<Tab title='Таб 4' id='tab-4'>
Таб 4
</Tab>
<Tab title='Таб 5' id='tab-5'>
Таб 5
</Tab>
</TabsMobile>
);
})}
</Story>

### TabsResponsive

Адаптивная версия табов, перестраивается при 768px

<Story name='TabsResponsive'>
{React.createElement(() => {
const [selectedId, setSelectedId] = React.useState('tab-1');
const handleChange = (event, { selectedId }) => setSelectedId(selectedId);
return (
<TabsResponsive
view={select('view', ['primary', 'secondary'], 'primary')}
selectedId={selectedId}
keepMounted={boolean('keepMounted')}
onChange={handleChange}
scrollable={boolean('scrollable')}
>
<Tab title='Таб 1' id='tab-1'>
Таб 1
</Tab>
<Tab title='Таб 2' id='tab-2'>
Таб 2
</Tab>
<Tab title='Таб 3' id='tab-3'>
Таб 3
</Tab>
<Tab title='Таб 4' id='tab-4'>
Таб 4
</Tab>
<Tab title='Таб 5' id='tab-5'>
Таб 5
</Tab>
</TabsResponsive>
);
})}
</Story>

<Props components={{
TabsDesktop,
TabsMobile
}} />
5 changes: 5 additions & 0 deletions packages/tabs/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './primary-tablist';
export * from './secondary-tablist';
export * from './scrollable-container';
export * from './tab';
export * from './tabs';
14 changes: 14 additions & 0 deletions packages/tabs/src/components/primary-tablist/Component.desktop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import cn from 'classnames';
import { PrimaryTabList } from './Component';
import { TabListProps, Gaps } from '../../typings';

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

export const PrimaryTabListDesktop = ({
gaps = 'default',
className,
...restProps
}: TabListProps & Gaps) => (
<PrimaryTabList {...restProps} styles={styles} className={cn(className, styles[gaps])} />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { PrimaryTabList } from './Component';
import { TabListProps } from '../../typings';

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

export const PrimaryTabListMobile = (props: TabListProps) => (
<PrimaryTabList {...props} styles={styles} />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import cn from 'classnames';
import { PrimaryTabList } from './Component';
import { TabListProps, Gaps } from '../../typings';
import { useWindowWidth } from '../../utils';

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

export const PrimaryTabListResponsive = ({
gaps = 'default',
className,
...restProps
}: TabListProps & Gaps) => {
const width = useWindowWidth();

/**
* Вызываем ререндер при переходе из одного вида в другой.
* Это нужно, чтобы подчеркивание имело правильную ширину (lineStyles)
*/
const key = width >= 768 ? 'desktop' : 'mobile';

return (
<PrimaryTabList
{...restProps}
styles={styles}
className={cn(className, styles[gaps])}
key={key}
/>
);
};
70 changes: 70 additions & 0 deletions packages/tabs/src/components/primary-tablist/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState, useEffect } from 'react';
import cn from 'classnames';
import { useTabs } from '../../useTabs';
import { ScrollableContainer } from '../scrollable-container';
import { TabListProps, Styles } from '../../typings';

export const PrimaryTabList = ({
styles = {},
className,
titles = [],
selectedId = titles.length ? titles[0].id : undefined,
scrollable = true,
onChange,
dataTestId,
}: TabListProps & Styles) => {
const { selectedTab, focusedTab, getTabListItemProps } = useTabs({
titles,
selectedId,
onChange,
});
const [lineStyles, setLineStyles] = useState<{ width?: number; transform?: string }>();

useEffect(() => {
if (selectedTab) {
setLineStyles({
width: selectedTab.offsetWidth,
transform: `translateX(${selectedTab.offsetLeft}px)`,
});
}
}, [selectedTab]);

const renderContent = () => (
<React.Fragment>
{titles.map((item, index) => (
<button
{...getTabListItemProps(index)}
type='button'
key={item.id}
className={cn(styles.title, {
[styles.selected]: item.id === selectedId,
})}
>
<span tabIndex={-1} className={styles.titleWrapper}>
{item.title}
</span>
</button>
))}

<div className={styles.line} style={lineStyles} />
</React.Fragment>
);

return (
<div
role='tablist'
data-test-id={dataTestId}
className={cn(styles.component, className, {
[styles.scrollable]: scrollable,
})}
>
{scrollable ? (
<ScrollableContainer activeChild={focusedTab || selectedTab}>
{renderContent()}
</ScrollableContainer>
) : (
<div className={styles.container}>{renderContent()}</div>
)}
</div>
);
};
37 changes: 37 additions & 0 deletions packages/tabs/src/components/primary-tablist/desktop.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@import './index.module.css';

.component {
composes: component from './index.module.css';

margin-bottom: var(--gap-2xl);
margin-top: var(--gap-2xl);
}

.title {
composes: title from './index.module.css';

@mixin system_18-22_regular;
font-weight: 300;
}

.default .title {
margin-right: var(--gap-2xl);

&:last-child {
margin-right: 0;
}
}

.wide .title {
margin-right: var(--gap-4xl);

&:last-child {
margin-right: 0;
}
}

.titleWrapper {
composes: titleWrapper from './index.module.css';

padding-bottom: var(--gap-m);
}

0 comments on commit b08b668

Please sign in to comment.