chore: Add the ability to use icons in WdsButton (#23014)
Adds the ability to pass icon in button component and adds button group to the storybook ## Description Fixes #21924 Fixes #21926 Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video ## Type of change - Bug fix ## How Has This Been Tested? - Manual ### Test Plan > Add Testsmith test cases links that relate to this PR ### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) ## Checklist: ### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag ### QA activity: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
parent
2a14687f35
commit
526a358329
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import { Button } from "./";
|
||||||
|
|
||||||
|
<Meta
|
||||||
|
title="Design-system/headless/Button"
|
||||||
|
component={Button}
|
||||||
|
args={{
|
||||||
|
children: "Button",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
export const Template = (args) => <Button {...args} />;
|
||||||
|
|
||||||
|
# Button
|
||||||
|
|
||||||
|
A button is a clickable element that is used to trigger an action.
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story name="Button">{Template.bind({})}</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
<ArgsTable story="Button" of={Button} />
|
||||||
|
|
@ -6,9 +6,14 @@ import { useFocusRing } from "@react-aria/focus";
|
||||||
import { useHover } from "@react-aria/interactions";
|
import { useHover } from "@react-aria/interactions";
|
||||||
import { useFocusableRef } from "@react-spectrum/utils";
|
import { useFocusableRef } from "@react-spectrum/utils";
|
||||||
import type { FocusableRef } from "@react-types/shared";
|
import type { FocusableRef } from "@react-types/shared";
|
||||||
import type { ButtonProps as SpectrumButtonProps } from "@react-types/button";
|
import type {
|
||||||
|
AriaButtonProps as SpectrumAriaBaseButtonProps,
|
||||||
|
ButtonProps as SpectrumButtonProps,
|
||||||
|
} from "@react-types/button";
|
||||||
|
|
||||||
export interface ButtonProps extends SpectrumButtonProps {
|
export interface ButtonProps
|
||||||
|
extends SpectrumButtonProps,
|
||||||
|
SpectrumAriaBaseButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from "react";
|
||||||
|
import type { AriaLabelingProps, DOMProps } from "@react-types/shared";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
import { filterDOMProps } from "@react-aria/utils";
|
||||||
|
|
||||||
|
export interface IconProps extends DOMProps, AriaLabelingProps {
|
||||||
|
"aria-label"?: string;
|
||||||
|
children: ReactElement;
|
||||||
|
"aria-hidden"?: boolean | "false" | "true";
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Icon(props: IconProps) {
|
||||||
|
const {
|
||||||
|
"aria-hidden": ariaHiddenProp,
|
||||||
|
"aria-label": ariaLabel,
|
||||||
|
children,
|
||||||
|
role = "img",
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const ariaHidden = !ariaHiddenProp ? undefined : ariaHiddenProp;
|
||||||
|
|
||||||
|
return React.cloneElement(children, {
|
||||||
|
...filterDOMProps(otherProps),
|
||||||
|
focusable: "false",
|
||||||
|
"aria-label": ariaLabel,
|
||||||
|
"aria-hidden": ariaLabel ? ariaHidden || undefined : true,
|
||||||
|
role,
|
||||||
|
"data-icon": "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { Icon } from "./Icon";
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
export * from "./components/Button";
|
export * from "./components/Button";
|
||||||
export * from "./components/Checkbox";
|
export * from "./components/Checkbox";
|
||||||
export * from "./components/Field";
|
export * from "./components/Field";
|
||||||
|
export * from "./components/Icon";
|
||||||
|
|
|
||||||
5
app/client/packages/design-system/widgets/jest.config.js
Normal file
5
app/client/packages/design-system/widgets/jest.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
roots: ["<rootDir>/src"],
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
};
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:ci": "eslint --cache .",
|
"lint:ci": "eslint --cache .",
|
||||||
"prettier:ci": "prettier --check ."
|
"prettier:ci": "prettier --check .",
|
||||||
|
"test:unit": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capsizecss/core": "^3.1.0",
|
"@capsizecss/core": "^3.1.0",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||||
|
|
||||||
import { Button } from "./";
|
import { Button } from "./";
|
||||||
|
import { Icon } from "@design-system/headless";
|
||||||
|
import EmotionHappyLineIcon from "remixicon-react/EmotionHappyLineIcon";
|
||||||
|
|
||||||
<Meta
|
<Meta
|
||||||
title="Design-system/widgets/Button"
|
title="Design-system/widgets/Button"
|
||||||
|
|
@ -78,3 +81,53 @@ There are 3 variants of the button component.
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
# With Icon
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="With Icon"
|
||||||
|
args={{
|
||||||
|
children: "With Icon",
|
||||||
|
icon: (
|
||||||
|
<Icon>
|
||||||
|
<EmotionHappyLineIcon />
|
||||||
|
</Icon>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
# Icon Position
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Icon Position - Start"
|
||||||
|
args={{
|
||||||
|
children: "Icon Start",
|
||||||
|
icon: (
|
||||||
|
<Icon>
|
||||||
|
<EmotionHappyLineIcon />
|
||||||
|
</Icon>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Icon Position - End"
|
||||||
|
args={{
|
||||||
|
children: "Icon End",
|
||||||
|
icon: (
|
||||||
|
<Icon>
|
||||||
|
<EmotionHappyLineIcon />
|
||||||
|
</Icon>
|
||||||
|
),
|
||||||
|
iconPosition: "end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { Icon } from "@design-system/headless";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import EmotionHappyLineIcon from "remixicon-react/EmotionHappyLineIcon";
|
||||||
|
|
||||||
|
import { Button } from "./";
|
||||||
|
|
||||||
|
describe("@design-system/widgets/Button", () => {
|
||||||
|
it("renders children when passed", () => {
|
||||||
|
render(<Button>Click me</Button>);
|
||||||
|
expect(screen.getByRole("button")).toHaveTextContent("Click me");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes type to button component", () => {
|
||||||
|
render(<Button type="submit" />);
|
||||||
|
expect(screen.getByRole("button")).toHaveAttribute("type", "submit");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets variant based on prop", () => {
|
||||||
|
render(<Button variant="primary" />);
|
||||||
|
expect(screen.getByRole("button")).toHaveAttribute(
|
||||||
|
"data-variant",
|
||||||
|
"primary",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets disabled attribute based on prop", () => {
|
||||||
|
render(<Button isDisabled />);
|
||||||
|
expect(screen.getByRole("button")).toBeDisabled();
|
||||||
|
expect(screen.getByRole("button")).toHaveAttribute("data-disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets data-loading attribute and icon based on loading prop", () => {
|
||||||
|
render(<Button isLoading />);
|
||||||
|
expect(screen.getByRole("button")).toHaveAttribute("data-loading");
|
||||||
|
|
||||||
|
const icon = screen.getByRole("button").querySelector("[data-icon]");
|
||||||
|
expect(icon).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders icon when passed", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Button
|
||||||
|
icon={
|
||||||
|
<Icon>
|
||||||
|
<EmotionHappyLineIcon />
|
||||||
|
</Icon>
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const icon = container.querySelector("button [data-icon]") as HTMLElement;
|
||||||
|
expect(icon).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets icon position attribute based on the prop ", () => {
|
||||||
|
const { container } = render(<Button iconPosition="end" />);
|
||||||
|
|
||||||
|
const button = container.querySelector("button") as HTMLElement;
|
||||||
|
expect(button).toHaveAttribute("data-icon-position", "end");
|
||||||
|
|
||||||
|
const styles = window.getComputedStyle(button);
|
||||||
|
expect(styles.flexDirection).toBe("row-reverse");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
|
import { Icon as HeadlessIcon } from "@design-system/headless";
|
||||||
import type {
|
import type {
|
||||||
ButtonProps as HeadlessButtonProps,
|
ButtonProps as HeadlessButtonProps,
|
||||||
ButtonRef as HeadlessButtonRef,
|
ButtonRef as HeadlessButtonRef,
|
||||||
|
|
@ -20,6 +21,7 @@ export interface ButtonProps extends Omit<HeadlessButtonProps, "className"> {
|
||||||
fontFamily?: fontFamilyTypes;
|
fontFamily?: fontFamilyTypes;
|
||||||
isFitContainer?: boolean;
|
isFitContainer?: boolean;
|
||||||
isFocused?: boolean;
|
isFocused?: boolean;
|
||||||
|
icon?: React.ReactNode;
|
||||||
iconPosition?: "start" | "end";
|
iconPosition?: "start" | "end";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,6 +30,8 @@ export const Button = forwardRef(
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
|
icon,
|
||||||
|
iconPosition = "start",
|
||||||
isFitContainer = false,
|
isFitContainer = false,
|
||||||
isLoading,
|
isLoading,
|
||||||
// eslint-disable-next-line -- TODO add onKeyUp when the bug is fixedhttps://github.com/adobe/react-spectrum/issues/4350
|
// eslint-disable-next-line -- TODO add onKeyUp when the bug is fixedhttps://github.com/adobe/react-spectrum/issues/4350
|
||||||
|
|
@ -38,19 +42,28 @@ export const Button = forwardRef(
|
||||||
|
|
||||||
const renderChildren = () => {
|
const renderChildren = () => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Spinner />;
|
return (
|
||||||
|
<HeadlessIcon>
|
||||||
|
<Spinner />
|
||||||
|
</HeadlessIcon>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{icon}
|
||||||
<Text fontFamily={fontFamily} lineClamp={1}>
|
<Text fontFamily={fontFamily} lineClamp={1}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButton
|
<StyledButton
|
||||||
|
data-button=""
|
||||||
data-fit-container={isFitContainer ? "" : undefined}
|
data-fit-container={isFitContainer ? "" : undefined}
|
||||||
|
data-icon-position={iconPosition === "start" ? undefined : "end"}
|
||||||
data-loading={isLoading ? "" : undefined}
|
data-loading={isLoading ? "" : undefined}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export const StyledButton = styled(HeadlessButton)<ButtonProps>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
gap: var(--spacing-4);
|
gap: var(--spacing-1);
|
||||||
padding: var(--spacing-2) var(--spacing-4);
|
padding: var(--spacing-2) var(--spacing-4);
|
||||||
height: calc(var(--sizing-root-unit) * 8);
|
height: calc(var(--sizing-root-unit) * 8);
|
||||||
border-radius: var(--border-radius-1);
|
border-radius: var(--border-radius-1);
|
||||||
|
|
@ -76,4 +76,13 @@ export const StyledButton = styled(HeadlessButton)<ButtonProps>`
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: var(--opacity-disabled);
|
opacity: var(--opacity-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& [data-icon] {
|
||||||
|
height: calc(var(--sizing-root-unit) * 5);
|
||||||
|
width: calc(var(--sizing-root-unit) * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-icon-position="end"] {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import { Button } from "../Button";
|
||||||
|
import { ButtonGroup } from "./";
|
||||||
|
|
||||||
|
<Meta
|
||||||
|
title="Design-system/widgets/Button Group"
|
||||||
|
component={ButtonGroup}
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button>Option 1</Button>
|
||||||
|
<Button>Option 2</Button>
|
||||||
|
<Button>Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
export const Template = (args) => <ButtonGroup {...args} />;
|
||||||
|
|
||||||
|
## Button Group
|
||||||
|
|
||||||
|
A button group is a group of buttons that are visually connected together.
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story name="Button Group">{Template.bind({})}</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
<ArgsTable story="Button Group" of={ButtonGroup} />
|
||||||
|
|
||||||
|
# Variants
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Button Group - Primary"
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="primary">Option 1</Button>
|
||||||
|
<Button variant="primary">Option 2</Button>
|
||||||
|
<Button variant="primary">Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Button Group - Secondary"
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="secondary">Option 1</Button>
|
||||||
|
<Button variant="secondary">Option 2</Button>
|
||||||
|
<Button variant="secondary">Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Button Group - Tertiary"
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="tertiary">Option 1</Button>
|
||||||
|
<Button variant="tertiary">Option 2</Button>
|
||||||
|
<Button variant="tertiary">Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
# Orientation
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Button Group - Vertical Primary"
|
||||||
|
args={{
|
||||||
|
orientation: "vertical",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="primary">Option 2</Button>
|
||||||
|
<Button variant="primary">Option 2</Button>
|
||||||
|
<Button variant="primary">Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Button Group - Vertical Secondary"
|
||||||
|
args={{
|
||||||
|
orientation: "vertical",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="secondary">Option 2</Button>
|
||||||
|
<Button variant="secondary">Option 2</Button>
|
||||||
|
<Button variant="secondary">Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Button Group - Vertical Tertiary"
|
||||||
|
args={{
|
||||||
|
orientation: "vertical",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="tertiary">Option 2</Button>
|
||||||
|
<Button variant="tertiary">Option 2</Button>
|
||||||
|
<Button variant="tertiary">Option 3</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
# Disabled
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Button Group Primary Disabled"
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="primary" isDisabled>
|
||||||
|
Option 2
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" isDisabled>
|
||||||
|
Option 2
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" isDisabled>
|
||||||
|
Option 3
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Button Group Secondary Disabled"
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="secondary" isDisabled>
|
||||||
|
Option 2
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" isDisabled>
|
||||||
|
Option 2
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" isDisabled>
|
||||||
|
Option 3
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
<Story
|
||||||
|
name="Button Group Tertiary Disabled"
|
||||||
|
args={{
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Button variant="tertiary" isDisabled>
|
||||||
|
Option 2
|
||||||
|
</Button>
|
||||||
|
<Button variant="tertiary" isDisabled>
|
||||||
|
Option 2
|
||||||
|
</Button>
|
||||||
|
<Button variant="tertiary" isDisabled>
|
||||||
|
Option 3
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
@ -3,11 +3,12 @@ import React, { forwardRef } from "react";
|
||||||
import { StyledContainer } from "./index.styled";
|
import { StyledContainer } from "./index.styled";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
export enum Orientation {
|
export const ORIENTATION = {
|
||||||
VERTICAL = "vertical",
|
VERTICAL: "vertical",
|
||||||
HORIZONTAL = "horizontal",
|
HORIZONTAL: "horizontal",
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
|
type Orientation = (typeof ORIENTATION)[keyof typeof ORIENTATION];
|
||||||
export interface ButtonGroupProps
|
export interface ButtonGroupProps
|
||||||
extends React.ComponentPropsWithoutRef<"div"> {
|
extends React.ComponentPropsWithoutRef<"div"> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -17,8 +18,17 @@ export interface ButtonGroupProps
|
||||||
// component
|
// component
|
||||||
export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
|
export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { orientation = Orientation.HORIZONTAL, ...others } = props;
|
const { orientation = ORIENTATION.HORIZONTAL, ...others } = props;
|
||||||
return <StyledContainer orientation={orientation} ref={ref} {...others} />;
|
|
||||||
|
return (
|
||||||
|
<StyledContainer
|
||||||
|
data-orientation={
|
||||||
|
orientation === ORIENTATION.VERTICAL ? "vertical" : undefined
|
||||||
|
}
|
||||||
|
ref={ref}
|
||||||
|
{...others}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,16 @@ import styled from "styled-components";
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
import type { ButtonGroupProps } from "./ButtonGroup";
|
||||||
|
|
||||||
export const StyledContainer = styled.div<ButtonGroupProps>`
|
export const StyledContainer = styled.div<ButtonGroupProps>`
|
||||||
--border-width: 1px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: ${({ orientation }) =>
|
flex-direction: row;
|
||||||
orientation === "vertical" ? "column" : "row"};
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&[data-orientation="vertical"] {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
& [data-button] {
|
& [data-button] {
|
||||||
// increasing z index to make sure the focused button is on top of the others
|
// increasing z index to make sure the focused button is on top of the others
|
||||||
|
|
@ -17,48 +20,65 @@ export const StyledContainer = styled.div<ButtonGroupProps>`
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:is([data-variant="filled"]):not([data-disabled]) {
|
|
||||||
border-color: var(--wds-vs-color-border-onaccent);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:is([data-variant="light"]):not([data-disabled]) {
|
|
||||||
border-color: var(--wds-vs-color-border-onaccent-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
${({ orientation }) =>
|
|
||||||
orientation === "vertical"
|
|
||||||
? "border-bottom-left-radius: 0; border-bottom-width: calc(var(--border-width) / 2);"
|
|
||||||
: "border-top-right-radius: 0; border-right-width: calc(var(--border-width) / 2);"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
${({ orientation }) =>
|
|
||||||
orientation === "vertical"
|
|
||||||
? "border-top-right-radius: 0; border-top-width: calc(var(--border-width) / 2);"
|
|
||||||
: "border-bottom-left-radius: 0; border-left-width: calc(var(--border-width) / 2);"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:first-child):not(:last-of-type) {
|
&:not(:first-child):not(:last-of-type) {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
${({ orientation }) =>
|
&:not([data-orientation="vertical"]) [data-button] {
|
||||||
orientation === "vertical"
|
&:first-child {
|
||||||
? "border-top-width: calc(var(--border-width) / 2); border-bottom-width: calc(var(--border-width) / 2);"
|
border-top-right-radius: 0;
|
||||||
: "border-left-width: calc(var(--border-width) / 2); border-right-width: calc(var(--border-width) / 2);"}
|
border-right-width: calc(var(--border-width-1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-left-width: calc(var(--border-width-1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child):not(:last-of-type) {
|
||||||
|
border-left-width: calc(var(--border-width-1) / 2);
|
||||||
|
border-right-width: calc(var(--border-width-1) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
& + [data-button] {
|
& + [data-button] {
|
||||||
${({ orientation }) =>
|
margin-left: calc(var(--border-width-1) * -1);
|
||||||
orientation === "vertical"
|
|
||||||
? "margin-top: calc(var(--border-width) * -1);"
|
|
||||||
: "margin-left: calc(var(--border-width) * -1);"}
|
|
||||||
|
|
||||||
@media (min-resolution: 192dpi) {
|
@media (min-resolution: 192dpi) {
|
||||||
${({ orientation }) =>
|
margin-left: 0px;
|
||||||
orientation === "vertical" ? "margin-top: 0px;" : "margin-left: 0px;"}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-orientation="vertical"] [data-button] {
|
||||||
|
&:first-child {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-width: calc(var(--border-width-1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-top-width: calc(var(--border-width-1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child):not(:last-of-type) {
|
||||||
|
border-top-width: calc(var(--border-width-1) / 2);
|
||||||
|
border-bottom-width: calc(var(--border-width-1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
& + [data-button] {
|
||||||
|
margin-top: calc(var(--border-width-1) * -1);
|
||||||
|
|
||||||
|
@media (min-resolution: 192dpi) {
|
||||||
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ async function webpackConfig(config) {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
stories: [
|
stories: [
|
||||||
"../../design-system/widgets/src/**/*.stories.mdx",
|
"../../design-system/widgets/src/**/*.stories.mdx",
|
||||||
|
"../../design-system/headless/src/**/*.stories.mdx",
|
||||||
"../../design-system/widgets/src/**/*.stories.@(js|jsx|ts|tsx)",
|
"../../design-system/widgets/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||||
|
"../../design-system/headless/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||||
],
|
],
|
||||||
addons: [
|
addons: [
|
||||||
"@storybook/addon-links",
|
"@storybook/addon-links",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user