-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #716 from Atom-Learning/HMP-1508-create-ds-segment…
…ed-control-component Feat: SegmentedControl component
- Loading branch information
Showing
28 changed files
with
3,529 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+21.5 KB
documentation/public/assets/images/segmented-control-dos-and-donts-01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+21.3 KB
documentation/public/assets/images/segmented-control-dos-and-donts-02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+13.4 KB
documentation/public/assets/images/segmented-control-dos-and-donts-03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+10.7 KB
documentation/public/assets/images/segmented-control-dos-and-donts-04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
lib/src/components/segmented-control/SegmentedControl.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import * as React from 'react' | ||
import { SegmentedControl } from './SegmentedControl' | ||
import { Alarm, Anchor } from '@atom-learning/icons' | ||
import { render, screen, waitFor } from '@testing-library/react' | ||
import { axe } from 'jest-axe' | ||
import userEvent from '@testing-library/user-event' | ||
|
||
const SegmentedControlComponent = ({ | ||
children, | ||
...props | ||
}: React.PropsWithChildren< | ||
React.ComponentProps<typeof SegmentedControl.Root> | ||
>) => { | ||
return ( | ||
<SegmentedControl.Root | ||
size="sm" | ||
defaultValue="one" | ||
theme="marsh" | ||
{...props} | ||
> | ||
<SegmentedControl.ItemList> | ||
<SegmentedControl.Item value="one"> | ||
<SegmentedControl.Icon is={Alarm} /> | ||
<SegmentedControl.Heading>Heading One</SegmentedControl.Heading> | ||
<SegmentedControl.Description> | ||
Description One | ||
</SegmentedControl.Description> | ||
<SegmentedControl.Badge>Status One</SegmentedControl.Badge> | ||
</SegmentedControl.Item> | ||
<SegmentedControl.Item value="two"> | ||
<SegmentedControl.Icon is={Anchor} /> | ||
<SegmentedControl.Heading>Heading Two</SegmentedControl.Heading> | ||
<SegmentedControl.Description> | ||
Description Two | ||
</SegmentedControl.Description> | ||
<SegmentedControl.Badge>Status Two</SegmentedControl.Badge> | ||
</SegmentedControl.Item> | ||
</SegmentedControl.ItemList> | ||
<SegmentedControl.Content value="one"> | ||
Content One | ||
</SegmentedControl.Content> | ||
<SegmentedControl.Content value="two"> | ||
Content One | ||
</SegmentedControl.Content> | ||
</SegmentedControl.Root> | ||
) | ||
} | ||
|
||
describe('SegmentedControl component', () => { | ||
it('renders', async () => { | ||
const { container } = render(<SegmentedControlComponent />) | ||
|
||
expect(screen.getByRole('heading', { name: 'Heading One' })).toBeVisible() | ||
expect(screen.getByText('Description One')).toBeVisible() | ||
expect(screen.getByText('Status One')).toBeVisible() | ||
|
||
expect(screen.getByRole('heading', { name: 'Heading Two' })).toBeVisible() | ||
expect(screen.getByText('Description Two')).toBeVisible() | ||
expect(screen.getByText('Status Two')).toBeVisible() | ||
|
||
expect(screen.getByText('Content One')).toBeVisible() | ||
await expect( | ||
waitFor(() => screen.getByText('Content Two')) | ||
).rejects.toThrow() | ||
|
||
expect(container).toMatchSnapshot() | ||
}) | ||
|
||
it('has no programmatically detectable a11y issues', async () => { | ||
const { container } = render(<SegmentedControlComponent />) | ||
expect(await axe(container)).toHaveNoViolations() | ||
}) | ||
|
||
it('renders md variant with primary theme', () => { | ||
const { container } = render( | ||
<SegmentedControlComponent size="md" theme="primary" /> | ||
) | ||
expect(container).toMatchSnapshot() | ||
}) | ||
|
||
it('renders lg variant with marsh theme', () => { | ||
const { container } = render( | ||
<SegmentedControlComponent size="lg" theme="marsh" /> | ||
) | ||
expect(container).toMatchSnapshot() | ||
}) | ||
|
||
it('renders with a default tab selected', () => { | ||
render(<SegmentedControlComponent defaultValue="two" />) | ||
|
||
const tabOne = screen.getByRole('tab', { name: /Heading One/ }) | ||
const tabTwo = screen.getByRole('tab', { name: /Heading Two/ }) | ||
|
||
expect(tabOne).toHaveAttribute('aria-selected', 'false') | ||
expect(tabTwo).toHaveAttribute('aria-selected', 'true') | ||
}) | ||
|
||
it('allows switching between tabs', async () => { | ||
render(<SegmentedControlComponent />) | ||
|
||
const tabOne = screen.getByRole('tab', { name: /Heading One/ }) | ||
const tabTwo = screen.getByRole('tab', { name: /Heading Two/ }) | ||
|
||
expect(tabOne).toHaveAttribute('aria-selected', 'true') | ||
expect(tabTwo).toHaveAttribute('aria-selected', 'false') | ||
|
||
await userEvent.click(tabTwo) | ||
|
||
expect(tabOne).toHaveAttribute('aria-selected', 'false') | ||
expect(tabTwo).toHaveAttribute('aria-selected', 'true') | ||
|
||
await userEvent.click(tabOne) | ||
|
||
expect(tabOne).toHaveAttribute('aria-selected', 'true') | ||
expect(tabTwo).toHaveAttribute('aria-selected', 'false') | ||
}) | ||
|
||
it('does not allow clicking on disabled tab', async () => { | ||
render( | ||
<SegmentedControl.Root size="sm" defaultValue="one" theme="marsh"> | ||
<SegmentedControl.ItemList> | ||
<SegmentedControl.Item value="one"> | ||
<SegmentedControl.Heading>Heading One</SegmentedControl.Heading> | ||
</SegmentedControl.Item> | ||
<SegmentedControl.Item value="two" disabled> | ||
<SegmentedControl.Heading>Heading Two</SegmentedControl.Heading> | ||
</SegmentedControl.Item> | ||
</SegmentedControl.ItemList> | ||
<SegmentedControl.Content value="one"> | ||
Content One | ||
</SegmentedControl.Content> | ||
<SegmentedControl.Content value="two"> | ||
Content One | ||
</SegmentedControl.Content> | ||
</SegmentedControl.Root> | ||
) | ||
|
||
const tabOne = screen.getByRole('tab', { name: 'Heading One' }) | ||
const tabTwo = screen.getByRole('tab', { name: 'Heading Two' }) | ||
|
||
expect(tabOne).toBeEnabled() | ||
expect(tabTwo).toBeDisabled() | ||
|
||
expect(tabOne).toHaveAttribute('aria-selected', 'true') | ||
expect(tabTwo).toHaveAttribute('aria-selected', 'false') | ||
|
||
await userEvent.click(tabTwo) | ||
|
||
expect(tabOne).toHaveAttribute('aria-selected', 'true') | ||
expect(tabTwo).toHaveAttribute('aria-selected', 'false') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { SegmentedControlBadge } from './SegmentedControlBadge' | ||
import { SegmentedControlContent } from './SegmentedControlContent' | ||
import { SegmentedControlDescription } from './SegmentedControlDescription' | ||
import { SegmentedControlHeading } from './SegmentedControlHeading' | ||
import { SegmentedControlIcon } from './SegmentedControlIcon' | ||
import { SegmentedControlItem } from './SegmentedControlItem' | ||
import { SegmentedControlItemList } from './SegmentedControlItemList' | ||
import { SegmentedControlRoot } from './SegmentedControlRoot' | ||
|
||
export const SegmentedControl = { | ||
Root: SegmentedControlRoot, | ||
Item: SegmentedControlItem, | ||
Heading: SegmentedControlHeading, | ||
Description: SegmentedControlDescription, | ||
Icon: SegmentedControlIcon, | ||
Content: SegmentedControlContent, | ||
Badge: SegmentedControlBadge, | ||
ItemList: SegmentedControlItemList | ||
} |
24 changes: 24 additions & 0 deletions
24
lib/src/components/segmented-control/SegmentedControlBadge.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as React from 'react' | ||
|
||
import { Badge } from '../badge' | ||
import { useSegmentedControl } from './SegmentedControlContext' | ||
|
||
const badgeSizeMap = { | ||
sm: 'xs', | ||
md: 'xs', | ||
lg: 'sm' | ||
} | ||
|
||
export const SegmentedControlBadge = ({ | ||
css, | ||
...props | ||
}: Omit<React.ComponentProps<typeof Badge>, 'size'>): JSX.Element => { | ||
const { size } = useSegmentedControl() | ||
return ( | ||
<Badge | ||
{...props} | ||
css={{ border: 'none', ...css, fontWeight: 'normal' }} | ||
size={badgeSizeMap[size as string]} | ||
/> | ||
) | ||
} |
5 changes: 5 additions & 0 deletions
5
lib/src/components/segmented-control/SegmentedControlContent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import * as React from 'react' | ||
|
||
import { Tabs } from '../tabs' | ||
|
||
export const SegmentedControlContent = Tabs.Content |
40 changes: 40 additions & 0 deletions
40
lib/src/components/segmented-control/SegmentedControlContext.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as React from 'react' | ||
|
||
import type { SegmentedControlRootProps } from './SegmentedControlRoot' | ||
|
||
export type SegmentedControlTheme = 'primary' | 'marsh' | ||
|
||
interface SegmentedControlContextValue { | ||
size: SegmentedControlRootProps['size'] | ||
theme: SegmentedControlTheme | ||
} | ||
|
||
interface SegmentedControlProviderProps extends SegmentedControlContextValue { | ||
children: React.ReactNode | ||
} | ||
|
||
const SegmentedControlContext = | ||
React.createContext<SegmentedControlContextValue>({ | ||
size: 'md', | ||
theme: 'primary' | ||
}) | ||
|
||
export const SegmentedControlProvider = ({ | ||
size, | ||
theme, | ||
children | ||
}: SegmentedControlProviderProps): JSX.Element => { | ||
const value = React.useMemo<SegmentedControlContextValue>( | ||
() => ({ size, theme }), | ||
[size, theme] | ||
) | ||
|
||
return ( | ||
<SegmentedControlContext.Provider value={value}> | ||
{children} | ||
</SegmentedControlContext.Provider> | ||
) | ||
} | ||
|
||
export const useSegmentedControl = (): SegmentedControlContextValue => | ||
React.useContext(SegmentedControlContext) |
31 changes: 31 additions & 0 deletions
31
lib/src/components/segmented-control/SegmentedControlDescription.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import * as React from 'react' | ||
|
||
import { styled } from '../../stitches' | ||
import { Text } from '../text' | ||
import { useSegmentedControl } from './SegmentedControlContext' | ||
|
||
const StyledText = styled(Text, { | ||
fontFamily: '$body', | ||
color: '$textSubtle', | ||
fontWeight: 400, | ||
variants: { | ||
size: { | ||
sm: { | ||
fontSize: '$xs' | ||
}, | ||
md: { | ||
fontSize: '$sm' | ||
}, | ||
lg: { | ||
fontSize: '$md' | ||
} | ||
} | ||
} | ||
}) | ||
|
||
export const SegmentedControlDescription = ( | ||
props: Omit<React.ComponentProps<typeof StyledText>, 'size'> | ||
): JSX.Element => { | ||
const { size } = useSegmentedControl() | ||
return <StyledText {...props} size={size} /> | ||
} |
29 changes: 29 additions & 0 deletions
29
lib/src/components/segmented-control/SegmentedControlHeading.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as React from 'react' | ||
|
||
import { styled } from '../../stitches' | ||
import { Heading } from '../heading' | ||
import { useSegmentedControl } from './SegmentedControlContext' | ||
|
||
const StyledHeading = styled(Heading, { | ||
fontFamily: '$body', | ||
variants: { | ||
size: { | ||
sm: { | ||
fontSize: '$sm' | ||
}, | ||
md: { | ||
fontSize: '$md' | ||
}, | ||
lg: { | ||
fontSize: '$lg' | ||
} | ||
} | ||
} | ||
}) | ||
|
||
export const SegmentedControlHeading = ( | ||
props: Omit<React.ComponentProps<typeof StyledHeading>, 'size'> | ||
): JSX.Element => { | ||
const { size } = useSegmentedControl() | ||
return <StyledHeading {...props} size={size} /> | ||
} |
17 changes: 17 additions & 0 deletions
17
lib/src/components/segmented-control/SegmentedControlIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import * as React from 'react' | ||
|
||
import { Icon } from '../icon' | ||
import { useSegmentedControl } from './SegmentedControlContext' | ||
|
||
const sizeMap = { | ||
sm: 'sm', | ||
md: 'md', | ||
lg: 'md' | ||
} | ||
|
||
export const SegmentedControlIcon = ( | ||
props: Omit<React.ComponentProps<typeof Icon>, 'size'> | ||
): JSX.Element => { | ||
const { size } = useSegmentedControl() | ||
return <Icon {...props} size={sizeMap[size as string]} /> | ||
} |
Oops, something went wrong.