Skip to content

Commit b60a7cd

Browse files
authored
Merge pull request #2072 from cprussin/form-components
feat(component-library): add first round of form components
2 parents a43818d + 2ac2b4f commit b60a7cd

File tree

11 files changed

+416
-4
lines changed

11 files changed

+416
-4
lines changed

packages/component-library/.storybook/preview.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ export const decorators: Decorator[] = [
3838
withRootClasses("font-sans antialiased", sans.variable),
3939
withThemeByClassName({
4040
themes: {
41-
white: "light bg-white",
42-
light: "light bg-beige-50",
43-
dark: "dark bg-steel-800",
44-
darker: "dark bg-steel-900",
41+
white: "light bg-white text-steel-900",
42+
light: "light bg-beige-100 text-steel-900",
43+
dark: "dark bg-steel-800 text-steel-50",
44+
darker: "dark bg-steel-900 text-steel-50",
4545
},
4646
defaultTheme: "light",
4747
}),
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
3+
import { Checkbox as CheckboxComponent } from "./index.js";
4+
5+
const meta = {
6+
component: CheckboxComponent,
7+
argTypes: {
8+
children: {
9+
control: "text",
10+
table: {
11+
category: "Contents",
12+
},
13+
},
14+
isDisabled: {
15+
control: "boolean",
16+
table: {
17+
category: "State",
18+
},
19+
},
20+
},
21+
decorators: [
22+
(Story) => (
23+
<div className="max-w-sm">
24+
<Story />
25+
</div>
26+
),
27+
],
28+
} satisfies Meta<typeof CheckboxComponent>;
29+
export default meta;
30+
31+
export const Checkbox = {
32+
args: {
33+
children:
34+
"By clicking here you agree that this is a checkbox and it's super duper checkboxy",
35+
isDisabled: false,
36+
},
37+
} satisfies StoryObj<typeof CheckboxComponent>;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import clsx from "clsx";
2+
import type { ComponentProps } from "react";
3+
import { Checkbox as BaseCheckbox } from "react-aria-components";
4+
5+
export const Checkbox = ({
6+
children,
7+
className,
8+
...props
9+
}: ComponentProps<typeof BaseCheckbox>) => (
10+
<BaseCheckbox
11+
className={clsx(
12+
"group/checkbox inline-flex cursor-pointer flex-row gap-2 py-1 text-sm data-[disabled]:cursor-not-allowed",
13+
className,
14+
)}
15+
{...props}
16+
>
17+
{(args) => (
18+
<>
19+
<div className="relative top-[0.0625rem] mx-1 size-4 flex-none">
20+
<div className="size-full rounded border border-stone-300 bg-white outline-4 outline-violet-500/40 transition duration-100 group-data-[hovered]/checkbox:border-2 group-data-[disabled]/checkbox:border-none group-data-[hovered]/checkbox:border-stone-400 group-data-[pressed]/checkbox:border-stone-500 group-data-[disabled]/checkbox:bg-stone-200 group-data-[focus-visible]/checkbox:outline dark:border-steel-700 dark:bg-steel-800 dark:group-data-[hovered]/checkbox:border-steel-600 dark:group-data-[pressed]/checkbox:border-steel-500 dark:group-data-[disabled]/checkbox:bg-steel-600" />
21+
<div className="absolute inset-0 grid place-content-center rounded bg-violet-500 stroke-white p-1 opacity-0 transition duration-100 group-data-[disabled]/checkbox:bg-transparent group-data-[disabled]/checkbox:stroke-stone-400 group-data-[selected]/checkbox:opacity-100 dark:bg-violet-600 dark:stroke-steel-950 dark:group-data-[disabled]/checkbox:stroke-steel-400">
22+
<svg
23+
className="w-full"
24+
viewBox="0 0 8 6"
25+
fill="none"
26+
xmlns="http://www.w3.org/2000/svg"
27+
>
28+
<path
29+
d="M1 3L2.76471 5L7 1"
30+
strokeWidth="2"
31+
strokeLinecap="round"
32+
strokeLinejoin="round"
33+
/>
34+
</svg>
35+
</div>
36+
<div className="pointer-events-none absolute -inset-1.5 -z-10 rounded-full bg-black/20 opacity-0 transition duration-100 group-data-[focus-visible]/checkbox:opacity-0 group-data-[hovered]/checkbox:opacity-50 group-data-[pressed]/checkbox:opacity-100 dark:bg-white/20" />
37+
</div>
38+
{typeof children === "function" ? children(args) : children}
39+
</>
40+
)}
41+
</BaseCheckbox>
42+
);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
3+
import {
4+
ORIENTATIONS,
5+
CheckboxGroup as CheckboxGroupComponent,
6+
} from "./index.js";
7+
import { Checkbox } from "../Checkbox/index.js";
8+
9+
const meta = {
10+
component: CheckboxGroupComponent,
11+
argTypes: {
12+
label: {
13+
control: "text",
14+
table: {
15+
category: "Contents",
16+
},
17+
},
18+
description: {
19+
control: "text",
20+
table: {
21+
category: "Contents",
22+
},
23+
},
24+
isDisabled: {
25+
control: "boolean",
26+
table: {
27+
category: "State",
28+
},
29+
},
30+
orientation: {
31+
control: "inline-radio",
32+
options: ORIENTATIONS,
33+
table: {
34+
category: "Layout",
35+
},
36+
},
37+
},
38+
decorators: [
39+
(Story) => (
40+
<div className="max-w-sm">
41+
<Story />
42+
</div>
43+
),
44+
],
45+
render: (args) => (
46+
<CheckboxGroupComponent {...args}>
47+
<Checkbox value="one">
48+
{
49+
"By clicking here you agree that this is a checkbox and it's super duper checkboxy"
50+
}
51+
</Checkbox>
52+
<Checkbox value="two">Second</Checkbox>
53+
<Checkbox value="three">Third</Checkbox>
54+
</CheckboxGroupComponent>
55+
),
56+
} satisfies Meta<typeof CheckboxGroupComponent>;
57+
export default meta;
58+
59+
export const CheckboxGroup = {
60+
args: {
61+
label: "This is a checkbox group!",
62+
description: "",
63+
isDisabled: false,
64+
orientation: "vertical",
65+
},
66+
} satisfies StoryObj<typeof CheckboxGroupComponent>;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import clsx from "clsx";
2+
import type { ComponentProps } from "react";
3+
import {
4+
CheckboxGroup as BaseCheckboxGroup,
5+
Label,
6+
Text,
7+
} from "react-aria-components";
8+
9+
export const ORIENTATIONS = ["vertical", "horizontal"] as const;
10+
11+
type CheckboxGroupProps = ComponentProps<typeof BaseCheckboxGroup> & {
12+
label: ComponentProps<typeof Label>["children"];
13+
description?: ComponentProps<typeof Text>["children"] | undefined;
14+
orientation?: (typeof ORIENTATIONS)[number] | undefined;
15+
};
16+
17+
export const CheckboxGroup = ({
18+
children,
19+
className,
20+
label,
21+
description,
22+
orientation = "vertical",
23+
...props
24+
}: CheckboxGroupProps) => (
25+
<BaseCheckboxGroup
26+
data-orientation={orientation}
27+
className={clsx("group/checkbox-group", className)}
28+
{...props}
29+
>
30+
{(args) => (
31+
<>
32+
<Label className="mb-1 text-sm font-medium">{label}</Label>
33+
<div className="flex group-data-[orientation=horizontal]/checkbox-group:flex-row group-data-[orientation=vertical]/checkbox-group:flex-col group-data-[orientation=horizontal]/checkbox-group:gap-6">
34+
{typeof children === "function" ? children(args) : children}
35+
</div>
36+
{description && description !== "" && (
37+
<Text slot="description" className="text-xs font-light">
38+
{description}
39+
</Text>
40+
)}
41+
</>
42+
)}
43+
</BaseCheckboxGroup>
44+
);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
3+
import { Link as LinkComponent } from "./index.js";
4+
5+
const meta = {
6+
component: LinkComponent,
7+
argTypes: {
8+
children: {
9+
control: "text",
10+
table: {
11+
category: "Contents",
12+
},
13+
},
14+
href: {
15+
control: "text",
16+
table: {
17+
category: "Link",
18+
},
19+
},
20+
target: {
21+
control: "text",
22+
table: {
23+
category: "Link",
24+
},
25+
},
26+
isDisabled: {
27+
control: "boolean",
28+
table: {
29+
category: "State",
30+
},
31+
},
32+
},
33+
} satisfies Meta<typeof LinkComponent>;
34+
export default meta;
35+
36+
export const Link = {
37+
args: {
38+
children: "Link",
39+
href: "https://www.pyth.network",
40+
target: "_blank",
41+
isDisabled: false,
42+
},
43+
} satisfies StoryObj<typeof LinkComponent>;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import clsx from "clsx";
2+
import type { ComponentProps } from "react";
3+
import { Link as BaseLink } from "react-aria-components";
4+
5+
export const Link = ({
6+
className,
7+
...props
8+
}: ComponentProps<typeof BaseLink>) => (
9+
<BaseLink
10+
className={clsx(
11+
"underline outline-0 outline-offset-4 outline-inherit data-[disabled]:cursor-not-allowed data-[disabled]:text-stone-400 data-[disabled]:no-underline data-[focus-visible]:outline-2 hover:no-underline dark:data-[disabled]:text-steel-400",
12+
className,
13+
)}
14+
{...props}
15+
/>
16+
);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { RadioGroup } from "react-aria-components";
3+
4+
import { Radio as RadioComponent } from "./index.js";
5+
6+
const meta = {
7+
component: RadioComponent,
8+
argTypes: {
9+
children: {
10+
control: "text",
11+
table: {
12+
category: "Contents",
13+
},
14+
},
15+
isDisabled: {
16+
control: "boolean",
17+
table: {
18+
category: "State",
19+
},
20+
},
21+
},
22+
decorators: [
23+
(Story) => (
24+
<RadioGroup className="max-w-sm">
25+
<Story />
26+
</RadioGroup>
27+
),
28+
],
29+
} satisfies Meta<typeof RadioComponent>;
30+
export default meta;
31+
32+
export const Radio = {
33+
args: {
34+
children:
35+
"This is a radio button, check out how radioish it is and how it handles multiline labels",
36+
isDisabled: false,
37+
},
38+
} satisfies StoryObj<typeof RadioComponent>;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import clsx from "clsx";
2+
import type { ComponentProps } from "react";
3+
import { Radio as BaseRadio } from "react-aria-components";
4+
5+
export const Radio = ({
6+
children,
7+
className,
8+
...props
9+
}: ComponentProps<typeof BaseRadio>) => (
10+
<BaseRadio
11+
className={clsx(
12+
"group/radio inline-flex cursor-pointer flex-row gap-2 py-1 text-sm data-[disabled]:cursor-not-allowed data-[selected]:cursor-default",
13+
className,
14+
)}
15+
{...props}
16+
>
17+
{(args) => (
18+
<>
19+
<div className="relative top-[0.0625rem] mx-1 size-4 flex-none">
20+
<div className="size-full rounded-full border border-stone-300 bg-white outline-4 outline-violet-500/40 transition duration-100 group-data-[hovered]/radio:border-2 group-data-[disabled]/radio:border-none group-data-[hovered]/radio:border-stone-400 group-data-[pressed]/radio:border-stone-500 group-data-[disabled]/radio:bg-stone-200 group-data-[focus-visible]/radio:outline dark:border-steel-700 dark:bg-steel-800 dark:group-data-[hovered]/radio:border-steel-600 dark:group-data-[pressed]/radio:border-steel-500 dark:group-data-[disabled]/radio:bg-steel-600" />
21+
<div className="absolute inset-0 rounded-full border-[0.3rem] border-violet-500 bg-white opacity-0 transition duration-100 group-data-[disabled]/radio:border-transparent group-data-[disabled]/radio:bg-stone-400 group-data-[selected]/radio:opacity-100 dark:border-violet-600 dark:bg-steel-950 dark:group-data-[disabled]/radio:bg-steel-400" />
22+
<div className="pointer-events-none absolute -inset-1.5 -z-10 rounded-full bg-black/20 opacity-0 transition duration-100 group-data-[focus-visible]/radio:opacity-0 group-data-[hovered]/radio:opacity-50 group-data-[pressed]/radio:opacity-100 group-data-[selected]/radio:opacity-0 dark:bg-white/20" />
23+
</div>
24+
{typeof children === "function" ? children(args) : children}
25+
</>
26+
)}
27+
</BaseRadio>
28+
);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
3+
import { RadioGroup as RadioGroupComponent } from "./index.js";
4+
import { Radio } from "../Radio/index.js";
5+
6+
const meta = {
7+
component: RadioGroupComponent,
8+
argTypes: {
9+
label: {
10+
control: "text",
11+
table: {
12+
category: "Contents",
13+
},
14+
},
15+
description: {
16+
control: "text",
17+
table: {
18+
category: "Contents",
19+
},
20+
},
21+
isDisabled: {
22+
control: "boolean",
23+
table: {
24+
category: "State",
25+
},
26+
},
27+
orientation: {
28+
control: "inline-radio",
29+
options: ["vertical", "horizontal"],
30+
table: {
31+
category: "Layout",
32+
},
33+
},
34+
},
35+
decorators: [
36+
(Story) => (
37+
<div className="max-w-sm">
38+
<Story />
39+
</div>
40+
),
41+
],
42+
render: (args) => (
43+
<RadioGroupComponent {...args}>
44+
<Radio value="one">
45+
This is a radio button, check out how radioish it is and how it handles
46+
multiline labels
47+
</Radio>
48+
<Radio value="two">Second</Radio>
49+
<Radio value="three">Third</Radio>
50+
</RadioGroupComponent>
51+
),
52+
} satisfies Meta<typeof RadioGroupComponent>;
53+
export default meta;
54+
55+
export const RadioGroup = {
56+
args: {
57+
label: "This is a radio group!",
58+
description: "",
59+
isDisabled: false,
60+
orientation: "vertical",
61+
},
62+
} satisfies StoryObj<typeof RadioGroupComponent>;

0 commit comments

Comments
 (0)