UI: replace TextInput with simple Layout of InputBase, InputLabel, ErrorMessage
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { createEffect, Show, type JSX } from "solid-js";
|
||||
import cx from "classnames";
|
||||
import { Label } from "../base/label";
|
||||
import { InputBase, InputLabel } from "@/src/components/inputBase";
|
||||
import { Typography } from "@/src/components/Typography";
|
||||
|
||||
interface TextInputProps {
|
||||
value: string;
|
||||
@@ -22,49 +24,39 @@ interface TextInputProps {
|
||||
}
|
||||
|
||||
export function TextInput(props: TextInputProps) {
|
||||
const value = () => props.value;
|
||||
|
||||
// createEffect(() => {
|
||||
// console.log("TextInput", props.error, props.value);
|
||||
// });
|
||||
return (
|
||||
<label
|
||||
class={cx("form-control w-full", props.class)}
|
||||
aria-disabled={props.disabled}
|
||||
>
|
||||
<div class="label">
|
||||
<Label label={props.label} required={props.required} />
|
||||
<span class="label-text-alt block">{props.altLabel}</span>
|
||||
</div>
|
||||
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<Show when={props.adornment && props.adornment.position === "start"}>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
{props.inlineLabel}
|
||||
<input
|
||||
{...props.inputProps}
|
||||
value={value()}
|
||||
type={props.type ? props.type : "text"}
|
||||
class="grow"
|
||||
<div
|
||||
class="grid grid-cols-12"
|
||||
classList={{
|
||||
"input-disabled": props.disabled,
|
||||
"mb-[14.5px]": !props.error,
|
||||
}}
|
||||
placeholder={`${props.placeholder || props.label}`}
|
||||
required
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<InputLabel
|
||||
class="col-span-2"
|
||||
required={props.required}
|
||||
error={!!props.error}
|
||||
>
|
||||
{props.label}
|
||||
</InputLabel>
|
||||
<InputBase
|
||||
error={!!props.error}
|
||||
class="col-span-10"
|
||||
{...props.inputProps}
|
||||
value={props.value}
|
||||
/>
|
||||
<Show when={props.adornment && props.adornment.position === "end"}>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
</div>
|
||||
<div class="label">
|
||||
{props.helperText && (
|
||||
<span class="label-text text-neutral">{props.helperText}</span>
|
||||
)}
|
||||
{props.error && (
|
||||
<span class="label-text-alt font-bold text-error-700">
|
||||
<Typography
|
||||
hierarchy="body"
|
||||
size="xxs"
|
||||
weight="medium"
|
||||
class="col-span-full px-1 !fg-semantic-4"
|
||||
>
|
||||
{props.error}
|
||||
</span>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
getValues,
|
||||
SubmitHandler,
|
||||
} from "@modular-forms/solid";
|
||||
import { TextInput } from "./components/TextInput";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import { Button } from "./components/button";
|
||||
import { callApi } from "./api";
|
||||
import { API } from "@/api/API";
|
||||
@@ -68,7 +68,6 @@ export const ApiTester = () => {
|
||||
label={"endpoint"}
|
||||
value={field.value || ""}
|
||||
inputProps={fieldProps}
|
||||
formStore={formStore}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
@@ -78,7 +77,6 @@ export const ApiTester = () => {
|
||||
label={"payload"}
|
||||
value={field.value || ""}
|
||||
inputProps={fieldProps}
|
||||
formStore={formStore}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { FieldValues, FormStore, ResponseData } from "@modular-forms/solid";
|
||||
import { Show, type JSX } from "solid-js";
|
||||
import cx from "classnames";
|
||||
|
||||
interface TextInputProps<T extends FieldValues, R extends ResponseData> {
|
||||
formStore: FormStore<T, R>;
|
||||
value: string;
|
||||
inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
|
||||
label: JSX.Element;
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
inlineLabel?: JSX.Element;
|
||||
class?: string;
|
||||
adornment?: {
|
||||
position: "start" | "end";
|
||||
content: JSX.Element;
|
||||
};
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export function TextInput<T extends FieldValues, R extends ResponseData>(
|
||||
props: TextInputProps<T, R>,
|
||||
) {
|
||||
const value = () => props.value;
|
||||
|
||||
return (
|
||||
<label
|
||||
class={cx("form-control w-full", props.class)}
|
||||
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">
|
||||
<Show when={props.adornment && props.adornment.position === "start"}>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
{props.inlineLabel}
|
||||
<input
|
||||
{...props.inputProps}
|
||||
value={value()}
|
||||
type={props.type ? props.type : "text"}
|
||||
class="grow"
|
||||
classList={{
|
||||
"input-disabled": props.formStore.submitting,
|
||||
}}
|
||||
placeholder={`${props.placeholder || props.label}`}
|
||||
required
|
||||
disabled={props.formStore.submitting}
|
||||
/>
|
||||
<Show when={props.adornment && props.adornment.position === "end"}>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
</div>
|
||||
{props.error && (
|
||||
<span class="label-text-alt font-bold text-error-700">
|
||||
{props.error}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export const InputBase = (props: InputBaseProps) => {
|
||||
|
||||
// Cursor
|
||||
"aria-readonly:cursor-no-drop",
|
||||
props.class
|
||||
props.class,
|
||||
)}
|
||||
classList={{
|
||||
[cx("!border !border-semantic-1 !outline-semantic-1")]: !!props.error,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@modular-forms/solid";
|
||||
import toast from "solid-toast";
|
||||
import { setActiveURI, setClanList } from "@/src/App";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { Button } from "@/src/components/button";
|
||||
import Icon from "@/src/components/icon";
|
||||
@@ -165,7 +165,6 @@ export const CreateClan = () => {
|
||||
),
|
||||
position: "start",
|
||||
}}
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Template to use"
|
||||
value={field.value ?? ""}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
setValue,
|
||||
SubmitHandler,
|
||||
} from "@modular-forms/solid";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import toast from "solid-toast";
|
||||
import { set_single_service } from "@/src/api/inventory";
|
||||
import { Button } from "@/src/components/button";
|
||||
@@ -215,7 +215,6 @@ const AdminModuleForm = (props: AdminModuleFormProps) => {
|
||||
<Field name={`allowedKeys.${idx()}.name`}>
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label={"Name"}
|
||||
adornment={{
|
||||
@@ -235,7 +234,6 @@ const AdminModuleForm = (props: AdminModuleFormProps) => {
|
||||
{(field, props) => (
|
||||
<>
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label={"Value"}
|
||||
value={field.value ?? ""}
|
||||
@@ -243,7 +241,10 @@ const AdminModuleForm = (props: AdminModuleFormProps) => {
|
||||
class="col-span-6"
|
||||
required
|
||||
/>
|
||||
<span class="tooltip mt-auto" data-tip="Select file">
|
||||
<span
|
||||
class="tooltip col-span-12 mt-auto"
|
||||
data-tip="Select file"
|
||||
>
|
||||
<label
|
||||
class={"form-control w-full"}
|
||||
aria-disabled={formStore.submitting}
|
||||
|
||||
@@ -41,8 +41,8 @@ export const Components = () => {
|
||||
readonly={readOnly}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
)
|
||||
)),
|
||||
),
|
||||
)}
|
||||
<span class="col-span-2">Input Ghost</span>
|
||||
{disabled.map((disabled) =>
|
||||
@@ -66,8 +66,8 @@ export const Components = () => {
|
||||
readonly={readOnly}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
)
|
||||
)),
|
||||
),
|
||||
)}
|
||||
<span class="col-span-2">Input Label</span>
|
||||
<span>Default</span>
|
||||
@@ -96,7 +96,7 @@ export const Components = () => {
|
||||
<Field
|
||||
name="ef"
|
||||
validate={required(
|
||||
"This field is required very long descriptive error message"
|
||||
"This field is required very long descriptive error message",
|
||||
)}
|
||||
>
|
||||
{(field, inputProps) => (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from "@/src/components/button";
|
||||
import { FileInput } from "@/src/components/FileInput";
|
||||
import Icon from "@/src/components/icon";
|
||||
import { SelectInput } from "@/src/components/SelectInput";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import { Typography } from "@/src/components/Typography";
|
||||
import { Header } from "@/src/layout/header";
|
||||
import {
|
||||
@@ -269,7 +269,6 @@ export const Flash = () => {
|
||||
>
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="SSID"
|
||||
value={field.value ?? ""}
|
||||
@@ -286,7 +285,6 @@ export const Flash = () => {
|
||||
{(field, props) => (
|
||||
<div class="relative col-span-3 w-full">
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
type={
|
||||
passwordVisibility()[index()] ? "text" : "password"
|
||||
@@ -357,7 +355,6 @@ export const Flash = () => {
|
||||
{(field, props) => (
|
||||
<>
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Source (flake URL)"
|
||||
value={String(field.value)}
|
||||
@@ -377,7 +374,6 @@ export const Flash = () => {
|
||||
{(field, props) => (
|
||||
<>
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Image Name (attribute name)"
|
||||
value={String(field.value)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { callApi, OperationArgs } from "@/src/api";
|
||||
import { activeURI } from "@/src/App";
|
||||
import { Button } from "@/src/components/button";
|
||||
import Icon from "@/src/components/icon";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import { Header } from "@/src/layout/header";
|
||||
import { createForm, required, reset } from "@modular-forms/solid";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
@@ -73,7 +73,7 @@ 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}>
|
||||
<Form onSubmit={handleSubmit} class="">
|
||||
<Field
|
||||
name="opts.machine.name"
|
||||
validate={[required("This field is required")]}
|
||||
@@ -85,7 +85,6 @@ export function CreateMachine() {
|
||||
</div>
|
||||
<TextInput
|
||||
inputProps={props}
|
||||
formStore={formStore}
|
||||
value={`${field.value}`}
|
||||
label={"name"}
|
||||
error={field.error}
|
||||
@@ -99,7 +98,6 @@ export function CreateMachine() {
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
inputProps={props}
|
||||
formStore={formStore}
|
||||
value={`${field.value}`}
|
||||
label={"description"}
|
||||
error={field.error}
|
||||
@@ -143,7 +141,6 @@ export function CreateMachine() {
|
||||
<>
|
||||
<TextInput
|
||||
inputProps={props}
|
||||
formStore={formStore}
|
||||
value={`${field.value}`}
|
||||
label={"Target"}
|
||||
error={field.error}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { activeURI } from "@/src/App";
|
||||
import { Button } from "@/src/components/button";
|
||||
import { FileInput } from "@/src/components/FileInput";
|
||||
import Icon from "@/src/components/icon";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import { selectSshKeys } from "@/src/hooks";
|
||||
import {
|
||||
createForm,
|
||||
@@ -17,6 +17,7 @@ import { createSignal, For, Show } from "solid-js";
|
||||
import toast from "solid-toast";
|
||||
import { MachineAvatar } from "./avatar";
|
||||
import { Header } from "@/src/layout/header";
|
||||
import { InputLabel } from "@/src/components/inputBase";
|
||||
|
||||
type MachineFormInterface = MachineData & {
|
||||
sshKey?: File;
|
||||
@@ -302,14 +303,13 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div class="card-body">
|
||||
<span class="text-xl text-primary-800">General</span>
|
||||
<MachineAvatar name={machineName()} />
|
||||
<Form onSubmit={handleSubmit} class="grid grid-cols-12 gap-y-4">
|
||||
<Field name="machine.name">
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Name"
|
||||
value={field.value ?? ""}
|
||||
@@ -321,22 +321,23 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
</Field>
|
||||
<Field name="machine.tags" type="string[]">
|
||||
{(field, props) => (
|
||||
<>
|
||||
<InputLabel class="col-span-2">Tags</InputLabel>
|
||||
<span class="col-span-10">
|
||||
<For each={field.value}>
|
||||
{(tag) => (
|
||||
<label class="p-1">
|
||||
Tags
|
||||
<span class="mx-2 w-fit rounded-full px-3 py-1 bg-inv-4 fg-inv-1">
|
||||
{tag}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</For>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="machine.description">
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Description"
|
||||
value={field.value ?? ""}
|
||||
@@ -348,16 +349,24 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
</Field>
|
||||
<Field name="hw_config">
|
||||
{(field, props) => (
|
||||
<label>Hardware report: {field.value || "None"}</label>
|
||||
<>
|
||||
<InputLabel class="col-span-2">
|
||||
Hardware Configuration
|
||||
</InputLabel>
|
||||
<span class="col-span-10">{field.value || "None"}</span>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="disk_schema">
|
||||
{(field, props) => (
|
||||
<span>Disk schema: {field.value || "None"}</span>
|
||||
<>
|
||||
<InputLabel class="col-span-2">Disk schema</InputLabel>
|
||||
<span class="col-span-10">{field.value || "None"}</span>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class="collapse collapse-arrow" tabindex="0">
|
||||
<div class="collapse collapse-arrow col-span-full" tabindex="0">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title link px-0 text-xl ">
|
||||
Connection Settings
|
||||
@@ -366,7 +375,6 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
<Field name="machine.deploy.targetHost">
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Target Host"
|
||||
value={field.value ?? ""}
|
||||
@@ -411,7 +419,7 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
</div>
|
||||
|
||||
{
|
||||
<div class="card-actions justify-end">
|
||||
<div class="card-actions col-span-full justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={formStore.submitting || !formStore.dirty}
|
||||
@@ -420,8 +428,8 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="divider"></div>
|
||||
@@ -579,11 +587,10 @@ function WifiModule(props: MachineWifiProps) {
|
||||
<span class="text-neutral">Preconfigure wireless networks</span>
|
||||
<For each={nets()}>
|
||||
{(_, idx) => (
|
||||
<div class="flex gap-4">
|
||||
<div class="grid grid-cols-2">
|
||||
<Field name={`networks.${idx()}.ssid`}>
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Name"
|
||||
value={field.value ?? ""}
|
||||
@@ -595,7 +602,6 @@ function WifiModule(props: MachineWifiProps) {
|
||||
<Field name={`networks.${idx()}.password`}>
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
formStore={formStore}
|
||||
inputProps={props}
|
||||
label="Password"
|
||||
value={field.value ?? ""}
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import { callApi } from "@/src/api";
|
||||
import { activeURI } from "@/src/App";
|
||||
import { BackButton } from "@/src/components/BackButton";
|
||||
import { createModulesQuery } from "@/src/queries";
|
||||
import { useParams, useNavigate } from "@solidjs/router";
|
||||
import {
|
||||
createEffect,
|
||||
createSignal,
|
||||
For,
|
||||
JSX,
|
||||
Match,
|
||||
Show,
|
||||
Switch,
|
||||
} from "solid-js";
|
||||
import { createEffect, For, Match, Switch } from "solid-js";
|
||||
import { SolidMarkdown } from "solid-markdown";
|
||||
import toast from "solid-toast";
|
||||
import { ModuleInfo } from "./list";
|
||||
import { createQuery } from "@tanstack/solid-query";
|
||||
import { JSONSchema7 } from "json-schema";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import {
|
||||
createForm,
|
||||
getValue,
|
||||
setValue,
|
||||
SubmitHandler,
|
||||
} from "@modular-forms/solid";
|
||||
import { SubmitHandler } from "@modular-forms/solid";
|
||||
import { DynForm } from "@/src/Form/form";
|
||||
import { Button } from "@/src/components/button";
|
||||
import Icon from "@/src/components/icon";
|
||||
|
||||
Reference in New Issue
Block a user