Skip to content

Commit

Permalink
extract button bar styles into presentational-only component
Browse files Browse the repository at this point in the history
  • Loading branch information
jivey committed Mar 4, 2024
1 parent 27f117b commit 7f08b08
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 92 deletions.
34 changes: 34 additions & 0 deletions src/components/ButtonBar.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render } from "@testing-library/react";
import { ButtonBar } from "./ButtonBar";

describe("ButtonBar", () => {
it("renders", () => {
const { asFragment } = render(
<ButtonBar>
<a>One</a>
<a>Two</a>
</ButtonBar>,
);
expect(asFragment()).toMatchSnapshot();
});

it("renders small size", () => {
const { asFragment } = render(
<ButtonBar size="small">
<button>One</button>
<button>Two</button>
</ButtonBar>,
);
expect(asFragment()).toMatchSnapshot();
});

it("renders large size", () => {
const { asFragment } = render(
<ButtonBar size="large">
<button>One</button>
<button data-selected>Two</button>
</ButtonBar>,
);
expect(asFragment()).toMatchSnapshot();
});
});
43 changes: 43 additions & 0 deletions src/components/ButtonBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import styled from "styled-components";
import { ButtonBar } from "./ButtonBar";

type ButtonBarProps = React.ComponentProps<typeof ButtonBar>;

const Wrapper = styled.div`
padding: 2.4rem;
`;

export const Examples = () => {
const [size, setSize] =
React.useState<ButtonBarProps["size"]>("medium");

const handleSetSize = (e: any) => setSize(e.currentTarget.value as ButtonBarProps['size']);

return <Wrapper>
<label>
<b>Size</b><br/>
<select onChange={handleSetSize} defaultValue={size}>
{['large', 'medium', 'small'].map((v) =>
<option key={v}>{v}</option>)}
</select>
</label>
<br/><br/>
<ButtonBar size={size}>
<a>First Item</a>
<a data-selected>Second Item</a>
</ButtonBar>
<br/><br/>
<ButtonBar size={size}>
<button>First Item</button>
<button data-selected>Second Item</button>
<button>Third Item</button>
</ButtonBar>
<br/><br/>
<ButtonBar size={size}>
<button>First Item with a really long title</button>
<button>Second Item</button>
<button>Third Item</button>
</ButtonBar>
</Wrapper>
};
36 changes: 36 additions & 0 deletions src/components/ButtonBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import styled from "styled-components";
import {
buttonBarItemCss,
buttonBarWrapperCss,
tabBaseCss,
tabListBaseCss,
TabsProps,
} from "./Tabs";

type ButtonBarProps = Pick<TabsProps, "size"> & {
children?: React.ReactNode;
};

const Wrapper = styled.div`
${tabListBaseCss}
${buttonBarWrapperCss}
> * {
all: unset;
${tabBaseCss}
${buttonBarItemCss}
}
`;

export const ButtonBar = ({
size = "medium",
children,
...restProps
}: ButtonBarProps) => {
return (
<Wrapper size={size} {...restProps}>
{children}
</Wrapper>
);
};
8 changes: 4 additions & 4 deletions src/components/Tabs.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Tabs, Tab, TabList, TabPanel } from "./Tabs";

describe("Tabs component", () => {
describe("normal styling", () => {
it("renders correctly", () => {
it("renders", () => {
const { asFragment } = render(
<Tabs>
<TabList aria-label="Items">
Expand All @@ -19,7 +19,7 @@ describe("Tabs component", () => {
expect(asFragment()).toMatchSnapshot();
});

it("renders correctly with small size", () => {
it("renders small size", () => {
const { asFragment } = render(
<Tabs size="small">
<TabList aria-label="Items">
Expand Down Expand Up @@ -53,7 +53,7 @@ describe("Tabs component", () => {
});

describe("button-bar styling", () => {
it("renders correctly with button-bar variant", () => {
it("renders", () => {
const { asFragment } = render(
<Tabs variant="button-bar">
<TabList aria-label="Items">
Expand All @@ -69,7 +69,7 @@ describe("Tabs component", () => {
expect(asFragment()).toMatchSnapshot();
});

it("renders correctly with button-bar variant and small size", () => {
it("renders small size", () => {
const { asFragment } = render(
<Tabs size="small" variant="button-bar">
<TabList aria-label="Items">
Expand Down
106 changes: 58 additions & 48 deletions src/components/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,73 @@ import { colors } from "../theme";
import styled, { css } from "styled-components";
import { palette } from "../../src/theme/palette";

type TabsProps = {
export type TabsProps = {
variant?: "button-bar";
size?: "large" | "medium" | "small";
className?: string;
children?: React.ReactNode;
} & RAC.TabsProps;

const buttonBarCss = css`
[role="tablist"] {
border: 0.1rem solid ${colors.palette.pale};
border-radius: 0.5rem;
export const tabListBaseCss = `
overflow-x: auto;
overscroll-behavior: contain;
display: flex;
flex-direction: row;
`;

export const tabBaseCss = css`
flex: 1 1 auto;
display: flex;
align-items: center;
justify-content: center;
outline-offset: -0.1rem; // Prevent overflow scroll from clipping outline
white-space: nowrap;
font-size: ${({ size }: TabsProps) =>
size === 'small' ? '1.6' : (size === 'large' ? '2.4' : '1.8')}rem;
&:hover {
cursor: pointer;
}
`;

[role="tab"] {
padding: 0 1.6rem;
min-height: ${({ size }: TabsProps) =>
size === 'small' ? '2.8' : ( size === 'large' ? '4.8' : '4.0')}rem;
background: #fff;
export const buttonBarWrapperCss = `
border: 0.1rem solid ${colors.palette.pale};
border-radius: 0.5rem;
`;

&[data-selected] {
background: ${colors.palette.neutralLight};
}
&:hover:not([data-selected]) {
background: ${colors.palette.neutralLighter};
}
export const buttonBarItemCss = css`
padding: 0 1.6rem;
min-height: ${({ size }: TabsProps) =>
size === 'small' ? '2.8' : ( size === 'large' ? '4.8' : '4.0')}rem;
background: #fff;
border-right: 0.1rem solid ${colors.palette.pale};
&:first-child {
border-top-left-radius: 0.4rem;
border-bottom-left-radius: 0.4rem;
border-left: 0;
}
&:last-child {
border-top-right-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
border-right: 0;
}
&[data-orientation="horizontal"] [role="tab"] {
border-right: 0.1rem solid ${colors.palette.pale};
&[data-selected] {
background: ${colors.palette.neutralLight};
}
&:hover:not([data-selected]) {
background: ${colors.palette.neutralLighter};
}
`;

&:first-child {
border-top-left-radius: 0.4rem;
border-bottom-left-radius: 0.4rem;
border-left: 0;
}
&:last-child {
border-top-right-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
border-right: 0;
}
const buttonBarCss = css`
[role="tablist"] {
${buttonBarWrapperCss}
}
[role="tab"] {
${buttonBarItemCss}
}
`;

Expand All @@ -66,28 +93,11 @@ const tabsCss = css`

const StyledTabs = styled(RAC.Tabs)`
[role="tablist"] {
overflow-x: auto;
overscroll-behavior: contain;
display: flex;
}
&[data-orientation="horizontal"] [role="tablist"] {
flex-direction: row;
${tabListBaseCss}
}
[role="tab"] {
flex: 1 1 auto;
display: flex;
align-items: center;
justify-content: center;
outline-offset: -0.1rem; // Prevent overflow scroll from clipping outline
white-space: nowrap;
font-size: ${({ size }: TabsProps) =>
size === 'small' ? '1.6' : (size === 'large' ? '2.4' : '1.8')}rem;
&:hover {
cursor: pointer;
}
${tabBaseCss}
}
${(props: TabsProps) =>
Expand All @@ -113,4 +123,4 @@ export const Tabs = ({
);
};

export { TabList, Tab, TabPanel, TabsContext, TabListStateContext } from "react-aria-components";
export { TabList, Tab, TabPanel } from "react-aria-components";
48 changes: 48 additions & 0 deletions src/components/__snapshots__/ButtonBar.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ButtonBar renders 1`] = `
<DocumentFragment>
<div
class="sc-gsnTZi dIbpvz"
>
<a>
One
</a>
<a>
Two
</a>
</div>
</DocumentFragment>
`;

exports[`ButtonBar renders large size 1`] = `
<DocumentFragment>
<div
class="sc-gsnTZi eAVYPw"
>
<button>
One
</button>
<button
data-selected="true"
>
Two
</button>
</div>
</DocumentFragment>
`;

exports[`ButtonBar renders small size 1`] = `
<DocumentFragment>
<div
class="sc-gsnTZi gXyDdH"
>
<button>
One
</button>
<button>
Two
</button>
</div>
</DocumentFragment>
`;
Loading

0 comments on commit 7f08b08

Please sign in to comment.