Clan-app: Flash improve form & file input
This commit is contained in:
91
pkgs/webview-ui/app/src/components/FileInput.tsx
Normal file
91
pkgs/webview-ui/app/src/components/FileInput.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
48
pkgs/webview-ui/app/src/components/SelectInput.tsx
Normal file
48
pkgs/webview-ui/app/src/components/SelectInput.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
54
pkgs/webview-ui/app/src/components/TextInput.tsx
Normal file
54
pkgs/webview-ui/app/src/components/TextInput.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user