From 9a14e37ea8c8753c418ef1f91f5bc017a008d007 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 28 Feb 2025 08:14:39 -0500 Subject: [PATCH 1/5] #55 refactor Tabs to use compound pattern --- src/common/components/Tabs/Tab.tsx | 55 ---- src/common/components/Tabs/TabContent.tsx | 53 ---- src/common/components/Tabs/TabList.tsx | 47 ---- src/common/components/Tabs/Tabs.tsx | 164 ++++++++++-- .../Tabs/__stories__/Tabs.stories.tsx | 35 ++- .../components/Tabs/__tests__/Tab.test.tsx | 136 ---------- .../Tabs/__tests__/TabContent.test.tsx | 94 ------- .../Tabs/__tests__/TabList.test.tsx | 69 ----- .../components/Tabs/__tests__/Tabs.test.tsx | 242 +++++++++++++++++- src/common/hooks/__tests__/useTabs.test.tsx | 33 --- src/common/hooks/useTabs.ts | 18 -- src/common/providers/TabsContext.ts | 14 - .../Components/components/TabsComponents.tsx | 84 +++--- 13 files changed, 447 insertions(+), 597 deletions(-) delete mode 100644 src/common/components/Tabs/Tab.tsx delete mode 100644 src/common/components/Tabs/TabContent.tsx delete mode 100644 src/common/components/Tabs/TabList.tsx delete mode 100644 src/common/components/Tabs/__tests__/Tab.test.tsx delete mode 100644 src/common/components/Tabs/__tests__/TabContent.test.tsx delete mode 100644 src/common/components/Tabs/__tests__/TabList.test.tsx delete mode 100644 src/common/hooks/__tests__/useTabs.test.tsx delete mode 100644 src/common/hooks/useTabs.ts delete mode 100644 src/common/providers/TabsContext.ts diff --git a/src/common/components/Tabs/Tab.tsx b/src/common/components/Tabs/Tab.tsx deleted file mode 100644 index 5d846d8..0000000 --- a/src/common/components/Tabs/Tab.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { PropsWithChildren } from 'react'; -import { cva } from 'class-variance-authority'; - -import { BaseComponentProps } from 'common/utils/types'; -import { useTabs } from 'common/hooks/useTabs'; -import { cn } from 'common/utils/css'; -import Button from '../Button/Button'; - -/** - * Defines the component base and variant styles. - */ -const variants = cva('rounded-none border-0 border-b-2 px-4 text-sm font-bold uppercase', { - variants: { - active: { - false: 'border-transparent', - true: 'border-blue-300 dark:border-blue-600', - }, - }, - defaultVariants: { - active: false, - }, -}); - -/** - * Properties for the `Tab` component. - */ -export interface TabProps extends BaseComponentProps, PropsWithChildren { - value: string; -} - -/** - * The `Tab` component renders a clickable tab button which, when clicked, - * displays the associated tab content. - * - * Note: The `Tab` and its associated `TabContent` must have the same `value` - * property value. - */ -const Tab = ({ children, className, testId = 'tab', value }: TabProps): JSX.Element => { - const { value: selectedTab, setValue } = useTabs(); - - const active = value === selectedTab; - - return ( - - ); -}; - -export default Tab; diff --git a/src/common/components/Tabs/TabContent.tsx b/src/common/components/Tabs/TabContent.tsx deleted file mode 100644 index 7be78dd..0000000 --- a/src/common/components/Tabs/TabContent.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { PropsWithChildren } from 'react'; -import { cva } from 'class-variance-authority'; - -import { BaseComponentProps } from 'common/utils/types'; -import { useTabs } from 'common/hooks/useTabs'; -import { cn } from 'common/utils/css'; - -/** - * Defines the component base and variant styles. - */ -const variants = cva('', { - variants: { - active: { - true: 'block', - false: 'hidden', - }, - }, - defaultVariants: { - active: false, - }, -}); - -/** - * Properties for the `TabContent` component. - */ -export interface TabContentProps extends BaseComponentProps, PropsWithChildren { - value: string; -} - -/** - * The `TabContent` component renders the content for a single tab. - * - * Note: The `TabContent` and its associated `Tab` must have the same `value` - * property value. - */ -const TabContent = ({ - children, - className, - testId = 'tab-content', - value, -}: TabContentProps): JSX.Element => { - const { value: selectedTab } = useTabs(); - - const active = value === selectedTab; - - return ( -
- {children} -
- ); -}; - -export default TabContent; diff --git a/src/common/components/Tabs/TabList.tsx b/src/common/components/Tabs/TabList.tsx deleted file mode 100644 index 045f59c..0000000 --- a/src/common/components/Tabs/TabList.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { PropsWithChildren } from 'react'; -import { cva, VariantProps } from 'class-variance-authority'; - -import { BaseComponentProps } from 'common/utils/types'; -import { cn } from 'common/utils/css'; - -/** - * Defines the component base and variant styles. - */ -const variants = cva('flex gap-4 border-b border-b-neutral-500/10', { - variants: { - align: { - stretch: '*:grow', - start: 'flex', - }, - }, - defaultVariants: { align: 'start' }, -}); - -/** - * The variant attributes of the TabList component. - */ -type TabListVariants = VariantProps; - -/** - * Properties for the `TabList` component. - */ -export interface TabListProps extends BaseComponentProps, PropsWithChildren, TabListVariants {} - -/** - * The `TabList` component is a container for one to many `Tab` components. It - * renders a horizontal bar of clickable tab labels. - */ -const TabList = ({ - align = 'start', - children, - className, - testId = 'tab-list', -}: TabListProps): JSX.Element => { - return ( -
- {children} -
- ); -}; - -export default TabList; diff --git a/src/common/components/Tabs/Tabs.tsx b/src/common/components/Tabs/Tabs.tsx index 6312e65..929a71f 100644 --- a/src/common/components/Tabs/Tabs.tsx +++ b/src/common/components/Tabs/Tabs.tsx @@ -1,27 +1,25 @@ -import { PropsWithChildren, useState } from 'react'; +import { createContext, PropsWithChildren, useContext, useState } from 'react'; +import { cva, VariantProps } from 'class-variance-authority'; import { cn } from 'common/utils/css'; import { BaseComponentProps } from 'common/utils/types'; -import { TabsContext, TabsContextValue } from 'common/providers/TabsContext'; +import Button from '../Button/Button'; /** - * Properties for the `TabsProvider` component. + * Defines the shape of the `TabsContext` value. */ -interface TabsProviderProps extends PropsWithChildren, Pick {} +type TabsContextValue = { + activeTab: string; + setActiveTab: (activeTab: string) => void; +}; /** - * The `TabsProvider` component renders a React Context Provider which provides access - * to the the value of the `TabsContext`, i.e. the `TabsContextValue`. + * The `TabsContext` instance. */ -const TabsProvider = ({ children, value }: TabsProviderProps): JSX.Element => { - const [selectedTab, setSelectedTab] = useState(value); - - const contextValue: TabsContextValue = { - setValue: setSelectedTab, - value: selectedTab, - }; - return {children}; -}; +const TabsContext = createContext({ + activeTab: '', + setActiveTab: () => {}, +}); /** * Properties for the `Tabs` component. @@ -56,11 +54,145 @@ export interface TabsProps extends BaseComponentProps, PropsWithChildren { * ``` */ const Tabs = ({ children, className, defaultValue, testId = 'tabs' }: TabsProps): JSX.Element => { + const [activeTab, setActiveTab] = useState(defaultValue); + return (
- {children} + {children} +
+ ); +}; + +/** + * Defines the `List` component base and variant styles. + */ +const listVariants = cva('flex gap-4 border-b border-b-neutral-500/10', { + variants: { + align: { + stretch: '*:grow', + start: 'flex', + }, + }, + defaultVariants: { align: 'start' }, +}); + +/** + * Properties for the `List` component. + */ +interface ListProps + extends BaseComponentProps, + PropsWithChildren, + VariantProps {} + +/** + * The `List` component is a container for one to many `Tab` components. It + * renders a horizontal bar of clickable tabs. + */ +const List = ({ + align = 'start', + children, + className, + testId = 'tabs-list', +}: ListProps): JSX.Element => { + return ( +
+ {children} +
+ ); +}; +Tabs.List = List; + +/** + * Defines the `Tab` component base and variant styles. + */ +const tabVariants = cva('rounded-none border-0 border-b-2 px-4 text-sm font-bold uppercase', { + variants: { + active: { + false: 'border-transparent', + true: 'border-blue-300 dark:border-blue-600', + }, + }, + defaultVariants: { + active: false, + }, +}); + +/** + * Properties for the `Tab` component. + */ +interface TabProps extends BaseComponentProps, PropsWithChildren { + value: string; +} + +/** + * The `Tab` component renders a clickable tab button which, when clicked, + * displays the associated tab content. + * + * Note: The `Tab` and its associated `TabContent` must have the same `value` + * property value. + */ +const Tab = ({ children, className, testId = 'tabs-tab', value }: TabProps): JSX.Element => { + const { activeTab, setActiveTab } = useContext(TabsContext); + + const active = value === activeTab; + + return ( + + ); +}; +Tabs.Tab = Tab; + +/** + * Defines the `Content` component base and variant styles. + */ +const contentVariants = cva('', { + variants: { + active: { + true: 'block', + false: 'hidden', + }, + }, + defaultVariants: { + active: false, + }, +}); + +/** + * Properties for the `Content` component. + */ +interface TabContentProps extends BaseComponentProps, PropsWithChildren { + value: string; +} + +/** + * The `TabContent` component renders the content for a single tab. + * + * Note: The `TabContent` and its associated `Tab` must have the same `value` + * property value. + */ +const Content = ({ + children, + className, + testId = 'tabs-content', + value, +}: TabContentProps): JSX.Element => { + const { activeTab } = useContext(TabsContext); + + const active = value === activeTab; + + return ( +
+ {children}
); }; +Tabs.Content = Content; export default Tabs; diff --git a/src/common/components/Tabs/__stories__/Tabs.stories.tsx b/src/common/components/Tabs/__stories__/Tabs.stories.tsx index 802d6dd..108a0b4 100644 --- a/src/common/components/Tabs/__stories__/Tabs.stories.tsx +++ b/src/common/components/Tabs/__stories__/Tabs.stories.tsx @@ -1,9 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import Tabs from '../Tabs'; -import TabList from '../TabList'; -import Tab from '../Tab'; -import TabContent from '../TabContent'; const meta = { title: 'Common/Tabs', @@ -27,16 +24,16 @@ type Story = StoryObj; export const Default: Story = { render: (args) => ( - - One - Two - - + + One + Two + +
I am the content for tab one!
-
- + +
I am the content for tab two!
-
+
), }; @@ -44,16 +41,16 @@ export const Default: Story = { export const StretchedTabs: Story = { render: (args) => ( - - One - Two - - + + One + Two + +
I am the content for tab one!
-
- + +
I am the content for tab two!
-
+
), }; diff --git a/src/common/components/Tabs/__tests__/Tab.test.tsx b/src/common/components/Tabs/__tests__/Tab.test.tsx deleted file mode 100644 index 5f95924..0000000 --- a/src/common/components/Tabs/__tests__/Tab.test.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import userEvent from '@testing-library/user-event'; - -import { render, screen, waitFor } from 'test/test-utils'; - -import Tab from '../Tab'; -import Tabs from '../Tabs'; -import TabList from '../TabList'; - -describe('Tab', () => { - it('should render successfully', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab'); - - // ASSERT - expect(screen.getByTestId('tab')).toBeDefined(); - }); - - it('should use custom testId', async () => { - // ARRANGE - render( - - - - One - - - , - ); - await screen.findByTestId('test-id'); - - // ASSERT - expect(screen.getByTestId('test-id')).toBeDefined(); - }); - - it('should use custom className', async () => { - // ARRANGE - render( - - - - One - - - , - ); - await screen.findByTestId('tab'); - - // ASSERT - expect(screen.getByTestId('tab').classList).toContain('class-name'); - }); - - it('should render label', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab'); - - // ASSERT - expect(screen.getByTestId('tab').textContent).toBe('One'); - }); - - it('should display active state', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab'); - - // ASSERT - expect(screen.getByTestId('tab').classList).toContain('border-blue-300'); - }); - - it('should display inactive state', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab'); - - // ASSERT - expect(screen.getByTestId('tab').classList).toContain('border-transparent'); - }); - - it('should set active tab when clicked', async () => { - // ARRANGE - const user = userEvent.setup(); - render( - - - - One - - - Two - - - , - ); - await screen.findByTestId('tab-one'); - // assert that tab "one" is active - expect(screen.getByTestId('tab-one')).toHaveClass('border-blue-300'); - - // ACT - // click tab two - await user.click(screen.getByTestId('tab-two')); - // wait until tab one becomes inactive - await waitFor(() => expect(screen.getByTestId('tab-one')).toHaveClass('border-transparent')); - - // ASSERT - // assert that tab one is inactive - expect(screen.getByTestId('tab-one')).toHaveClass('border-transparent'); - // assert that tab two is active - expect(screen.getByTestId('tab-two')).toHaveClass('border-blue-300'); - }); -}); diff --git a/src/common/components/Tabs/__tests__/TabContent.test.tsx b/src/common/components/Tabs/__tests__/TabContent.test.tsx deleted file mode 100644 index 8f009b2..0000000 --- a/src/common/components/Tabs/__tests__/TabContent.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { render, screen } from 'test/test-utils'; - -import TabContent from '../TabContent'; -import Tabs from '../Tabs'; - -describe('TabContent', () => { - it('should render successfully', async () => { - // ARRANGE - render( - - - , - ); - await screen.findByTestId('tab-content'); - - // ASSERT - expect(screen.getByTestId('tab-content')).toBeDefined(); - }); - - it('should use custom testId', async () => { - // ARRANGE - render( - - - , - ); - await screen.findByTestId('custom-testId'); - - // ASSERT - expect(screen.getByTestId('custom-testId')).toBeDefined(); - }); - - it('should use custom className', async () => { - // ARRANGE - render( - - - , - ); - await screen.findByTestId('tab-content'); - - // ASSERT - expect(screen.getByTestId('tab-content').classList).toContain('custom-className'); - }); - - it('should render children', async () => { - // ARRANGE - render( - - -
-
-
, - ); - await screen.findByTestId('children'); - - // ASSERT - expect(screen.getByTestId('children')).toBeDefined(); - }); - - it('should be displayed when active', async () => { - // ARRANGE - render( - - -
-
-
, - ); - await screen.findByTestId('tab-content'); - - // ASSERT - expect(screen.getByTestId('tab-content')).toBeDefined(); - expect(screen.getByTestId('tab-content')).toHaveClass('block'); - }); - - it('should be hidden when inactive', async () => { - // ARRANGE - render( - - -
-
-
, - ); - await screen.findByTestId('tab-content'); - - // ASSERT - expect(screen.getByTestId('tab-content')).toBeDefined(); - expect(screen.getByTestId('tab-content')).toHaveClass('hidden'); - }); -}); diff --git a/src/common/components/Tabs/__tests__/TabList.test.tsx b/src/common/components/Tabs/__tests__/TabList.test.tsx deleted file mode 100644 index 1d6d81b..0000000 --- a/src/common/components/Tabs/__tests__/TabList.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { render, screen } from 'test/test-utils'; - -import Tabs from '../Tabs'; -import TabList from '../TabList'; -import Tab from '../Tab'; - -describe('TabList', () => { - it('should render successfully', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab-list'); - - // ASSERT - expect(screen.getByTestId('tab-list')).toBeDefined(); - }); - - it('should display with align default', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab-list'); - - // ASSERT - expect(screen.getByTestId('tab-list')).toHaveClass('flex'); - }); - - it('should display with align start', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab-list'); - - // ASSERT - expect(screen.getByTestId('tab-list')).toHaveClass('flex'); - }); - - it('should display with align stretch', async () => { - // ARRANGE - render( - - - One - - , - ); - await screen.findByTestId('tab-list'); - - // ASSERT - expect(screen.getByTestId('tab-list')).toHaveClass('*:grow'); - }); -}); diff --git a/src/common/components/Tabs/__tests__/Tabs.test.tsx b/src/common/components/Tabs/__tests__/Tabs.test.tsx index ee5f627..d12cdc7 100644 --- a/src/common/components/Tabs/__tests__/Tabs.test.tsx +++ b/src/common/components/Tabs/__tests__/Tabs.test.tsx @@ -1,16 +1,254 @@ import { describe, expect, it } from 'vitest'; +import userEvent from '@testing-library/user-event'; -import { render, screen } from 'test/test-utils'; +import { render, screen, waitFor } from 'test/test-utils'; import Tabs from '../Tabs'; describe('Tabs', () => { it('should render successfully', async () => { // ARRANGE - render(); + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); await screen.findByTestId('tabs'); // ASSERT expect(screen.getByTestId('tabs')).toBeDefined(); }); + + it('should use align start', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tabs-list'); + + // ASSERT + expect(screen.getByTestId('tabs-list')).toHaveClass('flex'); + }); + + it('should use align stretch', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tabs-list'); + + // ASSERT + expect(screen.getByTestId('tabs-list')).toHaveClass('*:grow'); + }); + + it('should use default align value', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tabs-list'); + + // ASSERT + expect(screen.getByTestId('tabs-list')).toHaveClass('flex'); + }); + + it('should render tab label', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tab-one'); + + // ASSERT + expect(screen.getByTestId('tab-one')).toHaveTextContent(/One/); + }); + + it('should display active state', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tab-one'); + + // ASSERT + expect(screen.getByTestId('tab-one')).toHaveClass('border-blue-300'); + }); + + it('should display inactive state', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tab-two'); + + // ASSERT + expect(screen.getByTestId('tab-two')).toHaveClass('border-transparent'); + }); + + it('should display default tab', async () => { + // ARRANGE + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tab-one'); + + // ASSERT + expect(screen.getByTestId('tab-one')).toHaveClass('border-blue-300'); + expect(screen.getByTestId('tab-two')).toHaveClass('border-transparent'); + expect(screen.getByTestId('content-one')).toHaveClass('block'); + expect(screen.getByTestId('content-two')).toHaveClass('hidden'); + }); + + it('should display tab when clicked', async () => { + // ARRANGE + const user = userEvent.setup(); + render( + + + + One + + + Two + + + + Content One + + + Content Two + + , + ); + await screen.findByTestId('tabs'); + /* assert tab 1 active */ + expect(screen.getByTestId('tab-one')).toHaveClass('border-blue-300'); + + // ACT + /* click tab two */ + await user.click(screen.getByTestId('tab-two')); + /* wait for re-render for tab two to be active */ + await waitFor(() => expect(screen.getByTestId('tab-two')).toHaveClass('border-blue-300')); + + // ASSERT + expect(screen.getByTestId('tab-two')).toHaveClass('border-blue-300'); + }); }); diff --git a/src/common/hooks/__tests__/useTabs.test.tsx b/src/common/hooks/__tests__/useTabs.test.tsx deleted file mode 100644 index cd440a4..0000000 --- a/src/common/hooks/__tests__/useTabs.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { PropsWithChildren } from 'react'; -import { describe, expect, it } from 'vitest'; -import { renderHook as renderHookWithoutWrapper } from '@testing-library/react'; - -import { renderHook, waitFor } from 'test/test-utils'; -import Tabs from 'common/components/Tabs/Tabs'; - -import { useTabs } from '../useTabs'; - -const TabsWrapper = ({ children }: PropsWithChildren): JSX.Element => { - return {children}; -}; - -describe('useTabs', () => { - it('should return the context', async () => { - // ARRANGE - const { result } = renderHook(() => useTabs(), { wrapper: TabsWrapper }); - await waitFor(() => expect(result.current).not.toBeNull()); - - // ASSERT - expect(result.current).toBeDefined(); - expect(result.current.value).toBe('one'); - expect(result.current.setValue).toBeDefined(); - expect(typeof result.current.setValue).toBe('function'); - }); - - it('should throw an error when used outside of Tabs', async () => { - // ASSERT - expect(() => renderHookWithoutWrapper(() => useTabs())).toThrow( - /useTabs hook must be used in a child of the Tabs component/i, - ); - }); -}); diff --git a/src/common/hooks/useTabs.ts b/src/common/hooks/useTabs.ts deleted file mode 100644 index 27b266f..0000000 --- a/src/common/hooks/useTabs.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useContext } from 'react'; - -import { TabsContext, TabsContextValue } from 'common/providers/TabsContext'; - -/** - * The `useTabs` hook returns the current `TabsContextValue` value. This hook - * is used within the `Tabs` family of components to access and mutate the - * shared state of the tabs. - * @returns {TabsContextValue} The current `TabsContextValue` value. - */ -export const useTabs = (): TabsContextValue => { - const context = useContext(TabsContext); - if (!context) { - throw new Error('The useTabs hook must be used in a child of the Tabs component.'); - } - - return context; -}; diff --git a/src/common/providers/TabsContext.ts b/src/common/providers/TabsContext.ts deleted file mode 100644 index cff58fe..0000000 --- a/src/common/providers/TabsContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createContext } from 'react'; - -/** - * Defines the shape of the `TabsContext` value. - */ -export type TabsContextValue = { - setValue: (value: string) => void; - value: string; -}; - -/** - * The `TabsContext` instance. - */ -export const TabsContext = createContext(undefined); diff --git a/src/pages/Components/components/TabsComponents.tsx b/src/pages/Components/components/TabsComponents.tsx index e58203f..a5b0bb0 100644 --- a/src/pages/Components/components/TabsComponents.tsx +++ b/src/pages/Components/components/TabsComponents.tsx @@ -6,9 +6,6 @@ import Table from 'common/components/Table/Table'; import CodeSnippet from 'common/components/Text/CodeSnippet'; import Heading from 'common/components/Text/Heading'; import Tabs from 'common/components/Tabs/Tabs'; -import TabList from 'common/components/Tabs/TabList'; -import Tab from 'common/components/Tabs/Tab'; -import TabContent from 'common/components/Tabs/TabContent'; /** * The `TabsComponents` component renders a set of examples illustrating @@ -57,15 +54,19 @@ const TabsComponents = ({
- The Tabs component organizes content into - sections, i.e. tabs, allowing the user to select the section which is actively displayed. +
+ The Tabs component organizes content into + sections, i.e. tabs, allowing the user to select the section which is actively displayed. +
Properties data={data} columns={columns} />
+ Examples + Default Tabs @@ -73,34 +74,35 @@ const TabsComponents = ({
{/* Tabs Example Here */} - - List - Detail - - + + List + Detail + +
I am the LIST tab.
-
- + +
I am the DETAIL tab.
-
+
- - List - Detail - - -
I am the LIST tab.
-
- -
I am the DETAIL tab.
-
+ + List + Detail + + +
I am the LIST tab.
+
+ +
I am the DETAIL tab.
+
`} />
+ Stretched Tabs @@ -108,31 +110,31 @@ const TabsComponents = ({
{/* Tabs Example Here */} - - List - Detail - - + + List + Detail + +
I am the LIST tab.
-
- + +
I am the DETAIL tab.
-
+
- - List - Detail - - -
I am the LIST tab.
-
- -
I am the DETAIL tab.
-
+ + List + Detail + + +
I am the LIST tab.
+
+ +
I am the DETAIL tab.
+
`} /> From cb28538766688f1f1c0a249da8c02f0b470ca3fd Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 28 Feb 2025 08:21:25 -0500 Subject: [PATCH 2/5] #55 use noop --- src/common/components/Dropdown/DropdownMenu.tsx | 3 ++- src/common/components/Tabs/Tabs.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/components/Dropdown/DropdownMenu.tsx b/src/common/components/Dropdown/DropdownMenu.tsx index c883faa..5fd4609 100644 --- a/src/common/components/Dropdown/DropdownMenu.tsx +++ b/src/common/components/Dropdown/DropdownMenu.tsx @@ -1,4 +1,5 @@ import { createContext, PropsWithChildren, useContext, useState } from 'react'; +import noop from 'lodash/noop'; import { BaseComponentProps } from 'common/utils/types'; import { cn } from 'common/utils/css'; @@ -18,7 +19,7 @@ type DropdownMenuContextValue = { */ const DropdownMenuContext = createContext({ isHidden: true, - setIsHidden: () => {}, + setIsHidden: noop, }); /** diff --git a/src/common/components/Tabs/Tabs.tsx b/src/common/components/Tabs/Tabs.tsx index 929a71f..53bd5ee 100644 --- a/src/common/components/Tabs/Tabs.tsx +++ b/src/common/components/Tabs/Tabs.tsx @@ -1,5 +1,6 @@ import { createContext, PropsWithChildren, useContext, useState } from 'react'; import { cva, VariantProps } from 'class-variance-authority'; +import noop from 'lodash/noop'; import { cn } from 'common/utils/css'; import { BaseComponentProps } from 'common/utils/types'; @@ -18,7 +19,7 @@ type TabsContextValue = { */ const TabsContext = createContext({ activeTab: '', - setActiveTab: () => {}, + setActiveTab: noop, }); /** From c7172624a9d91ef63ab09c2696bb28699c5c9074 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 28 Feb 2025 08:41:23 -0500 Subject: [PATCH 3/5] #55 docs --- src/common/components/Tabs/Tabs.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/common/components/Tabs/Tabs.tsx b/src/common/components/Tabs/Tabs.tsx index 53bd5ee..fb24876 100644 --- a/src/common/components/Tabs/Tabs.tsx +++ b/src/common/components/Tabs/Tabs.tsx @@ -31,8 +31,8 @@ export interface TabsProps extends BaseComponentProps, PropsWithChildren { } /** - * The `Tabs` component is a wrapper for rendering tabbed content. Compose tabbed - * content using: `Tabs`, `TabList`, `Tab`, and `TabContent` as illustrated in + * The `Tabs` component is used to display tabbed content. Compose tabbed + * content using: `List`, `Tab`, and `Content` as illustrated in * the example. * * Note: The `defaultValue` property must match the `value` of one of the Tab @@ -41,16 +41,16 @@ export interface TabsProps extends BaseComponentProps, PropsWithChildren { * *Example:* * ``` * - * - * List - * Detail - * - * + * + * List + * Detail + * + * *
I am the LIST tab.
- *
- * + * + * *
I am the DETAIL tab.
- *
+ * *
* ``` */ From f6fb0c40956810d753a513a134ed5a9170f29788 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 28 Feb 2025 08:42:21 -0500 Subject: [PATCH 4/5] #55 docs --- src/common/components/Tabs/Tabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/components/Tabs/Tabs.tsx b/src/common/components/Tabs/Tabs.tsx index fb24876..547f74f 100644 --- a/src/common/components/Tabs/Tabs.tsx +++ b/src/common/components/Tabs/Tabs.tsx @@ -11,7 +11,7 @@ import Button from '../Button/Button'; */ type TabsContextValue = { activeTab: string; - setActiveTab: (activeTab: string) => void; + setActiveTab: (tab: string) => void; }; /** From 46ef9b4cbfefba7c1af15fe09ce10cf156bfdfcb Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Fri, 28 Feb 2025 13:30:58 -0500 Subject: [PATCH 5/5] #55 pr fixes --- src/common/components/Tabs/Tabs.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/components/Tabs/Tabs.tsx b/src/common/components/Tabs/Tabs.tsx index 547f74f..a541141 100644 --- a/src/common/components/Tabs/Tabs.tsx +++ b/src/common/components/Tabs/Tabs.tsx @@ -40,16 +40,16 @@ export interface TabsProps extends BaseComponentProps, PropsWithChildren { * * *Example:* * ``` - * + * * * List * Detail * - * - *
I am the LIST tab.
+ * + *
I am the LIST tab.
*
- * - *
I am the DETAIL tab.
+ * + *
I am the DETAIL tab.
*
*
* ``` @@ -70,8 +70,8 @@ const Tabs = ({ children, className, defaultValue, testId = 'tabs' }: TabsProps) const listVariants = cva('flex gap-4 border-b border-b-neutral-500/10', { variants: { align: { + start: 'justify-start', stretch: '*:grow', - start: 'flex', }, }, defaultVariants: { align: 'start' },