Skip to content

Commit

Permalink
Add Radio group primitive (#68)
Browse files Browse the repository at this point in the history
* Add types changes

* Add Radio Group primitive

* Add Radio Group changes

* Add RadioGroup testing changes

* Fix whitespace changes

* Fix RadioGroup Test cases

* Optimise test
  • Loading branch information
vineethasok authored Jul 25, 2023
1 parent c4ebcc7 commit 643999d
Show file tree
Hide file tree
Showing 11 changed files with 504 additions and 57 deletions.
12 changes: 6 additions & 6 deletions build-tokens.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import _ from "lodash";
import {registerTransforms,transforms} from"@tokens-studio/sd-transforms";
import { registerTransforms, transforms } from "@tokens-studio/sd-transforms";
import StyleDictionary from "style-dictionary";

registerTransforms(StyleDictionary);
const themes = ["classic", "dark", "light"];

function generateThemeFromDictionary (dictionary, valueFunc = (value) => value) {
function generateThemeFromDictionary(dictionary, valueFunc = (value) => value) {
const theme = {};
dictionary.allTokens.forEach((token) => {
_.setWith(theme, token.name, valueFunc(token.value), Object)
Expand All @@ -27,19 +27,19 @@ StyleDictionary.registerTransform({

StyleDictionary.registerFormat({
name: "ThemeFormat",
formatter: function({ dictionary, platform, options, file }) {
formatter: function ({ dictionary, platform, options, file }) {
const theme = generateThemeFromDictionary(dictionary);
return JSON.stringify(theme, null, 2);
}
});

StyleDictionary.registerFormat({
name: "TypescriptFormat",
formatter: function({ dictionary, platform, options, file }) {
formatter: function ({ dictionary, platform, options, file }) {
const theme = generateThemeFromDictionary(dictionary, (value) => typeof value);

return `
export interface Theme ${JSON.stringify(theme, null, 2).replaceAll("\"string\"", "string")}
export interface Theme ${JSON.stringify(theme, null, 2).replaceAll("\"string\"", "string").replaceAll("\"number\"", "number")}
`
}
});
Expand Down
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-separator": "^1.0.3",
Expand Down
43 changes: 43 additions & 0 deletions src/components/RadioGroup/RadioGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { RadioGroup, RadioGroupProps } from "./RadioGroup";

const RadioGroupComponent = (props: RadioGroupProps) => {
return (
<RadioGroup {...props}>
<RadioGroup.Item
label="Radio Button1"
value="RadioButton1"
/>
<RadioGroup.Item
label="Radio Button2"
value="RadioButton2"
/>
<RadioGroup.Item
label="Radio Button3"
value="RadioButton3"
/>
</RadioGroup>
);
};
export default {
component: RadioGroupComponent,
title: "RadioGroup",
tags: ["radio"],
argTypes: {
disabled: { control: "boolean" },
required: { control: "boolean" },
inline: { control: "boolean" },
dir: { control: "inline-radio", options: ["ltr", "rtl"] },
orientation: { control: "inline-radio", options: ["horizontal", "vertical"] },
loop: { control: "inline-radio", options: [undefined, true, false] },
value: {
control: "select",
options: [undefined, "RadioButton1", "RadioButton2", "RadioButton3"],
},
},
};

export const Default = {
args: {
disabled: false,
},
};
59 changes: 59 additions & 0 deletions src/components/RadioGroup/RadioGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ThemeProvider } from "styled-components";
import { themes } from "../../theme";
import { fireEvent, render, waitFor } from "@testing-library/react";
import { RadioGroup } from "@/components";
import { RadioGroupProps } from "./RadioGroup";

describe("RadioGroup", () => {
const renderRadioGroup = (props: RadioGroupProps) =>
render(
<ThemeProvider theme={themes.dark}>
<RadioGroup
inline
{...props}
>
<RadioGroup.Item
id="RadioButton1"
label="Radio Button1"
value="RadioButton1"
/>
<RadioGroup.Item
label="Radio Button2"
value="RadioButton2"
id="RadioButton2"
/>
<RadioGroup.Item
label="Radio Button3"
value="RadioButton3"
id="RadioButton3"
/>
</RadioGroup>
</ThemeProvider>
);

it("should execute action on click", () => {
const handleClick = jest.fn();
const { getByLabelText } = renderRadioGroup({
onValueChange: handleClick,
});
const radio = getByLabelText("Radio Button1");
expect(radio.dataset.state).toBe("unchecked");
waitFor(() => {
fireEvent.click(radio);
expect(radio.dataset.state).toBe("checked");
});
expect(handleClick).toBeCalledTimes(1);
});

it("should not execute action on click if the radio is disabled", () => {
const handleClick = jest.fn();
const { getByLabelText } = renderRadioGroup({
onValueChange: handleClick,
disabled: true,
});
const radio = getByLabelText("Radio Button2");
expect(radio).not.toBeNull();
fireEvent.click(radio);
expect(handleClick).toBeCalledTimes(0);
});
});
147 changes: 147 additions & 0 deletions src/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import * as RadixRadioGroup from "@radix-ui/react-radio-group";
import { HTMLAttributes, ReactNode, useId } from "react";
import styled from "styled-components";

export interface RadioGroupProps extends RadixRadioGroup.RadioGroupProps {
inline?: boolean;
}

const RadioGroupRoot = styled(RadixRadioGroup.Root)<{ inline: "true" | "false" }>`
display: flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.click.checkbox.space.gap};
flex-direction: ${({ inline }) => (inline === "true" ? "row" : "column")};
`;

export const RadioGroup = ({ children, inline, ...props }: RadioGroupProps) => {
return (
<RadioGroupRoot
inline={inline === true ? "true" : "false"}
{...props}
>
{children}
</RadioGroupRoot>
);
};

interface RadioGroupInputProps extends RadixRadioGroup.RadioGroupItemProps {
label?: ReactNode;
}

export type RadioGroupItemProps = RadioGroupInputProps & HTMLAttributes<HTMLDivElement>;

const RadioGroupItem = ({
id,
label,
value,
disabled,
required,
...props
}: RadioGroupItemProps) => {
const defaultId = useId();
return (
<Wrapper {...props}>
<RadioInput
value={value}
id={id ?? defaultId}
disabled={disabled}
required={required}
>
<RadioGroupIndicator />
</RadioInput>
{label && (
<label
className="Label"
htmlFor={id ?? defaultId}
>
{label}
</label>
)}
</Wrapper>
);
};

RadioGroupItem.displayName = "RadioGroupItem";
RadioGroup.Item = RadioGroupItem;

const Wrapper = styled.div`
padding: ${({ theme }) => theme.click.checkbox.space.all};
display: flex;
align-items: center;
gap: ${({ theme }) => theme.click.checkbox.space.gap};
`;

const RadioInput = styled(RadixRadioGroup.Item)`
display: flex;
align-items: center;
justify-content: center;
outline: none;
${({ theme }) => `
border-radius: ${theme.click.radio.radii.all};
width: 1rem;
height: 1rem;
background: ${theme.click.radio.color.background.default};
border: 1px solid ${theme.click.radio.color.stroke.default};
& ~ label {
color: ${theme.click.field.color.genericLabel.default};
font: ${theme.click.radio.typography.label.default};
}
&:hover {
background: ${theme.click.radio.color.background.hover};
& ~ label {
color: ${theme.click.field.color.genericLabel.hover};
font: ${theme.click.radio.typography.label.hover};
}
}
&[data-state="checked"] {
border-color: ${theme.click.radio.color.stroke.active};
background: ${theme.click.radio.color.background.active};
& ~ label {
color: ${theme.click.field.color.genericLabel.active};
font: ${theme.click.radio.typography.label.active};
}
}
&[data-disabled] {
background: ${theme.click.radio.color.background.disabled};
border-color: ${theme.click.radio.color.stroke.disabled};
& ~ label {
color: ${theme.click.field.color.genericLabel.disabled};
font: ${theme.click.radio.typography.label.disabled};
}
}
`};
`;

const RadioGroupIndicator = styled(RadixRadioGroup.Indicator)`
${({ theme }) => `
background: ${theme.click.radio.color.background.default};
&[data-state="checked"] {
background: ${theme.click.radio.color.background.active};
}
&:hover {
background: ${theme.click.radio.color.background.hover};
}
&[data-disabled] {
background: ${theme.click.radio.color.background.disabled};
}
&::after {
content: '';
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: ${theme.click.radio.color.indicator.default};
&:hover {
background: ${theme.click.radio.color.indicator.hover};
}
&[data-state="checked"] {
background: ${theme.click.radio.color.indicator.active};
}
&[data-disabled] {
background: ${theme.click.radio.color.indicator.disabled};
}
}
`}
`;
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { Alert, DangerAlert, InfoAlert, WarningAlert, SuccessAlert } from "./Ale
export { Checkbox } from "./Checkbox/Checkbox";
export { IconButton } from "./IconButton/IconButton";
export { UserIcon as ProfileIcon } from "./icons/UserIcon";
export { RadioGroup } from "./RadioGroup/RadioGroup";
export { Switch } from "./Switch/Switch";
export { SidebarNavigationItem } from "./SidebarNavigationItem/SidebarNavigationItem";
export { Label } from "./FormField/Label";
Expand Down
Loading

1 comment on commit 643999d

@vercel
Copy link

@vercel vercel bot commented on 643999d Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

click-ui – ./

click-ui.vercel.app
click-ui-git-main-clickhouse.vercel.app
click-ui-clickhouse.vercel.app

Please sign in to comment.