Updates
This commit is contained in:
@@ -13,6 +13,7 @@ import Button from "../layout/Button";
|
||||
|
||||
export type TWUI_LINK_LIST_LINK_OBJECT = {
|
||||
title?: string;
|
||||
component?: ReactNode;
|
||||
url?: string;
|
||||
strict?: boolean;
|
||||
icon?: ReactNode;
|
||||
@@ -70,7 +71,7 @@ export default function LinkList({
|
||||
className={twMerge(
|
||||
"flex flex-row items-center gap-1",
|
||||
"twui-link-list",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{links
|
||||
@@ -104,7 +105,7 @@ export default function LinkList({
|
||||
{...link.buttonProps}
|
||||
className={twMerge(
|
||||
"p-2 cursor-pointer whitespace-nowrap",
|
||||
linkProps?.className
|
||||
linkProps?.className,
|
||||
)}
|
||||
onClick={(e) => {
|
||||
link.onClick?.(e);
|
||||
@@ -113,7 +114,7 @@ export default function LinkList({
|
||||
>
|
||||
<Row>
|
||||
{link.icon}
|
||||
{link.title}
|
||||
{link.component || link.title}
|
||||
</Row>
|
||||
</Button>
|
||||
{finalDivider}
|
||||
@@ -131,7 +132,7 @@ export default function LinkList({
|
||||
className={twMerge(
|
||||
"p-2 cursor-pointer whitespace-nowrap",
|
||||
linkProps?.className,
|
||||
link.linkProps?.className
|
||||
link.linkProps?.className,
|
||||
)}
|
||||
strict={link.strict}
|
||||
onClick={(e) => {
|
||||
@@ -144,7 +145,7 @@ export default function LinkList({
|
||||
link.iconPosition == "before"
|
||||
? link.icon
|
||||
: null}
|
||||
{link.title}
|
||||
{link.component || link.title}
|
||||
{link.iconPosition == "after"
|
||||
? link.icon
|
||||
: null}
|
||||
|
||||
@@ -31,14 +31,18 @@ export default function Loading({ size, svgClassName, ...props }: Props) {
|
||||
})();
|
||||
|
||||
return (
|
||||
<div role="status" {...props}>
|
||||
<div
|
||||
role="status"
|
||||
{...props}
|
||||
className={twMerge(`twui-loading`, props.className)}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={twMerge(
|
||||
"text-gray animate-spin dark:text-gray-dark fill-primary",
|
||||
"dark:fill-white twui-loading",
|
||||
sizeClassName,
|
||||
svgClassName
|
||||
svgClassName,
|
||||
)}
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
|
||||
@@ -11,6 +11,7 @@ type Props = DetailedHTMLProps<
|
||||
> & {
|
||||
loadingProps?: ComponentProps<typeof Loading>;
|
||||
label?: string;
|
||||
fixed?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -20,16 +21,18 @@ type Props = DetailedHTMLProps<
|
||||
export default function LoadingOverlay({
|
||||
loadingProps,
|
||||
label,
|
||||
fixed,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"absolute top-0 left-0 w-full h-full z-[500]",
|
||||
"top-0 left-0 w-full h-full z-[500]",
|
||||
"bg-background-light/90 dark:bg-background-dark/90",
|
||||
fixed ? "fixed" : "absolute",
|
||||
props.className,
|
||||
"twui-loading-overlay"
|
||||
"twui-loading-overlay",
|
||||
)}
|
||||
>
|
||||
<Center>
|
||||
|
||||
@@ -146,7 +146,11 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
||||
{target ? (
|
||||
<div
|
||||
{...targetWrapperProps}
|
||||
onClick={(e) => setOpen(!open)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen(!open);
|
||||
}}
|
||||
ref={finalTargetRef}
|
||||
onMouseEnter={
|
||||
isPopover && (trigger === "hover" || hoverOpen)
|
||||
|
||||
@@ -9,6 +9,7 @@ export const TWUIPrismLanguages = ["shell", "javascript"] as const;
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
refresh?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,7 @@ type Props = {
|
||||
*
|
||||
* @className `twui-remote-code-block-wrapper`
|
||||
*/
|
||||
export default function RemoteCodeBlock({ content }: Props) {
|
||||
export default function RemoteCodeBlock({ content, refresh }: Props) {
|
||||
const [mdxSource, setMdxSource] =
|
||||
React.useState<MDXRemoteSerializeResult<any>>();
|
||||
|
||||
@@ -31,7 +32,7 @@ export default function RemoteCodeBlock({ content }: Props) {
|
||||
}).then((mdxSrc) => {
|
||||
setMdxSource(mdxSrc);
|
||||
});
|
||||
}, []);
|
||||
}, [refresh]);
|
||||
|
||||
if (!mdxSource) {
|
||||
return null;
|
||||
|
||||
@@ -78,12 +78,12 @@ export default function Search<KeyType extends string>({
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
className={twMerge(
|
||||
"rounded-r-none",
|
||||
"rounded-r-none!",
|
||||
"twui-search-input",
|
||||
inputProps?.className
|
||||
)}
|
||||
wrapperProps={{
|
||||
className: "rounded-r-none",
|
||||
className: "rounded-r-none!",
|
||||
}}
|
||||
componentRef={inputRef}
|
||||
/>
|
||||
@@ -93,7 +93,7 @@ export default function Search<KeyType extends string>({
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
className={twMerge(
|
||||
"rounded-l-none ml-[1px]",
|
||||
"rounded-l-none! ml-[1px]",
|
||||
"twui-search-button",
|
||||
buttonProps?.className
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ type StarProps = {
|
||||
starProps?: LucideProps;
|
||||
allowRating?: boolean;
|
||||
setValueExternal?: React.Dispatch<React.SetStateAction<number>>;
|
||||
changeHandler?: (value: number) => void;
|
||||
};
|
||||
|
||||
export type TWUI_STAR_RATING_PROPS = DetailedHTMLProps<
|
||||
@@ -35,6 +36,7 @@ export default function StarRating({
|
||||
starProps,
|
||||
allowRating,
|
||||
setValueExternal,
|
||||
changeHandler,
|
||||
...props
|
||||
}: TWUI_STAR_RATING_PROPS) {
|
||||
const totalArray = Array(total).fill(null);
|
||||
@@ -58,7 +60,7 @@ export default function StarRating({
|
||||
className={twMerge(
|
||||
"flex flex-row items-center gap-0 -ml-[2px]",
|
||||
"twui-star-rating",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
sectionHovered.current = true;
|
||||
@@ -68,6 +70,8 @@ export default function StarRating({
|
||||
}}
|
||||
>
|
||||
{totalArray.map((_, index) => {
|
||||
const isActive = index + 1 <= finalValue;
|
||||
|
||||
return (
|
||||
<StarComponent
|
||||
{...{
|
||||
@@ -83,6 +87,8 @@ export default function StarRating({
|
||||
selectedStarValue,
|
||||
sectionHovered,
|
||||
setSelectedStarValue,
|
||||
isActive,
|
||||
changeHandler,
|
||||
}}
|
||||
key={index}
|
||||
/>
|
||||
@@ -93,34 +99,31 @@ export default function StarRating({
|
||||
}
|
||||
|
||||
function StarComponent({
|
||||
value = 0,
|
||||
size = 20,
|
||||
starProps,
|
||||
index,
|
||||
allowRating,
|
||||
finalValue,
|
||||
setFinalValue,
|
||||
starClicked,
|
||||
sectionHovered,
|
||||
setSelectedStarValue,
|
||||
selectedStarValue,
|
||||
isActive,
|
||||
changeHandler,
|
||||
}: StarProps & {
|
||||
index: number;
|
||||
finalValue: number;
|
||||
setFinalValue: React.Dispatch<React.SetStateAction<number>>;
|
||||
setSelectedStarValue: React.Dispatch<React.SetStateAction<number>>;
|
||||
starClicked: React.MutableRefObject<boolean>;
|
||||
sectionHovered: React.MutableRefObject<boolean>;
|
||||
selectedStarValue: number;
|
||||
isActive: boolean;
|
||||
}) {
|
||||
const isActive = index < finalValue;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge("p-[2px]", allowRating && "cursor-pointer")}
|
||||
onMouseEnter={() => {
|
||||
if (!allowRating) return;
|
||||
|
||||
setFinalValue(index + 1);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
@@ -145,6 +148,7 @@ function StarComponent({
|
||||
|
||||
starClicked.current = true;
|
||||
setSelectedStarValue(index + 1);
|
||||
changeHandler?.(index + 1);
|
||||
}}
|
||||
>
|
||||
<Star
|
||||
@@ -155,7 +159,7 @@ function StarComponent({
|
||||
"text-orange-500 dark:text-orange-400 fill-orange-500 dark:fill-orange-400",
|
||||
// allowRating &&
|
||||
// "hover:text-orange-500 hover:dark:text-orange-400 hover:fill-orange-500 hover:dark:fill-orange-400",
|
||||
starProps?.className
|
||||
starProps?.className,
|
||||
)}
|
||||
{...starProps}
|
||||
/>
|
||||
|
||||
@@ -27,6 +27,8 @@ export type TWUI_TOGGLE_PROPS = React.ComponentProps<typeof Stack> & {
|
||||
switchComponent?: ReactNode;
|
||||
setActiveValue?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
changeHandler?: (value: TWUITabsObject) => void;
|
||||
defaultValue?: string | null;
|
||||
hrefUpdate?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,8 @@ export default function Tabs({
|
||||
switchComponent,
|
||||
setActiveValue: existingSetActiveValue,
|
||||
changeHandler,
|
||||
defaultValue,
|
||||
hrefUpdate,
|
||||
...props
|
||||
}: TWUI_TOGGLE_PROPS) {
|
||||
const finalTabsContentArray = tabsContentArray
|
||||
@@ -54,31 +58,60 @@ export default function Tabs({
|
||||
.filter((ct) => Boolean(ct?.title)) as TWUITabsObject[];
|
||||
|
||||
const values = finalTabsContentArray.map(
|
||||
(obj) => obj.value || twuiSlugify(obj.title)
|
||||
(obj) => obj.value || twuiSlugify(obj.title),
|
||||
);
|
||||
|
||||
const defaultActiveObj = finalTabsContentArray.find(
|
||||
(ctn) => ctn.defaultActive
|
||||
(ctn) => ctn.defaultActive,
|
||||
);
|
||||
|
||||
const [activeValue, setActiveValue] = React.useState(
|
||||
defaultActiveObj
|
||||
? defaultActiveObj?.value || twuiSlugify(defaultActiveObj.title)
|
||||
: values[0] || undefined
|
||||
defaultValue
|
||||
? defaultValue
|
||||
: defaultActiveObj
|
||||
? defaultActiveObj?.value || twuiSlugify(defaultActiveObj.title)
|
||||
: values[0] || undefined,
|
||||
);
|
||||
const [ready, setReady] = React.useState(false);
|
||||
|
||||
const targetContent = finalTabsContentArray.find(
|
||||
(ctn) =>
|
||||
ctn.value == activeValue || twuiSlugify(ctn.title) == activeValue
|
||||
ctn.value == activeValue || twuiSlugify(ctn.title) == activeValue,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ready) return;
|
||||
existingSetActiveValue?.(activeValue);
|
||||
if (targetContent && activeValue) {
|
||||
changeHandler?.(targetContent);
|
||||
|
||||
if (hrefUpdate) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("tab", activeValue);
|
||||
window.history.pushState({}, "", url);
|
||||
}
|
||||
}
|
||||
}, [activeValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hrefUpdate) {
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
const activeTab = url.searchParams.get("tab");
|
||||
|
||||
if (activeTab && activeValue !== activeTab) {
|
||||
setActiveValue(undefined);
|
||||
setActiveValue(activeTab);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setReady(true);
|
||||
}, 500);
|
||||
} else {
|
||||
setReady(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
{...props}
|
||||
@@ -89,7 +122,7 @@ export default function Tabs({
|
||||
className={twMerge(
|
||||
"w-full",
|
||||
"twui-tab-buttons-wrapper",
|
||||
tabsButtonsWrapperProps?.className
|
||||
tabsButtonsWrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
<Border
|
||||
@@ -100,14 +133,14 @@ export default function Tabs({
|
||||
className={twMerge(
|
||||
"gap-0 items-stretch w-full flex-nowrap overflow-x-auto",
|
||||
centered && "justify-center",
|
||||
"twui-tab-buttons-container"
|
||||
"twui-tab-buttons-container",
|
||||
)}
|
||||
>
|
||||
{values.map((value, index) => {
|
||||
const targetObject = finalTabsContentArray.find(
|
||||
(ctn) =>
|
||||
ctn.value == value ||
|
||||
twuiSlugify(ctn.title) == value
|
||||
twuiSlugify(ctn.title) == value,
|
||||
);
|
||||
|
||||
const isActive = value == activeValue;
|
||||
@@ -120,7 +153,7 @@ export default function Tabs({
|
||||
? "bg-primary dark:bg-primary-dark text-white outline-none twui-tab-button-active"
|
||||
: "text-slate-400 dark:text-white/40 hover:text-slate-800 dark:hover:text-white" +
|
||||
" cursor-pointer",
|
||||
"twui-tab-buttons"
|
||||
"twui-tab-buttons",
|
||||
)}
|
||||
onClick={() => {
|
||||
setActiveValue(undefined);
|
||||
|
||||
@@ -14,6 +14,7 @@ export type TWUIToastProps = DetailedHTMLProps<
|
||||
> & {
|
||||
open?: boolean;
|
||||
setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
closeDispatch?: (open?: boolean) => void;
|
||||
closeDelay?: number;
|
||||
color?: (typeof ToastStyles)[number];
|
||||
};
|
||||
@@ -33,6 +34,7 @@ export default function Toast({
|
||||
setOpen,
|
||||
closeDelay = 4000,
|
||||
color,
|
||||
closeDispatch,
|
||||
...props
|
||||
}: TWUIToastProps) {
|
||||
const [ready, setReady] = React.useState(false);
|
||||
@@ -56,10 +58,12 @@ export default function Toast({
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
setOpen?.(false);
|
||||
closeDispatch?.(open);
|
||||
}, closeDelay);
|
||||
|
||||
return function () {
|
||||
setOpen?.(false);
|
||||
closeDispatch?.(open);
|
||||
};
|
||||
}, [ready, open]);
|
||||
|
||||
@@ -73,12 +77,12 @@ export default function Toast({
|
||||
"fixed bottom-4 right-4 z-[250] border-none",
|
||||
"pl-6 pr-8 py-4 bg-primary dark:bg-primary-dark",
|
||||
color == "success"
|
||||
? "bg-success dark:bg-success-dark twui-toast-success"
|
||||
? "bg-success-dark dark:bg-success-dark twui-toast-success"
|
||||
: color == "error"
|
||||
? "bg-error dark:bg-error-dark twui-toast-error"
|
||||
: "",
|
||||
? "bg-error dark:bg-error-dark twui-toast-error"
|
||||
: "",
|
||||
props.className,
|
||||
"twui-toast"
|
||||
"twui-toast",
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
window.clearTimeout(timeout);
|
||||
@@ -86,22 +90,28 @@ export default function Toast({
|
||||
onMouseLeave={(e) => {
|
||||
timeout = setTimeout(() => {
|
||||
setOpen?.(false);
|
||||
closeDispatch?.(open);
|
||||
}, closeDelay);
|
||||
}}
|
||||
>
|
||||
<Span
|
||||
className={twMerge(
|
||||
"absolute top-2 right-2 z-[100] cursor-pointer",
|
||||
"text-white"
|
||||
"text-white",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen?.(false);
|
||||
closeDispatch?.(open);
|
||||
}}
|
||||
>
|
||||
<X size={15} />
|
||||
</Span>
|
||||
<Span className={twMerge("text-white")}>{props.children}</Span>
|
||||
<Span className={twMerge("text-white! font-semibold")}>
|
||||
{props.children}
|
||||
</Span>
|
||||
</Card>,
|
||||
document.getElementById(IDName) as HTMLElement
|
||||
document.getElementById(IDName) as HTMLElement,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function AIPromptHistoryModal({ history }: Props) {
|
||||
View History
|
||||
</Button>
|
||||
}
|
||||
className="max-w-[900px] bg-slate-100 dark:bg-white/5 xl:p-10"
|
||||
className="max-w-[900px] bg-slate-100 dark:bg-white/5 xl:p-8"
|
||||
>
|
||||
<Stack className="gap-10 w-full">
|
||||
<Stack className="gap-1">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Divider from "@/src/components/twui/layout/Divider";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import Divider from "../../layout/Divider";
|
||||
import Stack from "../../layout/Stack";
|
||||
import React from "react";
|
||||
import MarkdownEditorPreviewComponent from "@/src/components/twui/mdx/markdown/MarkdownEditorPreviewComponent";
|
||||
import MarkdownEditorPreviewComponent from "../../mdx/markdown/MarkdownEditorPreviewComponent";
|
||||
import { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { LucideProps } from "lucide-react";
|
||||
import * as icons from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
export type TWUILucideIconName = keyof typeof icons;
|
||||
|
||||
export type TWUILucideIconProps = LucideProps & {
|
||||
name: TWUILucideIconName;
|
||||
};
|
||||
|
||||
export default function LucideIcon({ name, ...props }: TWUILucideIconProps) {
|
||||
const IconComponent = icons[name] as any;
|
||||
|
||||
if (!IconComponent) {
|
||||
console.warn(`Lucide icon "${name}" not found`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return <IconComponent {...props} />;
|
||||
}
|
||||
Reference in New Issue
Block a user