Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: tab refactoring #79

Merged
merged 4 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/kaspersky-components/helpers/useIntersectionChildren.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { RefObject, useMemo } from 'react'
import { useResizeObserver } from './useResizeObserver'

/** Find position last inside element */
export const useIntersectionChildren = (ref: RefObject<Element>, padding = 0): number | undefined => {
/** The hook calculates the intersection of the container and its children, returns the index of last fitting child

@param ref External container ref
@param padding Padding to consider when intersecting
@param wrapperQuerySelector selector of internal container
@param renderCounter flag to trigger the recalculation
*/
export const useIntersectionChildren = (ref: RefObject<Element>, padding = 0, wrapperQuerySelector?: string, renderCounter?: number): number | undefined => {
const { right: containerRight } = useResizeObserver(ref) ?? { right: 0 }

return useMemo<number | undefined>(() => {
const container = ref.current
if (container === null) return undefined
const container = wrapperQuerySelector ? ref.current?.querySelector(wrapperQuerySelector) : ref.current
if (container === null || container === undefined) return undefined

const children = new Array(container.children.length)
.fill(null)
.map((_, i) => container.children[i])

const res = children.findIndex((child) => child.getBoundingClientRect().right + padding > containerRight)
return res === -1 ? undefined : Math.max(res - 1, 0)
}, [containerRight, ref])
}, [containerRight, ref, padding, renderCounter])
}
1 change: 1 addition & 0 deletions packages/kaspersky-components/src/indicator/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './Indicator'
export * from './types'
261 changes: 203 additions & 58 deletions packages/kaspersky-components/src/tabs/Tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import React from 'react'
import { useLocalization } from '@helpers/localization/useLocalization'
import { badges } from '@sb/badges'
import { withMeta } from '@sb/components/Meta'
import { sbHideControls } from '@sb/helpers'
import { Button } from '@src/button'
import { IndicatorMode } from '@src/indicator'
import { IndicatorModes } from '@src/indicator/types'
import { Textbox } from '@src/input'
import { Space } from '@src/space'
import { H3 } from '@src/typography'
import { Meta, StoryObj } from '@storybook/react'
import React from 'react'
import styled from 'styled-components'

import { Placeholder } from '@kl/icons/16'

import MetaData from './__meta__/meta.json'
import { Tabs, GroupTabs } from './Tabs'
import { TabsProps } from './types'
import { Tabs } from './Tabs'
import { badges } from '@sb/badges'
import { withMeta } from '@helpers/hocs/MetaComponent/withMeta'
import { sbHideControls } from '@helpers/storybookHelpers'
import { Placeholder } from '@kaspersky/icons/16'
import { Button, GroupTabs, H3, Space } from '..'
import styled from 'styled-components'

const meta: Meta<TabsProps> = {
title: 'Atoms/Tabs',
title: 'Hexa UI Components/Tabs',
component: Tabs,
argTypes: {
...sbHideControls(['onChange', 'onTabClick', 'style', 'destroyInactiveTabPane', 'theme', 'className', 'type'])
Expand All @@ -30,7 +38,7 @@ const meta: Meta<TabsProps> = {
klId: 'tabs-kl-id'
},
parameters: {
badges: [badges.reviewedByDesign],
badges: [badges.stable, badges.reviewedByDesign],
docs: {
page: withMeta(MetaData)
},
Expand All @@ -57,62 +65,142 @@ export const Basic: Story = {}
export const WithIconAndNumber: Story = {
render: (args: TabsProps) => (
<Tabs {...args}>
<Tabs.TabPane tab={<Tabs.TabPaneHead text="Tab" number={5} />} key="1">
<Tabs.TabPane
tab={<Tabs.TabPaneHead text="Tab" iconBefore={<Placeholder />} />}
key="1"
>
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane
tab={<Tabs.TabPaneHead text="Tab" icon={<Placeholder />} />}
tab={<Tabs.TabPaneHead text="Tab" iconAfter={<Placeholder />} />}
key="2"
>
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane
tab={
<Tabs.TabPaneHead text="Tab" icon={<Placeholder />} number={5} />
<Tabs.TabPaneHead text="Tab" iconBefore={<Placeholder />} iconAfter={<Placeholder />} />
}
key="3"
>
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
)
}

export const WithIndicator: Story = {
render: (args: TabsProps) => (
<Tabs {...args}>
<Tabs.TabPane tab={<Tabs.TabPaneHead text="Tab" indicator />} key="1">
Content of Tab Pane 1
<Tabs.TabPane tab={<Tabs.TabPaneHead text="Tab" number={5} />} key="4">
Content of Tab Pane 4
</Tabs.TabPane>
<Tabs.TabPane
tab={<Tabs.TabPaneHead text="Tab" indicator number={5} />}
key="2"
tab={
<Tabs.TabPaneHead text="Tab" iconBefore={<Placeholder />} number={5} />
}
key="5"
>
Content of Tab Pane 2
Content of Tab Pane 5
</Tabs.TabPane>
<Tabs.TabPane
tab={<Tabs.TabPaneHead text="Tab" indicator icon={<Placeholder />} />}
key="3"
tab={
<Tabs.TabPaneHead text="Tab" iconAfter={<Placeholder />} number={5} />
}
key="6"
>
Content of Tab Pane 3
Content of Tab Pane 6
</Tabs.TabPane>
<Tabs.TabPane
tab={
<Tabs.TabPaneHead
text="Tab"
indicator
icon={<Placeholder />}
number={5}
/>
<Tabs.TabPaneHead text="Tab" iconBefore={<Placeholder />} iconAfter={<Placeholder />} number={5} />
}
key="4"
key="7"
>
Content of Tab Pane 4
Content of Tab Pane 7
</Tabs.TabPane>
</Tabs>
)
}

type StoryTabsProps = TabsProps & { indicatorMode: IndicatorMode }

export const WithIndicator: StoryObj<StoryTabsProps> = {
render: (args: StoryTabsProps) => {
const { indicatorMode, ...props } = args
return (
<Tabs {...props}>
<Tabs.TabPane
tab={<Tabs.TabPaneHead text="Tab" indicator indicatorMode={indicatorMode} />}
key="1"
>
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane
tab={<Tabs.TabPaneHead text="Tab" indicator indicatorMode={indicatorMode} number={5} />}
key="2"
>
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane
tab={
<Tabs.TabPaneHead
text="Tab"
indicator
indicatorMode={indicatorMode}
iconBefore={<Placeholder />}
/>}
key="3"
>
Content of Tab Pane 3
</Tabs.TabPane>
<Tabs.TabPane
tab={
<Tabs.TabPaneHead
text="Tab"
indicator
indicatorMode={indicatorMode}
iconAfter={<Placeholder />}
/>
}
key="4"
>
Content of Tab Pane 4
</Tabs.TabPane>
<Tabs.TabPane
tab={
<Tabs.TabPaneHead
text="Tab"
indicator
indicatorMode={indicatorMode}
iconBefore={<Placeholder />}
number={5}
/>
}
key="5"
>
Content of Tab Pane 5
</Tabs.TabPane>
<Tabs.TabPane
tab={
<Tabs.TabPaneHead
text="Tab"
indicator
indicatorMode={indicatorMode}
iconBefore={<Placeholder />}
iconAfter={<Placeholder />}
number={5}
/>
}
key="6"
>
Content of Tab Pane 6
</Tabs.TabPane>
</Tabs>
)
},
argTypes: {
indicatorMode: {
control: 'select',
options: IndicatorModes,
description: 'Indicator mode'
}
}
}

export const WithDisabled: Story = {
render: (args: TabsProps) => (
<Tabs {...args}>
Expand Down Expand Up @@ -148,24 +236,29 @@ export const WithDisabled: Story = {
)
}

const generateTabs = (length = 15, tabText = 'Tab', contentText = 'Content of tab') => Array.from({ length: length }, (_, i) => i).map((i) => ({
text: tabText,
disabled: i === 8,
content: contentText + ' ' + (i + 1)
}
))

export const CollapsedHorizontalGroup: Story = {
render: (props: TabsProps) => (
<Tabs {...props}>
{[
...Array.from({ length: 15 }, (_, i) => i)
].map((i) => (
<Tabs.TabPane
tab={
<Tabs.TabPaneHead text={`Tab ${i + 1}`} icon={<Placeholder />} />
}
key={i + 1}
disabled={i === 8}
>
Content of tab {i + 1}
</Tabs.TabPane>
))}
</Tabs>
)
render: (props: TabsProps) => {
return (
<Tabs {...props}>
{generateTabs(20, 'tabs.dropdown.more').map((el, i) => <Tabs.TabPane
tab={
<Tabs.TabPaneHead text={useLocalization(el.text) + ' ' + (i + 2)} icon={<Placeholder/>}/>
}
key={i + 1}
disabled={el.disabled}
>
{el.content}
</Tabs.TabPane>
)}
</Tabs>)
}
}

const RightButton = <Button mode='secondary' text='Right extra button'/>
Expand All @@ -174,7 +267,7 @@ const LeftButton = <Button mode='secondary' text='Left extra button'/>
export const WithExtraContent: Story = {
render: (args: TabsProps) => (
<Space size={16}>
<H3>Отступы и другие стили пока задавать надо вручную, в будущем будет проработано со стороны дизайна</H3>
<H3>Отѝтупы и другие ѝтили временно необходимо задавать вручную, в будущем будет проработано ѝо ѝтороны дизайна</H3>
<Tabs {...args} tabBarExtraContent={RightButton} />
<Tabs {...args} tabBarExtraContent={{ right: RightButton, left: LeftButton }} />
</Space>
Expand All @@ -184,7 +277,7 @@ export const WithExtraContent: Story = {
export const WithGroupedTabs: Story = {
render: (args: TabsProps) => (
<GroupTabs {...args}>
{/* Группа 1 */}
{/* Group 1 */}
<GroupTabs.TabPaneHeader
title={'Group 1 heading'}
key="tab-group-header-1"
Expand All @@ -209,7 +302,7 @@ export const WithGroupedTabs: Story = {
Content of Tab Pane 3
</Tabs.TabPane>

{/* Группа 2 */}
{/* Group 2 */}
<GroupTabs.TabPaneHeader
divider
title={'Group 2 heading'}
Expand All @@ -222,7 +315,7 @@ export const WithGroupedTabs: Story = {
Content of Tab Pane 5
</Tabs.TabPane>

{/* Группа 3 */}
{/* Group 3 */}
<GroupTabs.TabPaneHeader
divider
title={'Group 3 heading'}
Expand All @@ -237,7 +330,7 @@ export const WithGroupedTabs: Story = {
</GroupTabs>
),
args: {
defaultActiveKey: undefined
defaultActiveKey: '1'
},
argTypes: {
...sbHideControls(['tabPosition'])
Expand All @@ -261,3 +354,55 @@ export const TabsInsideTabs: Story = {
</Tabs>
)
}

const HighContainer = styled.div`
min-height: 80vh;
display: flex;
flex-direction: column;
`

const StretchedTabs = styled(Tabs)`
margin-top: 15px;
flex: 1 0 auto;
`

export const StretchedInHeight: Story = {
render: (args: TabsProps) => (
<HighContainer>
<H3>The Header</H3>
<StretchedTabs {...args} />
</HighContainer>
),
args: {
tabPosition: 'left'
}
}

export const WithTextbox: Story = {
render: (args: TabsProps) => (
<Space size={48}>
<Tabs {...args}>
<Tabs.TabPane tab={<Textbox />} key="1">
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
<Tabs {...args}>
<Tabs.TabPane tab={<Textbox.Textarea />} key="1">
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
</Space>
)
}
Loading