update from cdrl
This commit is contained in:
@@ -324,12 +324,12 @@ export interface AutoTablePagination {
|
||||
CurrentPage: number;
|
||||
NextPage?: number;
|
||||
PreviousPage?: number;
|
||||
TotalPages: number;
|
||||
TotalItems: number;
|
||||
TotalPages?: number;
|
||||
TotalItems?: number;
|
||||
MaxItemsPerPage: number;
|
||||
ItemsThisPage?: number;
|
||||
ViewRangeLower: number;
|
||||
ViewRangeUpper: number;
|
||||
ViewRangeLower?: number;
|
||||
ViewRangeUpper?: number;
|
||||
}
|
||||
|
||||
export interface AutoTableOrderBy {
|
||||
@@ -449,7 +449,7 @@ export function AutoTableDateSearch(props: AutoTableDateSearchProps) {
|
||||
<DatePicker
|
||||
small={true}
|
||||
clearable={true}
|
||||
value={liveValue}
|
||||
value={liveValue()}
|
||||
onchange={props.onchange}
|
||||
placeholder={props.placeholder}
|
||||
/>
|
||||
@@ -4181,33 +4181,27 @@ function PaginationButton(props: JSX.ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||
</button>;
|
||||
}
|
||||
|
||||
interface TdProps {
|
||||
class?: string;
|
||||
style?: string;
|
||||
children: any;
|
||||
}
|
||||
|
||||
export function TdLeft(props: TdProps) {
|
||||
export function TdLeft(props: JSX.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td class={"text-left " + (props.class || "")} style={props.style}>{props.children}</td>;
|
||||
}
|
||||
|
||||
export function TdRight(props: TdProps) {
|
||||
export function TdRight(props: JSX.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td class={"text-right " + (props.class || "")} style={props.style}>{props.children}</td>;
|
||||
}
|
||||
|
||||
export function TdCenter(props: TdProps) {
|
||||
export function TdCenter(props: JSX.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td class={"text-center " + (props.class || "")} style={props.style}>{props.children}</td>;
|
||||
}
|
||||
|
||||
export function TdUniformLeft(props: TdProps) {
|
||||
export function TdUniformLeft(props: JSX.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td class={"text-left " + (props.class || "")} style={props.style}>{props.children}</td>;
|
||||
}
|
||||
|
||||
export function TdUniformRight(props: TdProps) {
|
||||
export function TdUniformRight(props: JSX.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td class={"text-right " + (props.class || "")} style={props.style}>{props.children}</td>;
|
||||
}
|
||||
|
||||
export function TdUniformCenter(props: TdProps) {
|
||||
export function TdUniformCenter(props: JSX.TdHTMLAttributes<HTMLTableCellElement>) {
|
||||
return <td class={"text-center " + (props.class || "")} style={props.style}>{props.children}</td>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JSX, JSXElement } from "solid-js";
|
||||
import { JSX, JSXElement, splitProps } from "solid-js";
|
||||
import { Icon } from "./Icons.tsx";
|
||||
|
||||
export const BUTTON_COLOR_NEUTRAL = "neutral";
|
||||
@@ -63,37 +63,36 @@ interface ButtonUIProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
}
|
||||
|
||||
export function ButtonUI(props: ButtonUIProps) {
|
||||
const hasText = () => props.text !== undefined ? !!props.text : !props.icon;
|
||||
const [local, buttonProps] = splitProps(props, ["text", "icon", "outline", "color", "small", "class", "type", "children"]);
|
||||
const hasText = () => local.text !== undefined ? !!local.text : !local.icon;
|
||||
|
||||
const cls = () => {
|
||||
let c = BASE;
|
||||
if (props.outline) {
|
||||
c += " " + OUTLINE_BASE + " " + (OUTLINE_COLORS[props.color!] || OUTLINE_COLORS["neutral"]);
|
||||
if (local.outline) {
|
||||
c += " " + OUTLINE_BASE + " " + (OUTLINE_COLORS[local.color!] || OUTLINE_COLORS["neutral"]);
|
||||
} else {
|
||||
c += " " + (COLORS[props.color!] || COLORS["neutral"]);
|
||||
c += " " + (COLORS[local.color!] || COLORS["neutral"]);
|
||||
}
|
||||
if (props.small) {
|
||||
c += props.icon ? " py-1 px-3" : " py-1 px-4";
|
||||
} else if (props.icon && !hasText()) {
|
||||
if (local.small) {
|
||||
c += local.icon ? " py-1 px-3" : " py-1 px-4";
|
||||
} else if (local.icon && !hasText()) {
|
||||
c += " py-2 px-3";
|
||||
} else if (props.icon) {
|
||||
} else if (local.icon) {
|
||||
c += " py-2 px-5";
|
||||
} else {
|
||||
c += " py-2 px-8";
|
||||
}
|
||||
if (local.class) c += " " + local.class;
|
||||
return c;
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type={props.type || "button"}
|
||||
onclick={(e) => typeof props.onclick === "function" && props.onclick(e)}
|
||||
onmousedown={(e) => typeof props.onmousedown === "function" && props.onmousedown(e)}
|
||||
disabled={props.disabled}
|
||||
title={props.title}
|
||||
type={local.type || "button"}
|
||||
class={cls()}
|
||||
{...buttonProps}
|
||||
>
|
||||
{props.children}
|
||||
{local.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSignal, createEffect, Show, onMount, onCleanup, For } from "solid-js";
|
||||
import { createSignal, createEffect, Show, onMount, onCleanup, For, JSX } from "solid-js";
|
||||
import { Portal } from "solid-js/web";
|
||||
import { Icon } from "./Icons.tsx";
|
||||
import { FormInput } from "./Forms.tsx";
|
||||
@@ -179,7 +179,10 @@ const DATE_PICKER_ICON_BTN = "absolute inset-y-0 right-0 z-[1] flex items-center
|
||||
const DATE_PICKER_CLEAR_BTN = "absolute inset-y-0 right-8 z-[1] flex items-center justify-center bg-transparent border-0 px-1.5 cursor-pointer text-ink-muted leading-none hover:text-ink pointer-events-auto";
|
||||
|
||||
interface DatePickerProps {
|
||||
value?: MaybeAccessor<string>;
|
||||
id?: string;
|
||||
name?: string;
|
||||
value?: string;
|
||||
oninput?: JSX.InputEventHandlerUnion<HTMLInputElement, InputEvent>;
|
||||
onchange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
small?: boolean;
|
||||
|
||||
@@ -107,8 +107,9 @@ export interface FormInputProps
|
||||
extends
|
||||
FormInputCommonProps,
|
||||
Pick<JSX.InputHTMLAttributes<HTMLInputElement>, CommonInputAttrs | "type" | "max" | "min" | "step" | "accept"> {
|
||||
small?: boolean;
|
||||
ref?: (el: HTMLInputElement) => void;
|
||||
prefix?: JSXElement;
|
||||
small?: boolean;
|
||||
class?: string;
|
||||
onDark?: boolean;
|
||||
}
|
||||
@@ -121,10 +122,6 @@ export interface FormTextareaProps
|
||||
onDark?: boolean;
|
||||
}
|
||||
|
||||
export interface FormInputGroupProps extends FormInputProps {
|
||||
prefix?: JSXElement;
|
||||
}
|
||||
|
||||
export interface FormNumberInputProps extends FormInputProps {
|
||||
int?: boolean;
|
||||
unsigned?: boolean;
|
||||
@@ -171,7 +168,7 @@ function _comboboxRootCls(className?: string, fieldWidth: ComboboxFieldWidth = "
|
||||
}
|
||||
|
||||
export interface FormComboboxProps extends Pick<JSX.SelectHTMLAttributes<HTMLSelectElement>, CommonSelectAttrs> {
|
||||
options: FormSelectOption[];
|
||||
options?: FormSelectOption[];
|
||||
placeholder?: string;
|
||||
searchable?: boolean;
|
||||
searchPlaceholder?: string;
|
||||
@@ -227,90 +224,104 @@ export interface FormMultiSelectTriggerProps {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export function FormInput(props: FormInputProps) {
|
||||
const cls = () => _inputCls(props.small, props.error, props.success, props.class, props.onDark);
|
||||
const inputValue = () => readAccessor(props.value, "");
|
||||
return <div class="ui-form">
|
||||
<input
|
||||
ref={(el) => typeof props.ref === "function" && props.ref(el)}
|
||||
class={cls()}
|
||||
type={props.type}
|
||||
value={inputValue()}
|
||||
placeholder={props.placeholder}
|
||||
autocomplete={props.autocomplete || (props.passwordManagerIgnore ? "off" : undefined)}
|
||||
inputmode={props.inputmode}
|
||||
data-1p-ignore={props.passwordManagerIgnore}
|
||||
data-lpignore={props.passwordManagerIgnore ? "other" : undefined}
|
||||
data-form-type={props.passwordManagerIgnore}
|
||||
data-bwignore={props.passwordManagerIgnore}
|
||||
data-protonpass-ignore={props.passwordManagerIgnore}
|
||||
disabled={props.disabled}
|
||||
maxlength={props.maxlength}
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
step={props.step}
|
||||
name={props.name}
|
||||
id={props.id}
|
||||
oninput={props.oninput}
|
||||
onchange={props.onchange}
|
||||
onBlur={props.onblur}
|
||||
onFocus={props.onfocus}
|
||||
onKeyDown={props.onkeydown}
|
||||
|
||||
/>
|
||||
<Show when={props.error}>
|
||||
<span class={FORM_ERROR}>{props.error}</span>
|
||||
</Show>
|
||||
<Show when={props.success}>
|
||||
<span class={FORM_SUCCESS}>{props.success}</span>
|
||||
</Show>
|
||||
</div>;
|
||||
export function FormInputOld(props: FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, [
|
||||
"prefix",
|
||||
"small",
|
||||
"error",
|
||||
"success",
|
||||
"class",
|
||||
"onDark",
|
||||
"ref",
|
||||
"autocomplete",
|
||||
"passwordManagerIgnore",
|
||||
]);
|
||||
|
||||
return (
|
||||
<div class="ui-form">
|
||||
<input
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
class={_inputCls(local.small, local.error, local.success, local.class, local.onDark)}
|
||||
autocomplete={local.autocomplete || (local.passwordManagerIgnore ? "off" : undefined)}
|
||||
data-1p-ignore={local.passwordManagerIgnore}
|
||||
data-lpignore={local.passwordManagerIgnore ? "other" : undefined}
|
||||
data-form-type={local.passwordManagerIgnore}
|
||||
data-bwignore={local.passwordManagerIgnore}
|
||||
data-protonpass-ignore={local.passwordManagerIgnore}
|
||||
{...inputProps}
|
||||
/>
|
||||
<Show when={local.error}>
|
||||
<span class={FORM_ERROR}>{local.error}</span>
|
||||
</Show>
|
||||
<Show when={local.success}>
|
||||
<span class={FORM_SUCCESS}>{local.success}</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormInputGroup(props: FormInputGroupProps) {
|
||||
const pfxCls = () => _prefixCls(props.small, props.error, props.onDark);
|
||||
const inputCls = () => _inputCls(props.small, props.error, props.success, "rounded-l-none" + (props.class ? " " + props.class : ""), props.onDark);
|
||||
const inputValue = () => readAccessor(props.value, "");
|
||||
return <div class="ui-form">
|
||||
<div class={INPUT_GROUP}>
|
||||
<span class={pfxCls()}>{props.prefix}</span>
|
||||
<span class="grow flex">
|
||||
<input
|
||||
ref={(el) => typeof props.ref === "function" && props.ref(el)}
|
||||
class={inputCls()}
|
||||
type={props.type}
|
||||
value={inputValue()}
|
||||
placeholder={props.placeholder}
|
||||
autocomplete={props.autocomplete || (props.passwordManagerIgnore ? "off" : undefined)}
|
||||
inputmode={props.inputmode}
|
||||
data-1p-ignore={props.passwordManagerIgnore}
|
||||
data-lpignore={props.passwordManagerIgnore ? "other" : undefined}
|
||||
data-form-type={props.passwordManagerIgnore}
|
||||
data-bwignore={props.passwordManagerIgnore}
|
||||
data-protonpass-ignore={props.passwordManagerIgnore}
|
||||
disabled={props.disabled}
|
||||
maxlength={props.maxlength}
|
||||
name={props.name}
|
||||
id={props.id}
|
||||
oninput={props.oninput}
|
||||
onchange={props.onchange}
|
||||
onBlur={props.onblur}
|
||||
onFocus={props.onfocus}
|
||||
onKeyDown={props.onkeydown}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<Show when={props.error}>
|
||||
<span class={FORM_ERROR}>{props.error}</span>
|
||||
export function FormInput(props: FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, [
|
||||
"prefix",
|
||||
"small",
|
||||
"error",
|
||||
"success",
|
||||
"class",
|
||||
"onDark",
|
||||
"ref",
|
||||
"autocomplete",
|
||||
"passwordManagerIgnore",
|
||||
]);
|
||||
|
||||
const cls = local.prefix ? ("rounded-l-none" + (local.class ? " " + local.class : "")) : local.class;
|
||||
|
||||
const InputEl = () => (
|
||||
<input
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
class={_inputCls(local.small, local.error, local.success, cls, local.onDark)}
|
||||
autocomplete={local.autocomplete || (local.passwordManagerIgnore ? "off" : undefined)}
|
||||
data-1p-ignore={local.passwordManagerIgnore}
|
||||
data-lpignore={local.passwordManagerIgnore ? "other" : undefined}
|
||||
data-form-type={local.passwordManagerIgnore}
|
||||
data-bwignore={local.passwordManagerIgnore}
|
||||
data-protonpass-ignore={local.passwordManagerIgnore}
|
||||
{...inputProps}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={local.prefix}
|
||||
fallback={
|
||||
<div class="ui-form">
|
||||
<InputEl />
|
||||
<Show when={local.error}>
|
||||
<span class={FORM_ERROR}>{local.error}</span>
|
||||
</Show>
|
||||
<Show when={local.success}>
|
||||
<span class={FORM_SUCCESS}>{local.success}</span>
|
||||
</Show>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="ui-form">
|
||||
<div class={INPUT_GROUP}>
|
||||
<span class={_prefixCls(local.small, local.error, local.onDark)}>{local.prefix}</span>
|
||||
<span class="grow flex"><InputEl /></span>
|
||||
</div>
|
||||
<Show when={local.error}>
|
||||
<span class={FORM_ERROR}>{local.error}</span>
|
||||
</Show>
|
||||
<Show when={local.success}>
|
||||
<span class={FORM_SUCCESS}>{local.success}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.success}>
|
||||
<span class={FORM_SUCCESS}>{props.success}</span>
|
||||
</Show>
|
||||
</div>;
|
||||
);
|
||||
}
|
||||
|
||||
export function FormNumberInput(props: FormNumberInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput", "int", "unsigned"]);
|
||||
const [local, inputProps] = splitProps(props, ["oninput", "int", "unsigned"]);
|
||||
var regex: RegExp;
|
||||
|
||||
if (local.int) {
|
||||
@@ -339,45 +350,11 @@ export function FormNumberInput(props: FormNumberInputProps) {
|
||||
}
|
||||
};
|
||||
|
||||
return <FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
{...inputProps}
|
||||
/>;
|
||||
return <FormInput inputMode="decimal" oninput={handleInput} {...inputProps} />;
|
||||
}
|
||||
|
||||
export function FormCarNumberInput(props: FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
if (!e || !e.currentTarget) return;
|
||||
const input = e.currentTarget;
|
||||
const raw = input.value.toUpperCase().replace(/[^0-9A-Z]/g, "");
|
||||
const match = raw.match(/^([0-9]{0,2})([A-Z]{0,2})/);
|
||||
input.value = match ? match[1] + match[2] : "";
|
||||
if (typeof local.oninput === "function") {
|
||||
local.oninput(e);
|
||||
}
|
||||
};
|
||||
|
||||
return <FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
maxLength="4"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
placeholder="e.g. 28"
|
||||
{...inputProps}
|
||||
/>;
|
||||
}
|
||||
|
||||
export function FormCurrencyInput(props: {hideSymbol?: boolean} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput", "hideSymbol"]);
|
||||
export function FormCurrencyInput(props: {showIcon?: boolean} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["oninput", "showIcon"]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
if (!e || !e.currentTarget) return;
|
||||
@@ -396,32 +373,12 @@ export function FormCurrencyInput(props: {hideSymbol?: boolean} & FormInputProp
|
||||
}
|
||||
};
|
||||
|
||||
if (local.hideSymbol) {
|
||||
return <FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
{...inputProps}
|
||||
/>;
|
||||
} else {
|
||||
return <FormInputGroup
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
prefix="$"
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
{...inputProps}
|
||||
/>;
|
||||
}
|
||||
return <FormInput prefix={local.showIcon ? "$" : null} inputMode="decimal" oninput={handleInput} {...inputProps} />;
|
||||
|
||||
}
|
||||
|
||||
export function FormPercentInput(props: FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
|
||||
export function FormPercentInput(props: {showIcon?: boolean} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["oninput", "showIcon"]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
if (!e || !e.currentTarget) return;
|
||||
@@ -436,20 +393,11 @@ export function FormPercentInput(props: FormInputProps) {
|
||||
}
|
||||
};
|
||||
|
||||
return <FormInputGroup
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
prefix="%"
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
{...inputProps}
|
||||
/>;
|
||||
return <FormInput prefix={local.showIcon ? "%" : null} inputMode="decimal" oninput={handleInput} {...inputProps} />;
|
||||
}
|
||||
|
||||
export function FormPhoneInput(props: FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
|
||||
export function FormPhoneInput(props: {showIcon?: boolean; icon?: string} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["showIcon", "icon", "oninput"]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
if (!e || !e.currentTarget) return;
|
||||
@@ -473,138 +421,45 @@ export function FormPhoneInput(props: FormInputProps) {
|
||||
}
|
||||
};
|
||||
|
||||
return <FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
placeholder="(555) 555-5555"
|
||||
{...inputProps}
|
||||
/>;
|
||||
}
|
||||
|
||||
// @TODO replace this with showIcon prop
|
||||
export function FormPhoneInputWithIcon(props: {icon?: string} & FormInputGroupProps) {
|
||||
const [local, inputProps] = splitProps(props, ["icon", "small", "ref", "class", "oninput"]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
if (!e || !e.currentTarget) return;
|
||||
const input = e.currentTarget;
|
||||
let digits = input.value.replace(/\D/g, "");
|
||||
digits = digits.slice(0, 10);
|
||||
let formatted = "";
|
||||
if (digits.length > 0) {
|
||||
formatted = "(" + digits.slice(0, 3);
|
||||
}
|
||||
if (digits.length > 3) {
|
||||
formatted += ") " + digits.slice(3, 6);
|
||||
}
|
||||
if (digits.length > 6) {
|
||||
formatted += "-" + digits.slice(6, 10);
|
||||
}
|
||||
input.value = formatted;
|
||||
if (typeof local.oninput === "function") {
|
||||
local.oninput(e);
|
||||
}
|
||||
};
|
||||
|
||||
const iconEl = <IconInline icon={local.icon || "phone"} size={16} />;
|
||||
|
||||
return <FormInputGroup
|
||||
prefix={iconEl}
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
placeholder="(555) 555-5555"
|
||||
{...inputProps}
|
||||
/>;
|
||||
return (
|
||||
<FormInput
|
||||
prefix={local.showIcon !== false ? <IconInline icon={local.icon || "phone"} size={16} /> : null}
|
||||
inputMode="numeric"
|
||||
oninput={handleInput}
|
||||
placeholder="(555) 555-5555"
|
||||
{...inputProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormEmailInput(props: {showIcon?: boolean} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["showIcon", "small", "ref", "class", "error", "success", "onDark"]);
|
||||
const showIcon = () => local.showIcon !== false;
|
||||
const pfxCls = () => _prefixCls(local.small, local.error, local.onDark);
|
||||
const inputCls = () => _inputCls(local.small, local.error, local.success, "rounded-l-none" + (local.class ? " " + local.class : ""), local.onDark);
|
||||
const [local, inputProps] = splitProps(props, ["showIcon"]);
|
||||
|
||||
return <Show when={showIcon()} fallback={
|
||||
return (
|
||||
<FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
prefix={local.showIcon !== false ? <IconInline icon="envelope" size={16} /> : null}
|
||||
type="email"
|
||||
inputMode="email"
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
error={local.error}
|
||||
success={local.success}
|
||||
onDark={local.onDark}
|
||||
{...inputProps}
|
||||
/>}>
|
||||
<div class="ui-form">
|
||||
<div class={INPUT_GROUP}>
|
||||
<span class={pfxCls()}>
|
||||
<IconInline icon="envelope" size={16} />
|
||||
</span>
|
||||
<span class="grow flex">
|
||||
<input
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="email"
|
||||
inputMode="email"
|
||||
class={inputCls()}
|
||||
{...inputProps}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<Show when={local.error}>
|
||||
<span class={FORM_ERROR}>{local.error}</span>
|
||||
</Show>
|
||||
<Show when={local.success}>
|
||||
<span class={FORM_SUCCESS}>{local.success}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>;
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormURLInput(props: {showIcon: boolean} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["showIcon", "small", "ref", "class", "onDark"]);
|
||||
const showIcon = () => local.showIcon !== false;
|
||||
const pfxCls = () => _prefixCls(local.small, false, local.onDark);
|
||||
const inputCls = () => _inputCls(local.small, false, false, "rounded-l-none" + (local.class ? " " + local.class : ""), local.onDark);
|
||||
export function FormURLInput(props: {showIcon?: boolean} & FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["showIcon"]);
|
||||
|
||||
return <Show when={showIcon()} fallback={
|
||||
return (
|
||||
<FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
prefix={local.showIcon !== false ? <IconInline icon="globe" size={16} /> : null}
|
||||
type="text"
|
||||
inputMode="url"
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
onDark={local.onDark}
|
||||
{...inputProps}
|
||||
/>}>
|
||||
<div class="ui-form">
|
||||
<div class={INPUT_GROUP}>
|
||||
<span class={pfxCls()}>
|
||||
<IconInline icon="globe" size={16} />
|
||||
</span>
|
||||
<span class="grow flex">
|
||||
<input
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
inputMode="url"
|
||||
class={inputCls()}
|
||||
{...inputProps}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Show>;
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormZipCodeInput(props: FormInputProps) {
|
||||
const [local, inputProps] = splitProps(props, ["small", "ref", "class", "oninput"]);
|
||||
const [local, inputProps] = splitProps(props, ["oninput"]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
if (!e || !e.currentTarget) return;
|
||||
@@ -617,18 +472,15 @@ export function FormZipCodeInput(props: FormInputProps) {
|
||||
return b ? a + "-" + b : a;
|
||||
});
|
||||
|
||||
if (typeof props.oninput === "function") {
|
||||
props.oninput(e);
|
||||
if (typeof local.oninput === "function") {
|
||||
local.oninput(e);
|
||||
}
|
||||
};
|
||||
|
||||
return <FormInput
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
oninput={handleInput}
|
||||
class={local.class}
|
||||
small={local.small}
|
||||
placeholder="12345"
|
||||
{...inputProps}
|
||||
/>;
|
||||
@@ -807,7 +659,6 @@ export function FormSignaturePad(props: { value?: () => string; onchange?: (svg:
|
||||
// @TODO Fix this
|
||||
export function FormSelect(props: FormSelectProps & { children?: JSXElement }) {
|
||||
const [local, selectProps] = splitProps(props, ["small", "ref", "class", "children", "value", "onDark"]);
|
||||
const cls = () => _inputCls(local.small, false, false, local.class, local.onDark);
|
||||
let selectEl;
|
||||
// Set value via deferred effect so it runs AFTER children (<option>s) are in the DOM.
|
||||
// The spread's createRenderEffect runs immediately (before children), so select.value
|
||||
@@ -823,7 +674,7 @@ export function FormSelect(props: FormSelectProps & { children?: JSXElement }) {
|
||||
return <div class="ui-form">
|
||||
<select
|
||||
ref={(el) => { selectEl = el; if (local.ref) local.ref(el); }}
|
||||
class={cls()}
|
||||
class={_inputCls(local.small, false, false, local.class, local.onDark)}
|
||||
{...selectProps}
|
||||
>{local.children}</select>
|
||||
</div>;
|
||||
@@ -967,24 +818,23 @@ export function FormSearchableSelect(props: {ref: string} & FormComboboxProps) {
|
||||
}
|
||||
|
||||
export function FormTextarea(props: FormTextareaProps) {
|
||||
const cls = () => _textareaCls(props.small, props.error, props.class, props.onDark);
|
||||
const inputValue = () => readAccessor(props.value, "");
|
||||
const [local, inputProps] = splitProps(props, [
|
||||
"small",
|
||||
"error",
|
||||
"class",
|
||||
"onDark",
|
||||
"ref",
|
||||
]);
|
||||
|
||||
const cls = () => _textareaCls(local.small, local.error, local.class, local.onDark);
|
||||
return <div class="ui-form">
|
||||
<textarea
|
||||
ref={(el) => typeof props.ref === "function" && props.ref(el)}
|
||||
ref={(el) => typeof local.ref === "function" && local.ref(el)}
|
||||
class={cls()}
|
||||
value={inputValue()}
|
||||
placeholder={props.placeholder}
|
||||
rows={props.rows}
|
||||
disabled={props.disabled}
|
||||
name={props.name}
|
||||
id={props.id}
|
||||
oninput={props.oninput}
|
||||
onchange={props.onchange}
|
||||
onblur={props.onblur}
|
||||
{...inputProps}
|
||||
/>
|
||||
<Show when={props.error}>
|
||||
<span class={FORM_ERROR}>{props.error}</span>
|
||||
<Show when={local.error}>
|
||||
<span class={FORM_ERROR}>{local.error}</span>
|
||||
</Show>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ interface PageContainerProps {
|
||||
children?: JSXElement;
|
||||
}
|
||||
|
||||
export function PageContainer(props: PageContainerProps) {
|
||||
export function PageContainer(props: PageContainerProps): JSXElement {
|
||||
return <div class="admin-page-container">{props.children}</div>;
|
||||
}
|
||||
|
||||
export function Divider() {
|
||||
export function Divider(): JSXElement {
|
||||
return <hr class="text-line mt-1 mb-3"/>;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ interface CodeBoxProps {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export function CodeBox(props: CodeBoxProps) {
|
||||
export function CodeBox(props: CodeBoxProps): JSXElement {
|
||||
return (
|
||||
<div class={"text-xs p-3 bg-neutral-800 text-neutral-100 border border-neutral-700 rounded-default " + (props.class || "")}>
|
||||
<pre><code>{props.code}</code></pre>
|
||||
@@ -28,11 +28,11 @@ export function CodeBox(props: CodeBoxProps) {
|
||||
}
|
||||
|
||||
interface PageHeaderProps {
|
||||
text: string;
|
||||
text: string | JSXElement;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export function PageHeader(props: PageHeaderProps) {
|
||||
export function PageHeader(props: PageHeaderProps): JSXElement {
|
||||
return (
|
||||
<header class={props.class || ""}>
|
||||
<div class="mt-1">
|
||||
@@ -50,7 +50,7 @@ interface PageLinkProps {
|
||||
children?: JSXElement;
|
||||
}
|
||||
|
||||
export function PageLink(props: PageLinkProps) {
|
||||
export function PageLink(props: PageLinkProps): JSXElement {
|
||||
return (
|
||||
<a
|
||||
href={props.href}
|
||||
@@ -60,7 +60,7 @@ export function PageLink(props: PageLinkProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function Loader(props: { class?: string; style?: string }) {
|
||||
export function Loader(props: { class?: string; style?: string }): JSXElement {
|
||||
return (
|
||||
<div class={"flex items-center justify-center p-8 " + (props.class || "")} style={props.style}>
|
||||
<div class="h-8 w-8 border-4 border-sky-700 border-t-transparent rounded-full animate-spin"></div>
|
||||
@@ -77,7 +77,7 @@ interface BreadcrumbsProps {
|
||||
items: BreadcrumbItem[];
|
||||
}
|
||||
|
||||
export function Breadcrumbs(props: BreadcrumbsProps) {
|
||||
export function Breadcrumbs(props: BreadcrumbsProps): JSXElement {
|
||||
return (
|
||||
<div class="flex flex-row items-center text-ink-faint text-xs">
|
||||
<For each={props.items}>{(crumb, index) => (
|
||||
@@ -102,7 +102,7 @@ interface ManagerPageHeaderProps {
|
||||
action?: JSXElement;
|
||||
}
|
||||
|
||||
export function ManagerPageHeader(props: ManagerPageHeaderProps) {
|
||||
export function ManagerPageHeader(props: ManagerPageHeaderProps): JSXElement {
|
||||
return (
|
||||
<div class="page-header">
|
||||
<div>
|
||||
|
||||
@@ -65,16 +65,16 @@ function resolveFAIcon(name: string, prefix: string): FAEntry | undefined {
|
||||
return FA_ICONS[prefix + ":" + name];
|
||||
}
|
||||
|
||||
export function Icon(props: IconProps) {
|
||||
const size = () => props.size ?? 16;
|
||||
export function Icon(props: IconProps): JSXElement {
|
||||
const size = props.size ?? 16;
|
||||
|
||||
const custom = () => customIcons[props.icon];
|
||||
const custom = customIcons[props.icon];
|
||||
// Style resolution: an explicit `prefix` wins; then a per-icon `solid`
|
||||
// override (true→solid, false→regular); otherwise the app-wide default set
|
||||
// by FA_DEFAULT_SOLID. The fallback chain lets any icon resolve regardless
|
||||
// of which style it actually ships in, so opting into solid never blanks.
|
||||
const faDef = (): FAEntry | undefined => {
|
||||
if (custom()) return undefined;
|
||||
if (custom) return undefined;
|
||||
let want: string;
|
||||
if (props.prefix) want = props.prefix;
|
||||
else if (props.solid === true) want = FA_PREFIX_SOLID;
|
||||
@@ -88,7 +88,7 @@ export function Icon(props: IconProps) {
|
||||
// viewBox as [minX, minY, width, height]: custom icons are 0-origin; FA icons
|
||||
// carry a viewBox cropped to the glyph so they render at their intended size.
|
||||
const box = (): [number, number, number, number] => {
|
||||
const c = custom();
|
||||
const c = custom;
|
||||
if (c) return [0, 0, c.viewBox[0], c.viewBox[1]];
|
||||
const fa = faDef();
|
||||
return fa ? [fa[0], fa[1], fa[2], fa[3]] : [0, 0, 512, 512];
|
||||
@@ -96,18 +96,18 @@ export function Icon(props: IconProps) {
|
||||
const vw = () => box()[2];
|
||||
const vh = () => box()[3];
|
||||
const svgContent = () => {
|
||||
const c = custom();
|
||||
const c = custom;
|
||||
if (c) return c.content;
|
||||
const fa = faDef();
|
||||
return fa ? '<path d="' + fa[4] + '"/>' : "";
|
||||
};
|
||||
|
||||
const h = (): number => {
|
||||
const s = size();
|
||||
const s = size;
|
||||
return Array.isArray(s) ? (s[1] ?? s[0]) : s;
|
||||
};
|
||||
const w = (): number => {
|
||||
const s = size();
|
||||
const s = size;
|
||||
return Array.isArray(s) ? s[0] : Math.round(h() * (vw() / vh()));
|
||||
};
|
||||
|
||||
@@ -116,22 +116,18 @@ export function Icon(props: IconProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function IconInline(props: IconProps) {
|
||||
export function IconInline(props: IconProps): JSXElement {
|
||||
return <Icon icon={props.icon} size={props.size} prefix={props.prefix} solid={props.solid} class={ICON_INLINE + " " + (props.class || "")}/>;
|
||||
}
|
||||
|
||||
export function IconSuccess(props: IconProps) {
|
||||
export function IconSuccess(props: IconProps): JSXElement {
|
||||
return <Icon icon={props.icon} size={props.size} prefix={props.prefix} solid={props.solid} class={ICON_INLINE + " text-green-600 dark:text-green-400 " + (props.class || "")}/>;
|
||||
}
|
||||
|
||||
export function IconError(props: IconProps) {
|
||||
export function IconError(props: IconProps): JSXElement {
|
||||
return <Icon icon={props.icon} size={props.size} prefix={props.prefix} solid={props.solid} class={ICON_INLINE + " text-red-600 dark:text-red-400 " + (props.class || "")}/>;
|
||||
}
|
||||
|
||||
interface IconContainerProps {
|
||||
children?: JSXElement;
|
||||
}
|
||||
|
||||
export function IconContainer(props: IconContainerProps) {
|
||||
export function IconContainer(props: {children?: JSXElement}): JSXElement {
|
||||
return <span class="flex flex-row items-center gap-2">{props.children}</span>;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const LAYOUT_STICKY_FULL = "sticky top-0 h-screen overflow-y-auto";
|
||||
// viewport edge. Scoped to lg, where the sidebar/gap exists.
|
||||
const LAYOUT_MAIN = "col-span-1 min-w-0 lg:col-span-10 lg:pr-8";
|
||||
|
||||
interface SidebarNavItem {
|
||||
export interface SidebarNavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: JSXElement;
|
||||
|
||||
@@ -21,7 +21,7 @@ export function createValidation(validation: Validation<string>): Accessor<strin
|
||||
|
||||
return createMemo(() => {
|
||||
if (!touched()[id] || (field() && !fieldBlur()) || (isValidFunc && isValidFunc(field()))) return "";
|
||||
if (required && !field()) return `${name} is required`;
|
||||
if (!field()) return required ? `${name} is required` : "";
|
||||
if (isValidFunc && !isValidFunc(fieldBlur())) return invalidMsg ?? `${name} is invalid`;
|
||||
return "";
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user