admin ui: started color centralization + made sidebar resizable

This commit is contained in:
dswbx
2025-03-18 10:56:39 +01:00
parent ea2aa7c76c
commit f6996b1953
13 changed files with 286 additions and 92 deletions

View File

@@ -18,13 +18,7 @@ const Base: React.FC<AlertProps> = ({
...props
}) =>
visible ? (
<div
{...props}
className={twMerge(
"flex flex-row items-center dark:bg-amber-300/20 bg-amber-200 p-4",
className,
)}
>
<div {...props} className={twMerge("flex flex-row items-center p-4", className)}>
<p>
{title && <b>{title}: </b>}
{message || children}
@@ -33,19 +27,19 @@ const Base: React.FC<AlertProps> = ({
) : null;
const Warning: React.FC<AlertProps> = ({ className, ...props }) => (
<Base {...props} className={twMerge("dark:bg-amber-300/20 bg-amber-200", className)} />
<Base {...props} className={twMerge("bg-warning text-warning-foreground", className)} />
);
const Exception: React.FC<AlertProps> = ({ className, ...props }) => (
<Base {...props} className={twMerge("dark:bg-red-950 bg-red-100", className)} />
<Base {...props} className={twMerge("bg-error text-error-foreground", className)} />
);
const Success: React.FC<AlertProps> = ({ className, ...props }) => (
<Base {...props} className={twMerge("dark:bg-green-950 bg-green-100", className)} />
<Base {...props} className={twMerge("bg-success text-success-foreground", className)} />
);
const Info: React.FC<AlertProps> = ({ className, ...props }) => (
<Base {...props} className={twMerge("dark:bg-blue-950 bg-blue-100", className)} />
<Base {...props} className={twMerge("bg-info text-info-foreground", className)} />
);
export const Alert = {

View File

@@ -3,7 +3,6 @@ import { getBrowser } from "core/utils";
import type { Field } from "data";
import { Switch as RadixSwitch } from "radix-ui";
import {
type ChangeEventHandler,
type ComponentPropsWithoutRef,
type ElementType,
forwardRef,
@@ -12,7 +11,7 @@ import {
useRef,
useState,
} from "react";
import { TbCalendar, TbChevronDown, TbInfoCircle } from "react-icons/tb";
import { TbCalendar, TbChevronDown, TbEye, TbEyeOff, TbInfoCircle } from "react-icons/tb";
import { twMerge } from "tailwind-merge";
import { IconButton } from "ui/components/buttons/IconButton";
import { useEvent } from "ui/hooks/use-event";
@@ -89,7 +88,7 @@ export const Input = forwardRef<HTMLInputElement, React.ComponentProps<"input">>
{...props}
ref={ref}
className={twMerge(
"bg-muted/40 h-11 rounded-md py-2.5 px-4 outline-none",
"bg-muted/40 h-11 rounded-md py-2.5 px-4 outline-none w-full",
disabledOrReadonly && "bg-muted/50 text-primary/50",
!disabledOrReadonly &&
"focus:bg-muted focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all",
@@ -99,6 +98,40 @@ export const Input = forwardRef<HTMLInputElement, React.ComponentProps<"input">>
);
});
export const TypeAwareInput = forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
(props, ref) => {
if (props.type === "password") {
return <Password {...props} ref={ref} />;
}
return <Input {...props} ref={ref} />;
},
);
export const Password = forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
(props, ref) => {
const [visible, setVisible] = useState(false);
function handleToggle() {
setVisible((v) => !v);
}
return (
<div className="relative w-full">
<Input {...props} type={visible ? "text" : "password"} className="w-full" ref={ref} />
<div className="absolute right-3 top-0 bottom-0 flex items-center">
<IconButton
Icon={visible ? TbEyeOff : TbEye}
onClick={handleToggle}
variant="ghost"
className="opacity-70"
/>
</div>
</div>
);
},
);
export const Textarea = forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
(props, ref) => {
return (

View File

@@ -1,5 +1,5 @@
import type { JsonSchema } from "json-schema-library";
import type { ChangeEvent, ComponentPropsWithoutRef } from "react";
import type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from "react";
import ErrorBoundary from "ui/components/display/ErrorBoundary";
import * as Formy from "ui/components/form/Formy";
import { useEvent } from "ui/hooks/use-event";
@@ -13,6 +13,7 @@ export type FieldProps = {
onChange?: (e: ChangeEvent<any>) => void;
placeholder?: string;
disabled?: boolean;
inputProps?: Partial<FieldComponentProps>;
} & Omit<FieldwrapperProps, "children" | "schema">;
export const Field = (props: FieldProps) => {
@@ -31,7 +32,14 @@ const fieldErrorBoundary =
</Pre>
);
const FieldImpl = ({ name, onChange, placeholder, required: _required, ...props }: FieldProps) => {
const FieldImpl = ({
name,
onChange,
placeholder,
required: _required,
inputProps,
...props
}: FieldProps) => {
const { path, setValue, schema, ...ctx } = useDerivedFieldContext(name);
const required = typeof _required === "boolean" ? _required : ctx.required;
//console.log("Field", { name, path, schema });
@@ -64,6 +72,7 @@ const FieldImpl = ({ name, onChange, placeholder, required: _required, ...props
return (
<FieldWrapper name={name} required={required} schema={schema} {...props}>
<FieldComponent
{...inputProps}
schema={schema}
name={name}
required={required}
@@ -81,10 +90,12 @@ export const Pre = ({ children }) => (
</pre>
);
export const FieldComponent = ({
schema,
..._props
}: { schema: JsonSchema } & ComponentPropsWithoutRef<"input">) => {
export type FieldComponentProps = {
schema: JsonSchema;
render?: (props: Omit<FieldComponentProps, "render">) => ReactNode;
} & ComponentPropsWithoutRef<"input">;
export const FieldComponent = ({ schema, render, ..._props }: FieldComponentProps) => {
const { value } = useFormValue(_props.name!, { strict: true });
if (!isTypeSchema(schema)) return null;
const props = {
@@ -97,6 +108,8 @@ export const FieldComponent = ({
: "",
};
if (render) return render({ schema, ...props });
if (schema.enum) {
return <Formy.Select id={props.name} options={schema.enum} {...(props as any)} />;
}
@@ -158,5 +171,7 @@ export const FieldComponent = ({
}
}
return <Formy.Input id={props.name} {...props} value={props.value ?? ""} {...additional} />;
return (
<Formy.TypeAwareInput id={props.name} {...props} value={props.value ?? ""} {...additional} />
);
};