Skip to content

ENSITECH-285 refactor tabs component #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
"lerna": "^7.4.2",
"lint-staged": "11.2.6",
"mockdate": "^3.0.5",
"next": "^14.0.3",
"npm": "^8.19.4",
"npm-run-all": "4.1.5",
"postcss-preset-env": "9.1.0",
Expand Down
39 changes: 36 additions & 3 deletions packages/tabs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# Компонент Tabs
Used for displaying various content in tabs.

Используется для удобного пользователю разбиения разностороннего контента на несколько вкладок.
List of components:
- TabList
- Tab
- TabLinkTitle

Поддерживает два режима отображения большого количества вкладок - рендер с горизонтальным скроллом и сворачивание не помещающихся табов в Select.
## TabList
TabList component is a container for rendering a list of tab titles in a scrollable or collapsible format. It manages tab selection, collapsibility, and responsiveness, and can be customized with additional functionality.

## Tab
Tab is a container for the tab content. It displays children inside a div and manages visibility, disabled state, and styling.

## TabLinkTitle
TabLinkTitle is a link representing a tab title. It supports custom styles, state (e.g., selected, disabled), and optional addons (left/right). You can use custom wrapper for link, such as ```Link``` component from ```next/link```.

## Usage
```tsx
<TabList scrollable>
<Tab title="First tab" id="1" leftAddons={<TicketIcon />}>
Content of first tab
</Tab>
<Tab title="2nd disabled" id="2" disabled>
<div>You cant reach me</div>
</Tab>
<Tab title="Third tab" id="custom_string" rightAddons={<span>99+</span>}>
<div>Its a third tab</div>
</Tab>
<Tab
title="Custom tab title"
id="with_link"
renderTitle={props => <TabLinkTitle href="about:blank" target="_blank" {...props} />}
unfocusable
>
<div>Fourth tab</div>
</Tab>
</TabList>
```
1 change: 0 additions & 1 deletion packages/tabs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
},
"dependencies": {
"tslib": "^2.6.2",
"next": "^14.0.3",
"@juggle/resize-observer": "^3.4.0",
"compute-scroll-into-view": "^3.1.0",
"@ensi-platform/core-components-common": "^1.0.0",
Expand Down
114 changes: 114 additions & 0 deletions packages/tabs/src/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { defaultTheme } from '@ensi-platform/core-components-common';

import { type FC, type MouseEvent, useMemo, useState } from 'react';

import { Tab } from './components/Tab';
import { TabHeadingList as DefaultTabList } from './components/TabHeadingList';
import { TabsComponent } from './components/Tabs';
import { TabLinkTitle } from './components/Title/LinkTitle';
import { TabsThemeProvider } from './context';
import { useMedia } from './scripts/hooks/useMedia';
import { TABS_THEMES } from './themes';
import type { ITabsProps, ITabsState, SelectedIdType, TabsMatchMediaType } from './types/component';

const {
mediaQueries: MEDIA_QUERIES,
tokens: {
layout: { breakpoints: Breakpoints },
},
} = defaultTheme;

export type { ITabsProps };

type TabListComponentPropsType = Omit<ITabsProps, 'TabHeadingList'> & {
TabHeadingList?: ITabsProps['TabHeadingList'];
};

interface ITabsCompositionProps {
Tab: typeof Tab;
LinkTitle: typeof TabLinkTitle;
}

/**
* Root component for tabs list with theming
* @param breakpoint width breakpoint for desktop appearance
* @param size size to use, passed into context
* @param ShowMoreButton component for 'Show more' button
* @param TabHeadingList component for rendering tabs headings
* @param className additional class for tabs component
* @param containerCSS additional CSS for tabs headings list container
* @param defaultMatch default component appearance
* @param children list of tabs
* @param selectedId id of current selected tab
* @param countErrors array of errors by each tab
* @param scrollable use scrollable container for headings
* @param collapsible collapse extra tab headings
* @param collapsedTabsIds ids of tabs to collapse into dropdown menu
* @param keepMounted render tabs even if they are not visible
* @param dataTestId id for automatic testing
* @param onChange tab change event handler
*/
export const TabList: FC<TabListComponentPropsType> & ITabsCompositionProps = ({
breakpoint = Breakpoints.md,
size = 'md',
TabHeadingList = DefaultTabList,
theme: themeName = 'basic',
variant = 'primary',
prefix: propsPrefix,
selectedId: propsSelectedId,
collapsible,
fullWidthScroll,
mobile: mobileProps,
scrollable,
onChange,
...props
}) => {
const isControlled = typeof propsSelectedId !== 'undefined' && typeof onChange !== 'undefined';
const theme = typeof themeName === 'string' ? TABS_THEMES[themeName] : themeName;

const [view] = useMedia<TabsMatchMediaType>([['desktop', MEDIA_QUERIES.mdMin]], 'desktop');
const mobile = typeof mobileProps === 'undefined' ? view === 'mobile' : mobileProps;

const state = useMemo<ITabsState>(
() => ({
mobile,
collapsible,
fullWidthScroll,
scrollable,
}),
[collapsible, fullWidthScroll, mobile, scrollable]
);

const localPrefix = '';
const prefix = typeof propsPrefix === 'undefined' ? localPrefix : propsPrefix;

const [localSelectedId, setLocalSelectedId] = useState<SelectedIdType>();

const handleChange = (e: MouseEvent, payload: { selectedId: SelectedIdType }) => {
if (isControlled) {
onChange?.(e, payload);
return;
}

setLocalSelectedId(payload.selectedId);
};

const selectedId = isControlled ? propsSelectedId : localSelectedId;

return (
<TabsThemeProvider idPrefix={prefix} size={size} state={state} theme={theme} variant={variant}>
<TabsComponent
TabHeadingList={TabHeadingList}
selectedId={selectedId}
collapsible={collapsible}
onChange={handleChange}
breakpoint={breakpoint}
{...props}
/>
</TabsThemeProvider>
);
};

TabList.displayName = 'Tabs';
TabList.Tab = Tab;
TabList.LinkTitle = TabLinkTitle;
Loading