Skip to content

Commit

Permalink
feat: ✨ form desgin
Browse files Browse the repository at this point in the history
  • Loading branch information
dfrnoch committed Apr 9, 2024
1 parent 16d0314 commit da00ab5
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 64 deletions.
14 changes: 14 additions & 0 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,17 @@ export type Template = {
html: string;
companyId: number;
};

export interface Client {
id: number;
name: string;
cin?: string;
vatId?: string;
streetAddress: string;
city: string;
postalCode: string;
email?: string;
phoneNumber?: string;
invoices?: Invoice[];
companyId: number;
}
118 changes: 57 additions & 61 deletions src/screens/Dashboard/components/Form/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,71 +24,67 @@ const Dropdown: Component<DropdownProps> = (props) => {
};

return (
<div class="w-full">
<Listbox defaultOpen={false} value={selected()} onSelectChange={handleSelect}>
<div class="relative mt-1">
<ListboxButton class="relative w-full py-2 pl-3 pr-10 text-left bg-element border-default border-1 rounded-lg cursor-default focus:outline-none sm:text-sm">
<span class="block truncate">{selected().name}</span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<TbSelector class="w-5 h-5 text-primary" aria-hidden="true" />
</span>
</ListboxButton>
<DisclosureStateChild>
{({ isOpen }): JSX.Element => (
<Transition
show={isOpen()}
enter="transition ease-in duration-100"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-out duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<ListboxOptions
unmount={false}
class="absolute w-full py-1 mt-1 overflow-auto text-base bg-element border-default border-1 rounded-md shadow-lg max-h-60 focus:outline-none sm:text-sm"
>
<For each={props.data}>
{(item): JSX.Element => (
<ListboxOption class="focus:outline-none group" value={item}>
{({ isActive, isSelected }): JSX.Element => (
<div
<Listbox defaultOpen={false} value={selected()} onSelectChange={handleSelect}>
<ListboxButton class="relative w-full py-1.5 pl-3 pr-10 text-left bg-element border-default border-1 rounded-lg cursor-default focus:outline-none text-sm">
<span class="block truncate">{selected().name}</span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<TbSelector class="w-5 h-5 text-primary" aria-hidden="true" />
</span>
</ListboxButton>
<DisclosureStateChild>
{({ isOpen }): JSX.Element => (
<Transition
show={isOpen()}
enter="transition ease-in duration-100"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-out duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<ListboxOptions
unmount={false}
class="absolute w-full py-1 mt-1 overflow-auto text-base bg-element border-default border-1 rounded-md shadow-lg max-h-60 focus:outline-none sm:text-sm"
>
<For each={props.data}>
{(item): JSX.Element => (
<ListboxOption class="focus:outline-none group" value={item}>
{({ isActive, isSelected }): JSX.Element => (
<div
classList={{
"bg-default": isActive(),
"cursor-default select-none relative py-2 pl-10 pr-4": true,
}}
>
<span
classList={{
"font-medium": isSelected(),
"font-normal": !isSelected(),
"block truncate": true,
}}
>
{item.name}
</span>
{isSelected() ? (
<span
classList={{
"bg-default": isActive(),
"cursor-default select-none relative py-2 pl-10 pr-4": true,
"text-primary": true,
"absolute inset-y-0 left-0 flex items-center pl-3": true,
}}
>
<span
classList={{
"font-medium": isSelected(),
"font-normal": !isSelected(),
"block truncate": true,
}}
>
{item.name}
</span>
{isSelected() ? (
<span
classList={{
"text-primary": true,
"absolute inset-y-0 left-0 flex items-center pl-3": true,
}}
>
<FiCheck class="w-5 h-5" />
</span>
) : null}
</div>
)}
</ListboxOption>
<FiCheck class="w-5 h-5" />
</span>
) : null}
</div>
)}
</For>
</ListboxOptions>
</Transition>
)}
</DisclosureStateChild>
</div>
</Listbox>
</div>
</ListboxOption>
)}
</For>
</ListboxOptions>
</Transition>
)}
</DisclosureStateChild>
</Listbox>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/screens/Dashboard/components/Form/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Component } from "solid-js";

type TextInputProps = {
type: "text" | "date";
type: "text" | "date" | "email" | "tel";
onChange: (value: string) => void;
label: string;
defaultValue?: string;
Expand Down Expand Up @@ -29,7 +29,7 @@ const Input: Component<InputProps> = (props) => {
return (
<input
type={props.type}
class="w-full px-2 py-1 border border-default rounded-lg bg-element border-default"
class="w-full px-2 py-1.5 border border-default rounded-lg bg-element text-sm border-default"
onInput={handleInput}
placeholder={props.label}
value={props.defaultValue}
Expand Down
3 changes: 2 additions & 1 deletion src/screens/Dashboard/components/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { ParentComponent } from "solid-js";

// TODO: View
// biome-ignore lint/suspicious/noExplicitAny: idk what the form type is
const Form: ParentComponent<{ form: FormApi<any, undefined> }> = (props) => {
const Form: ParentComponent<{ form: FormApi<any, undefined>; class?: string }> = (props) => {
return (
<form
class={props.class}
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
Expand Down
141 changes: 141 additions & 0 deletions src/screens/Dashboard/pages/Sales/Clients/ManageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@ import PageHeader from "@/screens/Dashboard/components/PageHeader";
import HeaderButton from "@/screens/Dashboard/components/PageHeader/HeaderButton";
import { useParams } from "@solidjs/router";
import type { Component } from "solid-js";
import { createForm } from "@tanstack/solid-form";
import Input from "@/screens/Dashboard/components/Form/Input";
import Dropdown from "@/screens/Dashboard/components/Form/Dropdown";
import type { Client } from "@/bindings";
import Form from "@/screens/Dashboard/components/Form";

const ManageClient: Component = () => {
const params = useParams<{ readonly id?: string }>();
const [t] = useI18n();
const form = createForm<Client>(() => ({
defaultValues: {
id: 0,
name: "",
cin: "",
vatId: "",
streetAddress: "",
city: "",
postalCode: "",
email: "",
phoneNumber: "",
companyId: 0,
},
onSubmit: (client) => alert(JSON.stringify(client)),
}));

return (
<Container>
Expand All @@ -19,6 +39,127 @@ const ManageClient: Component = () => {
</HeaderButton>,
]}
/>
<Form form={form}>
<div class="flex flex-col gap-8">
<div class="flex flex-col md:flex-row gap-8">
<div class="flex-1">
<div class="flex flex-col gap-4">
<form.Field name="name">
{(field) => (
<Input
type="text"
label="Name"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
<form.Field name="cin">
{(field) => (
<Input
type="text"
label="CIN"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
<form.Field name="vatId">
{(field) => (
<Input
type="text"
label="VAT ID"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
</div>
</div>
<div class="flex-1">
<div class="flex flex-col gap-4">
<form.Field name="streetAddress">
{(field) => (
<Input
type="text"
label="Street Address"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
<form.Field name="city">
{(field) => (
<Input
type="text"
label="City"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
<form.Field name="postalCode">
{(field) => (
<Input
type="text"
label="Postal Code"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
</div>
</div>
</div>
<div class="flex flex-col md:flex-row gap-8">
<div class="flex-1">
<div class="flex flex-col gap-4">
<form.Field name="email">
{(field) => (
<Input
type="email"
label="Email"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
<form.Field name="phoneNumber">
{(field) => (
<Input
type="tel"
label="Phone Number"
defaultValue={field().state.value}
onChange={(data) => field().handleChange(data)}
/>
)}
</form.Field>
</div>
</div>
<div class="flex-1">
<div class="flex flex-col gap-4">
<form.Field name="companyId">
{(field) => (
<Dropdown
data={[
{ name: "Company 1", id: 1 },
{ name: "Company 2", id: 2 },
{ name: "Company 3", id: 3 },
]}
onSelect={(data) => field().handleChange(data.id)}
/>
)}
</form.Field>
</div>
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">
Submit
</button>
</div>
</div>
</Form>
</Container>
);
};
Expand Down

0 comments on commit da00ab5

Please sign in to comment.