Updates
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import React, {
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
HTMLAttributes,
|
||||
ReactNode,
|
||||
type ComponentProps,
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import CheckMarkSVG from "../svgs/CheckMarkSVG";
|
||||
@@ -36,6 +36,7 @@ export type CheckboxProps = Omit<
|
||||
noLabel?: boolean;
|
||||
size?: number;
|
||||
changeHandler?: (value: boolean) => void;
|
||||
clickDispatchHandler?: (value: boolean) => void;
|
||||
info?: string | ReactNode;
|
||||
wrapperWrapperProps?: ComponentProps<typeof Stack>;
|
||||
};
|
||||
@@ -61,6 +62,7 @@ export default function Checkbox({
|
||||
wrapperWrapperProps,
|
||||
noLabel,
|
||||
title,
|
||||
clickDispatchHandler,
|
||||
...props
|
||||
}: CheckboxProps) {
|
||||
const finalSize = size || 20;
|
||||
@@ -95,9 +97,10 @@ export default function Checkbox({
|
||||
wrapperClassName,
|
||||
wrapperProps?.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
setChecked(!checked);
|
||||
externalSetChecked?.(!checked);
|
||||
clickDispatchHandler?.(!checked);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import Button from "../layout/Button";
|
||||
import Stack from "../layout/Stack";
|
||||
import { FileArchive, FilePlus2, X } from "lucide-react";
|
||||
import React, { ComponentProps, DetailedHTMLProps, ReactNode } from "react";
|
||||
import React, {
|
||||
type ComponentProps,
|
||||
type DetailedHTMLProps,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import Card from "../elements/Card";
|
||||
import Span from "../layout/Span";
|
||||
import Center from "../layout/Center";
|
||||
import { FileInputToBase64FunctionReturn } from "../utils/form/fileInputToBase64";
|
||||
import type { FileInputToBase64FunctionReturn } from "../utils/form/fileInputToBase64";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import fileInputToBase64 from "../utils/form/fileInputToBase64";
|
||||
import Row from "../layout/Row";
|
||||
@@ -149,7 +153,7 @@ export default function FileUpload({
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const fileObj = await fileInputToBase64({
|
||||
inputFile: file,
|
||||
inputFile: file!,
|
||||
});
|
||||
filesArr.push(fileObj);
|
||||
}
|
||||
@@ -330,7 +334,7 @@ export default function FileUpload({
|
||||
}
|
||||
});
|
||||
} else {
|
||||
inputFile = e.dataTransfer.files?.[0];
|
||||
inputFile = e.dataTransfer.files?.[0]!;
|
||||
}
|
||||
|
||||
if (!inputFile) return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from "lodash";
|
||||
import { DetailedHTMLProps, FormHTMLAttributes, RefObject } from "react";
|
||||
import type { DetailedHTMLProps, FormHTMLAttributes, RefObject } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props<T extends { [key: string]: any } = { [key: string]: any }> =
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Button from "../layout/Button";
|
||||
import Stack from "../layout/Stack";
|
||||
import { ImagePlus, X } from "lucide-react";
|
||||
import React, { DetailedHTMLProps } from "react";
|
||||
import React, { type DetailedHTMLProps } from "react";
|
||||
import Card from "../elements/Card";
|
||||
import Span from "../layout/Span";
|
||||
import Center from "../layout/Center";
|
||||
import imageInputToBase64, {
|
||||
ImageInputToBase64FunctionReturn,
|
||||
type ImageInputToBase64FunctionReturn,
|
||||
} from "../utils/form/imageInputToBase64";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Tag from "../elements/Tag";
|
||||
@@ -20,6 +20,9 @@ type ImageUploadProps = DetailedHTMLProps<
|
||||
onChangeHandler?: (
|
||||
imgData: ImageInputToBase64FunctionReturn | undefined,
|
||||
) => any;
|
||||
multipleOnChangeHandler?: (
|
||||
imgData: ImageInputToBase64FunctionReturn[] | undefined,
|
||||
) => any;
|
||||
fileInputProps?: DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
@@ -50,6 +53,7 @@ type ImageUploadProps = DetailedHTMLProps<
|
||||
setImgURL?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
externalImage?: ImageInputToBase64FunctionReturn;
|
||||
restoreImageFn?: () => void;
|
||||
upload_only?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -71,6 +75,8 @@ export default function ImageUpload({
|
||||
restoreImageFn,
|
||||
setLoading,
|
||||
setImgURL,
|
||||
multipleOnChangeHandler,
|
||||
upload_only,
|
||||
...props
|
||||
}: ImageUploadProps) {
|
||||
const [imageObject, setImageObject] = React.useState<
|
||||
@@ -119,7 +125,7 @@ export default function ImageUpload({
|
||||
});
|
||||
imgArr.push(fileObj);
|
||||
}
|
||||
|
||||
multipleOnChangeHandler?.(imgArr);
|
||||
externalSetImages?.(imgArr);
|
||||
setLoading?.(false);
|
||||
})();
|
||||
@@ -208,43 +214,50 @@ export default function ImageUpload({
|
||||
<Span size="smaller" variant="faded">
|
||||
{label || "Click to Upload Image"}
|
||||
</Span>
|
||||
<Stack className="cancel-upload w-full items-stretch gap-1">
|
||||
<Input
|
||||
placeholder="Eg. https://example.com/img.png"
|
||||
className="text-sm twui-image-url-input"
|
||||
title="Enter Image URL"
|
||||
wrapperWrapperProps={{ className: "mt-2" }}
|
||||
changeHandler={(value) => {
|
||||
imageUrlRef.current = value;
|
||||
}}
|
||||
showLabel
|
||||
/>
|
||||
<Button
|
||||
title="Restore Image Button"
|
||||
size="smaller"
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
if (!imageUrlRef.current) return;
|
||||
setSrc(imageUrlRef.current);
|
||||
}}
|
||||
>
|
||||
Set Image URL
|
||||
</Button>
|
||||
</Stack>
|
||||
{existingImageUrl && (
|
||||
<Button
|
||||
title="Restore Image Button"
|
||||
size="smaller"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
restoreImageFn?.() ||
|
||||
setSrc(existingImageUrl);
|
||||
}}
|
||||
className="cancel-upload"
|
||||
>
|
||||
Restore Original Image
|
||||
</Button>
|
||||
{upload_only ? null : (
|
||||
<>
|
||||
<Stack className="cancel-upload w-full items-stretch gap-1">
|
||||
<Input
|
||||
placeholder="URL Eg. https://example.com/img.png"
|
||||
className="text-sm twui-image-url-input"
|
||||
title="Enter Image URL"
|
||||
wrapperWrapperProps={{
|
||||
className: "mt-2",
|
||||
}}
|
||||
changeHandler={(value) => {
|
||||
imageUrlRef.current = value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="Restore Image Button"
|
||||
size="smaller"
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
if (!imageUrlRef.current)
|
||||
return;
|
||||
setSrc(imageUrlRef.current);
|
||||
}}
|
||||
className="opacity-70"
|
||||
>
|
||||
Set Image URL
|
||||
</Button>
|
||||
</Stack>
|
||||
{existingImageUrl && (
|
||||
<Button
|
||||
title="Restore Image Button"
|
||||
size="smaller"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
restoreImageFn?.() ||
|
||||
setSrc(existingImageUrl);
|
||||
}}
|
||||
className="cancel-upload"
|
||||
>
|
||||
Restore Original Image
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Center>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import Row from "../../layout/Row";
|
||||
import { Info, Minus, Plus } from "lucide-react";
|
||||
import twuiNumberfy from "../../utils/numberfy";
|
||||
import { InputProps } from ".";
|
||||
import type { InputProps } from ".";
|
||||
|
||||
let pressInterval: any;
|
||||
let pressTimeout: any;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React, {
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
InputHTMLAttributes,
|
||||
LabelHTMLAttributes,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
TextareaHTMLAttributes,
|
||||
type ComponentProps,
|
||||
type DetailedHTMLProps,
|
||||
type InputHTMLAttributes,
|
||||
type LabelHTMLAttributes,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
type TextareaHTMLAttributes,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Span from "../../layout/Span";
|
||||
import { Eye, EyeOff, Info, InfoIcon, X } from "lucide-react";
|
||||
import { Copy, Eye, EyeOff, Info, InfoIcon, X } from "lucide-react";
|
||||
import { AutocompleteOptions } from "../../types";
|
||||
import twuiNumberfy from "../../utils/numberfy";
|
||||
import Dropdown from "../../elements/Dropdown";
|
||||
@@ -19,7 +19,7 @@ import NumberInputButtons from "./NumberInputButtons";
|
||||
import twuiSlugToNormalText from "../../utils/slug-to-normal-text";
|
||||
import Row from "../../layout/Row";
|
||||
import Paper from "../../elements/Paper";
|
||||
import { TWUISelectValidityObject } from "../Select";
|
||||
import type { TWUISelectValidityObject } from "../Select";
|
||||
|
||||
export type InputProps<KeyType extends string> = Omit<
|
||||
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
||||
@@ -75,6 +75,7 @@ export type InputProps<KeyType extends string> = Omit<
|
||||
HTMLDivElement
|
||||
>;
|
||||
// refreshDefaultValue?: number;
|
||||
copy?: boolean;
|
||||
};
|
||||
|
||||
let refreshes = 0;
|
||||
@@ -117,6 +118,7 @@ export default function Input<KeyType extends string>(
|
||||
clearInputProps,
|
||||
rawNumber,
|
||||
// refreshDefaultValue,
|
||||
copy,
|
||||
...props
|
||||
} = inputProps;
|
||||
|
||||
@@ -163,7 +165,7 @@ export default function Input<KeyType extends string>(
|
||||
numberText ? "text" : props.type,
|
||||
);
|
||||
|
||||
const DEFAULT_DEBOUNCE = 500;
|
||||
const DEFAULT_DEBOUNCE = 200;
|
||||
const finalDebounce = debounce || DEFAULT_DEBOUNCE;
|
||||
const finalLabel =
|
||||
label ||
|
||||
@@ -225,11 +227,12 @@ export default function Input<KeyType extends string>(
|
||||
}
|
||||
};
|
||||
|
||||
const isControlled = props.value !== undefined && props.value !== null;
|
||||
|
||||
React.useEffect(() => {
|
||||
// if (!existingReady) return;
|
||||
if (typeof props.value !== "string" || !props.value.match(/./)) return;
|
||||
setValue(String(props.value));
|
||||
}, [props.value]);
|
||||
if (!isControlled) return;
|
||||
setValue(String(props.value ?? ""));
|
||||
}, [props.value, isControlled]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (!refreshDefaultValue) return;
|
||||
@@ -290,8 +293,8 @@ export default function Input<KeyType extends string>(
|
||||
onChange={handleValueChange}
|
||||
autoComplete={autoComplete}
|
||||
rows={props.height ? Number(props.height) : props.rows || 2}
|
||||
defaultValue={defaultInitialValue}
|
||||
value={props.value ? getFinalValue(props.value) : undefined}
|
||||
defaultValue={isControlled ? undefined : defaultInitialValue}
|
||||
value={isControlled ? getFinalValue(props.value) : undefined}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
@@ -318,9 +321,9 @@ export default function Input<KeyType extends string>(
|
||||
}}
|
||||
onChange={handleValueChange}
|
||||
type={inputType}
|
||||
defaultValue={defaultInitialValue}
|
||||
defaultValue={isControlled ? undefined : defaultInitialValue}
|
||||
autoComplete={autoComplete}
|
||||
value={props.value ? getFinalValue(props.value) : undefined}
|
||||
value={isControlled ? getFinalValue(props.value) : undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -393,7 +396,29 @@ export default function Input<KeyType extends string>(
|
||||
|
||||
{targetComponent}
|
||||
|
||||
{props.type == "search" || props.readOnly ? null : (
|
||||
{copy ? (
|
||||
<>
|
||||
<div
|
||||
title="Copy value"
|
||||
className="cursor-pointer opacity-50 hover:opacity-100"
|
||||
onClick={() => {
|
||||
const text =
|
||||
inputRef.current.value ||
|
||||
textAreaRef.current.value;
|
||||
|
||||
if (text) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
// window.alert(`Text Copied`);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Copy size={17} />
|
||||
</div>
|
||||
</>
|
||||
) : props.type == "search" || props.readOnly ? null : (
|
||||
<div
|
||||
title="Clear Input Field"
|
||||
{...clearInputProps}
|
||||
@@ -502,3 +527,26 @@ export default function Input<KeyType extends string>(
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const updateInput = ({
|
||||
newValue,
|
||||
inputRef,
|
||||
}: {
|
||||
newValue: string;
|
||||
inputRef: RefObject<HTMLInputElement & HTMLTextAreaElement>;
|
||||
}) => {
|
||||
const input = inputRef.current;
|
||||
if (!input) return;
|
||||
|
||||
// Get the native setter (React overrides the default one)
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
"value",
|
||||
)?.set;
|
||||
|
||||
// Call it directly on the input
|
||||
nativeInputValueSetter?.call(input, newValue);
|
||||
|
||||
// Dispatch the event - React listens for 'input', not 'change'
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
import type {
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
InputHTMLAttributes,
|
||||
|
||||
+561
-246
File diff suppressed because it is too large
Load Diff
+30
-26
@@ -1,14 +1,14 @@
|
||||
import { ChevronDown, Info, LucideProps } from "lucide-react";
|
||||
import { ChevronDown, Info, type LucideProps } from "lucide-react";
|
||||
import React, {
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
Dispatch,
|
||||
InputHTMLAttributes,
|
||||
LabelHTMLAttributes,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
SelectHTMLAttributes,
|
||||
SetStateAction,
|
||||
type ComponentProps,
|
||||
type DetailedHTMLProps,
|
||||
type Dispatch,
|
||||
type InputHTMLAttributes,
|
||||
type LabelHTMLAttributes,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
type SelectHTMLAttributes,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Row from "../layout/Row";
|
||||
@@ -18,6 +18,8 @@ import Span from "../layout/Span";
|
||||
import Stack from "../layout/Stack";
|
||||
import twuiSlugify from "../utils/slugify";
|
||||
import twuiSlugToNormalText from "../utils/slug-to-normal-text";
|
||||
import Input from "./Input";
|
||||
import Button from "../layout/Button";
|
||||
|
||||
export type TWUISelectValidityObject = {
|
||||
isValid?: boolean;
|
||||
@@ -26,7 +28,7 @@ export type TWUISelectValidityObject = {
|
||||
|
||||
export type TWUISelectOptionObject<
|
||||
KeyType extends string = string,
|
||||
T extends { [k: string]: any } = { [k: string]: any }
|
||||
T extends { [k: string]: any } = { [k: string]: any },
|
||||
> = {
|
||||
title?: string;
|
||||
value: KeyType;
|
||||
@@ -36,7 +38,7 @@ export type TWUISelectOptionObject<
|
||||
|
||||
export type TWUISelectProps<
|
||||
KeyType extends string,
|
||||
T extends { [k: string]: any } = { [k: string]: any }
|
||||
T extends { [k: string]: any } = { [k: string]: any },
|
||||
> = DetailedHTMLProps<
|
||||
SelectHTMLAttributes<HTMLSelectElement>,
|
||||
HTMLSelectElement
|
||||
@@ -60,11 +62,13 @@ export type TWUISelectProps<
|
||||
validateValueFn?: (value: string) => Promise<TWUISelectValidityObject>;
|
||||
dispatchState?: Dispatch<SetStateAction<T | undefined>>;
|
||||
name?: KeyType;
|
||||
input_props?: ComponentProps<typeof Input>;
|
||||
option_button_props?: Omit<ComponentProps<typeof Button>, "title">;
|
||||
};
|
||||
|
||||
export type TWUISelectValueObject<
|
||||
KeyType extends string,
|
||||
T extends { [k: string]: any } = { [k: string]: any }
|
||||
T extends { [k: string]: any } = { [k: string]: any },
|
||||
> = {
|
||||
value: KeyType;
|
||||
data?: T;
|
||||
@@ -78,7 +82,7 @@ export type TWUISelectValueObject<
|
||||
*/
|
||||
export default function Select<
|
||||
KeyType extends string,
|
||||
T extends { [k: string]: any } = { [k: string]: any }
|
||||
T extends { [k: string]: any } = { [k: string]: any },
|
||||
>({
|
||||
label,
|
||||
options,
|
||||
@@ -102,9 +106,9 @@ export default function Select<
|
||||
|
||||
const [value, setValue] = React.useState<TWUISelectValueObject<KeyType, T>>(
|
||||
{
|
||||
value: options[0]?.value,
|
||||
value: options[0]?.value!,
|
||||
data: options[0]?.data,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -128,10 +132,10 @@ export default function Select<
|
||||
const selectID = label
|
||||
? twuiSlugify(label)
|
||||
: props.name
|
||||
? twuiSlugify(props.name)
|
||||
: props.title
|
||||
? twuiSlugify(props.title)
|
||||
: `select-${Math.round(Math.random() * 1000000)}`;
|
||||
? twuiSlugify(props.name)
|
||||
: props.title
|
||||
? twuiSlugify(props.title)
|
||||
: `select-${Math.round(Math.random() * 1000000)}`;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -148,7 +152,7 @@ export default function Select<
|
||||
"focus:outline-slate-700 dark:focus:outline-white/50",
|
||||
"bg-white dark:bg-background-dark",
|
||||
validity.isValid ? "" : "outline-warning border-warning",
|
||||
wrapperProps?.className
|
||||
wrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
{showLabel && (
|
||||
@@ -159,7 +163,7 @@ export default function Select<
|
||||
"text-xs absolute -top-2.5 left-2 text-foreground-light/80 bg-background-light",
|
||||
"dark:text-foreground-dark/70 dark:bg-background-dark px-1.5 rounded-t",
|
||||
"twui-input-label",
|
||||
labelProps?.className
|
||||
labelProps?.className,
|
||||
)}
|
||||
>
|
||||
{label || props.title || props.name}
|
||||
@@ -174,7 +178,7 @@ export default function Select<
|
||||
"w-full pl-3 py-2 rounded-default appearance-none pr-8",
|
||||
"grow !border-none !outline-none",
|
||||
"twui-select",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
ref={selectRef}
|
||||
value={
|
||||
@@ -183,7 +187,7 @@ export default function Select<
|
||||
}
|
||||
onChange={(e) => {
|
||||
const targetValue = options.find(
|
||||
(opt) => opt.value == e.target.value
|
||||
(opt) => opt.value == e.target.value,
|
||||
);
|
||||
|
||||
if (targetValue) {
|
||||
@@ -192,7 +196,7 @@ export default function Select<
|
||||
|
||||
changeHandler?.(
|
||||
e.target.value as (typeof options)[number]["value"],
|
||||
targetValue?.data
|
||||
targetValue?.data,
|
||||
);
|
||||
|
||||
props.onChange?.(e);
|
||||
@@ -219,7 +223,7 @@ export default function Select<
|
||||
{...iconProps}
|
||||
className={twMerge(
|
||||
"pointer-events-none -ml-6",
|
||||
iconProps?.className
|
||||
iconProps?.className,
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Input, { InputProps } from "./Input";
|
||||
import Input, { type InputProps } from "./Input";
|
||||
|
||||
/**
|
||||
* # Textarea Component
|
||||
|
||||
Reference in New Issue
Block a user