diff --git a/src/components/ButtonBar.spec.tsx b/src/components/ButtonBar.spec.tsx new file mode 100644 index 000000000..576c0145a --- /dev/null +++ b/src/components/ButtonBar.spec.tsx @@ -0,0 +1,34 @@ +import { render } from "@testing-library/react"; +import { ButtonBar } from "./ButtonBar"; + +describe("ButtonBar", () => { + it("renders", () => { + const { asFragment } = render( + + One + Two + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders small size", () => { + const { asFragment } = render( + + + + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders large size", () => { + const { asFragment } = render( + + + + , + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/src/components/ButtonBar.stories.tsx b/src/components/ButtonBar.stories.tsx new file mode 100644 index 000000000..a63117477 --- /dev/null +++ b/src/components/ButtonBar.stories.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import styled from "styled-components"; +import { ButtonBar } from "./ButtonBar"; + +type ButtonBarProps = React.ComponentProps; + +const Wrapper = styled.div` + padding: 2.4rem; +`; + +export const Examples = () => { + const [size, setSize] = + React.useState("medium"); + + const handleSetSize = (e: any) => setSize(e.currentTarget.value as ButtonBarProps['size']); + + return + +

+ + First Item + Second Item + +

+ + + + + +

+ + + + + +
+}; diff --git a/src/components/ButtonBar.tsx b/src/components/ButtonBar.tsx new file mode 100644 index 000000000..fd1681b45 --- /dev/null +++ b/src/components/ButtonBar.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import styled from "styled-components"; +import { + buttonBarItemCss, + buttonBarWrapperCss, + tabBaseCss, + tabListBaseCss, + TabsProps, +} from "./Tabs"; + +type ButtonBarProps = Pick & { + children?: React.ReactNode; +}; + +const Wrapper = styled.div` + ${tabListBaseCss} + ${buttonBarWrapperCss} + + > * { + all: unset; + ${tabBaseCss} + ${buttonBarItemCss} + } +`; + +export const ButtonBar = ({ + size = "medium", + children, + ...restProps +}: ButtonBarProps) => { + return ( + + {children} + + ); +}; diff --git a/src/components/Tabs.spec.tsx b/src/components/Tabs.spec.tsx index de19d2fec..84f6266a8 100644 --- a/src/components/Tabs.spec.tsx +++ b/src/components/Tabs.spec.tsx @@ -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( @@ -19,7 +19,7 @@ describe("Tabs component", () => { expect(asFragment()).toMatchSnapshot(); }); - it("renders correctly with small size", () => { + it("renders small size", () => { const { asFragment } = render( @@ -53,7 +53,7 @@ describe("Tabs component", () => { }); describe("button-bar styling", () => { - it("renders correctly with button-bar variant", () => { + it("renders", () => { const { asFragment } = render( @@ -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( diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx index 7e245e8fb..e124100be 100644 --- a/src/components/Tabs.tsx +++ b/src/components/Tabs.tsx @@ -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} } `; @@ -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) => @@ -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"; diff --git a/src/components/__snapshots__/ButtonBar.spec.tsx.snap b/src/components/__snapshots__/ButtonBar.spec.tsx.snap new file mode 100644 index 000000000..5f0b33159 --- /dev/null +++ b/src/components/__snapshots__/ButtonBar.spec.tsx.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ButtonBar renders 1`] = ` + + + +`; + +exports[`ButtonBar renders large size 1`] = ` + +
+ + +
+
+`; + +exports[`ButtonBar renders small size 1`] = ` + +
+ + +
+
+`; diff --git a/src/components/__snapshots__/Tabs.spec.tsx.snap b/src/components/__snapshots__/Tabs.spec.tsx.snap index 32df3a9b0..0240a1313 100644 --- a/src/components/__snapshots__/Tabs.spec.tsx.snap +++ b/src/components/__snapshots__/Tabs.spec.tsx.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Tabs component button-bar styling renders correctly with button-bar variant 1`] = ` +exports[`Tabs component button-bar styling renders 1`] = `
@@ -66,10 +66,10 @@ exports[`Tabs component button-bar styling renders correctly with button-bar var `; -exports[`Tabs component button-bar styling renders correctly with button-bar variant and small size 1`] = ` +exports[`Tabs component button-bar styling renders large size 1`] = `
@@ -79,17 +79,17 @@ exports[`Tabs component button-bar styling renders correctly with button-bar var class="react-aria-TabList" data-orientation="horizontal" data-rac="" - id="react-aria-5" + id="react-aria-6" role="tablist" >
@@ -132,10 +132,10 @@ exports[`Tabs component button-bar styling renders correctly with button-bar var `; -exports[`Tabs component button-bar styling renders large size 1`] = ` +exports[`Tabs component button-bar styling renders small size 1`] = `
@@ -145,17 +145,17 @@ exports[`Tabs component button-bar styling renders large size 1`] = ` class="react-aria-TabList" data-orientation="horizontal" data-rac="" - id="react-aria-6" + id="react-aria-5" role="tablist" >
@@ -198,10 +198,10 @@ exports[`Tabs component button-bar styling renders large size 1`] = ` `; -exports[`Tabs component normal styling renders correctly 1`] = ` +exports[`Tabs component normal styling renders 1`] = `
@@ -264,10 +264,10 @@ exports[`Tabs component normal styling renders correctly 1`] = ` `; -exports[`Tabs component normal styling renders correctly with small size 1`] = ` +exports[`Tabs component normal styling renders large size 1`] = `
@@ -277,17 +277,17 @@ exports[`Tabs component normal styling renders correctly with small size 1`] = ` class="react-aria-TabList" data-orientation="horizontal" data-rac="" - id="react-aria-2" + id="react-aria-3" role="tablist" >
@@ -330,10 +330,10 @@ exports[`Tabs component normal styling renders correctly with small size 1`] = ` `; -exports[`Tabs component normal styling renders large size 1`] = ` +exports[`Tabs component normal styling renders small size 1`] = `
@@ -343,17 +343,17 @@ exports[`Tabs component normal styling renders large size 1`] = ` class="react-aria-TabList" data-orientation="horizontal" data-rac="" - id="react-aria-3" + id="react-aria-2" role="tablist" >
diff --git a/src/index.ts b/src/index.ts index 924c6dd02..af96b0f8a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './components/Button'; +export * from './components/ButtonBar'; export * from './components/Checkbox'; export * from './components/DropdownMenu'; export * from './components/Error';