UI: init {InputBase,InputLabel}
This commit is contained in:
146
pkgs/webview-ui/app/src/components/inputBase/index.tsx
Normal file
146
pkgs/webview-ui/app/src/components/inputBase/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -20,6 +20,7 @@ import { ModuleDetails } from "./routes/modules/details";
|
||||
import { ModuleDetails as AddModule } from "./routes/modules/add";
|
||||
import { ApiTester } from "./api_test";
|
||||
import { IconVariant } from "./components/icon";
|
||||
import { Components } from "./routes/components";
|
||||
|
||||
export const client = new QueryClient();
|
||||
|
||||
@@ -157,6 +158,12 @@ export const routes: AppRoute[] = [
|
||||
hidden: false,
|
||||
component: () => <ApiTester />,
|
||||
},
|
||||
{
|
||||
path: "/components",
|
||||
label: "Components",
|
||||
hidden: false,
|
||||
component: () => <Components />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
118
pkgs/webview-ui/app/src/routes/components/index.tsx
Normal file
118
pkgs/webview-ui/app/src/routes/components/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user