Clan-app: Flash improve form & file input

This commit is contained in:
Johannes Kirschbauer
2024-08-07 12:16:06 +02:00
parent 99dc5793b2
commit 7e9214053f
5 changed files with 362 additions and 263 deletions

View File

@@ -0,0 +1,91 @@
import cx from "classnames";
import { createMemo, JSX, Show, splitProps } from "solid-js";
interface FileInputProps {
ref: (element: HTMLInputElement) => void;
name: string;
value?: File[] | File;
onInput: JSX.EventHandler<HTMLInputElement, InputEvent>;
onClick: JSX.EventHandler<HTMLInputElement, Event>;
onChange: JSX.EventHandler<HTMLInputElement, Event>;
onBlur: JSX.EventHandler<HTMLInputElement, FocusEvent>;
accept?: string;
required?: boolean;
multiple?: boolean;
class?: string;
label?: string;
error?: string;
}
/**
* File input field that users can click or drag files into. Various
* decorations can be displayed in or around the field to communicate the entry
* requirements.
*/
export function FileInput(props: FileInputProps) {
// Split input element props
const [, inputProps] = splitProps(props, [
"class",
"value",
"label",
"error",
]);
// Create file list
const getFiles = createMemo(() =>
props.value
? Array.isArray(props.value)
? props.value
: [props.value]
: [],
);
return (
<div class={cx("form-control w-full", props.class)}>
<div class="label">
<span
class="label-text block"
classList={{
"after:ml-0.5 after:text-primary after:content-['*']":
props.required,
}}
>
{props.label}
</span>
</div>
<div
class={cx(
"relative flex min-h-[96px] w-full items-center justify-center rounded-2xl border-[3px] border-dashed p-8 text-center focus-within:ring-4 md:min-h-[112px] md:text-lg lg:min-h-[128px] lg:p-10 lg:text-xl",
!getFiles().length && "text-slate-500",
props.error
? "border-red-500/25 focus-within:border-red-500/50 focus-within:ring-red-500/10 hover:border-red-500/40 dark:border-red-400/25 dark:focus-within:border-red-400/50 dark:focus-within:ring-red-400/10 dark:hover:border-red-400/40"
: "border-slate-200 focus-within:border-sky-500/50 focus-within:ring-sky-500/10 hover:border-slate-300 dark:border-slate-800 dark:focus-within:border-sky-400/50 dark:focus-within:ring-sky-400/10 dark:hover:border-slate-700",
)}
>
<Show
when={getFiles().length}
fallback={<>Click to select file{props.multiple && "s"}</>}
>
Selected file{props.multiple && "s"}:{" "}
{getFiles()
.map(({ name }) => name)
.join(", ")}
</Show>
<input
{...inputProps}
// Disable drag n drop
onDrop={(e) => e.preventDefault()}
class="absolute size-full cursor-pointer opacity-0"
type="file"
id={props.name}
aria-invalid={!!props.error}
aria-errormessage={`${props.name}-error`}
/>
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,48 @@
import { FieldValues, FormStore, ResponseData } from "@modular-forms/solid";
import { type JSX } from "solid-js";
interface SelectInputProps<T extends FieldValues, R extends ResponseData> {
formStore: FormStore<T, R>;
value: string;
options: JSX.Element;
selectProps: JSX.HTMLAttributes<HTMLSelectElement>;
label: JSX.Element;
error?: string;
required?: boolean;
}
export function SelectInput<T extends FieldValues, R extends ResponseData>(
props: SelectInputProps<T, R>,
) {
return (
<label
class="form-control w-full"
aria-disabled={props.formStore.submitting}
>
<div class="label">
<span
class="label-text block"
classList={{
"after:ml-0.5 after:text-primary after:content-['*']":
props.required,
}}
>
{props.label}
</span>
</div>
<select
{...props.selectProps}
required={props.required}
class="select select-bordered w-full"
value={props.value}
>
{props.options}
</select>
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
)}
</label>
);
}

View File

@@ -0,0 +1,54 @@
import { FieldValues, FormStore, ResponseData } from "@modular-forms/solid";
import { type JSX } from "solid-js";
interface TextInputProps<T extends FieldValues, R extends ResponseData> {
formStore: FormStore<T, R>;
value: string;
inputProps: JSX.HTMLAttributes<HTMLInputElement>;
label: JSX.Element;
error?: string;
required?: boolean;
inlineLabel?: JSX.Element;
}
export function TextInput<T extends FieldValues, R extends ResponseData>(
props: TextInputProps<T, R>,
) {
return (
<label
class="form-control w-full"
aria-disabled={props.formStore.submitting}
>
<div class="label">
<span
class="label-text block"
classList={{
"after:ml-0.5 after:text-primary after:content-['*']":
props.required,
}}
>
{props.label}
</span>
</div>
<div class="input input-bordered flex items-center gap-2">
{props.inlineLabel}
<input
{...props.inputProps}
value={props.value}
type="text"
class="grow"
classList={{
"input-disabled": props.formStore.submitting,
}}
placeholder="name"
required
disabled={props.formStore.submitting}
/>
</div>
{props.error && (
<span class="label-text-alt font-bold text-error">{props.error}</span>
)}
</label>
);
}