feat(ui): add fieldset component

This commit is contained in:
Brian McGee
2025-06-26 17:54:26 +01:00
parent b2a1c8cf6a
commit dffbc28723
8 changed files with 216 additions and 5 deletions

View File

@@ -2,7 +2,7 @@ import meta, { Story } from "./TextField.stories";
const checkboxMeta = {
...meta,
title: "Components/Form/Checkbox",
title: "Components/Form/Fields/Checkbox",
};
export default checkboxMeta;

View File

@@ -17,7 +17,7 @@ import { Tooltip as KTooltip } from "@kobalte/core/tooltip";
import "./Field.css";
type Size = "default" | "s";
type Orientation = "horizontal" | "vertical";
export type Orientation = "horizontal" | "vertical";
type FieldType = "text" | "textarea" | "checkbox";
export interface TextFieldProps {

View File

@@ -0,0 +1,22 @@
fieldset {
@apply flex flex-col w-full;
legend {
@apply mb-2.5 w-full;
}
div.fields {
@apply flex flex-col gap-4 w-full rounded-md;
@apply px-4 py-5 bg-def-2;
}
div.error {
@apply w-full;
}
&.inverted {
div.fields {
@apply bg-inv-2;
}
}
}

View File

@@ -0,0 +1,119 @@
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { Fieldset, FieldsetProps } from "@/src/components/v2/Form/Fieldset";
import cx from "classnames";
const FieldsetExamples = (props: FieldsetProps) => (
<div class="flex flex-col gap-8">
<Fieldset {...props} />
<Fieldset {...props} inverted={true} />
</div>
);
const meta = {
title: "Components/Form/Fieldset",
component: FieldsetExamples,
decorators: [
(Story: StoryObj, context: StoryContext<FieldsetProps>) => {
return (
<div
class={cx({
"w-[600px]": (context.args.orientation || "vertical") == "vertical",
"w-[1024px]": context.args.orientation == "horizontal",
"bg-inv-acc-3": context.args.inverted,
})}
>
<Story />
</div>
);
},
],
} satisfies Meta<FieldsetProps>;
export default meta;
export type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
legend: "Signup",
fields: [
{
type: "text",
label: "First Name",
required: true,
control: { placeholder: "Ron" },
},
{
type: "text",
label: "Last Name",
required: true,
control: { placeholder: "Burgundy" },
},
{
type: "textarea",
label: "Bio",
control: { placeholder: "Tell us a bit about yourself", rows: 8 },
},
{
type: "checkbox",
label: "Accept Terms & Conditions",
required: true,
},
],
},
};
export const Horizontal: Story = {
args: {
...Default.args,
orientation: "horizontal",
},
};
export const Vertical: Story = {
args: {
...Default.args,
orientation: "vertical",
},
};
export const Disabled: Story = {
args: {
...Default.args,
disabled: true,
},
};
export const Error: Story = {
args: {
legend: "Signup",
error: "You must enter a First Name",
fields: [
{
type: "text",
label: "First Name",
required: true,
invalid: true,
control: { placeholder: "Ron" },
},
{
type: "text",
label: "Last Name",
required: true,
invalid: true,
control: { placeholder: "Burgundy" },
},
{
type: "textarea",
label: "Bio",
control: { placeholder: "Tell us a bit about yourself", rows: 8 },
},
{
type: "checkbox",
label: "Accept Terms & Conditions",
invalid: true,
required: true,
},
],
},
};

View File

@@ -0,0 +1,67 @@
import "./Fieldset.css";
import { Field, FieldProps, Orientation } from "./Field";
import { For } from "solid-js";
import cx from "classnames";
import { Typography } from "@/src/components/v2/Typography/Typography";
export interface FieldsetProps {
legend: string;
fields: FieldProps[];
inverted?: boolean;
disabled?: boolean;
orientation?: Orientation;
error?: string;
}
export const Fieldset = (props: FieldsetProps) => {
const orientation = () => props.orientation || "vertical";
return (
<fieldset
role="group"
class={cx(orientation(), { inverted: props.inverted })}
disabled={props.disabled}
>
<legend>
<Typography
hierarchy="label"
family="mono"
size="default"
weight="normal"
color="tertiary"
transform="uppercase"
inverted={props.inverted}
>
{props.legend}
</Typography>
</legend>
<div class="fields">
<For each={props.fields}>
{(fieldProps) => {
return (
<Field
{...fieldProps}
orientation={orientation()}
disabled={props.disabled}
inverted={props.inverted}
/>
);
}}
</For>
</div>
{props.error && (
<div class="error" role="alert">
<Typography
hierarchy="body"
size="xxs"
weight="medium"
color="error"
inverted={props.inverted}
>
{props.error}
</Typography>
</div>
)}
</fieldset>
);
};

View File

@@ -1,8 +1,8 @@
import meta, { Icon, Story } from "./TextField.stories";
import meta, { Story } from "./TextField.stories";
const textAreaMeta = {
...meta,
title: "Components/Form/TextArea",
title: "Components/Form/Fields/TextArea",
};
export default textAreaMeta;

View File

@@ -24,7 +24,7 @@ const FieldExamples = (props: FieldProps) => (
);
const meta = {
title: "Components/Form/TextField",
title: "Components/Form/Fields/TextField",
component: FieldExamples,
decorators: [
(Story: StoryObj, context: StoryContext<FieldProps>) => {

View File

@@ -8,6 +8,7 @@ export type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
export type Weight = "normal" | "medium" | "bold";
export type Family = "regular" | "condensed" | "mono";
export type Transform = "uppercase" | "lowercase" | "capitalize";
// type Size = "default" | "xs" | "s" | "m" | "l";
interface SizeForHierarchy {
@@ -94,6 +95,7 @@ interface _TypographyProps<H extends Hierarchy> {
inverted?: boolean;
tag?: Tag;
class?: string;
transform?: Transform;
}
export const Typography = <H extends Hierarchy>(props: _TypographyProps<H>) => {
@@ -113,6 +115,7 @@ export const Typography = <H extends Hierarchy>(props: _TypographyProps<H>) => {
weight(),
size(),
color(),
props.transform,
props.class,
)}
component={props.tag || "span"}