form components: adds general fieldset component and accordion component

This commit is contained in:
Timo
2025-04-30 08:48:29 +02:00
committed by Johannes Kirschbauer
parent 5b0334adda
commit 9bc23690a3
6 changed files with 157 additions and 36 deletions

View File

@@ -0,0 +1,32 @@
import { JSX } from "solid-js";
import { Typography } from "@/src/components/Typography";
type FieldsetProps = {
legend?: string;
children: JSX.Element;
class?: string;
};
export default function Fieldset(props: FieldsetProps) {
return (
<fieldset class="flex flex-col gap-y-2.5">
{props.legend && (
<div class="px-2">
<Typography
hierarchy="body"
tag="p"
size="s"
color="primary"
weight="medium"
>
{props.legend}
</Typography>
</div>
)}
<div class="flex flex-col gap-y-3 p-5 border border-secondary-200 bg-secondary-50 rounded-md">
{props.children}
</div>
</fieldset>
);
}

View File

@@ -0,0 +1,51 @@
import { createSignal, JSX, Show } from "solid-js";
import Icon from "../icon";
import { Typography } from "../Typography";
import { Button } from "../button";
type AccordionProps = {
title: string;
children: JSX.Element;
class?: string;
initiallyOpen?: boolean;
};
export default function Accordion(props: AccordionProps) {
const [isOpen, setIsOpen] = createSignal(props.initiallyOpen ?? false);
return (
<div
class={`accordion flex flex-col gap-y-5 ${props.class ?? ""}`}
tabindex="0"
>
<div
onClick={() => setIsOpen(!isOpen())}
class="accordion__title h-5 flex gap-x-0.5 items-center justify-end font-medium cursor-pointer px-1"
>
<Show
when={isOpen()}
fallback={
<Button
endIcon={<Icon size={12} icon={"CaretDown"} />}
variant="light"
size="s"
>
{props.title}
</Button>
}
>
<Button
endIcon={<Icon size={12} icon={"CaretUp"} />}
variant="dark"
size="s"
>
{props.title}
</Button>
</Show>
</div>
<Show when={isOpen()}>
<div class="accordion__body">{props.children}</div>
</Show>
</div>
);
}

View File

@@ -31,8 +31,8 @@
padding-left: theme(padding.2);
}
&:has(> .button__icon--end):has(> .button__label) {
padding-left: theme(padding.2);
&:has(> .button__label):has(> .button__icon--end) {
padding-right: theme(padding.2);
}
}

View File

@@ -94,9 +94,7 @@ export const Button = (props: ButtonProps) => {
{local.children}
</Typography>
)}
{local.endIcon && (
<span class="button__icon--start">{local.endIcon}</span>
)}
{local.endIcon && <span class="button__icon--end">{local.endIcon}</span>}
</button>
);
};

View File

@@ -9,7 +9,7 @@ interface HeaderProps {
}
export const Header = (props: HeaderProps) => {
return (
<div class="flex border-b px-6 py-4 border-def-3">
<div class="sticky top-0 z-20 navbar border-b px-6 py-4 border-def-3 bg-white bg-opacity-80 backdrop-blur-md">
<div class="flex-none">
{props.showBack && <BackButton />}
<span class=" lg:hidden" data-tip="Menu">

View File

@@ -11,6 +11,9 @@ import { Match, Switch } from "solid-js";
import toast from "solid-toast";
import { MachineAvatar } from "./avatar";
import { DynForm } from "@/src/Form/form";
import { Typography } from "@/src/components/Typography";
import Fieldset from "@/src/Form/fieldset";
import Accordion from "@/src/components/accordion";
type CreateMachineForm = OperationArgs<"create_machine">;
@@ -72,8 +75,11 @@ export function CreateMachine() {
<>
<Header title="Create Machine" />
<div class="flex w-full p-4">
<div class="mt-4 w-full self-stretch px-2">
<Form onSubmit={handleSubmit} class="gap-2 flex flex-col">
<div class="mt-4 w-full self-stretch px-8">
<Form
onSubmit={handleSubmit}
class="mx-auto w-full max-w-2xl flex flex-col gap-y-6"
>
<Field
name="opts.machine.name"
validate={[required("This field is required")]}
@@ -83,33 +89,66 @@ export function CreateMachine() {
<div class="flex justify-center mb-4 pb-4 border-b">
<MachineAvatar name={field.value} />
</div>
<TextInput
inputProps={props}
value={`${field.value}`}
label={"name"}
error={field.error}
required
placeholder="New_machine"
/>
</>
)}
</Field>
<Field name="opts.machine.description">
{(field, props) => (
<TextInput
inputProps={props}
value={`${field.value}`}
label={"description"}
error={field.error}
placeholder="My awesome machine"
/>
)}
</Field>
<div class=" " tabindex="0">
<input type="checkbox" />
<div class=" font-medium ">Deployment Settings</div>
<div class="">
<Fieldset legend="General">
<Field
name="opts.machine.name"
validate={[required("This field is required")]}
>
{(field, props) => (
<>
<TextInput
inputProps={props}
value={`${field.value}`}
label={"name"}
error={field.error}
required
placeholder="New_machine"
/>
</>
)}
</Field>
<Field name="opts.machine.description">
{(field, props) => (
<TextInput
inputProps={props}
value={`${field.value}`}
label={"description"}
error={field.error}
placeholder="My awesome machine"
/>
)}
</Field>
</Fieldset>
<Fieldset legend="Tags">
<Field name="opts.machine.tags" type="string[]">
{(field, props) => (
<div class="p-2">
<DynForm
initialValues={{ tags: ["all"] }}
schema={{
type: "object",
properties: {
tags: {
type: "array",
items: {
title: "Tag",
type: "string",
},
uniqueItems: true,
},
},
}}
/>
</div>
)}
</Field>
</Fieldset>
<Accordion title="Advanced">
<Fieldset>
<Field name="opts.machine.deploy.targetHost">
{(field, props) => (
<>
@@ -123,9 +162,10 @@ export function CreateMachine() {
</>
)}
</Field>
</div>
</div>
<div class="mt-12 flex justify-end">
</Fieldset>
</Accordion>
<footer class="gap-y-3 pt-5 border-t border-secondary-200 flex justify-end">
<Button
type="submit"
disabled={formStore.submitting}
@@ -141,7 +181,7 @@ export function CreateMachine() {
<Match when={!formStore.submitting}>Create</Match>
</Switch>
</Button>
</div>
</footer>
</Form>
</div>
</div>