Updates
This commit is contained in:
parent
f0850146be
commit
442ad5aa32
0
components/lib/editors/AceEditor.tsx
Executable file → Normal file
0
components/lib/editors/AceEditor.tsx
Executable file → Normal file
@ -17,7 +17,7 @@ export default function Border({ spacing, ...props }: TWUI_BORDER_PROPS) {
|
|||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"relative flex items-center gap-2 border rounded",
|
"relative flex items-center gap-2 border border-solid rounded",
|
||||||
"border-slate-300 dark:border-white/10",
|
"border-slate-300 dark:border-white/10",
|
||||||
spacing
|
spacing
|
||||||
? spacing == "normal"
|
? spacing == "normal"
|
||||||
|
0
components/lib/elements/Breadcrumbs.tsx
Executable file → Normal file
0
components/lib/elements/Breadcrumbs.tsx
Executable file → Normal file
@ -1,6 +1,19 @@
|
|||||||
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
type Props = DetailedHTMLProps<
|
||||||
|
HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
> & {
|
||||||
|
variant?: "normal";
|
||||||
|
href?: string;
|
||||||
|
linkProps?: DetailedHTMLProps<
|
||||||
|
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||||
|
HTMLAnchorElement
|
||||||
|
>;
|
||||||
|
noHover?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # General Card
|
* # General Card
|
||||||
* @className twui-card
|
* @className twui-card
|
||||||
@ -13,22 +26,18 @@ export default function Card({
|
|||||||
href,
|
href,
|
||||||
variant,
|
variant,
|
||||||
linkProps,
|
linkProps,
|
||||||
|
noHover,
|
||||||
...props
|
...props
|
||||||
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
|
}: Props) {
|
||||||
variant?: "normal";
|
|
||||||
href?: string;
|
|
||||||
linkProps?: DetailedHTMLProps<
|
|
||||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
||||||
HTMLAnchorElement
|
|
||||||
>;
|
|
||||||
}) {
|
|
||||||
const component = (
|
const component = (
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex flex-row items-center p-4 rounded bg-white dark:bg-white/10",
|
"flex flex-row items-center p-4 rounded bg-white dark:bg-white/10",
|
||||||
"border border-slate-200 dark:border-white/10 border-solid",
|
"border border-slate-200 dark:border-white/10 border-solid",
|
||||||
href
|
noHover
|
||||||
|
? ""
|
||||||
|
: href
|
||||||
? "hover:bg-slate-100 dark:hover:bg-white/30 hover:border-slate-400 dark:hover:border-white/20"
|
? "hover:bg-slate-100 dark:hover:bg-white/30 hover:border-slate-400 dark:hover:border-white/20"
|
||||||
: "",
|
: "",
|
||||||
"twui-card",
|
"twui-card",
|
||||||
|
@ -24,6 +24,7 @@ export type TWUI_DROPDOWN_PROPS = PropsWithChildren &
|
|||||||
>;
|
>;
|
||||||
debounce?: number;
|
debounce?: number;
|
||||||
hoverOpen?: boolean;
|
hoverOpen?: boolean;
|
||||||
|
above?: boolean;
|
||||||
position?: (typeof TWUIDropdownContentPositions)[number];
|
position?: (typeof TWUIDropdownContentPositions)[number];
|
||||||
topOffset?: number;
|
topOffset?: number;
|
||||||
externalSetOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
externalSetOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
@ -41,6 +42,7 @@ export default function Dropdown({
|
|||||||
contentWrapperProps,
|
contentWrapperProps,
|
||||||
targetWrapperProps,
|
targetWrapperProps,
|
||||||
hoverOpen,
|
hoverOpen,
|
||||||
|
above,
|
||||||
debounce = 500,
|
debounce = 500,
|
||||||
target,
|
target,
|
||||||
position = "center",
|
position = "center",
|
||||||
@ -122,6 +124,7 @@ export default function Dropdown({
|
|||||||
: position == "right"
|
: position == "right"
|
||||||
? "right-0"
|
? "right-0"
|
||||||
: "",
|
: "",
|
||||||
|
above ? "-translate-y-[120%]" : "",
|
||||||
open ? "flex" : "hidden",
|
open ? "flex" : "hidden",
|
||||||
"twui-dropdown-content",
|
"twui-dropdown-content",
|
||||||
contentWrapperProps?.className
|
contentWrapperProps?.className
|
||||||
|
@ -15,6 +15,7 @@ export type TWUI_TOGGLE_PROPS = PropsWithChildren &
|
|||||||
> & {
|
> & {
|
||||||
color?: "normal" | "secondary" | "error" | "success" | "gray";
|
color?: "normal" | "secondary" | "error" | "success" | "gray";
|
||||||
variant?: "normal" | "outlined" | "ghost";
|
variant?: "normal" | "outlined" | "ghost";
|
||||||
|
href?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,13 +26,15 @@ export default function Tag({
|
|||||||
color,
|
color,
|
||||||
variant,
|
variant,
|
||||||
children,
|
children,
|
||||||
|
href,
|
||||||
...props
|
...props
|
||||||
}: TWUI_TOGGLE_PROPS) {
|
}: TWUI_TOGGLE_PROPS) {
|
||||||
return (
|
const mainComponent = (
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"text-xs px-2 py-0.5 rounded-full outline outline-0",
|
"text-xs px-2 py-0.5 rounded-full outline outline-0",
|
||||||
|
"text-center flex items-center justify-center",
|
||||||
color == "secondary"
|
color == "secondary"
|
||||||
? "bg-violet-600 outline-violet-600"
|
? "bg-violet-600 outline-violet-600"
|
||||||
: color == "success"
|
: color == "success"
|
||||||
@ -63,7 +66,7 @@ export default function Tag({
|
|||||||
: color == "gray"
|
: color == "gray"
|
||||||
? "text-slate-700 dark:text-white/80"
|
? "text-slate-700 dark:text-white/80"
|
||||||
: "text-blue-600")
|
: "text-blue-600")
|
||||||
: "",
|
: "text-white",
|
||||||
|
|
||||||
"twui-tag",
|
"twui-tag",
|
||||||
props.className
|
props.className
|
||||||
@ -72,4 +75,14 @@ export default function Tag({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<a href={href} className={twMerge("hover:opacity-80")}>
|
||||||
|
{mainComponent}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainComponent;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ export default function Form<T extends object = { [key: string]: any }>({
|
|||||||
const formData = new FormData(formEl);
|
const formData = new FormData(formEl);
|
||||||
const data = Object.fromEntries(formData.entries()) as T;
|
const data = Object.fromEntries(formData.entries()) as T;
|
||||||
props.submitHandler?.(e, data);
|
props.submitHandler?.(e, data);
|
||||||
|
props.onSubmit?.(e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -14,7 +14,9 @@ type ImageUploadProps = DetailedHTMLProps<
|
|||||||
React.HTMLAttributes<HTMLDivElement>,
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
> & {
|
> & {
|
||||||
onChange?: (imgData: ImageInputToBase64FunctionReturn | undefined) => any;
|
onChangeHandler?: (
|
||||||
|
imgData: ImageInputToBase64FunctionReturn | undefined
|
||||||
|
) => any;
|
||||||
fileInputProps?: DetailedHTMLProps<
|
fileInputProps?: DetailedHTMLProps<
|
||||||
React.InputHTMLAttributes<HTMLInputElement>,
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
HTMLInputElement
|
HTMLInputElement
|
||||||
@ -35,8 +37,11 @@ type ImageUploadProps = DetailedHTMLProps<
|
|||||||
disablePreview?: boolean;
|
disablePreview?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note use the `onChangeHandler` prop to grab the parsed base64 image object
|
||||||
|
*/
|
||||||
export default function ImageUpload({
|
export default function ImageUpload({
|
||||||
onChange,
|
onChangeHandler,
|
||||||
fileInputProps,
|
fileInputProps,
|
||||||
placeHolderWrapper,
|
placeHolderWrapper,
|
||||||
previewImageWrapperProps,
|
previewImageWrapperProps,
|
||||||
@ -60,7 +65,7 @@ export default function ImageUpload({
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
imageInputToBase64({ imageInput: e.target }).then((res) => {
|
imageInputToBase64({ imageInput: e.target }).then((res) => {
|
||||||
setSrc(res.imageBase64Full);
|
setSrc(res.imageBase64Full);
|
||||||
onChange?.(res);
|
onChangeHandler?.(res);
|
||||||
fileInputProps?.onChange?.(e);
|
fileInputProps?.onChange?.(e);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -88,7 +93,7 @@ export default function ImageUpload({
|
|||||||
className="absolute p-2 top-2 right-2 z-20"
|
className="absolute p-2 top-2 right-2 z-20"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
setSrc(undefined);
|
setSrc(undefined);
|
||||||
onChange?.(undefined);
|
onChangeHandler?.(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X className="text-slate-950 dark:text-white" />
|
<X className="text-slate-950 dark:text-white" />
|
||||||
|
@ -107,6 +107,7 @@ export type InputProps<KeyType extends string> = DetailedHTMLProps<
|
|||||||
validationFunction?: (value: string) => Promise<boolean>;
|
validationFunction?: (value: string) => Promise<boolean>;
|
||||||
autoComplete?: (typeof autocompleteOptions)[number];
|
autoComplete?: (typeof autocompleteOptions)[number];
|
||||||
name?: KeyType;
|
name?: KeyType;
|
||||||
|
valueUpdate?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,11 +131,16 @@ export default function Input<KeyType extends string>({
|
|||||||
autoComplete,
|
autoComplete,
|
||||||
validationFunction,
|
validationFunction,
|
||||||
validationRegex,
|
validationRegex,
|
||||||
|
valueUpdate,
|
||||||
...props
|
...props
|
||||||
}: InputProps<KeyType>) {
|
}: InputProps<KeyType>) {
|
||||||
const [focus, setFocus] = React.useState(false);
|
const [focus, setFocus] = React.useState(false);
|
||||||
const [value, setValue] = React.useState(
|
const [value, setValue] = React.useState(
|
||||||
props.defaultValue ? String(props.defaultValue) : ""
|
props.value
|
||||||
|
? String(props.value)
|
||||||
|
: props.defaultValue
|
||||||
|
? String(props.defaultValue)
|
||||||
|
: ""
|
||||||
);
|
);
|
||||||
|
|
||||||
delete props.defaultValue;
|
delete props.defaultValue;
|
||||||
@ -163,6 +169,11 @@ export default function Input<KeyType extends string>({
|
|||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!props.value) return;
|
||||||
|
setValue(String(props.value));
|
||||||
|
}, [props.value]);
|
||||||
|
|
||||||
const targetComponent = istextarea ? (
|
const targetComponent = istextarea ? (
|
||||||
<textarea
|
<textarea
|
||||||
{...props}
|
{...props}
|
||||||
@ -189,7 +200,10 @@ export default function Input<KeyType extends string>({
|
|||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"w-full outline-none bg-transparent",
|
"w-full outline-none bg-transparent border-none",
|
||||||
|
"hover:border-none hover:outline-none focus:border-none focus:outline-none",
|
||||||
|
"dark:bg-transparent dark:outline-none dark:border-none",
|
||||||
|
"p-0",
|
||||||
"twui-input",
|
"twui-input",
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
@ -220,7 +234,7 @@ export default function Input<KeyType extends string>({
|
|||||||
: "border-slate-300 dark:border-white/20",
|
: "border-slate-300 dark:border-white/20",
|
||||||
focus && isValid
|
focus && isValid
|
||||||
? "outline-slate-700 dark:outline-white/50"
|
? "outline-slate-700 dark:outline-white/50"
|
||||||
: "outline-transparent",
|
: "outline-slate-300 dark:outline-white/20",
|
||||||
variant == "warning" &&
|
variant == "warning" &&
|
||||||
isValid &&
|
isValid &&
|
||||||
"border-yellow-500 dark:border-yellow-300 outline-yellow-500 dark:outline-yellow-300",
|
"border-yellow-500 dark:border-yellow-300 outline-yellow-500 dark:outline-yellow-300",
|
||||||
@ -246,6 +260,7 @@ export default function Input<KeyType extends string>({
|
|||||||
className={twMerge(
|
className={twMerge(
|
||||||
"text-xs absolute -top-2.5 left-2 text-slate-500 bg-white px-1.5 rounded-t",
|
"text-xs absolute -top-2.5 left-2 text-slate-500 bg-white px-1.5 rounded-t",
|
||||||
"dark:text-white/60 dark:bg-black",
|
"dark:text-white/60 dark:bg-black",
|
||||||
|
"twui-input-label",
|
||||||
labelProps?.className
|
labelProps?.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -16,24 +16,7 @@ type SelectOptionObject = {
|
|||||||
default?: boolean;
|
default?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SelectOption = SelectOptionObject | SelectOptionObject[];
|
type SelectProps = DetailedHTMLProps<
|
||||||
|
|
||||||
/**
|
|
||||||
* # Select Element
|
|
||||||
* @className twui-select-wrapper
|
|
||||||
* @className twui-select
|
|
||||||
* @className twui-select-dropdown-icon
|
|
||||||
*/
|
|
||||||
export default function Select({
|
|
||||||
label,
|
|
||||||
options,
|
|
||||||
componentRef,
|
|
||||||
labelProps,
|
|
||||||
wrapperProps,
|
|
||||||
showLabel,
|
|
||||||
iconProps,
|
|
||||||
...props
|
|
||||||
}: DetailedHTMLProps<
|
|
||||||
SelectHTMLAttributes<HTMLSelectElement>,
|
SelectHTMLAttributes<HTMLSelectElement>,
|
||||||
HTMLSelectElement
|
HTMLSelectElement
|
||||||
> & {
|
> & {
|
||||||
@ -50,7 +33,26 @@ export default function Select({
|
|||||||
>;
|
>;
|
||||||
componentRef?: RefObject<HTMLSelectElement>;
|
componentRef?: RefObject<HTMLSelectElement>;
|
||||||
iconProps?: LucideProps;
|
iconProps?: LucideProps;
|
||||||
}) {
|
changeHandler?: (value: SelectProps["options"][number]["value"]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # Select Element
|
||||||
|
* @className twui-select-wrapper
|
||||||
|
* @className twui-select
|
||||||
|
* @className twui-select-dropdown-icon
|
||||||
|
*/
|
||||||
|
export default function Select({
|
||||||
|
label,
|
||||||
|
options,
|
||||||
|
componentRef,
|
||||||
|
labelProps,
|
||||||
|
wrapperProps,
|
||||||
|
showLabel,
|
||||||
|
iconProps,
|
||||||
|
changeHandler,
|
||||||
|
...props
|
||||||
|
}: SelectProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...wrapperProps}
|
{...wrapperProps}
|
||||||
@ -64,8 +66,9 @@ export default function Select({
|
|||||||
htmlFor={props.name}
|
htmlFor={props.name}
|
||||||
{...labelProps}
|
{...labelProps}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"text-xs absolute -top-2 left-4 text-slate-600 bg-white px-2",
|
"text-xs absolute -top-2.5 left-2 text-slate-500 bg-white px-1.5 rounded-t",
|
||||||
"dark:text-white/60 dark:bg-black",
|
"dark:text-white/60 dark:bg-black",
|
||||||
|
"twui-input-label",
|
||||||
labelProps?.className
|
labelProps?.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -90,6 +93,12 @@ export default function Select({
|
|||||||
options.flat().find((opt) => opt.default)?.value ||
|
options.flat().find((opt) => opt.default)?.value ||
|
||||||
undefined
|
undefined
|
||||||
}
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
changeHandler?.(
|
||||||
|
e.target.value as (typeof options)[number]["value"]
|
||||||
|
);
|
||||||
|
props.onChange?.(e);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{options.flat().map((option, index) => {
|
{options.flat().map((option, index) => {
|
||||||
return (
|
return (
|
||||||
|
@ -25,10 +25,9 @@ export const WebSocketEventNames = ["wsDataEvent", "wsMessageEvent"] as const;
|
|||||||
* console.log(e.detail.message) // type string
|
* console.log(e.detail.message) // type string
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
export default function useWebSocket<T>({
|
export default function useWebSocket<
|
||||||
url,
|
T extends { [key: string]: any } = { [key: string]: any }
|
||||||
debounce,
|
>({ url, debounce }: UseWebsocketHookParams) {
|
||||||
}: UseWebsocketHookParams) {
|
|
||||||
const DEBOUNCE = debounce || 200;
|
const DEBOUNCE = debounce || 200;
|
||||||
|
|
||||||
const [socket, setSocket] = React.useState<WebSocket | undefined>(
|
const [socket, setSocket] = React.useState<WebSocket | undefined>(
|
||||||
@ -75,6 +74,8 @@ export default function useWebSocket<T>({
|
|||||||
|
|
||||||
ws.onclose = (ev) => {
|
ws.onclose = (ev) => {
|
||||||
console.log("Websocket closed ... Attempting to reconnect ...");
|
console.log("Websocket closed ... Attempting to reconnect ...");
|
||||||
|
console.log("URL:", url);
|
||||||
|
|
||||||
reconnectInterval = setInterval(() => {
|
reconnectInterval = setInterval(() => {
|
||||||
if (tries >= 3) {
|
if (tries >= 3) {
|
||||||
return window.clearInterval(reconnectInterval);
|
return window.clearInterval(reconnectInterval);
|
||||||
|
39
components/lib/hooks/useWebSocketEventHandler.tsx
Normal file
39
components/lib/hooks/useWebSocketEventHandler.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { WebSocketEventNames } from "./useWebSocket";
|
||||||
|
|
||||||
|
type Param = {
|
||||||
|
listener?: (typeof WebSocketEventNames)[number];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # Use Websocket Data Event Handler Hook
|
||||||
|
*/
|
||||||
|
export default function useWebSocketEventHandler<
|
||||||
|
T extends { [key: string]: any } = { [key: string]: any }
|
||||||
|
>(param?: Param) {
|
||||||
|
const [data, setData] = React.useState<T | undefined>(undefined);
|
||||||
|
const [message, setMessage] = React.useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const dataEventListenerCallback = (e: Event) => {
|
||||||
|
const customEvent = e as CustomEvent;
|
||||||
|
const data = customEvent.detail.data as T | undefined;
|
||||||
|
const message = customEvent.detail.message as string | undefined;
|
||||||
|
if (data) setData(data);
|
||||||
|
if (message) setMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageEventName: (typeof WebSocketEventNames)[number] =
|
||||||
|
param?.listener || "wsDataEvent";
|
||||||
|
window.addEventListener(messageEventName, dataEventListenerCallback);
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
window.removeEventListener(
|
||||||
|
messageEventName,
|
||||||
|
dataEventListenerCallback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { data, message };
|
||||||
|
}
|
@ -8,6 +8,36 @@ import {
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import Loading from "../elements/Loading";
|
import Loading from "../elements/Loading";
|
||||||
|
|
||||||
|
export type TWUIButtonProps = DetailedHTMLProps<
|
||||||
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
HTMLButtonElement
|
||||||
|
> & {
|
||||||
|
variant?: "normal" | "ghost" | "outlined";
|
||||||
|
color?:
|
||||||
|
| "primary"
|
||||||
|
| "secondary"
|
||||||
|
| "accent"
|
||||||
|
| "gray"
|
||||||
|
| "error"
|
||||||
|
| "warning"
|
||||||
|
| "success";
|
||||||
|
size?: "small" | "smaller" | "normal" | "large" | "larger";
|
||||||
|
loadingIconSize?: React.ComponentProps<typeof Loading>["size"];
|
||||||
|
href?: string;
|
||||||
|
target?: HTMLAttributeAnchorTarget;
|
||||||
|
loading?: boolean;
|
||||||
|
linkProps?: DetailedHTMLProps<
|
||||||
|
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||||
|
HTMLAnchorElement
|
||||||
|
>;
|
||||||
|
beforeIcon?: React.ReactNode;
|
||||||
|
afterIcon?: React.ReactNode;
|
||||||
|
buttonContentProps?: DetailedHTMLProps<
|
||||||
|
HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Buttons
|
* # Buttons
|
||||||
* @className twui-button-general
|
* @className twui-button-general
|
||||||
@ -36,35 +66,9 @@ export default function Button({
|
|||||||
beforeIcon,
|
beforeIcon,
|
||||||
afterIcon,
|
afterIcon,
|
||||||
loading,
|
loading,
|
||||||
|
loadingIconSize,
|
||||||
...props
|
...props
|
||||||
}: DetailedHTMLProps<
|
}: TWUIButtonProps) {
|
||||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
HTMLButtonElement
|
|
||||||
> & {
|
|
||||||
variant?: "normal" | "ghost" | "outlined";
|
|
||||||
color?:
|
|
||||||
| "primary"
|
|
||||||
| "secondary"
|
|
||||||
| "accent"
|
|
||||||
| "gray"
|
|
||||||
| "error"
|
|
||||||
| "warning"
|
|
||||||
| "success";
|
|
||||||
size?: "small" | "smaller" | "normal" | "large" | "larger";
|
|
||||||
href?: string;
|
|
||||||
target?: HTMLAttributeAnchorTarget;
|
|
||||||
loading?: boolean;
|
|
||||||
linkProps?: DetailedHTMLProps<
|
|
||||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
||||||
HTMLAnchorElement
|
|
||||||
>;
|
|
||||||
beforeIcon?: React.ReactNode;
|
|
||||||
afterIcon?: React.ReactNode;
|
|
||||||
buttonContentProps?: DetailedHTMLProps<
|
|
||||||
HTMLAttributes<HTMLDivElement>,
|
|
||||||
HTMLDivElement
|
|
||||||
>;
|
|
||||||
}) {
|
|
||||||
const finalClassName: string = (() => {
|
const finalClassName: string = (() => {
|
||||||
if (variant == "normal" || !variant) {
|
if (variant == "normal" || !variant) {
|
||||||
if (color == "primary" || !color)
|
if (color == "primary" || !color)
|
||||||
@ -194,6 +198,7 @@ export default function Button({
|
|||||||
<Loading
|
<Loading
|
||||||
className="absolute"
|
className="absolute"
|
||||||
size={(() => {
|
size={(() => {
|
||||||
|
if (loadingIconSize) return loadingIconSize;
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
return "small";
|
return "small";
|
||||||
|
26
components/lib/utils/fetch/fetchApi.ts
Executable file → Normal file
26
components/lib/utils/fetch/fetchApi.ts
Executable file → Normal file
@ -1,6 +1,6 @@
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
type FetchApiOptions = {
|
type FetchApiOptions<T extends { [k: string]: any } = { [k: string]: any }> = {
|
||||||
method:
|
method:
|
||||||
| "POST"
|
| "POST"
|
||||||
| "GET"
|
| "GET"
|
||||||
@ -12,7 +12,7 @@ type FetchApiOptions = {
|
|||||||
| "delete"
|
| "delete"
|
||||||
| "put"
|
| "put"
|
||||||
| "patch";
|
| "patch";
|
||||||
body?: object | string;
|
body?: T | string;
|
||||||
headers?: FetchHeader;
|
headers?: FetchHeader;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,13 +30,23 @@ export type FetchApiReturn = {
|
|||||||
/**
|
/**
|
||||||
* # Fetch API
|
* # Fetch API
|
||||||
*/
|
*/
|
||||||
export default async function fetchApi(
|
export default async function fetchApi<
|
||||||
|
T extends { [k: string]: any } = { [k: string]: any },
|
||||||
|
R extends any = any
|
||||||
|
>(
|
||||||
url: string,
|
url: string,
|
||||||
options?: FetchApiOptions,
|
options?: FetchApiOptions<T>,
|
||||||
csrf?: boolean,
|
csrf?: boolean,
|
||||||
/** Key to use to grab local Storage csrf value. */
|
/**
|
||||||
localStorageCSRFKey?: string
|
* Key to use to grab local Storage csrf value.
|
||||||
): Promise<any> {
|
*/
|
||||||
|
localStorageCSRFKey?: string,
|
||||||
|
/**
|
||||||
|
* Key with which to set the request header csrf
|
||||||
|
* value
|
||||||
|
*/
|
||||||
|
csrfHeaderKey?: string
|
||||||
|
): Promise<R> {
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
const csrfValue = localStorage.getItem(localStorageCSRFKey || "csrf");
|
const csrfValue = localStorage.getItem(localStorageCSRFKey || "csrf");
|
||||||
@ -46,7 +56,7 @@ export default async function fetchApi(
|
|||||||
} as FetchHeader;
|
} as FetchHeader;
|
||||||
|
|
||||||
if (csrf && csrfValue) {
|
if (csrf && csrfValue) {
|
||||||
finalHeaders[`'${csrfValue.replace(/\"/g, "")}'`] = "true";
|
finalHeaders[csrfHeaderKey || "x-csrf-key"] = csrfValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options === "string") {
|
if (typeof options === "string") {
|
||||||
|
@ -88,6 +88,17 @@ export const skills = {
|
|||||||
image: "/images/work/devops/server-management.png",
|
image: "/images/work/devops/server-management.png",
|
||||||
technologies: ["Node JS", "Bun JS", "Shell Script", "Python"],
|
technologies: ["Node JS", "Bun JS", "Shell Script", "Python"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "API Development and Integration",
|
||||||
|
description:
|
||||||
|
"Developing custom APIs and integrating existing APIs from external services",
|
||||||
|
technologies: [
|
||||||
|
"API",
|
||||||
|
"REST",
|
||||||
|
"API Development",
|
||||||
|
"Data Fetching",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "React JS",
|
title: "React JS",
|
||||||
description: "Development of React JS applications for the web",
|
description: "Development of React JS applications for the web",
|
||||||
|
@ -45,6 +45,13 @@ export const work = {
|
|||||||
"NGINX Reverse Proxy",
|
"NGINX Reverse Proxy",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Ifuekosa LLC",
|
||||||
|
description:
|
||||||
|
"Tax Preparation, Notary and Business Consulting Services in New Jersey",
|
||||||
|
href: "https://ifuekosallc.com/",
|
||||||
|
technologies: ["Wordpress", "Docker", "Email Server"],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Devops: {
|
Devops: {
|
||||||
|
Loading…
Reference in New Issue
Block a user