Many inputs added

This commit is contained in:
2025-07-20 23:33:21 -04:00
parent f84f0a0ebc
commit cb8c2c23be
13 changed files with 335 additions and 16 deletions

View File

@@ -1,10 +1,18 @@
import OptionInput from "./input/OptionInput"; import DragAndDropFileInput from "./input/file/DragAndDropFileInput";
import SelectInput from "./input/SelectInput"; import FileInput from "./input/file/FileInput";
import TextInput from "./input/TextInput"; import NumberInput from "./input/number/NumberInput";
import OptionInput from "./input/text/OptionInput";
import SelectInput from "./input/text/SelectInput";
import TextInput from "./input/text/TextInput";
export { export {
DragAndDropFileInput,
FileInput,
NumberInput,
//NumberSlider,
OptionInput, OptionInput,
SelectInput, SelectInput,
TextInput TextInput
}; };

View File

@@ -0,0 +1,20 @@
import { useRef } from "react";
export default function DragAndDropFileInput(){
const inputRef = useRef<HTMLInputElement>(null);
return (
<div
className="relative border-2 rounded-lg w-full h-full cursor-pointer"
>
<input
ref={inputRef}
type="file"
className="sr-only"
/>
Drag And Drop File Input
</div>
);
}

View File

@@ -0,0 +1,85 @@
import { SecondaryButton } from "$/component/button";
import type { FileInputProps } from "$/types/Input";
import { humanReadableBytes } from "$/util/FileUtil";
import clsx from "clsx";
import { useRef, useState } from "react";
export default function FileInput({
id,
className,
name,
minSize,
maxSize,
showFileName,
showSize,
onChange,
disabled,
children
}: FileInputProps){
const inputRef = useRef<HTMLInputElement>(null);
const [ file, setFile ] = useState<File>();
return (
<div
className="flex flex-row items-center justify-between w-full border-2 rounded-lg"
>
<input
ref={inputRef}
id={id}
type="file"
className={clsx(
"sr-only",
className
)}
name={name}
onChange={(e) => { setFile(e.target.files?.[0]); onChange?.(e.target.files?.[0]); }}
disabled={disabled}
/>
<div
className="flex flex-row items-center justify-between grow w-full px-2"
>
{
children && !showFileName &&
<div>{children}</div>
}
{
showFileName &&
<div>{file?.name}</div>
}
{
!children && !showFileName &&
<>&nbsp;</>
}
{
showSize &&
<div
className={clsx(
{
"text-red-600": minSize && file?.size && file?.size < minSize,
"text-red-600 ": maxSize && file?.size && file?.size > maxSize,
"text-green-600": minSize && !maxSize && file?.size && file?.size > minSize,
"text-green-600 ": !minSize && maxSize && file?.size && file?.size < maxSize,
" text-green-600": minSize && maxSize && file?.size && file?.size > minSize && file?.size < maxSize
}
)}
>
{humanReadableBytes(file?.size ?? 0)}
</div>
}
</div>
<div
className="border-l-2"
>
<SecondaryButton
className="text-nowrap rounded-r-lg"
rounding="none"
onClick={() => { inputRef.current?.click(); }}
>
Click Me
</SecondaryButton>
</div>
</div>
);
}

View File

@@ -0,0 +1,58 @@
import type { NumberInputProps } from "$/types/Input";
import clsx from "clsx";
export default function NumberInput({
id,
className,
inputClassName,
labelClassName,
name,
min,
max,
defaultValue,
value,
onChange,
disabled,
children
}: NumberInputProps){
return (
<div
className={clsx(
"flex flex-row items-center justify-center rounded-lg border-2 w-full",
className
)}
>
<div
className="relative flex flex-row items-center justify-center px-2 py-1 w-full"
>
<input
type="number"
id={id}
className={clsx(
"peer bg-transparent outline-none placeholder-transparent w-full",
"[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
inputClassName
)}
name={name}
min={min}
max={max}
defaultValue={defaultValue}
value={value}
onChange={(e) => onChange?.(e.target.valueAsNumber)}
disabled={disabled}
/>
<label
className={clsx(
"absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default",
labelClassName
)}
style={{ transitionProperty: "top, left, font-size, line-height", transitionTimingFunction: "cubic-bezier(0.4 0, 0.2, 1)", transitionDuration: "250ms" }}
htmlFor={id}
>
{children}
</label>
</div>
</div>
);
}

View File

@@ -0,0 +1,36 @@
import type { NumberSliderProps } from "$/types/Input";
import clsx from "clsx";
export default function NumberSlider({
id,
className,
name,
min,
max,
step,
defaultValue,
value,
onChange,
disabled
}: NumberSliderProps){
return (
<input
type="range"
id={id}
className={clsx(
"w-full appearance-none [-moz-range-thumb:background:#04AA6D]",
"h-6 bg-blue-300 accent-blue-600",
className
)}
name={name}
min={min}
max={max}
step={step}
defaultValue={defaultValue}
value={value}
onChange={(e) => onChange?.(e.target.valueAsNumber)}
disabled={disabled}
/>
);
}

View File

@@ -7,6 +7,7 @@ export default function TextArea({
className, className,
inputClassName, inputClassName,
labelClassName, labelClassName,
name,
maxLength, maxLength,
rows, rows,
cols, cols,
@@ -20,20 +21,21 @@ export default function TextArea({
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-row items-center justify-center rounded-lg border-2", "flex flex-row items-center justify-center rounded-lg border-2 w-full",
className className
)} )}
> >
<div <div
className="relative flex flex-row items-center justify-center px-2 py-1" className="relative flex flex-row items-center justify-center px-2 py-1 w-full"
> >
<textarea <textarea
id={id} id={id}
className={clsx( className={clsx(
"peer bg-transparent outline-none placeholder-transparent resize", "peer bg-transparent outline-none placeholder-transparent w-full",
inputClassName inputClassName
)} )}
placeholder={placeholder} placeholder={placeholder}
name={name}
maxLength={maxLength} maxLength={maxLength}
rows={rows} rows={rows}
cols={cols} cols={cols}
@@ -46,7 +48,7 @@ export default function TextArea({
<label <label
className={clsx( className={clsx(
"absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default", "absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default",
"peer-placeholder-shown:top-0 peer-placeholder-shown:-left-1 peer-placeholder-shown:text-inherit peer-placeholder-shown:text-base peer-placeholder-shown:bg-transparent peer-placeholder-shown:cursor-text", "peer-placeholder-shown:top-0 peer-placeholder-shown:-left-1 peer-placeholder-shown:text-inherit peer-placeholder-shown:text-base peer-placeholder-shown:bg-transparent peer-placeholder-shown:cursor-text peer-placeholder-shown:w-[99%]",
"flex items-center", "flex items-center",
labelClassName labelClassName
)} )}

View File

@@ -7,6 +7,7 @@ export default function TextInput({
className, className,
inputClassName, inputClassName,
labelClassName, labelClassName,
name,
maxLength, maxLength,
spellCheck, spellCheck,
placeholder, placeholder,
@@ -18,20 +19,21 @@ export default function TextInput({
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-row items-center justify-center rounded-lg border-2", "flex flex-row items-center justify-center rounded-lg border-2 w-full",
className className
)} )}
> >
<div <div
className="relative flex flex-row items-center justify-center px-2 py-1" className="relative flex flex-row items-center justify-center px-2 py-1 w-full"
> >
<input <input
type="text" type="text"
id={id} id={id}
className={clsx( className={clsx(
"peer bg-transparent outline-none placeholder-transparent", "peer bg-transparent outline-none placeholder-transparent w-full",
inputClassName inputClassName
)} )}
name={name}
placeholder={placeholder} placeholder={placeholder}
maxLength={maxLength} maxLength={maxLength}
defaultValue={defaultValue} defaultValue={defaultValue}
@@ -43,7 +45,7 @@ export default function TextInput({
<label <label
className={clsx( className={clsx(
"absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default", "absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default",
"peer-placeholder-shown:top-0 peer-placeholder-shown:-left-1 peer-placeholder-shown:text-inherit peer-placeholder-shown:text-base peer-placeholder-shown:bg-transparent peer-placeholder-shown:h-full peer-placeholder-shown:cursor-text", "peer-placeholder-shown:top-0 peer-placeholder-shown:-left-1 peer-placeholder-shown:text-inherit peer-placeholder-shown:text-base peer-placeholder-shown:bg-transparent peer-placeholder-shown:h-full peer-placeholder-shown:cursor-text peer-placeholder-shown:w-[99%]",
"flex items-center", "flex items-center",
labelClassName labelClassName
)} )}

44
lib/types/Input.d.ts vendored
View File

@@ -7,6 +7,7 @@ export interface TextInputProps {
className?: string; className?: string;
inputClassName?: string; inputClassName?: string;
labelClassName?: string; labelClassName?: string;
name?: string;
maxLength?: number; maxLength?: number;
spellCheck?: boolean; spellCheck?: boolean;
placeholder?: string; placeholder?: string;
@@ -67,3 +68,46 @@ export interface MattrixwvButtonSwitchProps {
onNode: React.ReactNode; onNode: React.ReactNode;
offNode: React.ReactNode; offNode: React.ReactNode;
} }
export interface NumberInputProps {
id?: string;
className?: string;
inputClassName?: string;
labelClassName?: string;
name?: string;
min?: number;
max?: number;
defaultValue?: number;
value?: number;
onChange?: (newValue: number) => void;
disabled?: boolean;
children?: React.ReactNode;
}
export interface NumberSliderProps {
id?: string;
className?: string;
name?: string;
min?: number;
max?: number;
step?: number;
defaultValue?: number;
value?: number;
onChange?: (newValue: number) => void;
disabled?: boolean;
}
export interface FileInputProps {
id?: string;
className?: string;
name?: string;
minSize?: number;
maxSize?: number;
showFileName?: boolean;
showSize?: boolean;
defaultValue?: File;
value?: File;
onChange?: (newFile: File | undefined) => void;
disabled?: boolean;
children?: React.ReactNode;
}

9
lib/util/FileUtil.ts Normal file
View File

@@ -0,0 +1,9 @@
export function humanReadableBytes(bytes: number, decimals: number = 2): string{
if(bytes === 0){
return "0 Bytes";
}
const sizes = [ "Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
const power = Math.floor(Math.log(bytes) / Math.log(1024));
return `${parseFloat((bytes / Math.pow(1024, power)).toFixed(decimals))} ${sizes[power]}`;
}

View File

@@ -1,6 +1,6 @@
import { MattrixwvTabGroup } from "$/component/tab"; import { MattrixwvTabGroup } from "$/component/tab";
import type { TabGroupContent } from "$/types/Tab"; import type { TabGroupContent } from "$/types/Tab";
import { SwitchContent, TextContent } from "@/util/InputUtils"; import { FileContent, SwitchContent, TextContent } from "@/util/InputUtils";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
@@ -11,6 +11,7 @@ export const Route = createFileRoute('/input/')({
function InputPage(){ function InputPage(){
const tabs: TabGroupContent[] = [ const tabs: TabGroupContent[] = [
{ tab: "Input", content: <FileContent/> },
{ tab: "Text", content: <TextContent/> }, { tab: "Text", content: <TextContent/> },
{ tab: "Switch", content: <SwitchContent/> } { tab: "Switch", content: <SwitchContent/> }
]; ];

View File

@@ -1,5 +1,5 @@
import { Button } from "$/component/button"; import { Button } from "$/component/button";
import { OptionInput, SelectInput, TextInput } from "$/component/input"; import { DragAndDropFileInput, FileInput, NumberInput, OptionInput, SelectInput, TextInput } from "$/component/input";
import ButtonSwitch from "$/component/input/switch/ButtonSwitch"; import ButtonSwitch from "$/component/input/switch/ButtonSwitch";
import DangerSwitch from "$/component/input/switch/DangerSwitch"; import DangerSwitch from "$/component/input/switch/DangerSwitch";
import DarkSwitch from "$/component/input/switch/DarkSwitch"; import DarkSwitch from "$/component/input/switch/DarkSwitch";
@@ -11,7 +11,7 @@ import SuccessDangerSwitch from "$/component/input/switch/SuccessDangerSwitch";
import SuccessSwitch from "$/component/input/switch/SuccessSwitch"; import SuccessSwitch from "$/component/input/switch/SuccessSwitch";
import TertiarySwitch from "$/component/input/switch/TertiarySwitch"; import TertiarySwitch from "$/component/input/switch/TertiarySwitch";
import WarningSwitch from "$/component/input/switch/WarningSwitch"; import WarningSwitch from "$/component/input/switch/WarningSwitch";
import TextArea from "$/component/input/TextArea"; import TextArea from "$/component/input/text/TextArea";
import type { MattrixwvSwitchSize } from "$/types/Input"; import type { MattrixwvSwitchSize } from "$/types/Input";
import { useState } from "react"; import { useState } from "react";
import { BsCheck, BsX } from "react-icons/bs"; import { BsCheck, BsX } from "react-icons/bs";
@@ -460,13 +460,13 @@ export function TextContent(){
return ( return (
<div <div
className="flex flex-col items-center justify-center gap-y-8 mt-8" className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full"
> >
<TextDisplay title="Text Input"> <TextDisplay title="Text Input">
<TextInput placeholder="Text Input" labelClassName="bg-(--bg-color)"/> <TextInput placeholder="Text Input" labelClassName="bg-(--bg-color)"/>
</TextDisplay> </TextDisplay>
<TextDisplay title="Text Area"> <TextDisplay title="Text Area">
<TextArea placeholder="Textarea" labelClassName="bg-(--bg-color)"/> <TextArea placeholder="Textarea" className="resize" labelClassName="bg-(--bg-color)"/>
</TextDisplay> </TextDisplay>
<TextDisplay title="Select"> <TextDisplay title="Select">
<SelectInput label={selected.label} onChange={(newValue) => setSelected(selectOptions.find((option) => option.value === newValue) || selectOptions[0])}> <SelectInput label={selected.label} onChange={(newValue) => setSelected(selectOptions.find((option) => option.value === newValue) || selectOptions[0])}>
@@ -482,6 +482,25 @@ export function TextContent(){
} }
</SelectInput> </SelectInput>
</TextDisplay> </TextDisplay>
<TextDisplay
title="Number Input"
>
<NumberInput
labelClassName="bg-(--bg-color)"
>
Number Test
</NumberInput>
</TextDisplay>
{/*
<TextDisplay
title="Number Slider"
>
<NumberSlider
min={0}
max={10}
/>
</TextDisplay>
*/}
</div> </div>
); );
} }
@@ -502,3 +521,38 @@ function TextDisplay({
</div> </div>
); );
} }
export function FileContent(){
return (
<div
className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full"
>
<FileDisplay title="File Input">
<FileInput
showFileName={true}
showSize={true}
/>
</FileDisplay>
<FileDisplay title="Drag and Drop File Input">
<DragAndDropFileInput/>
</FileDisplay>
</div>
);
}
function FileDisplay({
title,
children
}:{
title: React.ReactNode;
children: React.ReactNode;
}){
return (
<div
className="flex flex-col items-center justify-center gap-y-2 w-full"
>
<h2 className="text-2xl">{title}</h2>
{children}
</div>
);
}