UI: init {InputBase,InputLabel}

This commit is contained in:
Johannes Kirschbauer
2024-12-17 16:58:06 +01:00
parent 4709d07a88
commit f2a2c1d0d7
3 changed files with 271 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
import cx from "classnames";
import { createEffect, JSX, splitProps } from "solid-js";
import Icon, { IconVariant } from "../icon";
import { Typography } from "../Typography";
type Variants = "outlined" | "ghost";
interface InputBaseProps {
variant?: Variants;
value?: string;
inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
required?: boolean;
type?: string;
inlineLabel?: JSX.Element;
class?: string;
placeholder?: string;
disabled?: boolean;
readonly?: boolean;
error?: boolean;
icon?: IconVariant;
}
const variantBorder: Record<Variants, string> = {
outlined: "border border-inv-3",
ghost: "",
};
const fgStateClasses = cx("aria-disabled:fg-def-4 aria-readonly:fg-def-3");
export const InputBase = (props: InputBaseProps) => {
const [, inputProps] = splitProps(props, ["class"]);
createEffect(() => {
console.log("InputBase", props.value, props.variant);
});
return (
<div
class={cx(
// Layout
"flex px-2 py-[0.375rem] flex-shrink-0 items-center justify-center gap-2 text-sm leading-6",
// Background
"bg-def-1 hover:bg-acc-1",
// Text
"fg-def-1",
fgStateClasses,
// Border
variantBorder[props.variant || "outlined"],
"rounded-sm",
"hover:border-inv-4",
"aria-disabled:border-def-2 aria-disabled:border",
// Outline
"outline-offset-1 outline-1",
"active:outline active:outline-inv-3",
"focus-visible:outline-double focus-visible:outline-int-1",
// Cursor
"aria-readonly:cursor-no-drop",
props.class
)}
classList={{
[cx("!border !border-semantic-1 !outline-semantic-1")]: !!props.error,
}}
aria-invalid={props.error}
aria-disabled={props.disabled}
aria-readonly={props.readonly}
tabIndex={0}
role="textbox"
>
{props.icon && (
<i
class={cx("inline-flex fg-def-2", fgStateClasses)}
aria-invalid={props.error}
aria-disabled={props.disabled}
aria-readonly={props.readonly}
>
<Icon icon={props.icon} font-size="inherit" color="inherit" />
</i>
)}
<input
tabIndex={-1}
class="w-full bg-transparent outline-none aria-readonly:cursor-no-drop"
value={props.value}
type={props.type ? props.type : "text"}
readOnly={props.readonly}
placeholder={`${props.placeholder || ""}`}
required={props.required}
disabled={props.disabled}
aria-invalid={props.error}
aria-disabled={props.disabled}
aria-readonly={props.readonly}
{...inputProps}
/>
</div>
);
};
interface InputLabelProps extends JSX.LabelHTMLAttributes<HTMLLabelElement> {
description?: string;
required?: boolean;
error?: boolean;
help?: string;
}
export const InputLabel = (props: InputLabelProps) => {
const [labelProps, forwardProps] = splitProps(props, ["class"]);
return (
<label
class={cx("flex items-center gap-1", labelProps.class)}
{...forwardProps}
>
<Typography
hierarchy="label"
size="default"
weight="bold"
class="!fg-def-1"
classList={{
[cx("!fg-semantic-1")]: !!props.error,
}}
aria-invalid={props.error}
>
{props.children}
{props.required && (
<span class="inline-flex px-1 align-bottom leading-[0.5] fg-def-3">
{"*"}
</span>
)}
{props.help && (
<span
class="tooltip tooltip-bottom inline px-2"
data-tip={props.help}
style={{
"--tooltip-color": "#EFFFFF",
"--tooltip-text-color": "#0D1416",
"--tooltip-tail": "0.8125rem",
}}
>
<Icon class="inline fg-def-3" icon={"Info"} width={"0.8125rem"} />
</span>
)}
</Typography>
<Typography hierarchy="body" size="xs" weight="normal" color="secondary">
{props.description}
</Typography>
</label>
);
};

View File

@@ -20,6 +20,7 @@ import { ModuleDetails } from "./routes/modules/details";
import { ModuleDetails as AddModule } from "./routes/modules/add"; import { ModuleDetails as AddModule } from "./routes/modules/add";
import { ApiTester } from "./api_test"; import { ApiTester } from "./api_test";
import { IconVariant } from "./components/icon"; import { IconVariant } from "./components/icon";
import { Components } from "./routes/components";
export const client = new QueryClient(); export const client = new QueryClient();
@@ -157,6 +158,12 @@ export const routes: AppRoute[] = [
hidden: false, hidden: false,
component: () => <ApiTester />, component: () => <ApiTester />,
}, },
{
path: "/components",
label: "Components",
hidden: false,
component: () => <Components />,
},
], ],
}, },
]; ];

View File

@@ -0,0 +1,118 @@
import { Button } from "@/src/components/button";
import { InputBase, InputLabel } from "@/src/components/inputBase";
import { TextInput } from "@/src/Form/fields";
import { Header } from "@/src/layout/header";
import { createForm, required } from "@modular-forms/solid";
const disabled = [false, true];
const readOnly = [false, true];
const error = [false, true];
export const Components = () => {
const [formStore, { Form, Field }] = createForm<{ ef: string }>({});
return (
<>
<Header title="Components" />
<div class="grid grid-cols-2 gap-4 p-4">
<span class="col-span-2">Input </span>
<span>Default</span>
<span>Size S</span>
{disabled.map((disabled) =>
readOnly.map((readOnly) =>
error.map((hasError) => (
<>
<span>
{[
disabled ? "Disabled" : "(default)",
readOnly ? "ReadOnly" : "",
hasError ? "Error" : "",
]
.filter(Boolean)
.join(" + ")}
</span>
<InputBase
variant="outlined"
value="The Fox jumps!"
disabled={disabled}
error={hasError}
readonly={readOnly}
/>
</>
))
)
)}
<span class="col-span-2">Input Ghost</span>
{disabled.map((disabled) =>
readOnly.map((readOnly) =>
error.map((hasError) => (
<>
<span>
{[
disabled ? "Disabled" : "(default)",
readOnly ? "ReadOnly" : "",
hasError ? "Error" : "",
]
.filter(Boolean)
.join(" + ")}
</span>
<InputBase
variant="ghost"
value="The Fox jumps!"
disabled={disabled}
error={hasError}
readonly={readOnly}
/>
</>
))
)
)}
<span class="col-span-2">Input Label</span>
<span>Default</span>
<InputLabel>Labeltext</InputLabel>
<span>Required</span>
<InputLabel required>Labeltext</InputLabel>
<span>Error</span>
<InputLabel error>Labeltext</InputLabel>
<span>Error + Reuired</span>
<InputLabel error required>
Labeltext
</InputLabel>
<span>Icon</span>
<InputLabel help="Some Info">Labeltext</InputLabel>
<span>Description</span>
<InputLabel description="Some more words">Labeltext</InputLabel>
</div>
<div class="flex flex-col gap-2">
<span class="col-span-full gap-4">Form Layout</span>
<TextInput label="Label" value="Value" />
<Form
onSubmit={() => {
console.log("Nothing");
}}
>
<Field
name="ef"
validate={required(
"This field is required very long descriptive error message"
)}
>
{(field, inputProps) => (
<TextInput
label="Write something"
error={field.error}
required
value={field.value || ""}
inputProps={inputProps}
/>
)}
</Field>
<Button>Submit</Button>
</Form>
<TextInput label="Label" required value="Value" />
</div>
</>
);
};