From dffbc2872358085ea23fdb474b822c311c55f71d Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Thu, 26 Jun 2025 17:54:26 +0100 Subject: [PATCH] feat(ui): add fieldset component --- .../components/v2/Form/Checkbox.stories.tsx | 2 +- .../ui/src/components/v2/Form/Field.tsx | 2 +- .../ui/src/components/v2/Form/Fieldset.css | 22 ++++ .../components/v2/Form/Fieldset.stories.tsx | 119 ++++++++++++++++++ .../ui/src/components/v2/Form/Fieldset.tsx | 67 ++++++++++ .../components/v2/Form/TextArea.stories.tsx | 4 +- .../components/v2/Form/TextField.stories.tsx | 2 +- .../components/v2/Typography/Typography.tsx | 3 + 8 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 pkgs/clan-app/ui/src/components/v2/Form/Fieldset.css create mode 100644 pkgs/clan-app/ui/src/components/v2/Form/Fieldset.stories.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Form/Fieldset.tsx diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx index 4685a5e29..191c44e0c 100644 --- a/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx @@ -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; diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx b/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx index 2e762ee23..cebee474c 100644 --- a/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx @@ -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 { diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.css b/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.css new file mode 100644 index 000000000..f0a3cd5a7 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.css @@ -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; + } + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.stories.tsx new file mode 100644 index 000000000..632361e1d --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.stories.tsx @@ -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) => ( +
+
+
+
+); + +const meta = { + title: "Components/Form/Fieldset", + component: FieldsetExamples, + decorators: [ + (Story: StoryObj, context: StoryContext) => { + return ( +
+ +
+ ); + }, + ], +} satisfies Meta; + +export default meta; + +export type Story = StoryObj; + +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, + }, + ], + }, +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.tsx b/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.tsx new file mode 100644 index 000000000..632f5b6ac --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Form/Fieldset.tsx @@ -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 ( +
+ + + {props.legend} + + +
+ + {(fieldProps) => { + return ( + + ); + }} + +
+ {props.error && ( + + )} +
+ ); +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Form/TextArea.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Form/TextArea.stories.tsx index cb87a9586..f7c9ed997 100644 --- a/pkgs/clan-app/ui/src/components/v2/Form/TextArea.stories.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Form/TextArea.stories.tsx @@ -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; diff --git a/pkgs/clan-app/ui/src/components/v2/Form/TextField.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Form/TextField.stories.tsx index f4ca52ed6..bb2924e97 100644 --- a/pkgs/clan-app/ui/src/components/v2/Form/TextField.stories.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Form/TextField.stories.tsx @@ -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) => { diff --git a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.tsx b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.tsx index 6026b2c7b..dfb95f572 100644 --- a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.tsx @@ -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 { inverted?: boolean; tag?: Tag; class?: string; + transform?: Transform; } export const Typography = (props: _TypographyProps) => { @@ -113,6 +115,7 @@ export const Typography = (props: _TypographyProps) => { weight(), size(), color(), + props.transform, props.class, )} component={props.tag || "span"}