feat(ui): add fieldset component
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
22
pkgs/clan-app/ui/src/components/v2/Form/Fieldset.css
Normal file
22
pkgs/clan-app/ui/src/components/v2/Form/Fieldset.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
pkgs/clan-app/ui/src/components/v2/Form/Fieldset.stories.tsx
Normal file
119
pkgs/clan-app/ui/src/components/v2/Form/Fieldset.stories.tsx
Normal 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,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
67
pkgs/clan-app/ui/src/components/v2/Form/Fieldset.tsx
Normal file
67
pkgs/clan-app/ui/src/components/v2/Form/Fieldset.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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>) => {
|
||||
|
||||
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user