diff --git a/cypress/components/button-toggle/button-toggle.cy.tsx b/cypress/components/button-toggle/button-toggle.cy.tsx index 47e4c24c4a..5fb86ca8d3 100644 --- a/cypress/components/button-toggle/button-toggle.cy.tsx +++ b/cypress/components/button-toggle/button-toggle.cy.tsx @@ -4,7 +4,10 @@ import { ButtonToggle, ButtonToggleProps, } from "../../../src/components/button-toggle"; -import { ButtonToggleComponent } from "../../../src/components/button-toggle/button-toggle-test.stories"; +import { + ButtonToggleComponent, + ButtonToggleGroupComponent, +} from "../../../src/components/button-toggle/button-toggle-test.stories"; import * as stories from "../../../src/components/button-toggle/button-toggle-group/button-toggle-group-test.stories"; import { buttonTogglePreview, @@ -323,6 +326,22 @@ context("Testing Button-Toggle component", () => { }); }); + describe("should render Button Toggle with variant prop", () => { + const variants = ["default", "minor"]; + + it.each(variants)( + "renders with variant prop, which is set to %s", + (variantType) => { + CypressMountWithProviders( + + ); + } + ); + }); + describe("Accessibility tests for Button-Toggle component", () => { it("should pass accessibility tests for Button-Toggle default story", () => { CypressMountWithProviders(); diff --git a/src/components/button-toggle/__snapshots__/button-toggle.spec.tsx.snap b/src/components/button-toggle/__snapshots__/button-toggle.spec.tsx.snap index 306174ae3c..0d6b69c3b5 100644 --- a/src/components/button-toggle/__snapshots__/button-toggle.spec.tsx.snap +++ b/src/components/button-toggle/__snapshots__/button-toggle.spec.tsx.snap @@ -57,7 +57,7 @@ exports[`ButtonToggle General styling renders correctly when grouped 1`] = ` } .c3.c3.c3.c3 .c1 { - border-radius: var(--borderRadius400); + border-radius: var(--borderRadius050); } .c0 { @@ -66,13 +66,13 @@ exports[`ButtonToggle General styling renders correctly when grouped 1`] = ` } .c0.c0.c0.c0:first-of-type .c1 { - border-top-left-radius: var(--borderRadius400); - border-bottom-left-radius: var(--borderRadius400); + border-top-left-radius: var(--borderRadius050); + border-bottom-left-radius: var(--borderRadius050); } .c0.c0.c0.c0:last-of-type .c1 { - border-top-right-radius: var(--borderRadius400); - border-bottom-right-radius: var(--borderRadius400); + border-top-right-radius: var(--borderRadius050); + border-bottom-right-radius: var(--borderRadius050); } .c0:not(:first-of-type) { diff --git a/src/components/button-toggle/button-toggle-group/button-toggle-group.component.tsx b/src/components/button-toggle/button-toggle-group/button-toggle-group.component.tsx index 2aeca26c9c..1bdb9ef9c2 100644 --- a/src/components/button-toggle/button-toggle-group/button-toggle-group.component.tsx +++ b/src/components/button-toggle/button-toggle-group/button-toggle-group.component.tsx @@ -63,6 +63,7 @@ export interface ButtonToggleGroupProps extends MarginProps, TagProps { helpAriaLabel?: string; /** set this to true to allow the buttons within the group to be deselected when already selected, leaving no selected button */ allowDeselect?: boolean; + variant?: "default" | "minor"; } type ButtonToggleGroupContextType = { @@ -79,6 +80,7 @@ type ButtonToggleGroupContextType = { isInGroup: boolean; firstButton?: HTMLButtonElement; childButtonCallbackRef?: (button: HTMLButtonElement | null) => void; + variant?: "default" | "minor"; }; let deprecateNameWarnTriggered = false; @@ -111,6 +113,7 @@ const ButtonToggleGroup = ({ name, onChange, value, + variant = "default", "data-component": dataComponent = "button-toggle-group", "data-element": dataElement, "data-role": dataRole, @@ -237,6 +240,7 @@ const ButtonToggleGroup = ({ isInGroup: true, firstButton, childButtonCallbackRef, + variant, }} > { }); }); + describe("renders with variant prop", () => { + it("renders with variant prop, which is set to default", () => { + const wrapper = mount( + + Foo + + ); + expect(wrapper.find(StyledButtonToggle).prop("variant")).toEqual( + "default" + ); + }); + + it("renders with variant prop, which is set to minor", () => { + const wrapper = mount( + + Foo + + ); + expect(wrapper.find(StyledButtonToggle).prop("variant")).toEqual("minor"); + }); + }); + describe("keyboard navigation", () => { let wrapper: ReactWrapper; let container: HTMLElement | null; diff --git a/src/components/button-toggle/button-toggle-group/button-toggle-group.stories.tsx b/src/components/button-toggle/button-toggle-group/button-toggle-group.stories.tsx index 7e1333d6bb..3d12caefb8 100644 --- a/src/components/button-toggle/button-toggle-group/button-toggle-group.stories.tsx +++ b/src/components/button-toggle/button-toggle-group/button-toggle-group.stories.tsx @@ -43,6 +43,32 @@ export const Controlled: ComponentStory = () => { ); }; +export const ControlledMinor: ComponentStory = () => { + const [value, setValue] = useState("bar"); + function onChangeHandler( + event: React.MouseEvent, + selectedValue?: string + ) { + setValue(selectedValue as string); + } + return ( + + Foo + Bar + Baz + + ); +}; + export const Grouped: ComponentStory = () => ( = () => ( ); +export const GroupedMinor: ComponentStory = () => ( + {}} + variant="minor" + > + + Foo + + + Bar + + + Baz + + +); + export const FullWidth: ComponentStory = () => ( = () => ( ); +export const FullWidthMinor: ComponentStory = () => ( + {}} + variant="minor" + > + + Foo + + + Bar + + + Baz + + +); + export const FieldHelp: ComponentStory = () => ( = () => ( ); +export const FieldHelpMinor: ComponentStory = () => ( + {}} + variant="minor" + > + Foo + Bar + Baz + +); + export const LabelInline: ComponentStory = () => ( = () => ( ); +export const LabelInlineMinor: ComponentStory< + typeof ButtonToggleGroup +> = () => ( + {}} + variant="minor" + > + Foo + Bar + Baz + +); + export const AllowDeselection: ComponentStory< typeof ButtonToggleGroup > = () => { @@ -145,6 +252,34 @@ export const AllowDeselection: ComponentStory< ); }; +export const AllowDeselectionMinor: ComponentStory< + typeof ButtonToggleGroup +> = () => { + const [value, setValue] = useState("bar"); + function onChangeHandler( + event: React.MouseEvent, + selectedValue?: string + ) { + setValue(selectedValue as string); + } + return ( + + Foo + Bar + Baz + + ); +}; + export const AriaLabel: ComponentStory = () => { const [value, setValue] = useState("bar"); function onChangeHandler( @@ -167,3 +302,27 @@ export const AriaLabel: ComponentStory = () => { ); }; + +export const AriaLabelMinor: ComponentStory = () => { + const [value, setValue] = useState("bar"); + function onChangeHandler( + event: React.MouseEvent, + selectedValue?: string + ) { + setValue(selectedValue as string); + } + return ( + + Foo + Bar + Baz + + ); +}; diff --git a/src/components/button-toggle/button-toggle-test.stories.tsx b/src/components/button-toggle/button-toggle-test.stories.tsx index c019fd6280..4debbd9fd4 100644 --- a/src/components/button-toggle/button-toggle-test.stories.tsx +++ b/src/components/button-toggle/button-toggle-test.stories.tsx @@ -41,6 +41,34 @@ export const DefaultStory = () => { ); }; +export const ButtonToggleGroupMinorStory = () => { + const [value, setValue] = useState("bar"); + function onChangeHandler( + event: React.MouseEvent, + selectedButtonValue?: string + ) { + setValue(selectedButtonValue); + action("value set")(selectedButtonValue); + } + return ( + + Foo + Bar + Baz + + ); +}; + export const WithoutGroup = (args: Partial) => (
{ buttonRef.current = element; if (childButtonCallbackRef) { @@ -186,6 +186,7 @@ export const ButtonToggle = ({ data-element={dataElement} data-role={dataRole} grouped={grouped} + variant={variant} > { mount(toggle); }); - it("renders with the expected border radius styling", () => { - assertStyleMatch( - { + describe("renders with the expected border radius styling depending on a variant prop", () => { + it("renders with the expected border radius styling, when variant prop is set to default", () => { + const buttonToggleWrapper = mount( + + toggle + + ).find(StyledButtonToggleWrapper); + + const expectedButtonWithRadius400 = { borderRadius: "var(--borderRadius400)", - }, - mount(toggle).find( - StyledButtonToggleWrapper - ), - { modifier: `&&&& ${StyledButtonToggle}` } - ); + }; + + assertStyleMatch(expectedButtonWithRadius400, buttonToggleWrapper, { + modifier: `&&&& ${StyledButtonToggle}`, + }); + }); + + it("renders with the expected border radius styling, when variant prop is set to minor", () => { + const buttonToggleWrapper = mount( + + toggle + + ).find(StyledButtonToggleWrapper); + + const expectedButtonWithRadius050 = { + borderRadius: "var(--borderRadius050)", + }; + + assertStyleMatch(expectedButtonWithRadius050, buttonToggleWrapper, { + modifier: `&&&& ${StyledButtonToggle}`, + }); + }); }); }); diff --git a/src/components/button-toggle/button-toggle.stories.mdx b/src/components/button-toggle/button-toggle.stories.mdx index 2b63e210aa..01c0faefe3 100644 --- a/src/components/button-toggle/button-toggle.stories.mdx +++ b/src/components/button-toggle/button-toggle.stories.mdx @@ -49,18 +49,36 @@ import { ButtonToggle, ButtonToggleGroup } from "carbon-react/lib/components/but +### Default minor + + + + + ### Default with wrapped text +### Default with wrapped text minor + + + + + ### Controlled +### Controlled minor + + + + + ### With aria-label If you do not want a visible label to appear above the group, you should use the aria-label prop to give it a non-visible @@ -70,6 +88,12 @@ accessible name to be used by screenreaders. Failure to do this will result in a +### With aria-label minor + + + + + ### With fullWidth When the `fullWidth` prop is `true`, the buttons will expand in size to take up the full width of the container. @@ -78,18 +102,36 @@ When the `fullWidth` prop is `true`, the buttons will expand in size to take up +### With fullWidth minor + + + + + ### With field help inline +### With field help inline minor + + + + + ### With label inline +### With label inline minor + + + + + ### Allowing deselection By default, in a `ButtonToggleGroup` there is no way to deselect the currently-selected toggle button except by selecting another one. @@ -101,18 +143,36 @@ If you use this, you should include text in the `fieldHelp` prop which makes cle +### Allowing deselection minor + + + + + ### with small icon +### with small icon minor + + + + + ### with large icon +### with large icon minor + + + + + ### Icon only While we do not advocate the use of icon-only buttons for accessibility reasons, the `aria-label` or `aria-labelledby` props can be used with `buttonIcon` so assistive techniques are aware of the action that will be taken when the component pressed. @@ -121,54 +181,108 @@ While we do not advocate the use of icon-only buttons for accessibility reasons, +### Icon only minor + + + + + ### Small +### Small minor + + + + + ### Small with small icon +### Small with small icon minor + + + + + ### Small with large icon +### Small with large icon minor + + + + + ### Large +### Large minor + + + + + ### Large with small icon +### Large with small icon minor + + + + + ### Large with large icon +### Large with large icon minor + + + + + ### Disabled +### Disabled minor + + + + + ### Grouped +### Grouped minor + + + + + ## Props ### Button Toggle diff --git a/src/components/button-toggle/button-toggle.stories.tsx b/src/components/button-toggle/button-toggle.stories.tsx index 29d57c7c82..d20557eff3 100644 --- a/src/components/button-toggle/button-toggle.stories.tsx +++ b/src/components/button-toggle/button-toggle.stories.tsx @@ -2,10 +2,25 @@ import React from "react"; import { ComponentStory } from "@storybook/react"; import { ButtonToggle, ButtonToggleGroup } from "."; -import Box from "../box"; export const Default: ComponentStory = () => ( - + + Foo + Bar + Baz + +); + +export const DefaultMinor: ComponentStory = () => ( + Foo Bar Baz @@ -13,16 +28,31 @@ export const Default: ComponentStory = () => ( ); export const DefaultWrappedText: ComponentStory = () => ( - - - - Some text that wraps - - - FooBar - - - + + + Some text that wraps + + + FooBar + + +); + +export const DefaultWrappedTextMinor: ComponentStory< + typeof ButtonToggle +> = () => ( + + + Some text that wraps + + + FooBar + + ); export const DefaultSmallIcon: ComponentStory = () => ( @@ -39,6 +69,26 @@ export const DefaultSmallIcon: ComponentStory = () => ( ); +export const DefaultSmallIconMinor: ComponentStory< + typeof ButtonToggle +> = () => ( + + + Add + + + Share + + + Tick + + +); + export const DefaultLargeIcon: ComponentStory = () => ( @@ -53,6 +103,26 @@ export const DefaultLargeIcon: ComponentStory = () => ( ); +export const DefaultLargeIconMinor: ComponentStory< + typeof ButtonToggle +> = () => ( + + + Add + + + Share + + + Tick + + +); + export const iconOnly: ComponentStory = () => ( @@ -61,6 +131,18 @@ export const iconOnly: ComponentStory = () => ( ); +export const iconOnlyMinor: ComponentStory = () => ( + + + + + +); + export const small: ComponentStory = () => ( @@ -75,6 +157,24 @@ export const small: ComponentStory = () => ( ); +export const smallMinor: ComponentStory = () => ( + + + Add + + + Share + + + Tick + + +); + export const smallSmallIcon: ComponentStory = () => ( = () => ( ); +export const smallSmallIconMinor: ComponentStory = () => ( + + + Add + + + Share + + + Tick + + +); + export const smallLargeIcon: ComponentStory = () => ( = () => ( ); +export const smallLargeIconMinor: ComponentStory = () => ( + + + Add + + + Share + + + Tick + + +); + export const large: ComponentStory = () => ( @@ -138,6 +289,24 @@ export const large: ComponentStory = () => ( ); +export const largeMinor: ComponentStory = () => ( + + + Add + + + Share + + + Tick + + +); + export const largeSmallIcon: ComponentStory = () => ( = () => ( ); +export const largeSmallIconMinor: ComponentStory = () => ( + + + Add + + + Share + + + Tick + + +); + export const largeLargeIcon: ComponentStory = () => ( = () => ( ); +export const largeLargeIconMinor: ComponentStory = () => ( + + + Add + + + Share + + + Tick + + +); + export const disabled: ComponentStory = () => ( @@ -201,6 +421,24 @@ export const disabled: ComponentStory = () => ( ); +export const disabledMinor: ComponentStory = () => ( + + + Foo + + + Bar + + + Baz + + +); + export const grouped: ComponentStory = () => ( @@ -214,3 +452,21 @@ export const grouped: ComponentStory = () => ( ); + +export const groupedMinor: ComponentStory = () => ( + + + Foo + + + Bar + + + Baz + + +); diff --git a/src/components/button-toggle/button-toggle.style.ts b/src/components/button-toggle/button-toggle.style.ts index 18e4f008dc..1098afac6d 100644 --- a/src/components/button-toggle/button-toggle.style.ts +++ b/src/components/button-toggle/button-toggle.style.ts @@ -3,6 +3,7 @@ import { IconType } from "../icon"; import StyledIcon from "../icon/icon.style"; import addFocusStyling from "../../style/utils/add-focus-styling"; import baseTheme from "../../style/themes/base"; +import { ButtonToggleGroupProps } from "./button-toggle-group"; export type ButtonToggleIconSizes = "small" | "large"; @@ -57,6 +58,7 @@ export interface StyledButtonToggleProps { grouped?: boolean; /** set this to true to allow the button to be deselected when already selected */ allowDeselect?: boolean; + variant?: "default" | "minor"; } const oldFocusStyling = ` @@ -136,7 +138,46 @@ const StyledButtonToggle = styled.button` } } cursor: not-allowed; - `}; + `} + + ${({ variant, disabled }) => + variant === "minor" && + css` + & ${StyledIcon} { + color: var(--colorsActionMinor500); + :not([aria-pressed="true"]):not(:disabled):hover { + color: var(--colorsActionMinorYang100); + } + } + + color: var(--colorsActionMinor500); + border: 1px solid var(--colorsActionMinor500); + + :not([aria-pressed="true"]):not(:disabled):hover { + color: var(--colorsActionMinorYang100); + background-color: var(--colorsActionMinor600); + ${StyledIcon} { + color: var(--colorsActionMinorYang100); + } + } + + ${!disabled && + css` + :hover { + background-color: var(--colorsActionMinor600); + color: var(--colorsActionMinorYang100); + + ${StyledIcon} { + color: var(--colorsActionMinorYang100); + } + } + `} + + &[aria-pressed="true"]:not(:hover) { + box-shadow: inset 0px 0px 0px 3px var(--colorsActionMinor500); + background-color: transparent; + } + `} `; const iconFontSizes = { @@ -176,19 +217,22 @@ StyledButtonToggle.defaultProps = { theme: baseTheme }; export interface StyledButtonToggleWrapperProps { grouped?: boolean; + variant: Required; } const StyledButtonToggleWrapper = styled.div` display: inline-block; vertical-align: middle; - ${({ grouped }) => + ${({ grouped, variant }) => css` ${!grouped && css` &&&& { ${StyledButtonToggle} { - border-radius: var(--borderRadius400); + border-radius: ${variant === "default" + ? "var(--borderRadius400)" + : "var(--borderRadius050)"}; } } `} @@ -198,15 +242,23 @@ const StyledButtonToggleWrapper = styled.div` &&&& { :first-of-type { ${StyledButtonToggle} { - border-top-left-radius: var(--borderRadius400); - border-bottom-left-radius: var(--borderRadius400); + border-top-left-radius: ${variant === "default" + ? "var(--borderRadius400)" + : "var(--borderRadius050)"}; + border-bottom-left-radius: ${variant === "default" + ? "var(--borderRadius400)" + : "var(--borderRadius050)"}; } } :last-of-type { ${StyledButtonToggle} { - border-top-right-radius: var(--borderRadius400); - border-bottom-right-radius: var(--borderRadius400); + border-top-right-radius: ${variant === "default" + ? "var(--borderRadius400)" + : "var(--borderRadius050)"}; + border-bottom-right-radius: ${variant === "default" + ? "var(--borderRadius400)" + : "var(--borderRadius050)"}; } } }