diff --git a/cypress/snapshots/b2c/components/Tabs/Tabs.component-test.tsx/plasma-b2c Tabs -- _orientation.snap.png b/cypress/snapshots/b2c/components/Tabs/Tabs.component-test.tsx/plasma-b2c Tabs -- _orientation.snap.png new file mode 100644 index 0000000000..68943e0345 Binary files /dev/null and b/cypress/snapshots/b2c/components/Tabs/Tabs.component-test.tsx/plasma-b2c Tabs -- _orientation.snap.png differ diff --git a/cypress/snapshots/b2c/components/Tabs/Tabs.update-test.component-test.tsx/plasma-b2c Tabs -- [PLASMA-T800] Tabs size=s, enableContentLeft, enableContentRight.snap.png b/cypress/snapshots/b2c/components/Tabs/Tabs.update-test.component-test.tsx/plasma-b2c Tabs -- [PLASMA-T800] Tabs size=s, enableContentLeft, enableContentRight.snap.png index 14cd8c5196..b5c4aed3bd 100644 Binary files a/cypress/snapshots/b2c/components/Tabs/Tabs.update-test.component-test.tsx/plasma-b2c Tabs -- [PLASMA-T800] Tabs size=s, enableContentLeft, enableContentRight.snap.png and b/cypress/snapshots/b2c/components/Tabs/Tabs.update-test.component-test.tsx/plasma-b2c Tabs -- [PLASMA-T800] Tabs size=s, enableContentLeft, enableContentRight.snap.png differ diff --git a/cypress/snapshots/web/components/Tabs/Tabs.component-test.tsx/plasma-web Tabs -- _orientation.snap.png b/cypress/snapshots/web/components/Tabs/Tabs.component-test.tsx/plasma-web Tabs -- _orientation.snap.png new file mode 100644 index 0000000000..45d7bf65dd Binary files /dev/null and b/cypress/snapshots/web/components/Tabs/Tabs.component-test.tsx/plasma-web Tabs -- _orientation.snap.png differ diff --git a/packages/plasma-b2c/api/plasma-b2c.api.md b/packages/plasma-b2c/api/plasma-b2c.api.md index dfe910be29..9cb9adc88d 100644 --- a/packages/plasma-b2c/api/plasma-b2c.api.md +++ b/packages/plasma-b2c/api/plasma-b2c.api.md @@ -40,6 +40,8 @@ import { BaseboxProps } from '@salutejs/plasma-new-hope/styled-components'; import { BaseCallbackChangeInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseCallbackKeyboardInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseProps } from '@salutejs/plasma-new-hope/types/components/Autocomplete/Autocomplete.types'; +import { BaseTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/TabItem.types'; +import { BaseTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { BasicProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxNew/Combobox.types'; import { BlurProps } from '@salutejs/plasma-core'; import { blurs } from '@salutejs/plasma-core'; @@ -100,10 +102,10 @@ import { convertRoundnessMatrix } from '@salutejs/plasma-core'; import { CounterProps } from '@salutejs/plasma-new-hope/styled-components'; import { counterTokens } from '@salutejs/plasma-new-hope/styled-components'; import { CustomComboboxProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxOld/Combobox.types'; +import { CustomHorizontalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/Popover/Popover.types'; -import { CustomTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/TabItem/TabItem.types'; -import { CustomTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/Tabs/Tabs.types'; import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; +import { CustomVerticalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { DateInfo } from '@salutejs/plasma-new-hope/types/components/Calendar/Calendar.types'; import { DatePickerCalendarProps } from '@salutejs/plasma-new-hope/types/components/DatePicker/DatePickerBase.types'; import { datePickerClasses } from '@salutejs/plasma-new-hope/styled-components'; @@ -273,11 +275,9 @@ import { SubtitleProps } from '@salutejs/plasma-new-hope/styled-components'; import { SwitchProps as SwitchProps_2 } from '@salutejs/plasma-new-hope/styled-components'; import { SyntheticEvent } from 'react'; import { syntheticFocus } from '@salutejs/plasma-core'; -import { TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { TabItemRefs } from '@salutejs/plasma-new-hope/styled-components'; import { TabsContext } from '@salutejs/plasma-new-hope/styled-components'; import { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; -import { TabsProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextareaHTMLAttributes } from '@salutejs/plasma-core'; import type { TextAreaProps as TextAreaProps_2 } from '@salutejs/plasma-hope'; import { TextareaResize } from '@salutejs/plasma-core'; @@ -3317,15 +3317,17 @@ export type SwitchProps = ComponentProps; export { syntheticFocus } +// Warning: (ae-forgotten-export) The symbol "TabItemProps" needs to be exported by the entry point index.d.ts +// // @public -export const TabItem: ForwardRefExoticComponent & AsProps_2 & CustomTabItemProps & RefAttributes>; - -export { TabItemProps } +export const TabItem: (props: TabItemProps) => JSX.Element; export { TabItemRefs } +// Warning: (ae-forgotten-export) The symbol "TabsProps" needs to be exported by the entry point index.d.ts +// // @public -export const Tabs: ForwardRefExoticComponent & AsProps_2 & CustomTabsProps & RefAttributes>; +export const Tabs: (props: TabsProps) => JSX.Element; export { TabsContext } @@ -3334,8 +3336,6 @@ export const TabsController: ForwardRefExoticComponent>; diff --git a/packages/plasma-b2c/src/components/Tabs/TabItem.tsx b/packages/plasma-b2c/src/components/Tabs/TabItem.tsx index 516ff278bc..a8c2061a79 100644 --- a/packages/plasma-b2c/src/components/Tabs/TabItem.tsx +++ b/packages/plasma-b2c/src/components/Tabs/TabItem.tsx @@ -1,13 +1,29 @@ -import { tabItemConfig, component, mergeConfig, TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; -import { ForwardRefExoticComponent, RefAttributes } from 'react'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabItemConfig, + verticalTabItemConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './TabItem.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabItem.config'; +import { config as verticalConfig } from './vertical/VerticalTabItem.config'; -const mergedConfig = mergeConfig(tabItemConfig, config); +const mergedHorizontalTabItemConfig = mergeConfig(horizontalTabItemConfig, horizontalConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); + +const mergedVerticalTabItemConfig = mergeConfig(verticalTabItemConfig, verticalConfig); +const VerticalTabItem = component(mergedVerticalTabItemConfig); + +type TabItemProps = ComponentProps | ComponentProps; /** * Элемент списка, недопустимо использовать вне компонента Tabs. */ -export const TabItem = component(mergedConfig) as ForwardRefExoticComponent< - TabItemProps & RefAttributes ->; +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-b2c/src/components/Tabs/Tabs.component-test.tsx b/packages/plasma-b2c/src/components/Tabs/Tabs.component-test.tsx index 3ed0569952..16bff6c0e9 100644 --- a/packages/plasma-b2c/src/components/Tabs/Tabs.component-test.tsx +++ b/packages/plasma-b2c/src/components/Tabs/Tabs.component-test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { mount, CypressTestDecorator, getComponent } from '@salutejs/plasma-cy-utils'; +import { mount, CypressTestDecorator, getComponent, PadMe } from '@salutejs/plasma-cy-utils'; const items = [{ label: 'Joy' }, { label: 'Sber' }, { label: 'Athena' }]; @@ -15,14 +15,14 @@ describe('plasma-b2c: Tabs', () => { {items.map((item, i) => ( - + {item.label} ))} {items.map((item, i) => ( - + {item.label} ))} @@ -38,7 +38,38 @@ describe('plasma-b2c: Tabs', () => { {items.map((item, i) => ( - + + {item.label} + + ))} + + , + ); + + cy.matchImageSnapshot(); + }); + + it('_orientation', () => { + mount( + + + {items.map((item, i) => ( + + {item.label} + + ))} + + + + {items.map((item, i) => ( + {item.label} ))} @@ -54,7 +85,7 @@ describe('plasma-b2c: Tabs', () => { {items.map((item, i) => ( - + {item.label} ))} @@ -71,7 +102,7 @@ describe('plasma-b2c: Tabs', () => { {items.map((item, i) => ( - + {item.label} ))} @@ -92,7 +123,7 @@ describe('plasma-b2c: Tabs', () => { return ( {items.map((item, i) => ( - setIndex(i)}> + setIndex(i)}> {item.label} ))} @@ -117,7 +148,7 @@ describe('plasma-b2c: Tabs', () => { {items.map((item, i) => ( - + {item.label} ))} diff --git a/packages/plasma-b2c/src/components/Tabs/Tabs.stories.tsx b/packages/plasma-b2c/src/components/Tabs/Tabs.stories.tsx index db18da20e5..5efbe4fdc6 100644 --- a/packages/plasma-b2c/src/components/Tabs/Tabs.stories.tsx +++ b/packages/plasma-b2c/src/components/Tabs/Tabs.stories.tsx @@ -15,16 +15,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -40,8 +43,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -65,14 +66,25 @@ const meta: Meta = { control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['pilled', 'animated', 'view', 'as', 'forwardedAs', 'outsideScroll', 'index']), + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -81,32 +93,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -115,6 +148,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -127,26 +161,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })}
); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -155,6 +209,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -174,25 +229,45 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
setIndex(item.value as number)} > @@ -201,7 +276,7 @@ const StoryShowAll = (props: StoryTabsProps) => { view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -212,14 +287,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -237,11 +325,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; + case 'showAll': + return ; + default: + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; case 'showAll': - return ; + return ; default: - return ; + return ; } }, }; @@ -254,12 +603,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -293,7 +643,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, - ...disableProps(['clip']), + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/plasma-b2c/src/components/Tabs/Tabs.tsx b/packages/plasma-b2c/src/components/Tabs/Tabs.tsx index b39e2bf6b6..9475451e71 100644 --- a/packages/plasma-b2c/src/components/Tabs/Tabs.tsx +++ b/packages/plasma-b2c/src/components/Tabs/Tabs.tsx @@ -1,10 +1,29 @@ -import { tabsConfig, component, mergeConfig, TabsProps } from '@salutejs/plasma-new-hope/styled-components'; -import { ForwardRefExoticComponent, RefAttributes } from 'react'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabsConfig, + verticalTabsConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './Tabs.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; -const mergedConfig = mergeConfig(tabsConfig, config); /** * Контейнер вкладок, основной компонент для пользовательской сборки вкладок. */ -export const Tabs = component(mergedConfig) as ForwardRefExoticComponent>; +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-b2c/src/components/Tabs/TabsController.tsx b/packages/plasma-b2c/src/components/Tabs/TabsController.tsx index e5255de474..9de85c85d0 100644 --- a/packages/plasma-b2c/src/components/Tabs/TabsController.tsx +++ b/packages/plasma-b2c/src/components/Tabs/TabsController.tsx @@ -1,10 +1,28 @@ -import { createTabsController } from '@salutejs/plasma-new-hope/styled-components'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); /** * Контроллер вкладок. * Позволяет использовать клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. */ -export const TabsController = createTabsController(Tabs, TabItem); +export const TabsController = createTabsController( + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, +); diff --git a/packages/plasma-b2c/src/components/Tabs/TabItem.config.ts b/packages/plasma-b2c/src/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 89% rename from packages/plasma-b2c/src/components/Tabs/TabItem.config.ts rename to packages/plasma-b2c/src/components/Tabs/horizontal/HorizontalTabItem.config.ts index d9ba625e1b..5051fc4a16 100644 --- a/packages/plasma-b2c/src/components/Tabs/TabItem.config.ts +++ b/packages/plasma-b2c/src/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -9,13 +9,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -27,21 +32,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -52,22 +57,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -79,22 +83,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -105,12 +108,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--inverse-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--inverse-text-secondary); `, }, size: { diff --git a/packages/plasma-b2c/src/components/Tabs/Tabs.config.ts b/packages/plasma-b2c/src/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 100% rename from packages/plasma-b2c/src/components/Tabs/Tabs.config.ts rename to packages/plasma-b2c/src/components/Tabs/horizontal/HorizontalTabs.config.ts diff --git a/packages/plasma-b2c/src/components/Tabs/index.ts b/packages/plasma-b2c/src/components/Tabs/index.ts index f2d92ec950..10fada1d81 100644 --- a/packages/plasma-b2c/src/components/Tabs/index.ts +++ b/packages/plasma-b2c/src/components/Tabs/index.ts @@ -1,6 +1,6 @@ export { TabsController } from './TabsController'; export { TabItemRefs, TabsContext } from '@salutejs/plasma-new-hope/styled-components'; -export type { TabsProps, TabItemProps, TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; +export type { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; export { Tabs } from './Tabs'; export { TabItem } from './TabItem'; diff --git a/packages/plasma-b2c/src/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/plasma-b2c/src/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..97c7fbbf6d --- /dev/null +++ b/packages/plasma-b2c/src/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,118 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-b2c/src/components/Tabs/vertical/VerticalTabs.config.ts b/packages/plasma-b2c/src/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..5bef8112a6 --- /dev/null +++ b/packages/plasma-b2c/src/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,62 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + stretch: { + true: css` + ${tabsTokens.containerHeight}: 100%; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/components/Tabs/TabItem.types.ts b/packages/plasma-new-hope/src/components/Tabs/TabItem.types.ts new file mode 100644 index 0000000000..4a8a83d634 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/TabItem.types.ts @@ -0,0 +1,105 @@ +import type { ButtonHTMLAttributes, ReactNode } from 'react'; + +import type { AsProps } from '../../types'; + +type RightContent = + | { + /** + * Значение таба + */ + value?: string | number; + /** + * Слот для контента справа, например `Icon` + */ + contentRight?: never; + } + | { + /** + * Значение таба + */ + value?: never; + /** + * Слот для контента справа, например `Icon` + */ + contentRight?: ReactNode; + }; + +export interface BaseTabItemProps extends ButtonHTMLAttributes, AsProps { + /** + * Выбран ли TabItem + */ + selected?: boolean; + /** + * TabItem неактивен + * @default false + */ + disabled?: boolean; + /** + * Контент слева + */ + contentLeft?: ReactNode; + /** + * Контент справа + */ + contentRight?: ReactNode; + + /** + * Callback, необходимый для клавиатурной навигации + */ + onIndexChange?: (index: number) => void; + /** + * Индекс TabItem внутри Tabs - необходим для клавиатурной навигации + */ + itemIndex?: number; +} + +export type CustomHorizontalTabItemProps = { + /** + * Расположение табов + */ + orientation?: 'horizontal'; + /** + * TabItem c округлым border-radius + * @default false + */ + pilled?: boolean; + /** + * Фон TabItem меняется с анимацией + * @default true + */ + animated?: boolean; + /** + * Вид TabItem + */ + view?: string; + /** + * Размер TabItem + */ + size?: string; + /** + * Активен ли TabItem + * @deprecated Используйте свойство `selected` + */ + isActive?: boolean; +} & RightContent; + +export type CustomVerticalTabItemProps = { + /** + * Расположение табов + */ + orientation: 'vertical'; + /** + * Вид TabItem + */ + view?: string; + /** + * Размер TabItem + */ + size?: string; +} & RightContent; + +export type HorizontalTabItemProps = BaseTabItemProps & CustomHorizontalTabItemProps; + +export type VerticalTabItemProps = BaseTabItemProps & CustomVerticalTabItemProps; + +export type TabItemProps = HorizontalTabItemProps | VerticalTabItemProps; diff --git a/packages/plasma-new-hope/src/components/Tabs/Tabs.template-doc.mdx b/packages/plasma-new-hope/src/components/Tabs/Tabs.template-doc.mdx index 4b687b8523..bb63d0cef2 100644 --- a/packages/plasma-new-hope/src/components/Tabs/Tabs.template-doc.mdx +++ b/packages/plasma-new-hope/src/components/Tabs/Tabs.template-doc.mdx @@ -15,6 +15,11 @@ import { PropsTable, Description } from '@site/src/components'; +:::caution Взаимоисключающие свойства +Свойство `value` - это значение таба. Оно отображается справа от основного текста.
+`value` и `contentRight` взаимоисключающие: если передано одно, второе передать нельзя. +::: + ## TabsController (deprecated) Вместо этого используйте Tabs, TabItem, указав параметры index, itemIndex, onIndexChange. @@ -56,6 +61,41 @@ export function App() { } ``` +### Расположение табов +Табы могут быть горизонтальными (по умолчанию) и вертикальными. За это отвечает свойство `orientation`. + +```tsx live +import React, { useState } from 'react'; +import { Tabs, TabItem } from '@salutejs/{{ package }}'; +import { IconClock } from '@salutejs/plasma-icons'; + +export function App() { + const items = Array(8).fill(0); + const [index, setIndex] = useState(0); + + return ( +
+ + {items.map((_, i) => ( + } + onClick={() => setIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ ); +} +``` + ### Пример с прокруткой ```tsx live @@ -143,8 +183,9 @@ export function App() { ``` ### Подключение клавиатурной навигации -Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`. -Клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. +Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`.
+Для горизонтальных табов: клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам.
+Для вертикальных табов: клавиши ArrowUp, ArrowDown, Home, End для навигации по вкладкам. ```tsx live import React, { useState } from 'react'; @@ -153,27 +194,51 @@ import { IconClock } from '@salutejs/plasma-icons'; export function App() { const items = Array(4).fill(0); - const [index, setIndex] = useState(0); + const [horizontalIndex, setHorizontalIndex] = useState(0); + const [verticalIndex, setVerticalIndex] = useState(0); return ( -
- - {items.map((_, i) => ( - setIndex(i)} - selected={i === index} - tabIndex={0} - contentLeft={} - onClick={() => setIndex(i)} - > - {`Label${i + 1}`} - - ))} - +
+
+ + {items.map((_, i) => ( + setHorizontalIndex(i)} + selected={i === horizontalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setHorizontalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ +
+ + {items.map((_, i) => ( + setVerticalIndex(i)} + selected={i === verticalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setVerticalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
); } diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.types.ts b/packages/plasma-new-hope/src/components/Tabs/Tabs.types.ts similarity index 61% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.types.ts rename to packages/plasma-new-hope/src/components/Tabs/Tabs.types.ts index 6b811e903b..0d239f4d5c 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.types.ts +++ b/packages/plasma-new-hope/src/components/Tabs/Tabs.types.ts @@ -1,8 +1,8 @@ import type { HTMLAttributes } from 'react'; -import type { AsProps } from '../../../../types'; +import type { AsProps } from '../../types'; -export type CustomTabsProps = { +export interface BaseTabsProps extends HTMLAttributes, AsProps { /** * Как ведет себя компонент при ограничении ширины * @default 'scroll' @@ -16,6 +16,17 @@ export type CustomTabsProps = { * @default false */ disabled?: boolean; + /** + * Индекс активного элемента, необходим для клавиатурной навигации + */ + index?: number; +} + +export type CustomHorizontalTabsProps = { + /** + * Расположение табов + */ + orientation?: 'horizontal'; /** * Табы растянуты на доступную область * @default false @@ -26,18 +37,14 @@ export type CustomTabsProps = { * @default false */ pilled?: boolean; - /** - * Размер табов - */ - size?: string; /** * Вид табов */ view?: string; /** - * Индекс активного элемента, необходим для клавиатурной навигации + * Размер табов */ - index?: number; + size?: string; /** * Уберет скругление с выбранной стороны и подвинет контейнер * @deprecated @@ -45,4 +52,28 @@ export type CustomTabsProps = { outsideScroll?: boolean | { left?: string; right?: string }; }; -export type TabsProps = HTMLAttributes & AsProps & CustomTabsProps; +export type CustomVerticalTabsProps = { + /** + * Расположение табов + */ + orientation: 'vertical'; + /** + * Наличие divider + * @default true + */ + hasDivider?: boolean; + /** + * Вид табов + */ + view?: string; + /** + * Размер табов + */ + size?: string; +}; + +export type HorizontalTabsProps = BaseTabsProps & CustomHorizontalTabsProps; + +export type VerticalTabsProps = BaseTabsProps & CustomVerticalTabsProps; + +export type TabsProps = HorizontalTabsProps | VerticalTabsProps; diff --git a/packages/plasma-new-hope/src/components/Tabs/createTabsController.tsx b/packages/plasma-new-hope/src/components/Tabs/createTabsController.tsx index 1f20842fe9..e341bb8937 100644 --- a/packages/plasma-new-hope/src/components/Tabs/createTabsController.tsx +++ b/packages/plasma-new-hope/src/components/Tabs/createTabsController.tsx @@ -1,9 +1,10 @@ import React, { forwardRef, ForwardRefExoticComponent, RefAttributes } from 'react'; -import { TabsProps, TabItemProps } from '.'; +import { HorizontalTabItemProps } from './TabItem.types'; +import { HorizontalTabsProps } from './Tabs.types'; -export interface TabsControllerProps extends TabsProps { - items: Array<{ label: string } & TabItemProps>; +export interface TabsControllerProps extends HorizontalTabsProps { + items: Array<{ label: string } & HorizontalTabItemProps>; index: number; onIndexChange: (index: number) => void; children?: never; @@ -18,8 +19,8 @@ export interface TabsControllerProps extends TabsProps { * кастомизировать стили, при этом сохраняя единый интерфейс и функционал. */ export function createTabsController( - ListComponent: ForwardRefExoticComponent>, - ItemComponent: ForwardRefExoticComponent>, + ListComponent: ForwardRefExoticComponent>, + ItemComponent: ForwardRefExoticComponent>, ) { // eslint-disable-next-line prefer-arrow-callback return forwardRef(function TabsController({ items, index, autoscroll, onIndexChange, ...rest }, ref) { diff --git a/packages/plasma-new-hope/src/components/Tabs/index.ts b/packages/plasma-new-hope/src/components/Tabs/index.ts index 9cb98ffdb9..9b0546d2b3 100644 --- a/packages/plasma-new-hope/src/components/Tabs/index.ts +++ b/packages/plasma-new-hope/src/components/Tabs/index.ts @@ -1,8 +1,10 @@ -export { tabsRoot, tabsConfig } from './ui/Tabs/Tabs'; -export { tabItemRoot, tabItemConfig } from './ui/TabItem/TabItem'; +export { horizontalTabsRoot, horizontalTabsConfig } from './ui/horizontal/HorizontalTabs/HorizontalTabs'; +export { horizontalTabItemRoot, horizontalTabItemConfig } from './ui/horizontal/HorizontalTabItem/HorizontalTabItem'; +export { verticalTabsRoot, verticalTabsConfig } from './ui/vertical/VerticalTabs/VerticalTabs'; +export { verticalTabItemRoot, verticalTabItemConfig } from './ui/vertical/VerticalTabItem/VerticalTabItem'; export { tokens as tabsTokens } from './tokens'; export { TabItemRefs, TabsContext } from './TabsContext'; export { createTabsController } from './createTabsController'; export type { TabsControllerProps } from './createTabsController'; -export type { TabsProps } from './ui/Tabs/Tabs.types'; -export type { TabItemProps } from './ui/TabItem/TabItem.types'; +export type { HorizontalTabsProps } from './Tabs.types'; +export type { HorizontalTabItemProps } from './TabItem.types'; diff --git a/packages/plasma-new-hope/src/components/Tabs/tokens.ts b/packages/plasma-new-hope/src/components/Tabs/tokens.ts index 8a294602f3..7964370952 100644 --- a/packages/plasma-new-hope/src/components/Tabs/tokens.ts +++ b/packages/plasma-new-hope/src/components/Tabs/tokens.ts @@ -3,6 +3,7 @@ export const classes = { tabsPilled: 'tabs-pilled', tabsGroupFilledBackground: 'tabs-group-filled-background', tabsStretch: 'tabs-stretched', + tabsNoDivider: 'tabs-no-divider', tabItemDivider: 'tab-item-divider', tabItemAnimated: 'tab-item-animated', tabContent: 'tab-item-content', @@ -10,6 +11,8 @@ export const classes = { tabLeftContent: 'tab-item-left-content', tabsHasLeftArrow: 'tabs-has-left-arrow', tabsHasRightArrow: 'tabs-has-right-arrow', + tabsHasTopArrow: 'tabs-has-top-arrow', + tabsHasBottomArrow: 'tabs-has-bottom-arrow', tabsClipScroll: 'tabs-clip-scroll', tabsClipShowAll: 'tabs-clip-show-all', }; @@ -17,14 +20,17 @@ export const classes = { export const tokens = { disabledOpacity: '--plasma-tabs-disabled-opacity', containerWidth: '--plasma-tabs-container-width', + containerHeight: '--plasma-tabs-container-height', cursor: '--plasma-tabs-cursor', color: '--plasma-tabs-color', colorHover: '--plasma-tabs-color-hover', + tabsDividerWidth: '--plasma-tabs-divider-width', tabsDividerHeight: '--plasma-tabs-divider-height', tabsDividerColor: '--plasma-tabs-divider-color', tabsDividerBorderRadius: '--plasma-tabs-divider-border-radius', + itemSelectedDividerWidth: '--plasma-tab-item-selected-divider-width', itemSelectedDividerHeight: '--plasma-tab-item-selected-divider-height', itemSelectedDividerColor: '--plasma-tab-item-selected-divider-color', itemSelectedDividerColorHover: '--plasma-tab-item-selected-hover-divider-color', @@ -47,6 +53,7 @@ export const tokens = { itemPadding: '--plasma-tab-item-padding', itemPaddingClear: '--plasma-tab-item-padding-clear', itemPaddingPilled: '--plasma-tab-item-padding-pilled', + itemPaddingOrientationVertical: '--plasma-tab-item-padding-orientation-vertical', itemMarginLeft: '--plasma-tab-item-margin-left', itemMarginLeftFilled: '--plasma-tab-item-margin-left-filled', itemContentGap: '--plasma-tab-item-content-gap', @@ -59,23 +66,23 @@ export const tokens = { arrowViewOuterPadding: '--plasma-tab-arrow-view-outer-padding', itemColor: '--plasma-tab-item-color', + itemValueColor: '--plasma-tab-item-value-color', itemBackgroundColor: '--plasma-tab-item-background-color', itemColorHover: '--plasma-tab-item-color-hover', + itemValueColorHover: '--plasma-tab-item-value-color-hover', itemColorActive: '--plasma-tab-item-color-active', + itemValueColorActive: '--plasma-tab-item-value-color-active', itemBackgroundColorHover: '--plasma-tab-item-background-color-hover', itemSelectedColor: '--plasma-tab-item-selected-color', + itemSelectedValueColor: '--plasma-tab-item-selected-value-color', itemSelectedBackgroundColor: '--plasma-tab-item-selected-background-color', itemSelectedColorHover: '--plasma-tab-item-selected-color-hover', + itemSelectedValueColorHover: '--plasma-tab-item-selected-value-color-hover', itemSelectedBackgroundColorHover: '--plasma-tab-item-selected-background-color-hover', itemBackgroundTransition: '--plasma-tab-item-background-transition', - additionalContentColor: '--plasma-tab-item-additional-content-color', - additionalContentHoverColor: '--plasma-tab-item-hover-additional-content-color', - additionalContentSelectedColor: '--plasma-tab-item-selected-additional-content-color', - additionalContentSelectedHoverColor: '--plasma-tab-item-selected-hover-additional-content-color', - fontFamily: '--plasma-tab-item-font-family', fontSize: '--plasma-tab-item-font-size', fontStyle: '--plasma-tab-item-font-style', diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.types.ts b/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.types.ts deleted file mode 100644 index 1a3f6dbdfa..0000000000 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.types.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { ButtonHTMLAttributes, ReactNode } from 'react'; - -import { AsProps } from '../../../../types'; - -export type CustomTabItemProps = { - /** - * Активен ли TabItem - * @deprecated Используйте свойство `selected` - */ - isActive?: boolean; - /** - * Выбран ли TabItem - */ - selected?: boolean; - /** - * Скрыт ли TabItem - * @default false - */ - disabled?: boolean; - /** - * TabItem c округлым border-radius - * @default false - */ - pilled?: boolean; - /** - * Фон TabItem меняется с анимацией - * @default true - */ - animated?: boolean; - /** - * Контент слева - */ - contentLeft?: ReactNode; - /** - * Контент справа - */ - contentRight?: ReactNode; - /** - * Callback, необходимый для клавиатурной навигации - */ - onIndexChange?: (index: number) => void; - /** - * Индекс TabItem внутри Tabs - необходим для клавиатурной навигации - */ - itemIndex?: number; - /** - * Размер TabItem - */ - size?: string; - /** - * Вид TabItem - */ - view?: string; -}; - -export type TabItemProps = ButtonHTMLAttributes & AsProps & CustomTabItemProps; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.styles.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/HorizontalTabItem.styles.ts similarity index 84% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.styles.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/HorizontalTabItem.styles.ts index 6b05009ed4..bfd36a8357 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.styles.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/HorizontalTabItem.styles.ts @@ -1,8 +1,8 @@ import { styled } from '@linaria/react'; import { css } from '@linaria/core'; -import { addFocus } from '../../../../mixins'; -import { tokens } from '../../tokens'; +import { addFocus } from '../../../../../mixins'; +import { tokens } from '../../../tokens'; export const base = css` position: relative; @@ -19,7 +19,7 @@ export const base = css` border: none; outline: none; cursor: pointer; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-tap-highlight-color: transparent; &:first-child { margin-left: 0; @@ -50,12 +50,16 @@ export const StyledContent = styled.div` padding: 0 var(${tokens.itemContentPaddingClear}, var(${tokens.itemContentPadding})); `; +export const TabItemValue = styled.span` + color: var(${tokens.itemValueColor}); +`; + export const RightContent = styled.div` display: flex; - color: var(${tokens.additionalContentColor}); + color: inherit; &:hover { - color: var(${tokens.additionalContentHoverColor}); + color: inherit; } `; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.tsx b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/HorizontalTabItem.tsx similarity index 79% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.tsx rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/HorizontalTabItem.tsx index a2bb149dc6..ae66c77580 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/TabItem.tsx +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/HorizontalTabItem.tsx @@ -1,20 +1,20 @@ import React, { forwardRef, useRef, useContext, useEffect, useCallback } from 'react'; import { useForkRef } from '@salutejs/plasma-core'; -import { RootProps } from '../../../../engines'; -import { classes } from '../../tokens'; -import { cx } from '../../../../utils'; -import { TabsContext } from '../../TabsContext'; +import { RootProps } from '../../../../../engines'; +import { classes } from '../../../tokens'; +import { cx } from '../../../../../utils'; +import { TabsContext } from '../../../TabsContext'; +import { HorizontalTabItemProps } from '../../../TabItem.types'; import { base as viewCSS } from './variations/_view/base'; import { base as sizeCSS } from './variations/_size/base'; import { base as pilledCSS } from './variations/_pilled/base'; import { base as disabledCSS } from './variations/_disabled/base'; -import { TabItemProps } from './TabItem.types'; -import { LeftContent, RightContent, StyledContent, base } from './TabItem.styles'; +import { LeftContent, RightContent, StyledContent, TabItemValue, base } from './HorizontalTabItem.styles'; -export const tabItemRoot = (Root: RootProps) => - forwardRef((props, outerRef) => { +export const horizontalTabItemRoot = (Root: RootProps) => + forwardRef((props, outerRef) => { const { size, view, @@ -23,6 +23,7 @@ export const tabItemRoot = (Root: RootProps) => disabled = false, pilled = false, children, + value, contentLeft, contentRight, animated = true, @@ -59,6 +60,10 @@ export const tabItemRoot = (Root: RootProps) => const onItemFocus = useCallback( (event) => { + if (disabled) { + return; + } + if (!hasKeyNavigation && innerRef?.current) { innerRef.current.scrollTo({ top: 0, @@ -69,7 +74,7 @@ export const tabItemRoot = (Root: RootProps) => return; } - if (disabled || !refs) { + if (!refs) { return; } @@ -112,16 +117,19 @@ export const tabItemRoot = (Root: RootProps) => <> {contentLeft && {contentLeft}} {children} - {contentRight && {contentRight}} + {!contentRight && value && {value}} + {!value && contentRight && ( + {contentRight} + )} ); }); -export const tabItemConfig = { - name: 'TabItem', +export const horizontalTabItemConfig = { + name: 'HorizontalTabItem', tag: 'button', - layout: tabItemRoot, + layout: horizontalTabItemRoot, base, variations: { size: { diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_disabled/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_disabled/base.ts similarity index 78% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_disabled/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_disabled/base.ts index 5bec213e96..df68986047 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_disabled/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_disabled/base.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { classes, tokens } from '../../../../tokens'; +import { classes, tokens } from '../../../../../tokens'; export const base = css` &[disabled] { @@ -11,7 +11,7 @@ export const base = css` background-color: var(${tokens.itemBackgroundColor}); } - &.${String(classes.selectedTabsItem)}:hover { + &.${classes.selectedTabsItem}:hover { color: var(${tokens.itemSelectedColor}); background-color: var(${tokens.itemSelectedBackgroundColor}); } diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_disabled/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_disabled/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_disabled/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_disabled/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_pilled/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_pilled/base.ts similarity index 74% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_pilled/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_pilled/base.ts index 0ee2798404..0e691583e3 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_pilled/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_pilled/base.ts @@ -1,9 +1,9 @@ import { css } from '@linaria/core'; -import { classes, tokens } from '../../../../tokens'; +import { classes, tokens } from '../../../../../tokens'; export const base = css` - &.${String(classes.tabsPilled)} { + &.${classes.tabsPilled} { --plasma_private-outline-radius: var(${tokens.itemPilledBorderRadius}); border-radius: var(${tokens.itemPilledBorderRadius}); diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_pilled/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_pilled/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_pilled/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_pilled/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_size/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_size/base.ts similarity index 91% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_size/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_size/base.ts index e9160373e3..c53958182e 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_size/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_size/base.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tokens } from '../../../../tokens'; +import { tokens } from '../../../../../tokens'; export const base = css` font-family: var(${tokens.fontFamily}); diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_size/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_size/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_size/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_size/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_view/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_view/base.ts similarity index 68% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_view/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_view/base.ts index ac4b309548..8dcef6cad1 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_view/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_view/base.ts @@ -1,7 +1,7 @@ import { css } from '@linaria/core'; -import { classes, tokens } from '../../../../tokens'; -import { RightContent } from '../../TabItem.styles'; +import { classes, tokens } from '../../../../../tokens'; +import { TabItemValue } from '../../HorizontalTabItem.styles'; export const base = css` color: var(${tokens.itemColor}); @@ -13,20 +13,24 @@ export const base = css` color: var(${tokens.itemColorHover}); background-color: var(${tokens.itemBackgroundColorHover}); - ${RightContent} { - color: var(${tokens.additionalContentHoverColor}); + ${TabItemValue} { + color: var(${tokens.itemValueColorHover}); } } &:active { color: var(${tokens.itemColorActive}); + + ${TabItemValue} { + color: var(${tokens.itemValueColorActive}); + } } - &.${String(classes.tabItemAnimated)} { + &.${classes.tabItemAnimated} { transition: var(${tokens.itemBackgroundTransition}); } - &.${String(classes.selectedTabsItem)} { + &.${classes.selectedTabsItem} { color: var(${tokens.itemSelectedColor}); background-color: var(${tokens.itemSelectedBackgroundColor}); @@ -39,23 +43,23 @@ export const base = css` } } - ${RightContent} { - color: var(${tokens.additionalContentSelectedColor}); + ${TabItemValue} { + color: var(${tokens.itemSelectedValueColorHover}); &:hover { - color: var(${tokens.additionalContentSelectedHoverColor}); + color: var(${tokens.itemSelectedValueColorHover}); } } &::after { content: ''; position: absolute; - bottom: 0px; + bottom: 0; left: 0; right: 0; background: var(${tokens.itemSelectedDividerColor}); height: var(${tokens.itemSelectedDividerHeight}); - border-radius: 1px; + border-radius: 0.063rem; } } `; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_view/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/TabItem/variations/_view/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabItem/variations/_view/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.styles.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/HorizontalTabs.styles.ts similarity index 93% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.styles.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/HorizontalTabs.styles.ts index d399feb094..a1a8f60d00 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.styles.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/HorizontalTabs.styles.ts @@ -1,8 +1,8 @@ import { css } from '@linaria/core'; import { styled } from '@linaria/react'; -import { addFocus } from '../../../../mixins'; -import { classes, tokens } from '../../tokens'; +import { addFocus } from '../../../../../mixins'; +import { classes, tokens } from '../../../tokens'; export const base = css` display: flex; @@ -29,6 +29,8 @@ export const StyledContentWrapper = styled.div` display: flex; align-items: center; + z-index: 1; + &.${classes.tabsClipScroll} { overflow: scroll; scroll-padding: 0.25rem; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.tsx b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/HorizontalTabs.tsx similarity index 92% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.tsx rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/HorizontalTabs.tsx index c80da32c94..56efe6f945 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/Tabs.tsx +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/HorizontalTabs.tsx @@ -2,19 +2,19 @@ import React, { forwardRef, useCallback, useMemo, useState, useEffect, useRef, K import type { MutableRefObject } from 'react'; import { safeUseId } from '@salutejs/plasma-core'; -import type { RootProps } from '../../../../engines/types'; -import { IconDisclosureLeft, IconDisclosureRight } from '../../../_Icon'; -import { classes, tokens } from '../../tokens'; -import { cx } from '../../../../utils'; -import { TabItemRefs, TabsContext } from '../../TabsContext'; +import type { RootProps } from '../../../../../engines/types'; +import { IconDisclosureLeft, IconDisclosureRight } from '../../../../_Icon'; +import { classes, tokens } from '../../../tokens'; +import { cx } from '../../../../../utils'; +import { TabItemRefs, TabsContext } from '../../../TabsContext'; +import type { HorizontalTabsProps } from '../../../Tabs.types'; import { base as sizeCSS } from './variations/_size/base'; import { base as viewCSS } from './variations/_view/base'; import { base as disabledCSS } from './variations/_disabled/base'; import { base as pilledCSS } from './variations/_pilled/base'; import { base as stretchCSS } from './variations/_stretch/base'; -import { StyledArrow, StyledContent, StyledContentWrapper, base } from './Tabs.styles'; -import type { TabsProps } from './Tabs.types'; +import { StyledArrow, StyledContent, StyledContentWrapper, base } from './HorizontalTabs.styles'; enum Keys { end = 35, @@ -23,8 +23,9 @@ enum Keys { right = 39, } -export const tabsRoot = (Root: RootProps) => - forwardRef((props, outerRef) => { +// TODO: https://github.com/salute-developers/plasma/issues/1474 +export const horizontalTabsRoot = (Root: RootProps) => + forwardRef((props, outerRef) => { const { id, stretch = false, @@ -176,7 +177,7 @@ export const tabsRoot = (Root: RootProps) => event.preventDefault(); refs.items[nextIndex].current?.focus(); refs.items[nextIndex].current?.scrollIntoView({ - block: 'center', + block: 'nearest', inline: 'center', behavior: 'smooth', }); @@ -230,10 +231,10 @@ export const tabsRoot = (Root: RootProps) => ); }); -export const tabsConfig = { - name: 'Tabs', +export const horizontalTabsConfig = { + name: 'HorizontalTabs', tag: 'div', - layout: tabsRoot, + layout: horizontalTabsRoot, base, variations: { size: { diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_disabled/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_disabled/base.ts similarity index 70% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_disabled/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_disabled/base.ts index 47633a10fc..27c5c0f7dc 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_disabled/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_disabled/base.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tokens } from '../../../../tokens'; +import { tokens } from '../../../../../tokens'; export const base = css` opacity: var(${tokens.disabledOpacity}); diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_disabled/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_disabled/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_disabled/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_disabled/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_pilled/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_pilled/base.ts similarity index 70% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_pilled/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_pilled/base.ts index b74d910f61..0e91e8c688 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_pilled/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_pilled/base.ts @@ -1,9 +1,9 @@ import { css } from '@linaria/core'; -import { classes, tokens } from '../../../../tokens'; +import { classes, tokens } from '../../../../../tokens'; export const base = css` - &.${String(classes.tabsPilled)} { + &.${classes.tabsPilled} { --plasma_private-outline-radius: var(${tokens.tabsPilledBorderRadius}); border-radius: var(${tokens.tabsPilledBorderRadius}); } diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_pilled/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_pilled/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_pilled/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_pilled/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_size/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_size/base.ts similarity index 84% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_size/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_size/base.ts index 86ac9e535e..c0458b6a9e 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_size/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_size/base.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tokens } from '../../../../tokens'; +import { tokens } from '../../../../../tokens'; export const base = css` width: var(${tokens.tabsWidth}); diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_size/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_size/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_size/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_size/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_stretch/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_stretch/base.ts similarity index 67% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_stretch/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_stretch/base.ts index 7117288cb1..1bf7bf9649 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_stretch/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_stretch/base.ts @@ -1,7 +1,7 @@ import { css } from '@linaria/core'; -import { classes } from '../../../../tokens'; -import { StyledContent, StyledContentWrapper } from '../../Tabs.styles'; +import { classes } from '../../../../../tokens'; +import { StyledContent, StyledContentWrapper } from '../../HorizontalTabs.styles'; export const base = css` &.${classes.tabsStretch} { diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_stretch/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_stretch/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_stretch/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_stretch/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_view/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_view/base.ts similarity index 91% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_view/base.ts rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_view/base.ts index 98d46990e2..6afeffb079 100644 --- a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_view/base.ts +++ b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_view/base.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { classes, tokens } from '../../../../tokens'; +import { classes, tokens } from '../../../../../tokens'; export const base = css` background-color: var(${tokens.tabsBackgroundColor}); diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_view/tokens.json similarity index 100% rename from packages/plasma-new-hope/src/components/Tabs/ui/Tabs/variations/_view/tokens.json rename to packages/plasma-new-hope/src/components/Tabs/ui/horizontal/HorizontalTabs/variations/_view/tokens.json diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/VerticalTabItem.styles.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/VerticalTabItem.styles.ts new file mode 100644 index 0000000000..36fae153ce --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/VerticalTabItem.styles.ts @@ -0,0 +1,69 @@ +import { css } from '@linaria/core'; +import { styled } from '@linaria/react'; + +import { addFocus } from '../../../../../mixins'; +import { tokens } from '../../../tokens'; + +export const base = css` + position: relative; + display: flex; + align-items: center; + justify-content: flex-start; + box-sizing: border-box; + align-items: center; + + gap: var(${tokens.itemContentGap}); + padding: var(${tokens.itemPaddingOrientationVertical}); + + appearance: none; + border: none; + outline: none; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + + ${addFocus({ + outlineSize: '0.063rem', + outlineOffset: '-0.125rem', + outlineColor: `var(${tokens.outlineFocusColor})`, + outlineRadius: 'calc(var(--plasma_private-outline-radius) + 0.063rem)', + customFocusRules: ` + &.focus-visible:focus, + &[data-focus-visible-added] { + &::before { + z-index: 1; + outline: none; + box-shadow: 0 0 0 0.063rem var(${tokens.outlineFocusColor}); + } + } + `, + })}; +`; + +export const StyledContent = styled.div` + display: inline-block; + width: fit-content; + + padding: 0 var(${tokens.itemContentPaddingClear}, var(${tokens.itemContentPadding})); +`; + +export const TabItemValue = styled.span` + color: var(${tokens.itemValueColor}); +`; + +export const RightContent = styled.div` + display: flex; + color: inherit; + + &:hover { + color: inherit; + } +`; + +export const LeftContent = styled.div` + display: flex; + color: inherit; + + &:hover { + color: inherit; + } +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/VerticalTabItem.tsx b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/VerticalTabItem.tsx new file mode 100644 index 0000000000..cb2ff66ff7 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/VerticalTabItem.tsx @@ -0,0 +1,141 @@ +import React, { forwardRef, useRef, useContext, useEffect, useCallback } from 'react'; +import { useForkRef } from '@salutejs/plasma-core'; + +import { RootProps } from '../../../../../engines'; +import { classes } from '../../../tokens'; +import { cx } from '../../../../../utils'; +import { TabsContext } from '../../../TabsContext'; +import { VerticalTabItemProps } from '../../../TabItem.types'; + +import { base, LeftContent, RightContent, StyledContent, TabItemValue } from './VerticalTabItem.styles'; +import { base as viewCSS } from './variations/_view/base'; +import { base as sizeCSS } from './variations/_size/base'; +import { base as disabledCSS } from './variations/_disabled/base'; + +export const verticalTabItemRoot = (Root: RootProps) => + forwardRef((props, outerRef) => { + const { + size, + view, + selected, + disabled = false, + children, + value, + contentLeft, + contentRight, + onIndexChange, + itemIndex, + tabIndex, + className, + onClick, + ...rest + } = props; + + const innerRef = useRef(null); + const ref = useForkRef(outerRef, innerRef); + const refs = useContext(TabsContext); + + const role = 'tab'; + + const selectedClass = selected ? classes.selectedTabsItem : undefined; + + const hasKeyNavigation = itemIndex !== undefined && onIndexChange !== undefined; + const navigationTabIndex = !disabled && refs?.current === itemIndex ? 0 : -1; + + useEffect(() => { + if (!refs) { + return; + } + + refs.register(innerRef); + + return () => refs.unregister(innerRef); + }, [refs]); + + const onItemFocus = useCallback( + (event) => { + if (disabled) { + return; + } + + if (!hasKeyNavigation && innerRef?.current) { + innerRef.current.scrollTo({ + top: innerRef.current.offsetTop, + behavior: 'smooth', + }); + + return; + } + + if (!refs) { + return; + } + + const focusIndex = refs.items.findIndex((itemRef) => itemRef.current === event.target); + + if (focusIndex === refs.current) { + return; + } + + onIndexChange?.(focusIndex); + refs.setCurrent(focusIndex); + }, + [refs, innerRef, onIndexChange, disabled], + ); + + const handleClick = (event: React.MouseEvent) => { + event.currentTarget.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + + if (!onClick) { + return; + } + + onClick(event); + }; + + return ( + + <> + {contentLeft && {contentLeft}} + {children} + {!contentRight && value && {value}} + {!value && contentRight && ( + {contentRight} + )} + + + ); + }); + +export const verticalTabItemConfig = { + name: 'VerticalTabItem', + tag: 'button', + layout: verticalTabItemRoot, + base, + variations: { + size: { + css: sizeCSS, + }, + view: { + css: viewCSS, + }, + disabled: { + css: disabledCSS, + attrs: true, + }, + }, + defaults: { + view: 'divider', + }, +}; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_disabled/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_disabled/base.ts new file mode 100644 index 0000000000..df68986047 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_disabled/base.ts @@ -0,0 +1,19 @@ +import { css } from '@linaria/core'; + +import { classes, tokens } from '../../../../../tokens'; + +export const base = css` + &[disabled] { + cursor: not-allowed; + + &:hover { + color: var(${tokens.itemColor}); + background-color: var(${tokens.itemBackgroundColor}); + } + + &.${classes.selectedTabsItem}:hover { + color: var(${tokens.itemSelectedColor}); + background-color: var(${tokens.itemSelectedBackgroundColor}); + } + } +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_disabled/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_disabled/tokens.json new file mode 100644 index 0000000000..68b83a7303 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_disabled/tokens.json @@ -0,0 +1,4 @@ +{ + "type": "boolean", + "tokens": ["--plasma-tab-disabled-opacity"] +} diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_size/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_size/base.ts new file mode 100644 index 0000000000..df663a281d --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_size/base.ts @@ -0,0 +1,18 @@ +import { css } from '@linaria/core'; + +import { tokens } from '../../../../../tokens'; + +export const base = css` + font-family: var(${tokens.fontFamily}); + font-size: var(${tokens.fontSize}); + font-style: var(${tokens.fontStyle}); + font-weight: var(${tokens.fontWeight}); + letter-spacing: var(${tokens.letterSpacing}); + line-height: var(${tokens.lineHeight}); + + --plasma_private-outline-radius: var(${tokens.itemBorderRadius}); + border-radius: var(${tokens.itemBorderRadius}); + + width: 100%; + height: var(${tokens.itemHeight}); +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_size/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_size/tokens.json new file mode 100644 index 0000000000..155fd1f1e6 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_size/tokens.json @@ -0,0 +1,11 @@ +[ + "--plasma-tab-item-font-family", + "--plasma-tab-item-font-size", + "--plasma-tab-item-font-style", + "--plasma-tab-item-font-weight", + "--plasma-tab-item-letter-spacing", + "--plasma-tab-item-lineheight", + "--plasma-tab-item-border-radius", + "--plasma-tab-item-width", + "--plasma-tab-item-height" +] diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_view/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_view/base.ts new file mode 100644 index 0000000000..3a91c5d0ef --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_view/base.ts @@ -0,0 +1,64 @@ +import { css } from '@linaria/core'; + +import { classes, tokens } from '../../../../../tokens'; +import { TabItemValue } from '../../VerticalTabItem.styles'; + +export const base = css` + color: var(${tokens.itemColor}); + background-color: var(${tokens.itemBackgroundColor}); + + &:hover { + color: var(${tokens.itemColorHover}); + background-color: var(${tokens.itemBackgroundColorHover}); + + ${TabItemValue} { + color: var(${tokens.itemValueColorHover}); + } + } + + &:active { + color: var(${tokens.itemColorActive}); + + ${TabItemValue} { + color: var(${tokens.itemValueColorActive}); + } + } + + &.${classes.tabItemAnimated} { + transition: var(${tokens.itemBackgroundTransition}); + } + + &.${classes.selectedTabsItem} { + color: var(${tokens.itemSelectedColor}); + background-color: var(${tokens.itemSelectedBackgroundColor}); + + &:hover { + color: var(${tokens.itemSelectedColorHover}); + background-color: var(${tokens.itemSelectedBackgroundColorHover}); + + &::after { + background: var(${tokens.itemSelectedDividerColorHover}); + } + } + + ${TabItemValue} { + color: var(${tokens.itemSelectedValueColorHover}); + + &:hover { + color: var(${tokens.itemSelectedValueColorHover}); + } + } + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + background: var(${tokens.itemSelectedDividerColor}); + width: var(${tokens.itemSelectedDividerWidth}); + + border-radius: 0.063rem; + } + } +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_view/tokens.json new file mode 100644 index 0000000000..2dacbdc4ed --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabItem/variations/_view/tokens.json @@ -0,0 +1,8 @@ +[ + "--plasma-tabs-underline-height", + "--plasma-tabs-underline-color", + "--plasma-tabs-font-weight", + "--plasma-tabs-font-color", + "--plasma-tabs-font-color-hover", + "--plasma-tabs-cursor" +] diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/VerticalTabs.styles.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/VerticalTabs.styles.ts new file mode 100644 index 0000000000..76c5b59d48 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/VerticalTabs.styles.ts @@ -0,0 +1,74 @@ +import { css } from '@linaria/core'; +import { styled } from '@linaria/react'; + +import { addFocus } from '../../../../../mixins'; +import { classes, tokens } from '../../../tokens'; + +export const base = css` + position: relative; + display: flex; + flex-direction: column; + gap: 0.125rem; + align-items: center; +`; + +export const StyledContent = styled.div<{ hasDivider?: boolean }>` + display: inline-flex; + flex-direction: column; + align-items: start; +`; + +export const StyledContentWrapper = styled.div` + /* allows correctly display outline focus on tabs item */ + margin: -0.125rem; + padding: 0.25rem; + + box-sizing: content-box; + position: relative; + height: 100%; + width: 100%; + display: flex; + align-items: center; + + z-index: 1; + + &.${classes.tabsClipScroll} { + overflow: scroll; + scroll-padding: 0.25rem; + overscroll-behavior: contain; + + scrollbar-width: none; + ::-webkit-scrollbar { + display: none; + } + + ${StyledContent} { + margin-top: auto; + } + } + + &.${classes.tabsClipShowAll} { + overflow: visible; + } +`; + +export const StyledArrow = styled.button` + display: flex; + cursor: pointer; + border: none; + background-color: transparent; + padding: 0; + outline: none; + transform: rotate(90deg); + + ${addFocus({ + outlineSize: '0.063rem', + outlineOffset: '-0.125rem', + outlineColor: `var(${tokens.outlineFocusColor})`, + outlineRadius: 'calc(var(--plasma_private-outline-radius) - 0.063rem)', + })}; + + &[disabled] { + cursor: not-allowed; + } +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/VerticalTabs.tsx b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/VerticalTabs.tsx new file mode 100644 index 0000000000..3f8acc421c --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/VerticalTabs.tsx @@ -0,0 +1,241 @@ +import React, { forwardRef, useCallback, useMemo, useState, useEffect, useRef, KeyboardEvent } from 'react'; +import type { MutableRefObject } from 'react'; +import { safeUseId } from '@salutejs/plasma-core'; + +import type { RootProps } from '../../../../../engines/types'; +import { classes, tokens } from '../../../tokens'; +import { cx } from '../../../../../utils'; +import { TabItemRefs, TabsContext } from '../../../TabsContext'; +import type { VerticalTabsProps } from '../../../Tabs.types'; +import { IconDisclosureLeft, IconDisclosureRight } from '../../../../_Icon'; + +import { base, StyledArrow, StyledContent, StyledContentWrapper } from './VerticalTabs.styles'; +import { base as sizeCSS } from './variations/_size/base'; +import { base as viewCSS } from './variations/_view/base'; +import { base as disabledCSS } from './variations/_disabled/base'; + +enum Keys { + end = 35, + home = 36, + up = 38, + down = 40, +} + +// TODO: https://github.com/salute-developers/plasma/issues/1474 +export const verticalTabsRoot = (Root: RootProps) => + forwardRef((props, outerRef) => { + const { + id, + disabled = false, + clip = 'showAll', + size, + view, + children, + index, + className, + orientation, + hasDivider = true, + ...rest + } = props; + + const [firstItemVisible, setFirstItemVisible] = useState(true); + const [lastItemVisible, setLastItemVisible] = useState(true); + + const refs = useMemo(() => new TabItemRefs(index), []); + + const uniqId = safeUseId(); + const tabsId = id || uniqId; + + const noDividerClass = !hasDivider ? classes.tabsNoDivider : undefined; + const hasTopArrowClass = !firstItemVisible ? classes.tabsHasTopArrow : undefined; + const hasBottomArrowClass = !lastItemVisible ? classes.tabsHasBottomArrow : undefined; + const clipScrollClass = clip === 'scroll' ? classes.tabsClipScroll : undefined; + const clipShowAllClass = clip === 'showAll' ? classes.tabsClipShowAll : undefined; + + const scrollRef = useRef(null); + const trackRef = useRef(null); + const upArrowRef = useRef(null); + + const onPrev = useCallback(() => { + if (disabled || !scrollRef.current) { + return; + } + + const scrollTop = Math.round(scrollRef.current.scrollTop); + const firstOverflowingTab = refs.items + .slice() + .reverse() + .find((item: MutableRefObject) => { + if (!item.current || item.current.offsetTop === undefined) { + return; + } + const tabStartY = item.current.offsetTop; + + return tabStartY < scrollTop; + }); + + firstOverflowingTab?.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + }, [disabled, scrollRef, refs]); + + const onNext = useCallback(() => { + if (disabled || !scrollRef.current) { + return; + } + + const scrollBottom = Math.round(scrollRef.current.scrollTop + scrollRef.current.clientHeight); + const lastOverflowingTab = refs.items.find((item: MutableRefObject) => { + if (!item.current || item.current.offsetTop === undefined) { + return; + } + const tabEndY = item.current.offsetTop + item.current.offsetHeight; + + return tabEndY > scrollBottom; + }); + + lastOverflowingTab?.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + }, [disabled, scrollRef, refs]); + + const PreviousButton = ( + + + + ); + + const NextButton = ( + + + + ); + + const handleScroll = useCallback( + (event: React.UIEvent): void => { + event.stopPropagation(); + const maxScrollTop = event.currentTarget.scrollHeight - event.currentTarget.clientHeight; + const scrollTop = Math.round(event.currentTarget.scrollTop); + + setFirstItemVisible(scrollTop <= 0); + setLastItemVisible(scrollTop >= maxScrollTop); + }, + [setFirstItemVisible, setLastItemVisible], + ); + + const onKeyDown = useCallback( + (event: KeyboardEvent) => { + if (index === undefined) { + return; + } + + const minIndex = 0; + const maxIndex = refs.items.length - 1; + let nextIndex: number; + + switch (event.keyCode) { + case Keys.end: + nextIndex = maxIndex; + break; + case Keys.up: + nextIndex = index > minIndex ? index - 1 : index; + break; + case Keys.down: + nextIndex = index < maxIndex ? index + 1 : index; + break; + case Keys.home: + nextIndex = minIndex; + break; + default: + return; + } + + if (nextIndex !== index) { + event.preventDefault(); + refs.items[nextIndex].current?.focus(); + refs.items[nextIndex].current?.scrollIntoView({ + block: 'nearest', + inline: 'nearest', + behavior: 'smooth', + }); + } + }, + [index], + ); + + useEffect(() => { + setLastItemVisible(scrollRef.current?.scrollHeight === scrollRef.current?.clientHeight); + }, []); + + // Этот хук компенсирует появление верхней стрелки при прокрутке + useEffect(() => { + if (firstItemVisible || !scrollRef.current || !upArrowRef.current) { + return; + } + + scrollRef.current.scrollTo({ + top: Math.round(scrollRef.current.scrollTop + upArrowRef.current.clientHeight), + }); + }, [firstItemVisible, scrollRef, upArrowRef]); + + return ( + + + {!firstItemVisible && PreviousButton} + } + onScroll={handleScroll} + > + }> + {children} + + + {!lastItemVisible && NextButton} + + + ); + }); + +export const verticalTabsConfig = { + name: 'VerticalTabs', + tag: 'div', + layout: verticalTabsRoot, + base, + variations: { + size: { + css: sizeCSS, + }, + view: { + css: viewCSS, + }, + disabled: { + css: disabledCSS, + attrs: true, + }, + }, + defaults: { + view: 'divider', + size: 'xs', + }, +}; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_disabled/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_disabled/base.ts new file mode 100644 index 0000000000..27c5c0f7dc --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_disabled/base.ts @@ -0,0 +1,7 @@ +import { css } from '@linaria/core'; + +import { tokens } from '../../../../../tokens'; + +export const base = css` + opacity: var(${tokens.disabledOpacity}); +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_disabled/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_disabled/tokens.json new file mode 100644 index 0000000000..d90cc7dcdd --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_disabled/tokens.json @@ -0,0 +1,4 @@ +{ + "type": "boolean", + "tokens": ["--plasma-tabs-disabled-opacity"] +} diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_size/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_size/base.ts new file mode 100644 index 0000000000..c0458b6a9e --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_size/base.ts @@ -0,0 +1,11 @@ +import { css } from '@linaria/core'; + +import { tokens } from '../../../../../tokens'; + +export const base = css` + width: var(${tokens.tabsWidth}); + height: var(${tokens.tabsHeight}); + + --plasma_private-outline-radius: var(${tokens.tabsBorderRadius}); + border-radius: var(${tokens.tabsBorderRadius}); +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_size/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_size/tokens.json new file mode 100644 index 0000000000..6d03095ab1 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_size/tokens.json @@ -0,0 +1 @@ +["--plasma-tabs-width", "--plasma-tabs-height", "--plasma-tabs-border-radius"] diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_view/base.ts b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_view/base.ts new file mode 100644 index 0000000000..4174aea95a --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_view/base.ts @@ -0,0 +1,36 @@ +import { css } from '@linaria/core'; + +import { classes, tokens } from '../../../../../tokens'; + +export const base = css` + background-color: var(${tokens.tabsBackgroundColor}); + + &::after { + content: ''; + position: absolute; + top: 0.125rem; + bottom: 0.125rem; + left: 0; + background: var(${tokens.tabsDividerColor}); + width: var(${tokens.tabsDividerWidth}); + border-radius: var(${tokens.tabsDividerBorderRadius}); + } + + &.${classes.tabsHasTopArrow} { + &::after { + top: 1.5rem; + } + } + + &.${classes.tabsHasBottomArrow} { + &::after { + bottom: 1.5rem; + } + } + + &.${classes.tabsNoDivider} { + &::after { + width: 0; + } + } +`; diff --git a/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_view/tokens.json b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_view/tokens.json new file mode 100644 index 0000000000..89f8cf5139 --- /dev/null +++ b/packages/plasma-new-hope/src/components/Tabs/ui/vertical/VerticalTabs/variations/_view/tokens.json @@ -0,0 +1 @@ +["--plasma-tabs-background-color", "--plasma-tabs-divider-height", "--plasma-tabs-divider-color"] diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.ts deleted file mode 100644 index d2450a6118..0000000000 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { tabItemConfig } from '../../../../components/Tabs'; -import { component, mergeConfig } from '../../../../engines'; - -import { config } from './TabItem.config'; - -const mergedConfig = mergeConfig(tabItemConfig, config); - -export const TabItem = component(mergedConfig); diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.tsx new file mode 100644 index 0000000000..5de5d4bd15 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.tsx @@ -0,0 +1,23 @@ +import React, { ComponentProps } from 'react'; + +import { horizontalTabItemConfig, verticalTabItemConfig } from '../../../../components/Tabs'; +import { component, mergeConfig } from '../../../../engines'; + +import { config as horizontalConfig } from './horizontal/HorizontalTabItem.config'; +import { config as verticalConfig } from './vertical/VerticalTabItem.config'; + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalTabItemConfig, horizontalConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); + +const mergedVerticalTabItemConfig = mergeConfig(verticalTabItemConfig, verticalConfig); +const VerticalTabItem = component(mergedVerticalTabItemConfig); + +type TabItemProps = ComponentProps | ComponentProps; + +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.stories.tsx index dad9c19a6a..65a3cd7d01 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.stories.tsx @@ -3,14 +3,11 @@ import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { disableProps } from '@salutejs/plasma-sb-utils'; -import { tabsConfig } from '../../../../components/Tabs'; -import { mergeConfig } from '../../../../engines'; -import { argTypesFromConfig, WithTheme } from '../../../_helpers'; +import { WithTheme } from '../../../_helpers'; import { IconMic } from '../../../../components/_Icon'; import { Dropdown } from '../Dropdown/Dropdown'; import { Counter } from '../Counter/Counter'; -import { config } from './Tabs.config'; import { Tabs } from './Tabs'; import { TabItem } from './TabItem'; @@ -19,16 +16,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -44,8 +44,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -58,26 +56,30 @@ const meta: Meta = { component: Tabs, decorators: [WithTheme], argTypes: { - ...argTypesFromConfig(mergeConfig(tabsConfig, config)), - contentLeft: { - options: contentLeftOptions, - control: { - type: 'select', - }, - }, + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), contentRight: { options: contentRightOptions, control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['itemsNumber', 'pilled', 'animated', 'view']), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -86,32 +88,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -120,6 +143,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -132,26 +156,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -160,6 +204,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -179,21 +224,41 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
{ view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -217,14 +282,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -242,11 +320,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; case 'showAll': - return ; + return ; default: - return ; + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; + case 'showAll': + return ; + default: + return ; } }, }; @@ -259,12 +598,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -298,6 +638,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.ts deleted file mode 100644 index 8f06426d6f..0000000000 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { tabsConfig } from '../../../../components/Tabs'; -import { component, mergeConfig } from '../../../../engines'; - -import { config } from './Tabs.config'; - -const mergedConfig = mergeConfig(tabsConfig, config); - -export const Tabs = component(mergedConfig); diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.tsx new file mode 100644 index 0000000000..f342aebc79 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.tsx @@ -0,0 +1,23 @@ +import React, { ComponentProps } from 'react'; + +import { horizontalTabsConfig, verticalTabsConfig } from '../../../../components/Tabs'; +import { component, mergeConfig } from '../../../../engines'; + +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; + +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabsController.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabsController.tsx index 28fd2f655f..09d60f9ed9 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabsController.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabsController.tsx @@ -1,11 +1,24 @@ import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { createTabsController, TabItemProps, TabsProps } from '../../../../components/Tabs'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, +} from '../../../../components/Tabs'; +import { component, mergeConfig } from '../../../../engines'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); export const TabsController = createTabsController( - Tabs as ForwardRefExoticComponent>, - TabItem as ForwardRefExoticComponent>, + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, ); diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 89% rename from packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.config.ts rename to packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/horizontal/HorizontalTabItem.config.ts index 30ccc06615..3f21411a46 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.config.ts +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tabsTokens } from '../../../../components/Tabs'; +import { tabsTokens } from '../../../../../components/Tabs'; export const config = { defaults: { @@ -11,13 +11,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -29,21 +34,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -54,21 +59,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -80,21 +85,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -105,11 +110,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--inverse-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--inverse-text-secondary); `, }, size: { diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 98% rename from packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.config.ts rename to packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/horizontal/HorizontalTabs.config.ts index fc5eaa0506..c4e6037cef 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/Tabs.config.ts +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/horizontal/HorizontalTabs.config.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tabsTokens } from '../../../../components/Tabs'; +import { tabsTokens } from '../../../../../components/Tabs'; export const config = { defaults: { diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..44a119ae6a --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,120 @@ +import { css } from '@linaria/core'; + +import { tabsTokens } from '../../../../../components/Tabs'; + +export const config = { + defaults: { + view: 'divider', + size: 'xs', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/vertical/VerticalTabs.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..d646bd6943 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,59 @@ +import { css } from '@linaria/core'; + +import { tabsTokens } from '../../../../../components/Tabs'; + +export const config = { + defaults: { + view: 'divider', + size: 'xs', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.ts deleted file mode 100644 index d2450a6118..0000000000 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { tabItemConfig } from '../../../../components/Tabs'; -import { component, mergeConfig } from '../../../../engines'; - -import { config } from './TabItem.config'; - -const mergedConfig = mergeConfig(tabItemConfig, config); - -export const TabItem = component(mergedConfig); diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.tsx new file mode 100644 index 0000000000..5de5d4bd15 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabItem.tsx @@ -0,0 +1,23 @@ +import React, { ComponentProps } from 'react'; + +import { horizontalTabItemConfig, verticalTabItemConfig } from '../../../../components/Tabs'; +import { component, mergeConfig } from '../../../../engines'; + +import { config as horizontalConfig } from './horizontal/HorizontalTabItem.config'; +import { config as verticalConfig } from './vertical/VerticalTabItem.config'; + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalTabItemConfig, horizontalConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); + +const mergedVerticalTabItemConfig = mergeConfig(verticalTabItemConfig, verticalConfig); +const VerticalTabItem = component(mergedVerticalTabItemConfig); + +type TabItemProps = ComponentProps | ComponentProps; + +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.stories.tsx index 99dedb4971..4c012ae635 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.stories.tsx @@ -3,14 +3,11 @@ import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { disableProps } from '@salutejs/plasma-sb-utils'; -import { tabsConfig } from '../../../../components/Tabs'; -import { mergeConfig } from '../../../../engines'; -import { argTypesFromConfig, WithTheme } from '../../../_helpers'; +import { WithTheme } from '../../../_helpers'; import { IconMic } from '../../../../components/_Icon'; import { Dropdown } from '../Dropdown/Dropdown'; import { Counter } from '../Counter/Counter'; -import { config } from './Tabs.config'; import { Tabs } from './Tabs'; import { TabItem } from './TabItem'; @@ -19,16 +16,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -44,8 +44,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -58,26 +56,30 @@ const meta: Meta = { component: Tabs, decorators: [WithTheme], argTypes: { - ...argTypesFromConfig(mergeConfig(tabsConfig, config)), - contentLeft: { - options: contentLeftOptions, - control: { - type: 'select', - }, - }, + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), contentRight: { options: contentRightOptions, control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['itemsNumber', 'pilled', 'animated', 'view']), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -86,32 +88,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -120,6 +143,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -132,26 +156,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })}
); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -160,6 +204,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -179,21 +224,41 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
{ view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -217,14 +282,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -242,11 +320,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; case 'showAll': - return ; + return ; default: - return ; + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; + case 'showAll': + return ; + default: + return ; } }, }; @@ -259,12 +598,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -298,6 +638,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.ts deleted file mode 100644 index 8f06426d6f..0000000000 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { tabsConfig } from '../../../../components/Tabs'; -import { component, mergeConfig } from '../../../../engines'; - -import { config } from './Tabs.config'; - -const mergedConfig = mergeConfig(tabsConfig, config); - -export const Tabs = component(mergedConfig); diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.tsx new file mode 100644 index 0000000000..f342aebc79 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.tsx @@ -0,0 +1,23 @@ +import React, { ComponentProps } from 'react'; + +import { horizontalTabsConfig, verticalTabsConfig } from '../../../../components/Tabs'; +import { component, mergeConfig } from '../../../../engines'; + +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; + +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabsController.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabsController.tsx index 28fd2f655f..09d60f9ed9 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabsController.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/TabsController.tsx @@ -1,11 +1,24 @@ import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { createTabsController, TabItemProps, TabsProps } from '../../../../components/Tabs'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, +} from '../../../../components/Tabs'; +import { component, mergeConfig } from '../../../../engines'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); export const TabsController = createTabsController( - Tabs as ForwardRefExoticComponent>, - TabItem as ForwardRefExoticComponent>, + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, ); diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.config.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 89% rename from packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.config.ts rename to packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/horizontal/HorizontalTabItem.config.ts index 4aa07a8428..3f21411a46 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Tabs/TabItem.config.ts +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tabsTokens } from '../../../../components/Tabs'; +import { tabsTokens } from '../../../../../components/Tabs'; export const config = { defaults: { @@ -11,13 +11,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -29,21 +34,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -54,22 +59,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -81,22 +85,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -107,12 +110,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--inverse-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--inverse-text-secondary); `, }, size: { diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.config.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 98% rename from packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.config.ts rename to packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/horizontal/HorizontalTabs.config.ts index fc5eaa0506..c4e6037cef 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/Tabs.config.ts +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/horizontal/HorizontalTabs.config.ts @@ -1,6 +1,6 @@ import { css } from '@linaria/core'; -import { tabsTokens } from '../../../../components/Tabs'; +import { tabsTokens } from '../../../../../components/Tabs'; export const config = { defaults: { diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..44a119ae6a --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,120 @@ +import { css } from '@linaria/core'; + +import { tabsTokens } from '../../../../../components/Tabs'; + +export const config = { + defaults: { + view: 'divider', + size: 'xs', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/vertical/VerticalTabs.config.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..d646bd6943 --- /dev/null +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,59 @@ +import { css } from '@linaria/core'; + +import { tabsTokens } from '../../../../../components/Tabs'; + +export const config = { + defaults: { + view: 'divider', + size: 'xs', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-web/api/plasma-web.api.md b/packages/plasma-web/api/plasma-web.api.md index 7612730706..360c78e405 100644 --- a/packages/plasma-web/api/plasma-web.api.md +++ b/packages/plasma-web/api/plasma-web.api.md @@ -40,6 +40,8 @@ import { BaseboxProps } from '@salutejs/plasma-new-hope/styled-components'; import { BaseCallbackChangeInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseCallbackKeyboardInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseProps } from '@salutejs/plasma-new-hope/types/components/Autocomplete/Autocomplete.types'; +import { BaseTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/TabItem.types'; +import { BaseTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { BasicProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxNew/Combobox.types'; import { BlurProps } from '@salutejs/plasma-core'; import { blurs } from '@salutejs/plasma-core'; @@ -100,10 +102,10 @@ import { convertRoundnessMatrix } from '@salutejs/plasma-core'; import { CounterProps } from '@salutejs/plasma-new-hope/styled-components'; import { counterTokens } from '@salutejs/plasma-new-hope/styled-components'; import { CustomComboboxProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxOld/Combobox.types'; +import { CustomHorizontalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/Popover/Popover.types'; -import { CustomTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/TabItem/TabItem.types'; -import { CustomTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/Tabs/Tabs.types'; import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; +import { CustomVerticalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { DateInfo } from '@salutejs/plasma-new-hope/types/components/Calendar/Calendar.types'; import { DatePickerCalendarProps } from '@salutejs/plasma-new-hope/types/components/DatePicker/DatePickerBase.types'; import { datePickerClasses } from '@salutejs/plasma-new-hope/styled-components'; @@ -273,11 +275,9 @@ import { SubtitleProps } from '@salutejs/plasma-new-hope/styled-components'; import { SwitchProps as SwitchProps_2 } from '@salutejs/plasma-new-hope/styled-components'; import { SyntheticEvent } from 'react'; import { syntheticFocus } from '@salutejs/plasma-core'; -import { TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { TabItemRefs } from '@salutejs/plasma-new-hope/styled-components'; import { TabsContext } from '@salutejs/plasma-new-hope/styled-components'; import { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; -import { TabsProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextareaHTMLAttributes } from '@salutejs/plasma-core'; import type { TextAreaProps as TextAreaProps_2 } from '@salutejs/plasma-hope'; import { TextareaResize } from '@salutejs/plasma-core'; @@ -3319,15 +3319,17 @@ export type SwitchProps = ComponentProps; export { syntheticFocus } +// Warning: (ae-forgotten-export) The symbol "TabItemProps" needs to be exported by the entry point index.d.ts +// // @public -export const TabItem: ForwardRefExoticComponent & AsProps_2 & CustomTabItemProps & RefAttributes>; - -export { TabItemProps } +export const TabItem: (props: TabItemProps) => JSX.Element; export { TabItemRefs } +// Warning: (ae-forgotten-export) The symbol "TabsProps" needs to be exported by the entry point index.d.ts +// // @public -export const Tabs: ForwardRefExoticComponent & AsProps_2 & CustomTabsProps & RefAttributes>; +export const Tabs: (props: TabsProps) => JSX.Element; export { TabsContext } @@ -3336,8 +3338,6 @@ export const TabsController: ForwardRefExoticComponent>; diff --git a/packages/plasma-web/src/components/Tabs/TabItem.tsx b/packages/plasma-web/src/components/Tabs/TabItem.tsx index 516ff278bc..a8c2061a79 100644 --- a/packages/plasma-web/src/components/Tabs/TabItem.tsx +++ b/packages/plasma-web/src/components/Tabs/TabItem.tsx @@ -1,13 +1,29 @@ -import { tabItemConfig, component, mergeConfig, TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; -import { ForwardRefExoticComponent, RefAttributes } from 'react'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabItemConfig, + verticalTabItemConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './TabItem.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabItem.config'; +import { config as verticalConfig } from './vertical/VerticalTabItem.config'; -const mergedConfig = mergeConfig(tabItemConfig, config); +const mergedHorizontalTabItemConfig = mergeConfig(horizontalTabItemConfig, horizontalConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); + +const mergedVerticalTabItemConfig = mergeConfig(verticalTabItemConfig, verticalConfig); +const VerticalTabItem = component(mergedVerticalTabItemConfig); + +type TabItemProps = ComponentProps | ComponentProps; /** * Элемент списка, недопустимо использовать вне компонента Tabs. */ -export const TabItem = component(mergedConfig) as ForwardRefExoticComponent< - TabItemProps & RefAttributes ->; +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-web/src/components/Tabs/Tabs.component-test.tsx b/packages/plasma-web/src/components/Tabs/Tabs.component-test.tsx index 526e09b91e..27c2772379 100644 --- a/packages/plasma-web/src/components/Tabs/Tabs.component-test.tsx +++ b/packages/plasma-web/src/components/Tabs/Tabs.component-test.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import { mount, CypressTestDecorator, getComponent } from '@salutejs/plasma-cy-utils'; +import { mount, CypressTestDecorator, getComponent, PadMe } from '@salutejs/plasma-cy-utils'; import { standard as standardTypo } from '@salutejs/plasma-typo'; import { createGlobalStyle } from 'styled-components'; @@ -25,7 +25,7 @@ describe('plasma-web: Tabs', () => { {items.map((item, i) => ( - + {item.label} ))} @@ -37,12 +37,43 @@ describe('plasma-web: Tabs', () => { cy.matchImageSnapshot(); }); + it('_orientation', () => { + mount( + + + {items.map((item, i) => ( + + {item.label} + + ))} + + + + {items.map((item, i) => ( + + {item.label} + + ))} + + , + ); + + cy.matchImageSnapshot(); + }); + it('Clicking on arrows scrolls to previous or next tab', () => { mount( {items.map((item, i) => ( - + {item.label} ))} diff --git a/packages/plasma-web/src/components/Tabs/Tabs.stories.tsx b/packages/plasma-web/src/components/Tabs/Tabs.stories.tsx index db18da20e5..5efbe4fdc6 100644 --- a/packages/plasma-web/src/components/Tabs/Tabs.stories.tsx +++ b/packages/plasma-web/src/components/Tabs/Tabs.stories.tsx @@ -15,16 +15,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -40,8 +43,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -65,14 +66,25 @@ const meta: Meta = { control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['pilled', 'animated', 'view', 'as', 'forwardedAs', 'outsideScroll', 'index']), + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -81,32 +93,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -115,6 +148,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -127,26 +161,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })}
); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -155,6 +209,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -174,25 +229,45 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
setIndex(item.value as number)} > @@ -201,7 +276,7 @@ const StoryShowAll = (props: StoryTabsProps) => { view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -212,14 +287,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -237,11 +325,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; + case 'showAll': + return ; + default: + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; case 'showAll': - return ; + return ; default: - return ; + return ; } }, }; @@ -254,12 +603,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -293,7 +643,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, - ...disableProps(['clip']), + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/plasma-web/src/components/Tabs/Tabs.tsx b/packages/plasma-web/src/components/Tabs/Tabs.tsx index b39e2bf6b6..9475451e71 100644 --- a/packages/plasma-web/src/components/Tabs/Tabs.tsx +++ b/packages/plasma-web/src/components/Tabs/Tabs.tsx @@ -1,10 +1,29 @@ -import { tabsConfig, component, mergeConfig, TabsProps } from '@salutejs/plasma-new-hope/styled-components'; -import { ForwardRefExoticComponent, RefAttributes } from 'react'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabsConfig, + verticalTabsConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './Tabs.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; -const mergedConfig = mergeConfig(tabsConfig, config); /** * Контейнер вкладок, основной компонент для пользовательской сборки вкладок. */ -export const Tabs = component(mergedConfig) as ForwardRefExoticComponent>; +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/plasma-web/src/components/Tabs/TabsController.tsx b/packages/plasma-web/src/components/Tabs/TabsController.tsx index e5255de474..9de85c85d0 100644 --- a/packages/plasma-web/src/components/Tabs/TabsController.tsx +++ b/packages/plasma-web/src/components/Tabs/TabsController.tsx @@ -1,10 +1,28 @@ -import { createTabsController } from '@salutejs/plasma-new-hope/styled-components'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); /** * Контроллер вкладок. * Позволяет использовать клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. */ -export const TabsController = createTabsController(Tabs, TabItem); +export const TabsController = createTabsController( + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, +); diff --git a/packages/plasma-web/src/components/Tabs/TabItem.config.ts b/packages/plasma-web/src/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 90% rename from packages/plasma-web/src/components/Tabs/TabItem.config.ts rename to packages/plasma-web/src/components/Tabs/horizontal/HorizontalTabItem.config.ts index d86688e3b6..6bf4f6c3cc 100644 --- a/packages/plasma-web/src/components/Tabs/TabItem.config.ts +++ b/packages/plasma-web/src/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -9,13 +9,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -27,21 +32,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -52,21 +57,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -78,21 +83,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--inverse-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -103,11 +108,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--inverse-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--inverse-text-secondary); `, }, size: { diff --git a/packages/plasma-web/src/components/Tabs/Tabs.config.ts b/packages/plasma-web/src/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 100% rename from packages/plasma-web/src/components/Tabs/Tabs.config.ts rename to packages/plasma-web/src/components/Tabs/horizontal/HorizontalTabs.config.ts diff --git a/packages/plasma-web/src/components/Tabs/index.ts b/packages/plasma-web/src/components/Tabs/index.ts index f2d92ec950..10fada1d81 100644 --- a/packages/plasma-web/src/components/Tabs/index.ts +++ b/packages/plasma-web/src/components/Tabs/index.ts @@ -1,6 +1,6 @@ export { TabsController } from './TabsController'; export { TabItemRefs, TabsContext } from '@salutejs/plasma-new-hope/styled-components'; -export type { TabsProps, TabItemProps, TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; +export type { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; export { Tabs } from './Tabs'; export { TabItem } from './TabItem'; diff --git a/packages/plasma-web/src/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/plasma-web/src/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..97c7fbbf6d --- /dev/null +++ b/packages/plasma-web/src/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,118 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/plasma-web/src/components/Tabs/vertical/VerticalTabs.config.ts b/packages/plasma-web/src/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..5bef8112a6 --- /dev/null +++ b/packages/plasma-web/src/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,62 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + stretch: { + true: css` + ${tabsTokens.containerHeight}: 100%; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-cs/api/sdds-cs.api.md b/packages/sdds-cs/api/sdds-cs.api.md index 2478a95301..f911a0f662 100644 --- a/packages/sdds-cs/api/sdds-cs.api.md +++ b/packages/sdds-cs/api/sdds-cs.api.md @@ -20,6 +20,8 @@ import { BaseboxProps } from '@salutejs/plasma-new-hope/styled-components'; import { BaseCallbackChangeInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseCallbackKeyboardInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseProps } from '@salutejs/plasma-new-hope/types/components/Autocomplete/Autocomplete.types'; +import { BaseTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/TabItem.types'; +import { BaseTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { BasicProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxNew/Combobox.types'; import { bodyL } from '@salutejs/sdds-themes/tokens'; import { bodyLBold } from '@salutejs/sdds-themes/tokens'; @@ -63,10 +65,10 @@ import { ColSizeProps } from '@salutejs/plasma-new-hope/styled-components'; import { ComponentProps } from 'react'; import { CounterProps } from '@salutejs/plasma-new-hope/styled-components'; import { counterTokens } from '@salutejs/plasma-new-hope/styled-components'; +import { CustomHorizontalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/Popover/Popover.types'; -import { CustomTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/TabItem/TabItem.types'; -import { CustomTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/Tabs/Tabs.types'; import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; +import { CustomVerticalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { DateInfo } from '@salutejs/plasma-new-hope/types/components/Calendar/Calendar.types'; import { DatePickerCalendarProps } from '@salutejs/plasma-new-hope/types/components/DatePicker/DatePickerBase.types'; import { datePickerClasses } from '@salutejs/plasma-new-hope/styled-components'; @@ -184,11 +186,9 @@ import { StepItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { StepsProps } from '@salutejs/plasma-new-hope/types/components/Steps/Steps.types'; import { StyledComponent } from 'styled-components'; import { SwitchProps as SwitchProps_2 } from '@salutejs/plasma-new-hope/styled-components'; -import { TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { TabItemRefs } from '@salutejs/plasma-new-hope/styled-components'; import { TabsContext } from '@salutejs/plasma-new-hope/styled-components'; import { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; -import { TabsProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextareaHTMLAttributes } from '@salutejs/plasma-new-hope/types/types'; import { TextFieldPrimitiveValue } from '@salutejs/plasma-new-hope/types/components/TextField/TextField.types'; import { TextfieldPrimitiveValue } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; @@ -2521,57 +2521,17 @@ true: PolymorphicClassName; // @public (undocumented) export type SwitchProps = ComponentProps; +// Warning: (ae-forgotten-export) The symbol "TabItemProps" needs to be exported by the entry point index.d.ts +// // @public -export const TabItem: FunctionComponent & ButtonHTMLAttributes & AsProps & CustomTabItemProps & RefAttributes>; - -export { TabItemProps } +export const TabItem: (props: TabItemProps) => JSX.Element; export { TabItemRefs } +// Warning: (ae-forgotten-export) The symbol "TabsProps" needs to be exported by the entry point index.d.ts +// // @public -export const Tabs: FunctionComponent & HTMLAttributes & AsProps & CustomTabsProps & RefAttributes>; +export const Tabs: (props: TabsProps) => JSX.Element; export { TabsContext } @@ -2580,8 +2540,6 @@ export const TabsController: ForwardRefExoticComponent | ComponentProps; /** * Элемент списка, недопустимо использовать вне компонента Tabs. */ -export const TabItem = component(mergedConfig); +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-cs/src/components/Tabs/Tabs.stories.tsx b/packages/sdds-cs/src/components/Tabs/Tabs.stories.tsx index 1f54a644a7..6ecae948b6 100644 --- a/packages/sdds-cs/src/components/Tabs/Tabs.stories.tsx +++ b/packages/sdds-cs/src/components/Tabs/Tabs.stories.tsx @@ -12,36 +12,35 @@ import { TabItem } from './TabItem'; const clips = ['none', 'scroll', 'showAll']; const sizes = ['s'] as const; -const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; -const getContentLeft = (contentLeftOption: string) => { +const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = 's'; return contentLeftOption === 'icon' ? : undefined; }; -const getContentRight = (contentRightOption: string) => { +const getContentRight = (contentRightOption: string, size: Size) => { const iconSize = 's'; - const counterSize = 'xs'; + const counterSize = 's'; switch (contentRightOption) { case 'icon': return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -65,14 +64,25 @@ const meta: Meta = { control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['pilled', 'animated', 'view', 'as', 'forwardedAs', 'outsideScroll', 'index']), + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -81,32 +91,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption)} - contentRight={getContentRight(contentRightOption)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -115,6 +146,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -127,26 +159,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })}
); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -155,6 +207,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -174,25 +227,45 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption)} - contentRight={getContentRight(contentRightOption)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
setIndex(item.value as number)} > @@ -201,7 +274,7 @@ const StoryShowAll = (props: StoryTabsProps) => { view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -212,14 +285,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 's', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -237,11 +323,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; + case 'showAll': + return ; + default: + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 's', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; case 'showAll': - return ; + return ; default: - return ; + return ; } }, }; diff --git a/packages/sdds-cs/src/components/Tabs/Tabs.tsx b/packages/sdds-cs/src/components/Tabs/Tabs.tsx index abb91d163f..9475451e71 100644 --- a/packages/sdds-cs/src/components/Tabs/Tabs.tsx +++ b/packages/sdds-cs/src/components/Tabs/Tabs.tsx @@ -1,9 +1,29 @@ -import { tabsConfig, component, mergeConfig } from '@salutejs/plasma-new-hope/styled-components'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabsConfig, + verticalTabsConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './Tabs.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; -const mergedConfig = mergeConfig(tabsConfig, config); /** * Контейнер вкладок, основной компонент для пользовательской сборки вкладок. */ -export const Tabs = component(mergedConfig); +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-cs/src/components/Tabs/TabsController.tsx b/packages/sdds-cs/src/components/Tabs/TabsController.tsx index 275962d0fc..9de85c85d0 100644 --- a/packages/sdds-cs/src/components/Tabs/TabsController.tsx +++ b/packages/sdds-cs/src/components/Tabs/TabsController.tsx @@ -1,15 +1,28 @@ -import type { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { createTabsController } from '@salutejs/plasma-new-hope/styled-components'; -import type { TabsProps, TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); /** * Контроллер вкладок. * Позволяет использовать клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. */ export const TabsController = createTabsController( - Tabs as ForwardRefExoticComponent>, - TabItem as ForwardRefExoticComponent>, + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, ); diff --git a/packages/sdds-cs/src/components/Tabs/TabItem.config.ts b/packages/sdds-cs/src/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 78% rename from packages/sdds-cs/src/components/Tabs/TabItem.config.ts rename to packages/sdds-cs/src/components/Tabs/horizontal/HorizontalTabItem.config.ts index d5b84023be..09d42bbe99 100644 --- a/packages/sdds-cs/src/components/Tabs/TabItem.config.ts +++ b/packages/sdds-cs/src/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -3,19 +3,24 @@ import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; export const config = { defaults: { view: 'secondary', - size: 'l', + size: 's', }, variations: { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -27,21 +32,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-transparent-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-transparent-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -52,21 +57,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` - ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemColor}: var(--text-accent); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; - ${tabsTokens.itemColorHover}: var(--text-secondary-hover); - ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemColorHover}: var(--text-accent-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-accent-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -78,21 +83,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -103,11 +108,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--on-dark-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--on-dark-text-secondary); `, }, size: { @@ -132,6 +132,7 @@ export const config = { disabled: { true: css` ${tabsTokens.disabledOpacity}: 0.4; + ${tabsTokens.itemColor}: var(--text-secondary); `, }, pilled: { diff --git a/packages/sdds-serv/src/components/Tabs/Tabs.config.ts b/packages/sdds-cs/src/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 96% rename from packages/sdds-serv/src/components/Tabs/Tabs.config.ts rename to packages/sdds-cs/src/components/Tabs/horizontal/HorizontalTabs.config.ts index 85adbb930e..00ea99d107 100644 --- a/packages/sdds-serv/src/components/Tabs/Tabs.config.ts +++ b/packages/sdds-cs/src/components/Tabs/horizontal/HorizontalTabs.config.ts @@ -26,12 +26,12 @@ export const config = { ${tabsTokens.tabsDividerBorderRadius}: 0rem; `, divider: css` - ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.arrowColor}: var(--text-accent); ${tabsTokens.tabsBackgroundColor}: transparent; ${tabsTokens.outlineFocusColor}: var(--surface-accent); ${tabsTokens.tabsDividerHeight}: 0.0625rem; - ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerColor}: var(--outline-solid-primary); ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; `, }, diff --git a/packages/sdds-cs/src/components/Tabs/index.ts b/packages/sdds-cs/src/components/Tabs/index.ts index f2d92ec950..10fada1d81 100644 --- a/packages/sdds-cs/src/components/Tabs/index.ts +++ b/packages/sdds-cs/src/components/Tabs/index.ts @@ -1,6 +1,6 @@ export { TabsController } from './TabsController'; export { TabItemRefs, TabsContext } from '@salutejs/plasma-new-hope/styled-components'; -export type { TabsProps, TabItemProps, TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; +export type { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; export { Tabs } from './Tabs'; export { TabItem } from './TabItem'; diff --git a/packages/sdds-cs/src/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/sdds-cs/src/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..114dadfda9 --- /dev/null +++ b/packages/sdds-cs/src/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,64 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 's', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-accent); + ${tabsTokens.itemValueColor}: var(--text-secondary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-accent-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-accent-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-cs/src/components/Tabs/vertical/VerticalTabs.config.ts b/packages/sdds-cs/src/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..c0a0a43dd2 --- /dev/null +++ b/packages/sdds-cs/src/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,41 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 's', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-accent); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--outline-solid-primary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + }, + stretch: { + true: css` + ${tabsTokens.containerHeight}: 100%; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-dfa/api/sdds-dfa.api.md b/packages/sdds-dfa/api/sdds-dfa.api.md index 18e82f8105..f79aa4ea4f 100644 --- a/packages/sdds-dfa/api/sdds-dfa.api.md +++ b/packages/sdds-dfa/api/sdds-dfa.api.md @@ -18,6 +18,8 @@ import { BaseboxProps } from '@salutejs/plasma-new-hope/styled-components'; import { BaseCallbackChangeInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseCallbackKeyboardInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseProps } from '@salutejs/plasma-new-hope/types/components/Autocomplete/Autocomplete.types'; +import { BaseTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/TabItem.types'; +import { BaseTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { BasicProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxNew/Combobox.types'; import { bodyL } from '@salutejs/sdds-themes/tokens'; import { bodyLBold } from '@salutejs/sdds-themes/tokens'; @@ -59,10 +61,10 @@ import { ColSizeProps } from '@salutejs/plasma-new-hope/styled-components'; import { ComponentProps } from 'react'; import { CounterProps } from '@salutejs/plasma-new-hope/styled-components'; import { counterTokens } from '@salutejs/plasma-new-hope/styled-components'; +import { CustomHorizontalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/Popover/Popover.types'; -import { CustomTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/TabItem/TabItem.types'; -import { CustomTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/Tabs/Tabs.types'; import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; +import { CustomVerticalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { DateInfo } from '@salutejs/plasma-new-hope/types/components/Calendar/Calendar.types'; import { DatePickerCalendarProps } from '@salutejs/plasma-new-hope/types/components/DatePicker/DatePickerBase.types'; import { datePickerClasses } from '@salutejs/plasma-new-hope/styled-components'; @@ -175,11 +177,9 @@ import { StepItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { StepsProps } from '@salutejs/plasma-new-hope/types/components/Steps/Steps.types'; import { StyledComponent } from 'styled-components'; import { SwitchProps as SwitchProps_2 } from '@salutejs/plasma-new-hope/styled-components'; -import { TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { TabItemRefs } from '@salutejs/plasma-new-hope/styled-components'; import { TabsContext } from '@salutejs/plasma-new-hope/styled-components'; import { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; -import { TabsProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextareaHTMLAttributes } from '@salutejs/plasma-new-hope/types/types'; import { TextFieldGroupProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextFieldPrimitiveValue } from '@salutejs/plasma-new-hope/types/components/TextField/TextField.types'; @@ -2610,65 +2610,17 @@ true: PolymorphicClassName; // @public (undocumented) export type SwitchProps = ComponentProps; +// Warning: (ae-forgotten-export) The symbol "TabItemProps" needs to be exported by the entry point index.d.ts +// // @public -export const TabItem: FunctionComponent & ButtonHTMLAttributes & AsProps & CustomTabItemProps & RefAttributes>; - -export { TabItemProps } +export const TabItem: (props: TabItemProps) => JSX.Element; export { TabItemRefs } +// Warning: (ae-forgotten-export) The symbol "TabsProps" needs to be exported by the entry point index.d.ts +// // @public -export const Tabs: FunctionComponent & HTMLAttributes & AsProps & CustomTabsProps & RefAttributes>; +export const Tabs: (props: TabsProps) => JSX.Element; export { TabsContext } @@ -2677,8 +2629,6 @@ export const TabsController: ForwardRefExoticComponent | ComponentProps; /** * Элемент списка, недопустимо использовать вне компонента Tabs. */ -export const TabItem = component(mergedConfig); +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-dfa/src/components/Tabs/Tabs.stories.tsx b/packages/sdds-dfa/src/components/Tabs/Tabs.stories.tsx index 2ff5410bd7..5efbe4fdc6 100644 --- a/packages/sdds-dfa/src/components/Tabs/Tabs.stories.tsx +++ b/packages/sdds-dfa/src/components/Tabs/Tabs.stories.tsx @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { IconPlasma } from '@salutejs/plasma-icons'; -import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; +import { disableProps, InSpacingDecorator } from '@salutejs/plasma-sb-utils'; -import { Dropdown } from '../Dropdown'; -import { Counter } from '../Counter'; +import { Dropdown } from '../Dropdown/Dropdown'; +import { Counter } from '../Counter/Counter'; import { Tabs } from './Tabs'; import { TabItem } from './TabItem'; @@ -15,17 +15,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; - stretch?: boolean; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -41,8 +43,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -66,14 +66,25 @@ const meta: Meta = { control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['pilled', 'animated', 'view', 'as', 'forwardedAs', 'outsideScroll', 'index']), + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -82,32 +93,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -116,6 +148,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -128,26 +161,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -156,6 +209,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -175,25 +229,45 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
setIndex(item.value as number)} > @@ -202,7 +276,7 @@ const StoryShowAll = (props: StoryTabsProps) => { view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -213,14 +287,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -238,11 +325,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; + case 'showAll': + return ; + default: + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; case 'showAll': - return ; + return ; default: - return ; + return ; } }, }; @@ -255,12 +603,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -294,7 +643,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, - ...disableProps(['clip']), + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/sdds-dfa/src/components/Tabs/Tabs.tsx b/packages/sdds-dfa/src/components/Tabs/Tabs.tsx index abb91d163f..9475451e71 100644 --- a/packages/sdds-dfa/src/components/Tabs/Tabs.tsx +++ b/packages/sdds-dfa/src/components/Tabs/Tabs.tsx @@ -1,9 +1,29 @@ -import { tabsConfig, component, mergeConfig } from '@salutejs/plasma-new-hope/styled-components'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabsConfig, + verticalTabsConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './Tabs.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; -const mergedConfig = mergeConfig(tabsConfig, config); /** * Контейнер вкладок, основной компонент для пользовательской сборки вкладок. */ -export const Tabs = component(mergedConfig); +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-dfa/src/components/Tabs/TabsController.tsx b/packages/sdds-dfa/src/components/Tabs/TabsController.tsx index 275962d0fc..9de85c85d0 100644 --- a/packages/sdds-dfa/src/components/Tabs/TabsController.tsx +++ b/packages/sdds-dfa/src/components/Tabs/TabsController.tsx @@ -1,15 +1,28 @@ -import type { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { createTabsController } from '@salutejs/plasma-new-hope/styled-components'; -import type { TabsProps, TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); /** * Контроллер вкладок. * Позволяет использовать клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. */ export const TabsController = createTabsController( - Tabs as ForwardRefExoticComponent>, - TabItem as ForwardRefExoticComponent>, + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, ); diff --git a/packages/sdds-serv/src/components/Tabs/TabItem.config.ts b/packages/sdds-dfa/src/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 90% rename from packages/sdds-serv/src/components/Tabs/TabItem.config.ts rename to packages/sdds-dfa/src/components/Tabs/horizontal/HorizontalTabItem.config.ts index 0f80348fde..5cc1df8fbe 100644 --- a/packages/sdds-serv/src/components/Tabs/TabItem.config.ts +++ b/packages/sdds-dfa/src/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -9,13 +9,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -27,21 +32,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-transparent-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-transparent-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -52,21 +57,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -78,21 +83,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -103,11 +108,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--on-dark-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--on-dark-text-secondary); `, }, size: { diff --git a/packages/sdds-cs/src/components/Tabs/Tabs.config.ts b/packages/sdds-dfa/src/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 100% rename from packages/sdds-cs/src/components/Tabs/Tabs.config.ts rename to packages/sdds-dfa/src/components/Tabs/horizontal/HorizontalTabs.config.ts diff --git a/packages/sdds-dfa/src/components/Tabs/index.ts b/packages/sdds-dfa/src/components/Tabs/index.ts index f2d92ec950..10fada1d81 100644 --- a/packages/sdds-dfa/src/components/Tabs/index.ts +++ b/packages/sdds-dfa/src/components/Tabs/index.ts @@ -1,6 +1,6 @@ export { TabsController } from './TabsController'; export { TabItemRefs, TabsContext } from '@salutejs/plasma-new-hope/styled-components'; -export type { TabsProps, TabItemProps, TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; +export type { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; export { Tabs } from './Tabs'; export { TabItem } from './TabItem'; diff --git a/packages/sdds-dfa/src/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/sdds-dfa/src/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..97c7fbbf6d --- /dev/null +++ b/packages/sdds-dfa/src/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,118 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-dfa/src/components/Tabs/vertical/VerticalTabs.config.ts b/packages/sdds-dfa/src/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..5bef8112a6 --- /dev/null +++ b/packages/sdds-dfa/src/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,62 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + stretch: { + true: css` + ${tabsTokens.containerHeight}: 100%; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-finportal/api/sdds-finportal.api.md b/packages/sdds-finportal/api/sdds-finportal.api.md index bfad12df70..5bc69d40dc 100644 --- a/packages/sdds-finportal/api/sdds-finportal.api.md +++ b/packages/sdds-finportal/api/sdds-finportal.api.md @@ -20,6 +20,8 @@ import { BaseboxProps } from '@salutejs/plasma-new-hope/styled-components'; import { BaseCallbackChangeInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseCallbackKeyboardInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseProps } from '@salutejs/plasma-new-hope/types/components/Autocomplete/Autocomplete.types'; +import { BaseTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/TabItem.types'; +import { BaseTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { BasicProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxNew/Combobox.types'; import { bodyL } from '@salutejs/sdds-themes/tokens'; import { bodyLBold } from '@salutejs/sdds-themes/tokens'; @@ -63,10 +65,10 @@ import { ColSizeProps } from '@salutejs/plasma-new-hope/styled-components'; import { ComponentProps } from 'react'; import { CounterProps } from '@salutejs/plasma-new-hope/styled-components'; import { counterTokens } from '@salutejs/plasma-new-hope/styled-components'; +import { CustomHorizontalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/Popover/Popover.types'; -import { CustomTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/TabItem/TabItem.types'; -import { CustomTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/Tabs/Tabs.types'; import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; +import { CustomVerticalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { DateInfo } from '@salutejs/plasma-new-hope/types/components/Calendar/Calendar.types'; import { DatePickerCalendarProps } from '@salutejs/plasma-new-hope/types/components/DatePicker/DatePickerBase.types'; import { datePickerClasses } from '@salutejs/plasma-new-hope/styled-components'; @@ -185,11 +187,9 @@ import { StepItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { StepsProps } from '@salutejs/plasma-new-hope/types/components/Steps/Steps.types'; import { StyledComponent } from 'styled-components'; import { SwitchProps as SwitchProps_2 } from '@salutejs/plasma-new-hope/styled-components'; -import { TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { TabItemRefs } from '@salutejs/plasma-new-hope/styled-components'; import { TabsContext } from '@salutejs/plasma-new-hope/styled-components'; import { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; -import { TabsProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextareaHTMLAttributes } from '@salutejs/plasma-new-hope/types/types'; import { TextFieldGroupProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextFieldPrimitiveValue } from '@salutejs/plasma-new-hope/types/components/TextField/TextField.types'; @@ -2704,65 +2704,17 @@ true: PolymorphicClassName; // @public (undocumented) export type SwitchProps = ComponentProps; +// Warning: (ae-forgotten-export) The symbol "TabItemProps" needs to be exported by the entry point index.d.ts +// // @public -export const TabItem: FunctionComponent & ButtonHTMLAttributes & AsProps & CustomTabItemProps & RefAttributes>; - -export { TabItemProps } +export const TabItem: (props: TabItemProps) => JSX.Element; export { TabItemRefs } +// Warning: (ae-forgotten-export) The symbol "TabsProps" needs to be exported by the entry point index.d.ts +// // @public -export const Tabs: FunctionComponent & HTMLAttributes & AsProps & CustomTabsProps & RefAttributes>; +export const Tabs: (props: TabsProps) => JSX.Element; export { TabsContext } @@ -2771,8 +2723,6 @@ export const TabsController: ForwardRefExoticComponent | ComponentProps; /** * Элемент списка, недопустимо использовать вне компонента Tabs. */ -export const TabItem = component(mergedConfig); +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-finportal/src/components/Tabs/Tabs.stories.tsx b/packages/sdds-finportal/src/components/Tabs/Tabs.stories.tsx index bfa04cc845..5efbe4fdc6 100644 --- a/packages/sdds-finportal/src/components/Tabs/Tabs.stories.tsx +++ b/packages/sdds-finportal/src/components/Tabs/Tabs.stories.tsx @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { IconPlasma } from '@salutejs/plasma-icons'; -import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; +import { disableProps, InSpacingDecorator } from '@salutejs/plasma-sb-utils'; -import { Dropdown } from '../Dropdown'; -import { Counter } from '../Counter'; +import { Dropdown } from '../Dropdown/Dropdown'; +import { Counter } from '../Counter/Counter'; import { Tabs } from './Tabs'; import { TabItem } from './TabItem'; @@ -15,16 +15,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -40,8 +43,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -65,14 +66,25 @@ const meta: Meta = { control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['pilled', 'animated', 'view', 'as', 'forwardedAs', 'outsideScroll', 'index']), + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -81,32 +93,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -115,6 +148,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -127,26 +161,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })}
); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -155,6 +209,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -174,25 +229,45 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
setIndex(item.value as number)} > @@ -201,7 +276,7 @@ const StoryShowAll = (props: StoryTabsProps) => { view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -212,14 +287,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -237,11 +325,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; + case 'showAll': + return ; + default: + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; case 'showAll': - return ; + return ; default: - return ; + return ; } }, }; @@ -254,12 +603,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -293,7 +643,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, - ...disableProps(['clip']), + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/sdds-finportal/src/components/Tabs/Tabs.tsx b/packages/sdds-finportal/src/components/Tabs/Tabs.tsx index abb91d163f..9475451e71 100644 --- a/packages/sdds-finportal/src/components/Tabs/Tabs.tsx +++ b/packages/sdds-finportal/src/components/Tabs/Tabs.tsx @@ -1,9 +1,29 @@ -import { tabsConfig, component, mergeConfig } from '@salutejs/plasma-new-hope/styled-components'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabsConfig, + verticalTabsConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './Tabs.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; -const mergedConfig = mergeConfig(tabsConfig, config); /** * Контейнер вкладок, основной компонент для пользовательской сборки вкладок. */ -export const Tabs = component(mergedConfig); +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-finportal/src/components/Tabs/TabsController.tsx b/packages/sdds-finportal/src/components/Tabs/TabsController.tsx index 275962d0fc..9de85c85d0 100644 --- a/packages/sdds-finportal/src/components/Tabs/TabsController.tsx +++ b/packages/sdds-finportal/src/components/Tabs/TabsController.tsx @@ -1,15 +1,28 @@ -import type { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { createTabsController } from '@salutejs/plasma-new-hope/styled-components'; -import type { TabsProps, TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); /** * Контроллер вкладок. * Позволяет использовать клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. */ export const TabsController = createTabsController( - Tabs as ForwardRefExoticComponent>, - TabItem as ForwardRefExoticComponent>, + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, ); diff --git a/packages/sdds-dfa/src/components/Tabs/TabItem.config.ts b/packages/sdds-finportal/src/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 90% rename from packages/sdds-dfa/src/components/Tabs/TabItem.config.ts rename to packages/sdds-finportal/src/components/Tabs/horizontal/HorizontalTabItem.config.ts index 0f80348fde..5cc1df8fbe 100644 --- a/packages/sdds-dfa/src/components/Tabs/TabItem.config.ts +++ b/packages/sdds-finportal/src/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -9,13 +9,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -27,21 +32,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-transparent-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-transparent-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -52,21 +57,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -78,21 +83,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -103,11 +108,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--on-dark-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--on-dark-text-secondary); `, }, size: { diff --git a/packages/sdds-dfa/src/components/Tabs/Tabs.config.ts b/packages/sdds-finportal/src/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 100% rename from packages/sdds-dfa/src/components/Tabs/Tabs.config.ts rename to packages/sdds-finportal/src/components/Tabs/horizontal/HorizontalTabs.config.ts diff --git a/packages/sdds-finportal/src/components/Tabs/index.ts b/packages/sdds-finportal/src/components/Tabs/index.ts index f2d92ec950..10fada1d81 100644 --- a/packages/sdds-finportal/src/components/Tabs/index.ts +++ b/packages/sdds-finportal/src/components/Tabs/index.ts @@ -1,6 +1,6 @@ export { TabsController } from './TabsController'; export { TabItemRefs, TabsContext } from '@salutejs/plasma-new-hope/styled-components'; -export type { TabsProps, TabItemProps, TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; +export type { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; export { Tabs } from './Tabs'; export { TabItem } from './TabItem'; diff --git a/packages/sdds-finportal/src/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/sdds-finportal/src/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..97c7fbbf6d --- /dev/null +++ b/packages/sdds-finportal/src/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,118 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-finportal/src/components/Tabs/vertical/VerticalTabs.config.ts b/packages/sdds-finportal/src/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..5bef8112a6 --- /dev/null +++ b/packages/sdds-finportal/src/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,62 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + stretch: { + true: css` + ${tabsTokens.containerHeight}: 100%; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-serv/api/sdds-serv.api.md b/packages/sdds-serv/api/sdds-serv.api.md index 75c1eec5e9..e918e309ca 100644 --- a/packages/sdds-serv/api/sdds-serv.api.md +++ b/packages/sdds-serv/api/sdds-serv.api.md @@ -20,6 +20,8 @@ import { BaseboxProps } from '@salutejs/plasma-new-hope/styled-components'; import { BaseCallbackChangeInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseCallbackKeyboardInstance } from '@salutejs/plasma-new-hope/types/components/Range/Range.types'; import { BaseProps } from '@salutejs/plasma-new-hope/types/components/Autocomplete/Autocomplete.types'; +import { BaseTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/TabItem.types'; +import { BaseTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { BasicProps } from '@salutejs/plasma-new-hope/types/components/Combobox/ComboboxNew/Combobox.types'; import { bodyL } from '@salutejs/sdds-themes/tokens'; import { bodyLBold } from '@salutejs/sdds-themes/tokens'; @@ -63,10 +65,10 @@ import { ColSizeProps } from '@salutejs/plasma-new-hope/styled-components'; import { ComponentProps } from 'react'; import { CounterProps } from '@salutejs/plasma-new-hope/styled-components'; import { counterTokens } from '@salutejs/plasma-new-hope/styled-components'; +import { CustomHorizontalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { CustomPopoverProps } from '@salutejs/plasma-new-hope/types/components/Popover/Popover.types'; -import { CustomTabItemProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/TabItem/TabItem.types'; -import { CustomTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/ui/Tabs/Tabs.types'; import { CustomToastProps } from '@salutejs/plasma-new-hope/types/components/Toast/Toast.types'; +import { CustomVerticalTabsProps } from '@salutejs/plasma-new-hope/types/components/Tabs/Tabs.types'; import { DateInfo } from '@salutejs/plasma-new-hope/types/components/Calendar/Calendar.types'; import { DatePickerCalendarProps } from '@salutejs/plasma-new-hope/types/components/DatePicker/DatePickerBase.types'; import { datePickerClasses } from '@salutejs/plasma-new-hope/styled-components'; @@ -185,11 +187,9 @@ import { StepItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { StepsProps } from '@salutejs/plasma-new-hope/types/components/Steps/Steps.types'; import { StyledComponent } from 'styled-components'; import { SwitchProps as SwitchProps_2 } from '@salutejs/plasma-new-hope/styled-components'; -import { TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; import { TabItemRefs } from '@salutejs/plasma-new-hope/styled-components'; import { TabsContext } from '@salutejs/plasma-new-hope/styled-components'; import { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; -import { TabsProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextareaHTMLAttributes } from '@salutejs/plasma-new-hope/types/types'; import { TextFieldGroupProps } from '@salutejs/plasma-new-hope/styled-components'; import { TextFieldPrimitiveValue } from '@salutejs/plasma-new-hope/types/components/TextField/TextField.types'; @@ -2704,65 +2704,17 @@ true: PolymorphicClassName; // @public (undocumented) export type SwitchProps = ComponentProps; +// Warning: (ae-forgotten-export) The symbol "TabItemProps" needs to be exported by the entry point index.d.ts +// // @public -export const TabItem: FunctionComponent & ButtonHTMLAttributes & AsProps & CustomTabItemProps & RefAttributes>; - -export { TabItemProps } +export const TabItem: (props: TabItemProps) => JSX.Element; export { TabItemRefs } +// Warning: (ae-forgotten-export) The symbol "TabsProps" needs to be exported by the entry point index.d.ts +// // @public -export const Tabs: FunctionComponent & HTMLAttributes & AsProps & CustomTabsProps & RefAttributes>; +export const Tabs: (props: TabsProps) => JSX.Element; export { TabsContext } @@ -2771,8 +2723,6 @@ export const TabsController: ForwardRefExoticComponent | ComponentProps; /** * Элемент списка, недопустимо использовать вне компонента Tabs. */ -export const TabItem = component(mergedConfig); +export const TabItem = (props: TabItemProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-serv/src/components/Tabs/Tabs.stories.tsx b/packages/sdds-serv/src/components/Tabs/Tabs.stories.tsx index bfa04cc845..5efbe4fdc6 100644 --- a/packages/sdds-serv/src/components/Tabs/Tabs.stories.tsx +++ b/packages/sdds-serv/src/components/Tabs/Tabs.stories.tsx @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { IconPlasma } from '@salutejs/plasma-icons'; -import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; +import { disableProps, InSpacingDecorator } from '@salutejs/plasma-sb-utils'; -import { Dropdown } from '../Dropdown'; -import { Counter } from '../Counter'; +import { Dropdown } from '../Dropdown/Dropdown'; +import { Counter } from '../Counter/Counter'; import { Tabs } from './Tabs'; import { TabItem } from './TabItem'; @@ -15,16 +15,19 @@ const sizes = ['xs', 's', 'm', 'l'] as const; const headerSizes = ['h5', 'h4', 'h3', 'h2', 'h1'] as const; type Size = typeof sizes[number]; +type HeaderSize = typeof headerSizes[number]; type CustomStoryTabsProps = { - hasDivider: boolean; itemQuantity: number; + hasDivider: boolean; contentLeft: string; contentRight: string; + stretch: boolean; + helperText: string; }; const contentLeftOptions = ['none', 'icon']; -const contentRightOptions = ['none', 'text', 'counter', 'icon']; +const contentRightOptions = ['none', 'counter', 'icon']; const getContentLeft = (contentLeftOption: string, size: Size) => { const iconSize = size === 'xs' ? 'xs' : 's'; @@ -40,8 +43,6 @@ const getContentRight = (contentRightOption: string, size: Size) => { return ; case 'counter': return ; - case 'text': - return
Text
; default: return undefined; } @@ -65,14 +66,25 @@ const meta: Meta = { control: { type: 'select', }, + if: { arg: 'helperText', eq: '' }, }, - ...disableProps(['pilled', 'animated', 'view', 'as', 'forwardedAs', 'outsideScroll', 'index']), + ...disableProps([ + 'orientation', + 'tabItemContentLeft', + 'pilled', + 'animated', + 'view', + 'as', + 'forwardedAs', + 'outsideScroll', + 'index', + ]), }, }; export default meta; -const StoryDefault = (props: StoryTabsProps) => { +const StoryHorizontalDefault = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -81,32 +93,53 @@ const StoryDefault = (props: StoryTabsProps) => { contentRight: contentRightOption, hasDivider, stretch, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} ); }; -const StoryScroll = (props: StoryTabsProps) => { +const StoryHorizontalScroll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -115,6 +148,7 @@ const StoryScroll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); @@ -127,26 +161,46 @@ const StoryScroll = (props: StoryTabsProps) => { size={size} style={{ width: '15rem' }} > - {items.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })}
); }; -const StoryShowAll = (props: StoryTabsProps) => { +const StoryHorizontalShowAll = (props: StoryTabsProps) => { const { disabled, itemQuantity, @@ -155,6 +209,7 @@ const StoryShowAll = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + helperText, } = props; const maxItemQuantity = 3; const items = Array(itemQuantity).fill(0); @@ -174,25 +229,45 @@ const StoryShowAll = (props: StoryTabsProps) => { return ( - {visibleItems.map((_, i) => ( - !disabled && setIndex(i)} - tabIndex={!disabled ? 0 : -1} - disabled={disabled} - contentLeft={getContentLeft(contentLeftOption, size as Size)} - contentRight={getContentRight(contentRightOption, size as Size)} - size={size} - > - {`Label${i + 1}`} - - ))} + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} {dropdownItems.length > 0 && (
setIndex(item.value as number)} > @@ -201,7 +276,7 @@ const StoryShowAll = (props: StoryTabsProps) => { view="divider" tabIndex={!disabled ? 0 : -1} disabled={disabled} - size={size} + size={size as Size} > ShowAll @@ -212,14 +287,27 @@ const StoryShowAll = (props: StoryTabsProps) => { ); }; -export const Default: StoryObj = { +export const HorizontalTabs: StoryObj = { args: { size: 'xs', disabled: false, hasDivider: true, + helperText: '', itemQuantity: 8, }, argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, clip: { options: clips, control: { @@ -237,11 +325,272 @@ export const Default: StoryObj = { render: (args) => { switch (args.clip) { case 'scroll': - return ; + return ; + case 'showAll': + return ; + default: + return ; + } + }, +}; + +const StoryVerticalDefault = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalScroll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + return ( + + {items.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + + ); +}; + +const StoryVerticalShowAll = (props: StoryTabsProps) => { + const { + disabled, + itemQuantity, + clip, + size, + contentLeft: contentLeftOption, + contentRight: contentRightOption, + hasDivider, + helperText, + } = props; + const maxItemQuantity = 3; + const items = Array(itemQuantity).fill(0); + const [index, setIndex] = useState(0); + + const visibleItems = items.slice(0, maxItemQuantity); + const otherItems = items.slice(maxItemQuantity); + + const dropdownItems = otherItems.map((_, i) => { + const itemIndex = maxItemQuantity + i; + + return { + label: `Label${itemIndex + 1}`, + value: itemIndex, + }; + }); + + return ( + + {visibleItems.map((_, i) => { + if (helperText !== '') { + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + value={helperText} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + } + + return ( + !disabled && setIndex(i)} + tabIndex={!disabled ? 0 : -1} + disabled={disabled} + contentLeft={getContentLeft(contentLeftOption, size as Size)} + contentRight={getContentRight(contentRightOption, size as Size)} + size={size as Size} + > + {`Label${i + 1}`} + + ); + })} + {dropdownItems.length > 0 && ( + setIndex(item.value as number)} + placement="right" + > + + ShowAll + + + )} + + ); +}; + +export const VerticalTabs: StoryObj = { + args: { + size: 'xs', + disabled: false, + hasDivider: true, + itemQuantity: 8, + orientation: 'vertical', + helperText: '', + }, + argTypes: { + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + size: { + options: sizes, + control: { + type: 'select', + }, + }, + clip: { + options: clips, + control: { + type: 'select', + }, + if: { arg: 'stretch', truthy: false }, + }, + stretch: { + table: { + disable: true, + }, + }, + }, + render: (args) => { + switch (args.clip) { + case 'scroll': + return ; case 'showAll': - return ; + return ; default: - return ; + return ; } }, }; @@ -254,12 +603,13 @@ const StoryHeaderTabs = (props: StoryTabsProps) => { contentLeft: contentLeftOption, contentRight: contentRightOption, hasDivider, + stretch, } = props; const items = Array(itemQuantity).fill(0); const [index, setIndex] = useState(0); return ( - + {items.map((_, i) => ( { disabled={disabled} contentLeft={getContentLeft(contentLeftOption, size as Size)} contentRight={getContentRight(contentRightOption, size as Size)} - size={size} + size={size as HeaderSize} > {`Label${i + 1}`} @@ -293,7 +643,23 @@ export const HeaderTabs: StoryObj = { type: 'select', }, }, - ...disableProps(['clip']), + contentLeft: { + options: contentLeftOptions, + control: { + type: 'select', + }, + }, + contentRight: { + options: contentRightOptions, + control: { + type: 'select', + }, + }, + clip: { + table: { + disable: true, + }, + }, }, render: (args) => , }; diff --git a/packages/sdds-serv/src/components/Tabs/Tabs.tsx b/packages/sdds-serv/src/components/Tabs/Tabs.tsx index abb91d163f..9475451e71 100644 --- a/packages/sdds-serv/src/components/Tabs/Tabs.tsx +++ b/packages/sdds-serv/src/components/Tabs/Tabs.tsx @@ -1,9 +1,29 @@ -import { tabsConfig, component, mergeConfig } from '@salutejs/plasma-new-hope/styled-components'; +import React, { ComponentProps } from 'react'; +import { + horizontalTabsConfig, + verticalTabsConfig, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; -import { config } from './Tabs.config'; +import { config as horizontalConfig } from './horizontal/HorizontalTabs.config'; +import { config as verticalConfig } from './vertical/VerticalTabs.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalTabsConfig, horizontalConfig); +const mergedVerticalTabsConfig = mergeConfig(verticalTabsConfig, verticalConfig); + +const HorizontalTabs = component(mergedHorizontalTabsConfig); +const VerticalTabs = component(mergedVerticalTabsConfig); + +type TabsProps = ComponentProps | ComponentProps; -const mergedConfig = mergeConfig(tabsConfig, config); /** * Контейнер вкладок, основной компонент для пользовательской сборки вкладок. */ -export const Tabs = component(mergedConfig); +export const Tabs = (props: TabsProps) => { + if (props.orientation === 'vertical') { + return ; + } + + return ; +}; diff --git a/packages/sdds-serv/src/components/Tabs/TabsController.tsx b/packages/sdds-serv/src/components/Tabs/TabsController.tsx index 275962d0fc..9de85c85d0 100644 --- a/packages/sdds-serv/src/components/Tabs/TabsController.tsx +++ b/packages/sdds-serv/src/components/Tabs/TabsController.tsx @@ -1,15 +1,28 @@ -import type { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { createTabsController } from '@salutejs/plasma-new-hope/styled-components'; -import type { TabsProps, TabItemProps } from '@salutejs/plasma-new-hope/styled-components'; +import { + createTabsController, + horizontalTabItemConfig as horizontalBaseTabItemConfig, + horizontalTabsConfig as horizontalBaseTabsConfig, + HorizontalTabItemProps, + HorizontalTabsProps, + component, + mergeConfig, +} from '@salutejs/plasma-new-hope/styled-components'; +import { ForwardRefExoticComponent, RefAttributes } from 'react'; -import { Tabs } from './Tabs'; -import { TabItem } from './TabItem'; +import { config as horizontalTabsConfig } from './horizontal/HorizontalTabs.config'; +import { config as horizontalTabItemConfig } from './horizontal/HorizontalTabItem.config'; + +const mergedHorizontalTabsConfig = mergeConfig(horizontalBaseTabsConfig, horizontalTabsConfig); +const HorizontalTabs = component(mergedHorizontalTabsConfig); + +const mergedHorizontalTabItemConfig = mergeConfig(horizontalBaseTabItemConfig, horizontalTabItemConfig); +const HorizontalTabItem = component(mergedHorizontalTabItemConfig); /** * Контроллер вкладок. * Позволяет использовать клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. */ export const TabsController = createTabsController( - Tabs as ForwardRefExoticComponent>, - TabItem as ForwardRefExoticComponent>, + HorizontalTabs as ForwardRefExoticComponent>, + HorizontalTabItem as ForwardRefExoticComponent>, ); diff --git a/packages/sdds-finportal/src/components/Tabs/TabItem.config.ts b/packages/sdds-serv/src/components/Tabs/horizontal/HorizontalTabItem.config.ts similarity index 90% rename from packages/sdds-finportal/src/components/Tabs/TabItem.config.ts rename to packages/sdds-serv/src/components/Tabs/horizontal/HorizontalTabItem.config.ts index 0f80348fde..5cc1df8fbe 100644 --- a/packages/sdds-finportal/src/components/Tabs/TabItem.config.ts +++ b/packages/sdds-serv/src/components/Tabs/horizontal/HorizontalTabItem.config.ts @@ -9,13 +9,18 @@ export const config = { view: { clear: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -27,21 +32,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, secondary: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-transparent-card); ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-transparent-card); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -52,21 +57,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, divider: css` ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: transparent; ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -78,21 +83,21 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-tertiary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--text-secondary); `, default: css` ${tabsTokens.itemColor}: var(--text-primary); + ${tabsTokens.itemValueColor}: var(--text-secondary); ${tabsTokens.itemBackgroundColor}: transparent; ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); ${tabsTokens.itemBackgroundColorHover}: transparent; ${tabsTokens.itemSelectedColor}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColor}: var(--surface-solid-default); ${tabsTokens.itemSelectedColorHover}: var(--inverse-text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--on-dark-text-secondary); ${tabsTokens.itemSelectedBackgroundColorHover}: var(--surface-solid-default); ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; @@ -103,11 +108,6 @@ export const config = { ${tabsTokens.itemSelectedDividerHeight}: 0rem; ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); - - ${tabsTokens.additionalContentColor}: var(--text-secondary); - ${tabsTokens.additionalContentHoverColor}: var(--text-secondary); - ${tabsTokens.additionalContentSelectedColor}: var(--on-dark-text-secondary); - ${tabsTokens.additionalContentSelectedHoverColor}: var(--on-dark-text-secondary); `, }, size: { diff --git a/packages/sdds-finportal/src/components/Tabs/Tabs.config.ts b/packages/sdds-serv/src/components/Tabs/horizontal/HorizontalTabs.config.ts similarity index 100% rename from packages/sdds-finportal/src/components/Tabs/Tabs.config.ts rename to packages/sdds-serv/src/components/Tabs/horizontal/HorizontalTabs.config.ts diff --git a/packages/sdds-serv/src/components/Tabs/index.ts b/packages/sdds-serv/src/components/Tabs/index.ts index f2d92ec950..10fada1d81 100644 --- a/packages/sdds-serv/src/components/Tabs/index.ts +++ b/packages/sdds-serv/src/components/Tabs/index.ts @@ -1,6 +1,6 @@ export { TabsController } from './TabsController'; export { TabItemRefs, TabsContext } from '@salutejs/plasma-new-hope/styled-components'; -export type { TabsProps, TabItemProps, TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; +export type { TabsControllerProps } from '@salutejs/plasma-new-hope/styled-components'; export { Tabs } from './Tabs'; export { TabItem } from './TabItem'; diff --git a/packages/sdds-serv/src/components/Tabs/vertical/VerticalTabItem.config.ts b/packages/sdds-serv/src/components/Tabs/vertical/VerticalTabItem.config.ts new file mode 100644 index 0000000000..97c7fbbf6d --- /dev/null +++ b/packages/sdds-serv/src/components/Tabs/vertical/VerticalTabItem.config.ts @@ -0,0 +1,118 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.itemColor}: var(--text-secondary); + ${tabsTokens.itemValueColor}: var(--text-tertiary); + ${tabsTokens.itemBackgroundColor}: transparent; + ${tabsTokens.itemColorHover}: var(--text-secondary-hover); + ${tabsTokens.itemValueColorHover}: var(--text-secondary); + ${tabsTokens.itemColorActive}: var(--text-secondary-active); + ${tabsTokens.itemValueColorActive}: var(--text-secondary); + ${tabsTokens.itemBackgroundColorHover}: transparent; + ${tabsTokens.itemSelectedColor}: var(--text-primary); + ${tabsTokens.itemSelectedValueColor}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColor}: transparent; + ${tabsTokens.itemSelectedColorHover}: var(--text-primary); + ${tabsTokens.itemSelectedValueColorHover}: var(--text-secondary); + ${tabsTokens.itemSelectedBackgroundColorHover}: transparent; + ${tabsTokens.itemBackgroundTransition}: background-color 0.3s ease-in-out; + + ${tabsTokens.itemPaddingClear}: 0; + ${tabsTokens.itemContentPaddingClear}: 0; + + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.itemSelectedDividerWidth}: 0.125rem; + ${tabsTokens.itemSelectedDividerHeight}: 0.125rem; + ${tabsTokens.itemSelectedDividerColor}: var(--text-primary); + ${tabsTokens.itemSelectedDividerColorHover}: var(--text-primary); + `, + }, + size: { + xs: css` + ${tabsTokens.itemBorderRadius}: 0.375rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2rem; + ${tabsTokens.itemPadding}: 0 0.5rem; + ${tabsTokens.itemPaddingPilled}: 0 0.375rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 0.625rem; + ${tabsTokens.itemMarginLeft}: 0.625rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-xs-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-xs-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-xs-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-xs-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-xs-line-height); + `, + s: css` + ${tabsTokens.itemBorderRadius}: 0.5rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 2.5rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.5rem 1rem; + ${tabsTokens.itemMarginLeft}: 0.75rem; + ${tabsTokens.itemContentGap}: 0.25rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-s-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-s-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-s-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-s-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-s-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-s-line-height); + `, + m: css` + ${tabsTokens.itemBorderRadius}: 0.625rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3rem; + ${tabsTokens.itemPadding}: 0 0.625rem; + ${tabsTokens.itemPaddingPilled}: 0 0.5rem; + ${tabsTokens.itemPaddingOrientationVertical}: 0.75rem 1.25rem; + ${tabsTokens.itemMarginLeft}: 1.125rem; + ${tabsTokens.itemContentGap}: 0.375rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-m-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-m-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-m-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-m-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-m-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-m-line-height); + `, + l: css` + ${tabsTokens.itemBorderRadius}: 0.75rem; + ${tabsTokens.itemWidth}: auto; + ${tabsTokens.itemHeight}: 3.5rem; + ${tabsTokens.itemPadding}: 0 0.875rem; + ${tabsTokens.itemPaddingPilled}: 0 0.75rem; + ${tabsTokens.itemPaddingOrientationVertical}: 1rem 1.5rem; + ${tabsTokens.itemMarginLeft}: 1.25rem; + ${tabsTokens.itemContentGap}: 0.5rem; + ${tabsTokens.itemContentPadding}: 0.125rem; + + ${tabsTokens.fontFamily}: var(--plasma-typo-body-l-font-family); + ${tabsTokens.fontSize}: var(--plasma-typo-body-l-font-size); + ${tabsTokens.fontStyle}: var(--plasma-typo-body-l-font-style); + ${tabsTokens.fontWeight}: var(--plasma-typo-body-l-font-weight); + ${tabsTokens.letterSpacing}: var(--plasma-typo-body-l-letter-spacing); + ${tabsTokens.lineHeight}: var(--plasma-typo-body-l-line-height); + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/packages/sdds-serv/src/components/Tabs/vertical/VerticalTabs.config.ts b/packages/sdds-serv/src/components/Tabs/vertical/VerticalTabs.config.ts new file mode 100644 index 0000000000..5bef8112a6 --- /dev/null +++ b/packages/sdds-serv/src/components/Tabs/vertical/VerticalTabs.config.ts @@ -0,0 +1,62 @@ +import { css, tabsTokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'divider', + size: 'l', + }, + variations: { + view: { + divider: css` + ${tabsTokens.arrowColor}: var(--text-secondary); + ${tabsTokens.tabsBackgroundColor}: transparent; + ${tabsTokens.outlineFocusColor}: var(--surface-accent); + + ${tabsTokens.tabsDividerWidth}: 0.0625rem; + ${tabsTokens.tabsDividerHeight}: 0.0625rem; + ${tabsTokens.tabsDividerColor}: var(--surface-transparent-tertiary); + ${tabsTokens.tabsDividerBorderRadius}: 0.0625rem; + `, + }, + size: { + xs: css` + ${tabsTokens.tabsBorderRadius}: 0.5rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.125rem; + `, + s: css` + ${tabsTokens.tabsBorderRadius}: 0.625rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.25rem; + `, + m: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.625rem; + `, + l: css` + ${tabsTokens.tabsBorderRadius}: 0.75rem; + ${tabsTokens.tabsWidth}: fit-content; + ${tabsTokens.tabsHeight}: auto; + ${tabsTokens.arrowInnerPadding}: 0rem; + ${tabsTokens.arrowOuterPadding}: 0.75rem; + `, + }, + stretch: { + true: css` + ${tabsTokens.containerHeight}: 100%; + `, + }, + disabled: { + true: css` + ${tabsTokens.disabledOpacity}: 0.4; + `, + }, + }, +}; diff --git a/website/plasma-b2c-docs/docs/components/Tabs.mdx b/website/plasma-b2c-docs/docs/components/Tabs.mdx index b4c484b584..9c4fcc6d47 100644 --- a/website/plasma-b2c-docs/docs/components/Tabs.mdx +++ b/website/plasma-b2c-docs/docs/components/Tabs.mdx @@ -21,6 +21,11 @@ import { PropsTable, Description, StorybookLink } from '@site/src/components'; +:::caution Взаимоисключающие свойства +Свойство `value` - это значение таба. Оно отображается справа от основного текста.
+`value` и `contentRight` взаимоисключающие: если передано одно, второе передать нельзя. +::: + ## TabsController (deprecated) Вместо этого используйте Tabs, TabItem, указав параметры index, itemIndex, onIndexChange. @@ -62,6 +67,41 @@ export function App() { } ``` +### Расположение табов +Табы могут быть горизонтальными (по умолчанию) и вертикальными. За это отвечает свойство `orientation`. + +```tsx live +import React, { useState } from 'react'; +import { Tabs, TabItem } from '@salutejs/plasma-b2c'; +import { IconClock } from '@salutejs/plasma-icons'; + +export function App() { + const items = Array(8).fill(0); + const [index, setIndex] = useState(0); + + return ( +
+ + {items.map((_, i) => ( + } + onClick={() => setIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ ); +} +``` + ### Пример с прокруткой ```tsx live @@ -149,8 +189,9 @@ export function App() { ``` ### Подключение клавиатурной навигации -Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`. -Клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. +Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`.
+Для горизонтальных табов: клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам.
+Для вертикальных табов: клавиши ArrowUp, ArrowDown, Home, End для навигации по вкладкам. ```tsx live import React, { useState } from 'react'; @@ -159,27 +200,51 @@ import { IconClock } from '@salutejs/plasma-icons'; export function App() { const items = Array(4).fill(0); - const [index, setIndex] = useState(0); + const [horizontalIndex, setHorizontalIndex] = useState(0); + const [verticalIndex, setVerticalIndex] = useState(0); return ( -
- - {items.map((_, i) => ( - setIndex(i)} - selected={i === index} - tabIndex={0} - contentLeft={} - onClick={() => setIndex(i)} - > - {`Label${i + 1}`} - - ))} - +
+
+ + {items.map((_, i) => ( + setHorizontalIndex(i)} + selected={i === horizontalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setHorizontalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ +
+ + {items.map((_, i) => ( + setVerticalIndex(i)} + selected={i === verticalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setVerticalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
); } diff --git a/website/plasma-web-docs/docs/components/Tabs.mdx b/website/plasma-web-docs/docs/components/Tabs.mdx index 53f724b01a..121322f23d 100644 --- a/website/plasma-web-docs/docs/components/Tabs.mdx +++ b/website/plasma-web-docs/docs/components/Tabs.mdx @@ -21,6 +21,11 @@ import { PropsTable, Description, StorybookLink } from '@site/src/components'; +:::caution Взаимоисключающие свойства +Свойство `value` - это значение таба. Оно отображается справа от основного текста.
+`value` и `contentRight` взаимоисключающие: если передано одно, второе передать нельзя. +::: + ## TabsController (deprecated) Вместо этого используйте Tabs, TabItem, указав параметры index, itemIndex, onIndexChange. @@ -95,6 +100,41 @@ export function App() { } ``` +### Расположение табов +Табы могут быть горизонтальными (по умолчанию) и вертикальными. За это отвечает свойство `orientation`. + +```tsx live +import React, { useState } from 'react'; +import { Tabs, TabItem } from '@salutejs/plasma-web'; +import { IconClock } from '@salutejs/plasma-icons'; + +export function App() { + const items = Array(8).fill(0); + const [index, setIndex] = useState(0); + + return ( +
+ + {items.map((_, i) => ( + } + onClick={() => setIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ ); +} +``` + ### Пример с Dropdown ```tsx live @@ -149,8 +189,9 @@ export function App() { ``` ### Подключение клавиатурной навигации -Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`. -Клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. +Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`.
+Для горизонтальных табов: клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам.
+Для вертикальных табов: клавиши ArrowUp, ArrowDown, Home, End для навигации по вкладкам. ```tsx live import React, { useState } from 'react'; @@ -159,27 +200,51 @@ import { IconClock } from '@salutejs/plasma-icons'; export function App() { const items = Array(4).fill(0); - const [index, setIndex] = useState(0); + const [horizontalIndex, setHorizontalIndex] = useState(0); + const [verticalIndex, setVerticalIndex] = useState(0); return ( -
- - {items.map((_, i) => ( - setIndex(i)} - selected={i === index} - tabIndex={0} - contentLeft={} - onClick={() => setIndex(i)} - > - {`Label${i + 1}`} - - ))} - +
+
+ + {items.map((_, i) => ( + setHorizontalIndex(i)} + selected={i === horizontalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setHorizontalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ +
+ + {items.map((_, i) => ( + setVerticalIndex(i)} + selected={i === verticalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setVerticalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
); } diff --git a/website/sdds-cs-docs/docs/components/Tabs.mdx b/website/sdds-cs-docs/docs/components/Tabs.mdx index ea0fdc48e8..d96c48e505 100644 --- a/website/sdds-cs-docs/docs/components/Tabs.mdx +++ b/website/sdds-cs-docs/docs/components/Tabs.mdx @@ -19,6 +19,11 @@ import { PropsTable, Description } from '@site/src/components'; +:::caution Взаимоисключающие свойства +Свойство `value` - это значение таба. Оно отображается справа от основного текста.
+`value` и `contentRight` взаимоисключающие: если передано одно, второе передать нельзя. +::: + ## TabsController (deprecated) Вместо этого используйте Tabs, TabItem, указав параметры index, itemIndex, onIndexChange. @@ -60,6 +65,41 @@ export function App() { } ``` +### Расположение табов +Табы могут быть горизонтальными (по умолчанию) и вертикальными. За это отвечает свойство `orientation`. + +```tsx live +import React, { useState } from 'react'; +import { Tabs, TabItem } from '@salutejs/sdds-cs'; +import { IconClock } from '@salutejs/plasma-icons'; + +export function App() { + const items = Array(8).fill(0); + const [index, setIndex] = useState(0); + + return ( +
+ + {items.map((_, i) => ( + } + onClick={() => setIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ ); +} +``` + ### Пример с прокруткой ```tsx live @@ -147,8 +187,9 @@ export function App() { ``` ### Подключение клавиатурной навигации -Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`. -Клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. +Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`.
+Для горизонтальных табов: клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам.
+Для вертикальных табов: клавиши ArrowUp, ArrowDown, Home, End для навигации по вкладкам. ```tsx live import React, { useState } from 'react'; @@ -157,27 +198,51 @@ import { IconClock } from '@salutejs/plasma-icons'; export function App() { const items = Array(4).fill(0); - const [index, setIndex] = useState(0); + const [horizontalIndex, setHorizontalIndex] = useState(0); + const [verticalIndex, setVerticalIndex] = useState(0); return ( -
- - {items.map((_, i) => ( - setIndex(i)} - selected={i === index} - tabIndex={0} - contentLeft={} - onClick={() => setIndex(i)} - > - {`Label${i + 1}`} - - ))} - +
+
+ + {items.map((_, i) => ( + setHorizontalIndex(i)} + selected={i === horizontalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setHorizontalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ +
+ + {items.map((_, i) => ( + setVerticalIndex(i)} + selected={i === verticalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setVerticalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
); } diff --git a/website/sdds-dfa-docs/docs/components/Tabs.mdx b/website/sdds-dfa-docs/docs/components/Tabs.mdx index 4dff4d450d..9c4282a1c2 100644 --- a/website/sdds-dfa-docs/docs/components/Tabs.mdx +++ b/website/sdds-dfa-docs/docs/components/Tabs.mdx @@ -19,6 +19,11 @@ import { PropsTable, Description } from '@site/src/components'; +:::caution Взаимоисключающие свойства +Свойство `value` - это значение таба. Оно отображается справа от основного текста.
+`value` и `contentRight` взаимоисключающие: если передано одно, второе передать нельзя. +::: + ## TabsController (deprecated) Вместо этого используйте Tabs, TabItem, указав параметры index, itemIndex, onIndexChange. @@ -60,6 +65,41 @@ export function App() { } ``` +### Расположение табов +Табы могут быть горизонтальными (по умолчанию) и вертикальными. За это отвечает свойство `orientation`. + +```tsx live +import React, { useState } from 'react'; +import { Tabs, TabItem } from '@salutejs/sdds-dfa'; +import { IconClock } from '@salutejs/plasma-icons'; + +export function App() { + const items = Array(8).fill(0); + const [index, setIndex] = useState(0); + + return ( +
+ + {items.map((_, i) => ( + } + onClick={() => setIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ ); +} +``` + ### Пример с прокруткой ```tsx live @@ -147,8 +187,9 @@ export function App() { ``` ### Подключение клавиатурной навигации -Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`. -Клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. +Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`.
+Для горизонтальных табов: клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам.
+Для вертикальных табов: клавиши ArrowUp, ArrowDown, Home, End для навигации по вкладкам. ```tsx live import React, { useState } from 'react'; @@ -157,27 +198,51 @@ import { IconClock } from '@salutejs/plasma-icons'; export function App() { const items = Array(4).fill(0); - const [index, setIndex] = useState(0); + const [horizontalIndex, setHorizontalIndex] = useState(0); + const [verticalIndex, setVerticalIndex] = useState(0); return ( -
- - {items.map((_, i) => ( - setIndex(i)} - selected={i === index} - tabIndex={0} - contentLeft={} - onClick={() => setIndex(i)} - > - {`Label${i + 1}`} - - ))} - +
+
+ + {items.map((_, i) => ( + setHorizontalIndex(i)} + selected={i === horizontalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setHorizontalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ +
+ + {items.map((_, i) => ( + setVerticalIndex(i)} + selected={i === verticalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setVerticalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
); } diff --git a/website/sdds-serv-docs/docs/components/Tabs.mdx b/website/sdds-serv-docs/docs/components/Tabs.mdx index aba5540810..92d3ce2c52 100644 --- a/website/sdds-serv-docs/docs/components/Tabs.mdx +++ b/website/sdds-serv-docs/docs/components/Tabs.mdx @@ -19,6 +19,11 @@ import { PropsTable, Description } from '@site/src/components'; +:::caution Взаимоисключающие свойства +Свойство `value` - это значение таба. Оно отображается справа от основного текста.
+`value` и `contentRight` взаимоисключающие: если передано одно, второе передать нельзя. +::: + ## TabsController (deprecated) Вместо этого используйте Tabs, TabItem, указав параметры index, itemIndex, onIndexChange. @@ -60,6 +65,41 @@ export function App() { } ``` +### Расположение табов +Табы могут быть горизонтальными (по умолчанию) и вертикальными. За это отвечает свойство `orientation`. + +```tsx live +import React, { useState } from 'react'; +import { Tabs, TabItem } from '@salutejs/sdds-serv'; +import { IconClock } from '@salutejs/plasma-icons'; + +export function App() { + const items = Array(8).fill(0); + const [index, setIndex] = useState(0); + + return ( +
+ + {items.map((_, i) => ( + } + onClick={() => setIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ ); +} +``` + ### Пример с прокруткой ```tsx live @@ -147,8 +187,9 @@ export function App() { ``` ### Подключение клавиатурной навигации -Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`. -Клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам. +Для этого необходимо дополнительно прокинуть свойства `index, itemIndex, onIndexChange`.
+Для горизонтальных табов: клавиши ArrowLeft, ArrowRight, Home, End для навигации по вкладкам.
+Для вертикальных табов: клавиши ArrowUp, ArrowDown, Home, End для навигации по вкладкам. ```tsx live import React, { useState } from 'react'; @@ -157,27 +198,51 @@ import { IconClock } from '@salutejs/plasma-icons'; export function App() { const items = Array(4).fill(0); - const [index, setIndex] = useState(0); + const [horizontalIndex, setHorizontalIndex] = useState(0); + const [verticalIndex, setVerticalIndex] = useState(0); return ( -
- - {items.map((_, i) => ( - setIndex(i)} - selected={i === index} - tabIndex={0} - contentLeft={} - onClick={() => setIndex(i)} - > - {`Label${i + 1}`} - - ))} - +
+
+ + {items.map((_, i) => ( + setHorizontalIndex(i)} + selected={i === horizontalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setHorizontalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
+ +
+ + {items.map((_, i) => ( + setVerticalIndex(i)} + selected={i === verticalIndex} + tabIndex={0} + contentLeft={} + onClick={() => setVerticalIndex(i)} + > + {`Label${i + 1}`} + + ))} + +
); }