Updates
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes, RefObject } from "react";
|
||||
import type { DetailedHTMLProps, HTMLAttributes, RefObject } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TWUI_BORDER_PROPS = DetailedHTMLProps<
|
||||
@@ -28,11 +28,11 @@ export default function Border({
|
||||
? spacing == "normal"
|
||||
? "px-3 py-2"
|
||||
: spacing == "tight"
|
||||
? "px-2 py-1"
|
||||
: ""
|
||||
? "px-2 py-1"
|
||||
: ""
|
||||
: "px-3 py-2",
|
||||
"twui-border",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
ref={componentRef}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ComponentProps, ReactNode } from "react";
|
||||
import React, { type ComponentProps, type ReactNode } from "react";
|
||||
import Link from "../layout/Link";
|
||||
import Divider from "../layout/Divider";
|
||||
import Row from "../layout/Row";
|
||||
@@ -47,7 +47,7 @@ export default function Breadcrumbs({
|
||||
const [links, setLinks] = React.useState<LinkObject[] | null>(
|
||||
pageUrl
|
||||
? twuiBreadcrumbsGenerateLinksFromUrl({ url: pageUrl, skipHome })
|
||||
: null
|
||||
: null,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -76,13 +76,13 @@ export default function Breadcrumbs({
|
||||
<nav
|
||||
className={twMerge(
|
||||
"overflow-x-auto",
|
||||
"twui-current-breadcrumb-wrapper"
|
||||
"twui-current-breadcrumb-wrapper",
|
||||
)}
|
||||
aria-label="Breadcrumb"
|
||||
>
|
||||
<Row
|
||||
className={twMerge(
|
||||
"gap-4 flex-nowrap whitespace-nowrap overflow-x-auto overflow-y-hidden w-full"
|
||||
"gap-4 flex-nowrap whitespace-nowrap overflow-x-auto overflow-y-hidden w-full",
|
||||
)}
|
||||
>
|
||||
{backButton && (
|
||||
@@ -94,7 +94,7 @@ export default function Breadcrumbs({
|
||||
className={twMerge(
|
||||
"p-1 -my-2 -mx-2",
|
||||
"twui-breadcrumbs-back-button",
|
||||
backButtonProps?.className
|
||||
backButtonProps?.className,
|
||||
)}
|
||||
onClick={(e) => {
|
||||
window.history.back();
|
||||
@@ -108,7 +108,7 @@ export default function Breadcrumbs({
|
||||
vertical
|
||||
className={twMerge(
|
||||
"twui-breadcrumbs-divider",
|
||||
dividerProps?.className
|
||||
dividerProps?.className,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -131,7 +131,7 @@ export default function Breadcrumbs({
|
||||
isTarget ? "current" : "",
|
||||
"twui-breadcrumb-link",
|
||||
linkProps?.className,
|
||||
isTarget && currentLinkProps?.className
|
||||
isTarget && currentLinkProps?.className,
|
||||
)}
|
||||
title={
|
||||
currentLinkProps?.title || linkObject.title
|
||||
@@ -152,7 +152,7 @@ export default function Breadcrumbs({
|
||||
isTarget ? "current" : "",
|
||||
"twui-breadcrumb-link",
|
||||
linkProps?.className,
|
||||
isTarget && currentLinkProps?.className
|
||||
isTarget && currentLinkProps?.className,
|
||||
)}
|
||||
>
|
||||
{currentLinkProps?.title ||
|
||||
@@ -164,7 +164,7 @@ export default function Breadcrumbs({
|
||||
{...dividerProps}
|
||||
className={twMerge(
|
||||
"twui-breadcrumbs-divider",
|
||||
dividerProps?.className
|
||||
dividerProps?.className,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -214,7 +214,7 @@ export function twuiBreadcrumbsGenerateLinksFromUrl({
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const lnText = array[i];
|
||||
if (i > index || !lnText.match(/./)) continue;
|
||||
if (i > index || !lnText?.match(/./)) continue;
|
||||
|
||||
path += `/${lnText}`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
HTMLAttributes,
|
||||
type ComponentProps,
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Link from "../layout/Link";
|
||||
@@ -43,7 +43,7 @@ export default function Card({
|
||||
"flex flex-row items-center p-4 rounded-default bg-background-light dark:bg-background-dark",
|
||||
"border border-slate-200 dark:border-white/10 border-solid",
|
||||
noHover ? "" : "twui-card",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
@@ -60,7 +60,7 @@ export default function Card({
|
||||
"cursor-pointer",
|
||||
"twui-card",
|
||||
"twui-card-link",
|
||||
linkProps?.className
|
||||
linkProps?.className,
|
||||
)}
|
||||
>
|
||||
{component}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ComponentProps, ReactNode } from "react";
|
||||
import Stack from "../layout/Stack copy";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import Stack from "../layout/Stack";
|
||||
import Row from "../layout/Row";
|
||||
import { Check, CheckCircle, CheckCircle2 } from "lucide-react";
|
||||
import Span from "../layout/Span";
|
||||
@@ -37,7 +37,7 @@ export default function CheckBulletPoints({
|
||||
{...bulletWrapperProps}
|
||||
className={twMerge(
|
||||
"gap-2 xl:flex-nowrap",
|
||||
bulletWrapperProps?.className
|
||||
bulletWrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
{bulletPoint.icon || (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import React, {
|
||||
DetailedHTMLProps,
|
||||
HTMLAttributes,
|
||||
PropsWithChildren,
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
type PropsWithChildren,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Stack from "../layout/Stack";
|
||||
@@ -56,7 +56,7 @@ export default function CodeBlock({
|
||||
`rounded w-full transition-all items-start`,
|
||||
"relative max-w-[80vw] sm:max-w-[85vw] xl:max-w-[880px]",
|
||||
"twui-code-block-wrapper",
|
||||
wrapperProps?.className
|
||||
wrapperProps?.className,
|
||||
)}
|
||||
style={{
|
||||
boxShadow: copied
|
||||
@@ -69,14 +69,14 @@ export default function CodeBlock({
|
||||
<Stack
|
||||
className={twMerge(
|
||||
"gap-0 w-full overflow-x-auto relative",
|
||||
"max-h-[600px] overflow-y-auto"
|
||||
"max-h-[600px] overflow-y-auto",
|
||||
)}
|
||||
>
|
||||
<Row
|
||||
className={twMerge(
|
||||
"w-full px-1 h-10 sticky top-0 py-2",
|
||||
singleBlock ? "absolute !bg-transparent" : "",
|
||||
"twui-code-block-header"
|
||||
"twui-code-block-header",
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: finalBackgroundColor,
|
||||
@@ -128,7 +128,7 @@ export default function CodeBlock({
|
||||
className={twMerge(
|
||||
`p-1 w-full [&_pre]:!bg-transparent`,
|
||||
singleBlock ? "" : "-mt-1",
|
||||
"twui-code-pre-wrapper"
|
||||
"twui-code-pre-wrapper",
|
||||
)}
|
||||
ref={codeRef as any}
|
||||
>
|
||||
@@ -138,7 +138,7 @@ export default function CodeBlock({
|
||||
"!my-0 whitespace-pre-wrap",
|
||||
language ? `language-${language}` : "",
|
||||
"twui-code-block-pre",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import React, { type DetailedHTMLProps, type HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
|
||||
@@ -75,21 +75,21 @@ export default function ColorSchemeSelector({
|
||||
className={twMerge(
|
||||
"flex flex-row items-center",
|
||||
"twui-color-scheme-selector",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<button
|
||||
title="Color Scheme Selector Button"
|
||||
onClick={() => setActive(!active)}
|
||||
className={twMerge(
|
||||
"cursor-pointer hover:opacity-70 flex items-center justify-center"
|
||||
"cursor-pointer hover:opacity-70 flex items-center justify-center",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
{...iconWrapperProps}
|
||||
className={twMerge(
|
||||
"w-6 h-6 flex items-center justify-center",
|
||||
iconWrapperProps?.className
|
||||
iconWrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
{active == false && <Sun />}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, {
|
||||
import type {
|
||||
ComponentProps,
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import { Copy, LucideProps } from "lucide-react";
|
||||
import { Copy, type LucideProps } from "lucide-react";
|
||||
import Button from "../layout/Button";
|
||||
|
||||
type Props = Omit<ComponentProps<typeof Button>, "title"> & {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {
|
||||
DetailedHTMLProps,
|
||||
HTMLAttributes,
|
||||
PropsWithChildren,
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
type PropsWithChildren,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function Dropdown({
|
||||
className={twMerge(
|
||||
"flex flex-col items-center relative",
|
||||
"twui-dropdown-wrapper",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
if (!hoverOpen) return;
|
||||
@@ -142,7 +142,7 @@ export default function Dropdown({
|
||||
className={twMerge(
|
||||
"cursor-pointer",
|
||||
"twui-dropdown-target",
|
||||
targetWrapperProps?.className
|
||||
targetWrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
{target}
|
||||
@@ -155,20 +155,20 @@ export default function Dropdown({
|
||||
position == "left"
|
||||
? "left-[100%] top-[50%] -translate-y-[50%]"
|
||||
: position == "right"
|
||||
? "right-[100%] top-[50%] -translate-y-[50%]"
|
||||
: position == "bottom-left"
|
||||
? "left-0 top-[100%]"
|
||||
: position == "bottom-right"
|
||||
? "right-0 top-[100%]"
|
||||
: position == "center"
|
||||
? "left-[50%] -translate-x-[50%] top-[100%]"
|
||||
: position == "top"
|
||||
? "left-[50%] -translate-x-[50%] bottom-[100%]"
|
||||
: "top-[100%]",
|
||||
? "right-[100%] top-[50%] -translate-y-[50%]"
|
||||
: position == "bottom-left"
|
||||
? "left-0 top-[100%]"
|
||||
: position == "bottom-right"
|
||||
? "right-0 top-[100%]"
|
||||
: position == "center"
|
||||
? "left-[50%] -translate-x-[50%] top-[100%]"
|
||||
: position == "top"
|
||||
? "left-[50%] -translate-x-[50%] bottom-[100%]"
|
||||
: "top-[100%]",
|
||||
above ? "-translate-y-[120%]" : "",
|
||||
open ? "flex" : "hidden",
|
||||
"twui-dropdown-content",
|
||||
contentWrapperProps?.className
|
||||
contentWrapperProps?.className,
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
if (!hoverOpen) return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ComponentProps, PropsWithChildren, ReactNode } from "react";
|
||||
import type { ComponentProps, PropsWithChildren, ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Stack from "../layout/Stack";
|
||||
import Border from "./Border";
|
||||
@@ -46,7 +46,7 @@ export default function EmptyContent({
|
||||
className={twMerge(
|
||||
"w-full",
|
||||
borderProps?.className,
|
||||
"twui-empty-content-border"
|
||||
"twui-empty-content-border",
|
||||
)}
|
||||
>
|
||||
<Center>
|
||||
@@ -57,7 +57,7 @@ export default function EmptyContent({
|
||||
{...textProps}
|
||||
className={twMerge(
|
||||
"opacity-70 text-foreground-light dark:text-foreground-dark",
|
||||
textProps?.className
|
||||
textProps?.className,
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
@@ -75,7 +75,7 @@ export default function EmptyContent({
|
||||
className={twMerge(
|
||||
"w-full",
|
||||
"twui-empty-content-link",
|
||||
linkProps?.className
|
||||
linkProps?.className,
|
||||
)}
|
||||
href={url}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import type { ComponentProps } from "react";
|
||||
import Link from "../layout/Link";
|
||||
import { TwuiHeaderLink } from "./HeaderNav";
|
||||
import type { TwuiHeaderLink } from "./HeaderNav";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Row from "../layout/Row";
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function HeaderLink({ link, ...props }: TWUI_HEADER_LINK_PROPS) {
|
||||
className={twMerge(
|
||||
"grow p-2 hover:opacity-50",
|
||||
"twui-header-link",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<Row>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
|
||||
import React, {
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Row from "../layout/Row";
|
||||
import HeaderNavLinkComponent from "./HeaderNavLinkComponent";
|
||||
@@ -44,13 +48,13 @@ export default function HeaderNav({
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"twui-header-nav w-full xl:w-auto",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<Row className="gap-x-2 gap-y-2 flex-col xl:flex-row items-start xl:items-stretch">
|
||||
{headerLinks.map((link, index) => {
|
||||
const targetCustomDropdown = customDropdowns?.find(
|
||||
(d) => d.url == link.url
|
||||
(d) => d.url == link.url,
|
||||
);
|
||||
return (
|
||||
<HeaderNavLinkComponent
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
|
||||
import React, { type ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Row from "../layout/Row";
|
||||
import HeaderLink from "./HeaderLink";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import Dropdown from "./Dropdown";
|
||||
import { TwuiHeaderLink } from "./HeaderNav";
|
||||
import type { TwuiHeaderLink } from "./HeaderNav";
|
||||
import Card from "./Card";
|
||||
import Stack from "../layout/Stack";
|
||||
import Button from "../layout/Button";
|
||||
@@ -31,7 +31,7 @@ export default function HeaderNavLinkComponent({
|
||||
<ChevronDown
|
||||
className={twMerge(
|
||||
"hidden xl:flex xl:-ml-1",
|
||||
"twui-header-nav-link-icon"
|
||||
"twui-header-nav-link-icon",
|
||||
)}
|
||||
size={20}
|
||||
/>
|
||||
@@ -45,7 +45,7 @@ export default function HeaderNavLinkComponent({
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative w-full xl:w-auto [&_a.active]:font-bold",
|
||||
"twui-header-nav-link-component"
|
||||
"twui-header-nav-link-component",
|
||||
)}
|
||||
>
|
||||
{isDropdown ? (
|
||||
@@ -62,7 +62,7 @@ export default function HeaderNavLinkComponent({
|
||||
>
|
||||
<ChevronDown
|
||||
className={twMerge(
|
||||
"twui-header-nav-link-icon !text-link dark:!text-white"
|
||||
"twui-header-nav-link-icon !text-link dark:!text-white",
|
||||
)}
|
||||
size={20}
|
||||
/>
|
||||
@@ -77,7 +77,7 @@ export default function HeaderNavLinkComponent({
|
||||
<Card
|
||||
className={twMerge(
|
||||
"w-full p-0",
|
||||
"twui-header-nav-link-dropdown"
|
||||
"twui-header-nav-link-dropdown",
|
||||
)}
|
||||
>
|
||||
<Stack className="w-full items-stretch gap-0 py-2">
|
||||
@@ -90,7 +90,7 @@ export default function HeaderNavLinkComponent({
|
||||
className="px-6 py-4"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
@@ -113,7 +113,7 @@ export default function HeaderNavLinkComponent({
|
||||
<Card
|
||||
className={twMerge(
|
||||
"min-w-[200px] mt-2 p-0",
|
||||
"twui-header-nav-link-dropdown"
|
||||
"twui-header-nav-link-dropdown",
|
||||
)}
|
||||
>
|
||||
<Stack className="w-full items-stretch gap-0 py-2">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import HtmlToReact from "html-to-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
|
||||
@@ -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 Link from "../layout/Link";
|
||||
@@ -25,15 +25,16 @@ export type TWUI_LINK_LIST_LINK_OBJECT = {
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
};
|
||||
|
||||
export type TWUILink =
|
||||
| TWUI_LINK_LIST_LINK_OBJECT
|
||||
| TWUI_LINK_LIST_LINK_OBJECT[]
|
||||
| undefined;
|
||||
|
||||
export type TWUI_LINK_LIST_PROPS = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
links: (
|
||||
| TWUI_LINK_LIST_LINK_OBJECT
|
||||
| TWUI_LINK_LIST_LINK_OBJECT[]
|
||||
| undefined
|
||||
)[];
|
||||
links: TWUILink[];
|
||||
linkProps?: ComponentProps<typeof Link>;
|
||||
buttonProps?: Omit<ComponentProps<typeof Button>, "title">;
|
||||
divider?: boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
/**
|
||||
@@ -15,7 +15,7 @@ export default function LoadingBlock({
|
||||
"bg-slate-200 dark:bg-white/10",
|
||||
"rounded animate-pulse w-full h-[60px]",
|
||||
"twui-loading-block",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import type { ComponentProps, DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Center from "../layout/Center";
|
||||
import Loading from "./Loading";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import React, { type DetailedHTMLProps, type HTMLAttributes } from "react";
|
||||
import ModalComponent from "../(partials)/ModalComponent";
|
||||
import PopoverComponent from "../(partials)/PopoverComponent";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -33,6 +33,7 @@ export type TWUI_MODAL_PROPS = DetailedHTMLProps<
|
||||
debounce?: number;
|
||||
onClose?: () => any;
|
||||
hoverOpen?: boolean;
|
||||
no_cancel_button?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -43,6 +44,8 @@ export type TWUI_MODAL_PROPS = DetailedHTMLProps<
|
||||
* @ID twui-popover-root
|
||||
* @className twui-popover-content
|
||||
* @className twui-popover-target
|
||||
* @className twui-modal-content
|
||||
* @className twui-modal-target
|
||||
*/
|
||||
export default function Modal(props: TWUI_MODAL_PROPS) {
|
||||
const {
|
||||
@@ -57,6 +60,7 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
||||
debounce = 500,
|
||||
onClose,
|
||||
hoverOpen,
|
||||
no_cancel_button,
|
||||
} = props;
|
||||
|
||||
const [ready, setReady] = React.useState(false);
|
||||
@@ -94,23 +98,19 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
||||
const popoverTargetActiveRef = React.useRef(false);
|
||||
const popoverContentActiveRef = React.useRef(false);
|
||||
|
||||
let closeTimeout: any;
|
||||
const closeTimeoutRef = React.useRef<any>(null);
|
||||
|
||||
const popoverEnterFn = React.useCallback((e: any) => {
|
||||
popoverTargetActiveRef.current = true;
|
||||
popoverContentActiveRef.current = false;
|
||||
window.clearTimeout(closeTimeoutRef.current);
|
||||
setOpen(true);
|
||||
props.onMouseEnter?.(e);
|
||||
}, []);
|
||||
|
||||
const popoverLeaveFn = React.useCallback((e: any) => {
|
||||
window.clearTimeout(closeTimeout);
|
||||
closeTimeout = setTimeout(() => {
|
||||
// if (popoverTargetActiveRef.current) {
|
||||
// popoverTargetActiveRef.current = false;
|
||||
// return;
|
||||
// }
|
||||
|
||||
window.clearTimeout(closeTimeoutRef.current);
|
||||
closeTimeoutRef.current = setTimeout(() => {
|
||||
if (popoverContentActiveRef.current) {
|
||||
popoverContentActiveRef.current = false;
|
||||
return;
|
||||
@@ -120,11 +120,22 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
||||
props.onMouseLeave?.(e);
|
||||
}, []);
|
||||
|
||||
const target_class_name = isPopover
|
||||
? "twui-popover-target"
|
||||
: "twui-modal-target";
|
||||
const content_class_name = isPopover
|
||||
? "twui-popover-content"
|
||||
: "twui-modal-content";
|
||||
|
||||
const handleClickOutside = React.useCallback((e: MouseEvent) => {
|
||||
const targetEl = e.target as HTMLElement;
|
||||
|
||||
const closestWrapper = targetEl.closest(".twui-popover-content");
|
||||
const closestTarget = targetEl.closest(".twui-popover-target");
|
||||
if (!isPopover) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closestWrapper = targetEl.closest(`.${content_class_name}`);
|
||||
const closestTarget = targetEl.closest(`.${target_class_name}`);
|
||||
|
||||
if (closestTarget) return;
|
||||
|
||||
@@ -163,8 +174,8 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
||||
: targetWrapperProps?.onMouseLeave
|
||||
}
|
||||
className={twMerge(
|
||||
"twui-popover-target",
|
||||
targetWrapperProps?.className
|
||||
target_class_name,
|
||||
targetWrapperProps?.className,
|
||||
)}
|
||||
>
|
||||
{target}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ComponentProps, Dispatch, SetStateAction } from "react";
|
||||
import type { ComponentProps, Dispatch, SetStateAction } from "react";
|
||||
import _ from "lodash";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -35,12 +35,30 @@ export default function Pagination({
|
||||
|
||||
const pages = Math.ceil(count / limit);
|
||||
|
||||
const next_button = (
|
||||
<Button
|
||||
title="Next Page Button"
|
||||
onClick={() => {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
setPage?.((prev) => prev + 1);
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={twMerge(
|
||||
"p-1",
|
||||
isLimit ? "opacity-40 pointer-events-none" : "",
|
||||
)}
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Row
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"w-full justify-between flex-nowrap",
|
||||
props.className
|
||||
"w-full justify-between md:flex-nowrap",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{pages > 1 && (
|
||||
@@ -54,21 +72,31 @@ export default function Pagination({
|
||||
size="small"
|
||||
className={twMerge(
|
||||
"p-1",
|
||||
page == 1 ? "opacity-40 pointer-events-none" : ""
|
||||
page == 1 ? "opacity-40 pointer-events-none" : "",
|
||||
)}
|
||||
>
|
||||
<ChevronLeft size={20} />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Row className={twMerge("gap-6 w-full flex-nowrap justify-center")}>
|
||||
<Span size="small" variant="faded">
|
||||
{pages > 1 && <div className="flex md:hidden">{next_button}</div>}
|
||||
|
||||
<Row
|
||||
className={twMerge(
|
||||
"gap-6 w-full md:flex-nowrap justify-center",
|
||||
)}
|
||||
>
|
||||
<Span
|
||||
size="small"
|
||||
variant="faded"
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
Page {page} / {pages}
|
||||
</Span>
|
||||
{pages > 1 && (
|
||||
<Row
|
||||
className={twMerge(
|
||||
"flex-nowrap overflow-x-auto p-1 max-w-[90%]"
|
||||
"flex-nowrap overflow-x-auto p-1 max-w-[90%]",
|
||||
)}
|
||||
>
|
||||
{Array(pages)
|
||||
@@ -92,7 +120,7 @@ export default function Pagination({
|
||||
size="small"
|
||||
color={isCurrent ? "primary" : "gray"}
|
||||
className={twMerge(
|
||||
"p-1 w-6 h-6 min-w-6"
|
||||
"p-1 w-6 h-6 min-w-6",
|
||||
)}
|
||||
key={index}
|
||||
>
|
||||
@@ -104,23 +132,7 @@ export default function Pagination({
|
||||
)}
|
||||
</Row>
|
||||
|
||||
{pages > 1 && (
|
||||
<Button
|
||||
title="Next Page Button"
|
||||
onClick={() => {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
setPage?.((prev) => prev + 1);
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={twMerge(
|
||||
"p-1",
|
||||
isLimit ? "opacity-40 pointer-events-none" : ""
|
||||
)}
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
</Button>
|
||||
)}
|
||||
{pages > 1 && <div className="hidden md:flex">{next_button}</div>}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes, RefObject } from "react";
|
||||
import React, {
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
/**
|
||||
@@ -27,7 +31,7 @@ export default function Paper({
|
||||
"border border-slate-200 dark:border-white/10 border-solid w-full",
|
||||
"relative",
|
||||
"twui-paper",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import Modal, { TWUI_MODAL_PROPS } from "./Modal";
|
||||
import Modal, { type TWUI_MODAL_PROPS } from "./Modal";
|
||||
|
||||
/**
|
||||
* # Popover Component
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypePrismPlus from "rehype-prism-plus";
|
||||
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import { MDXRemote, type MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import { useMDXComponents } from "../mdx/mdx-components";
|
||||
|
||||
export const TWUIPrismLanguages = ["shell", "javascript"] as const;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Input, { InputProps } from "../form/Input";
|
||||
import Input from "../form/Input";
|
||||
import Button from "../layout/Button";
|
||||
import Row from "../layout/Row";
|
||||
import { Search as SearchIcon } from "lucide-react";
|
||||
import React, { DetailedHTMLProps } from "react";
|
||||
import React, {
|
||||
type ComponentProps,
|
||||
type DetailedHTMLProps,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
let timeout: any;
|
||||
|
||||
@@ -14,14 +18,13 @@ export type SearchProps<KeyType extends string> = DetailedHTMLProps<
|
||||
dispatch?: (value?: string) => void;
|
||||
changeHandler?: (value?: string) => void;
|
||||
delay?: number;
|
||||
inputProps?: InputProps<KeyType>;
|
||||
buttonProps?: DetailedHTMLProps<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
>;
|
||||
inputProps?: ComponentProps<typeof Input>;
|
||||
buttonProps?: Omit<ComponentProps<typeof Button>, "title">;
|
||||
loading?: boolean;
|
||||
placeholder?: string;
|
||||
componentRef?: React.RefObject<HTMLInputElement | null>;
|
||||
beforeSearchIconComponent?: ReactNode;
|
||||
no_search_button?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -39,10 +42,12 @@ export default function Search<KeyType extends string>({
|
||||
loading,
|
||||
placeholder,
|
||||
componentRef,
|
||||
beforeSearchIconComponent,
|
||||
no_search_button,
|
||||
...props
|
||||
}: SearchProps<KeyType>) {
|
||||
const [input, setInput] = React.useState(
|
||||
props.defaultValue?.toString() || ""
|
||||
props.defaultValue?.toString() || "",
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -68,7 +73,7 @@ export default function Search<KeyType extends string>({
|
||||
className={twMerge(
|
||||
"relative xl:flex-nowrap items-stretch gap-0 flex-nowrap",
|
||||
"twui-search-wrapper",
|
||||
props?.className
|
||||
props?.className,
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
@@ -76,39 +81,45 @@ export default function Search<KeyType extends string>({
|
||||
placeholder={placeholder || "Search"}
|
||||
{...inputProps}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
inputProps?.onChange?.(e as any);
|
||||
}}
|
||||
className={twMerge(
|
||||
"rounded-r-none!",
|
||||
"twui-search-input",
|
||||
inputProps?.className
|
||||
inputProps?.className,
|
||||
)}
|
||||
wrapperProps={{
|
||||
className: "rounded-r-none!",
|
||||
}}
|
||||
componentRef={inputRef}
|
||||
/>
|
||||
<Button
|
||||
loadingProps={{ size: "small" }}
|
||||
{...buttonProps}
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
className={twMerge(
|
||||
"rounded-l-none! ml-[1px]",
|
||||
"twui-search-button",
|
||||
buttonProps?.className
|
||||
)}
|
||||
onClick={() => {
|
||||
dispatch?.(input);
|
||||
changeHandler?.(input);
|
||||
}}
|
||||
title="Search Button"
|
||||
loading={loading}
|
||||
>
|
||||
<SearchIcon
|
||||
className="text-slate-800 dark:text-white"
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
{beforeSearchIconComponent}
|
||||
{no_search_button ? null : (
|
||||
<Button
|
||||
loadingProps={{ size: "small" }}
|
||||
{...buttonProps}
|
||||
variant="outlined"
|
||||
color="gray"
|
||||
className={twMerge(
|
||||
"rounded-l-none! ml-[1px]",
|
||||
"twui-search-button",
|
||||
buttonProps?.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
dispatch?.(input);
|
||||
changeHandler?.(input);
|
||||
}}
|
||||
title="Search Button"
|
||||
loading={loading}
|
||||
>
|
||||
<SearchIcon
|
||||
className="text-slate-800 dark:text-white"
|
||||
size={15}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { DetailedHTMLProps, PropsWithChildren } from "react";
|
||||
import React, { type DetailedHTMLProps, type PropsWithChildren } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = PropsWithChildren &
|
||||
@@ -15,7 +15,7 @@ export default function SingleLineCodeBlock({ children, ...props }: Props) {
|
||||
"[&_.twui-code-block-header]:absolute [&_.twui-code-block-header]:!bg-transparent",
|
||||
"[&_.twui-code-block-header]:mt-2 [&_.twui-code-block-header]:pr-3 [&_.twui-divider]:hidden",
|
||||
"[&_pre]:!pr-14 [&_.twui-code-block-copied-text]:!hidden",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { LucideProps, Star } from "lucide-react";
|
||||
import React, {
|
||||
DetailedHTMLProps,
|
||||
ForwardRefExoticComponent,
|
||||
HTMLAttributes,
|
||||
RefAttributes,
|
||||
} from "react";
|
||||
import { type LucideProps, Star } from "lucide-react";
|
||||
import React, { type DetailedHTMLProps, type HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type StarProps = {
|
||||
|
||||
@@ -16,13 +16,13 @@ export default function Table({ data }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const headers = Object.keys(data[0]);
|
||||
const headers = Object.keys(data[0]!);
|
||||
|
||||
return (
|
||||
<div className={twMerge("overflow-x-auto w-full")}>
|
||||
<table
|
||||
className={twMerge(
|
||||
"min-w-full divide-y divide-gray dark:divide-gray-dark"
|
||||
"min-w-full divide-y divide-gray dark:divide-gray-dark",
|
||||
)}
|
||||
>
|
||||
<thead className="bg-gray dark:bg-gray-dark">
|
||||
@@ -32,7 +32,7 @@ export default function Table({ data }: Props) {
|
||||
key={header}
|
||||
className={twMerge(
|
||||
"px-3 py-2 text-left opacity-50",
|
||||
"font-semibold"
|
||||
"font-semibold",
|
||||
)}
|
||||
title={header}
|
||||
>
|
||||
@@ -44,14 +44,14 @@ export default function Table({ data }: Props) {
|
||||
<tbody
|
||||
className={twMerge(
|
||||
"bg-background-light dark:bg-background-dark",
|
||||
"divide-y divide-gray dark:divide-gray-dark"
|
||||
"divide-y divide-gray dark:divide-gray-dark",
|
||||
)}
|
||||
>
|
||||
{data.map((row, index) => (
|
||||
<tr
|
||||
key={index}
|
||||
className={twMerge(
|
||||
"hover:bg-gray/20 dark:hover:bg-gray-dark/10"
|
||||
"hover:bg-gray/20 dark:hover:bg-gray-dark/10",
|
||||
)}
|
||||
>
|
||||
{headers.map((header) => (
|
||||
@@ -60,7 +60,7 @@ export default function Table({ data }: Props) {
|
||||
className={twMerge(
|
||||
"px-3 py-2 whitespace-nowrap text-foreground-light",
|
||||
"dark:text-foreground-dark max-w-[200px] overflow-hidden",
|
||||
"overflow-ellipsis"
|
||||
"overflow-ellipsis",
|
||||
)}
|
||||
title={row[header]}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
|
||||
import React, { type ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Border from "./Border";
|
||||
import Stack from "../layout/Stack";
|
||||
|
||||
+13
-47
@@ -1,20 +1,18 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import React, { type PropsWithChildren } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TWUITabsObject = {
|
||||
title: string;
|
||||
value: string;
|
||||
content: React.ReactNode;
|
||||
defaultActive?: boolean;
|
||||
};
|
||||
import {
|
||||
ButtonClasses,
|
||||
type TWUIButtonColorType,
|
||||
type TWUIButtonVariantType,
|
||||
} from "../layout/Button";
|
||||
|
||||
export type TWUI_TOGGLE_PROPS = PropsWithChildren &
|
||||
React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
color?: "normal" | "secondary" | "error" | "success" | "gray";
|
||||
variant?: "normal" | "outlined" | "ghost";
|
||||
color?: TWUIButtonColorType;
|
||||
variant?: TWUIButtonVariantType;
|
||||
href?: string;
|
||||
newTab?: boolean;
|
||||
linkProps?: React.DetailedHTMLProps<
|
||||
@@ -37,50 +35,18 @@ export default function Tag({
|
||||
linkProps,
|
||||
...props
|
||||
}: TWUI_TOGGLE_PROPS) {
|
||||
const finalClassName =
|
||||
ButtonClasses[variant || "normal"]?.[color || "primary"] ?? "";
|
||||
|
||||
const mainComponent = (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"text-xs px-2 py-0.5 rounded-full outline-0",
|
||||
"text-center flex items-center justify-center",
|
||||
color == "secondary"
|
||||
? "bg-secondary text-white outline-secbg-secondary"
|
||||
: color == "success"
|
||||
? "bg-success outline-success text-white"
|
||||
: color == "error"
|
||||
? "bg-orange-700 outline-orange-700"
|
||||
: color == "gray"
|
||||
? twMerge(
|
||||
"bg-slate-100 outline-slate-200 dark:bg-gray-dark dark:outline-gray-dark",
|
||||
"text-slate-800 dark:text-white"
|
||||
)
|
||||
: "bg-primary text-white outline-primbg-primary twui-tag-primary",
|
||||
variant == "outlined"
|
||||
? "!bg-transparent outline-1 " +
|
||||
(color == "secondary"
|
||||
? "text-secondary"
|
||||
: color == "success"
|
||||
? "text-success dark:text-success-dark"
|
||||
: color == "error"
|
||||
? "text-orange-700"
|
||||
: color == "gray"
|
||||
? "text-slate-700 dark:text-white/80"
|
||||
: "text-primary dark:text-primary-dark twui-tag-primary-outlined")
|
||||
: variant == "ghost"
|
||||
? "!bg-transparent outline-none border-none " +
|
||||
(color == "secondary"
|
||||
? "text-secondary"
|
||||
: color == "success"
|
||||
? "text-success dark:text-success-dark"
|
||||
: color == "error"
|
||||
? "text-orange-700"
|
||||
: color == "gray"
|
||||
? "text-slate-700 dark:text-white/80"
|
||||
: "text-primary dark:text-primary-dark")
|
||||
: "",
|
||||
|
||||
finalClassName,
|
||||
"twui-tag",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import React, { type DetailedHTMLProps, type HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Card from "./Card";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import {
|
||||
type DetailedHTMLProps,
|
||||
type HTMLAttributes,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TWUI_TOGGLE_PROPS = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
active?: boolean;
|
||||
setActive?: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
||||
circleProps?: DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
default_active?: boolean;
|
||||
changeHandler?: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -20,10 +25,24 @@ export type TWUI_TOGGLE_PROPS = DetailedHTMLProps<
|
||||
*/
|
||||
export default function Toggle({
|
||||
circleProps,
|
||||
active,
|
||||
setActive,
|
||||
default_active,
|
||||
changeHandler,
|
||||
...props
|
||||
}: TWUI_TOGGLE_PROPS) {
|
||||
const [active, setActive] = useState(default_active || false);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready) return;
|
||||
changeHandler?.(active);
|
||||
}, [active]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setReady(true);
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@@ -32,9 +51,11 @@ export default function Toggle({
|
||||
"border border-slate-300 dark:border-white/30 border-solid rounded-full",
|
||||
active ? "justify-end" : "justify-start",
|
||||
"twui-toggle-wrapper",
|
||||
props.className
|
||||
props.className,
|
||||
)}
|
||||
onClick={() => setActive?.(!active)}
|
||||
onClick={() => {
|
||||
setActive(!active);
|
||||
}}
|
||||
>
|
||||
{typeof active == "undefined" ? (
|
||||
<div className="w-3.5 h-3.5 twui-toggle-circle"></div>
|
||||
@@ -47,7 +68,7 @@ export default function Toggle({
|
||||
? "bg-blue-600 dark:bg-blue-500"
|
||||
: "bg-slate-300 dark:bg-white/40",
|
||||
"twui-toggle-circle",
|
||||
circleProps?.className
|
||||
circleProps?.className,
|
||||
)}
|
||||
></div>
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Send, X } from "lucide-react";
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import React, { type Dispatch, type SetStateAction } from "react";
|
||||
import type { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import Row from "../../layout/Row";
|
||||
import Button from "../../layout/Button";
|
||||
import CopySlug from "../CopySlug";
|
||||
import AIPromptHistoryModal from "./AIPromptHistoryModal";
|
||||
import AIVoiceTranscription from "./AIVoiceTranscription";
|
||||
|
||||
type Props = {
|
||||
streamRes: string;
|
||||
@@ -12,10 +13,15 @@ type Props = {
|
||||
setPrompt: Dispatch<SetStateAction<string>>;
|
||||
loading: boolean;
|
||||
promptFn: (prompt: string) => void;
|
||||
voice_transcription_fn?: (params: { last_message?: string }) => void;
|
||||
transcribing?: boolean;
|
||||
history: ChatCompletionMessageParam[];
|
||||
prompt: string;
|
||||
currentPromptRef: React.MutableRefObject<string>;
|
||||
promptInputRef: React.RefObject<HTMLTextAreaElement>;
|
||||
user_image_url?: string;
|
||||
last_response?: string;
|
||||
cancel_voice_transcription_fn?: () => void;
|
||||
};
|
||||
|
||||
export default function AIPromptActionSection({
|
||||
@@ -28,10 +34,24 @@ export default function AIPromptActionSection({
|
||||
setPrompt,
|
||||
currentPromptRef,
|
||||
promptInputRef,
|
||||
user_image_url,
|
||||
transcribing,
|
||||
voice_transcription_fn,
|
||||
last_response,
|
||||
cancel_voice_transcription_fn,
|
||||
}: Props) {
|
||||
return (
|
||||
<Row className="w-full justify-between">
|
||||
<Row className="gap-4">
|
||||
<Row className="gap-4 px-2">
|
||||
<AIVoiceTranscription
|
||||
transcribing={transcribing}
|
||||
voice_transcription_fn={voice_transcription_fn}
|
||||
cancel_voice_transcription_fn={
|
||||
cancel_voice_transcription_fn
|
||||
}
|
||||
history={history}
|
||||
last_response={last_response}
|
||||
/>
|
||||
{streamRes.match(/./) && (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
@@ -53,10 +73,13 @@ export default function AIPromptActionSection({
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<AIPromptHistoryModal
|
||||
history={history}
|
||||
user_image_url={user_image_url}
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<AIPromptHistoryModal history={history} />
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Button
|
||||
title="Send Prompt"
|
||||
@@ -74,6 +97,7 @@ export default function AIPromptActionSection({
|
||||
promptFn(prompt);
|
||||
}}
|
||||
loadingProps={{ size: "smaller" }}
|
||||
disabled={transcribing}
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import type { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import React from "react";
|
||||
import Paper from "../Paper";
|
||||
import Stack from "../../layout/Stack";
|
||||
import AIPromptPreview from "./AIPromptPreview";
|
||||
import LoadingOverlay from "../LoadingOverlay";
|
||||
import Textarea from "../../form/Textarea";
|
||||
import AIPromptActionSection from "./AIPromptActionSection";
|
||||
import Card from "../Card";
|
||||
import Row from "../../layout/Row";
|
||||
import Span from "../../layout/Span";
|
||||
import { MessageCircleMore } from "lucide-react";
|
||||
import AIPromptBlockInputSection from "./AIPromptBlockInputSection";
|
||||
|
||||
type Props = {
|
||||
model?: string;
|
||||
@@ -57,47 +55,17 @@ export default function AIPromptBlock({
|
||||
history={history}
|
||||
loading={loading}
|
||||
/>
|
||||
<Stack className="w-full relative">
|
||||
{loading && <LoadingOverlay />}
|
||||
<Textarea
|
||||
placeholder={
|
||||
placeholder ||
|
||||
(model ? `Prompt ${model}` : "Prompt AI")
|
||||
}
|
||||
wrapperProps={{ className: "outline-none" }}
|
||||
wrapperWrapperProps={{ className: "w-full" }}
|
||||
value={prompt}
|
||||
onChange={(e) => {
|
||||
setPrompt(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter" && !e.ctrlKey && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
currentPromptRef.current = prompt;
|
||||
setTimeout(() => {
|
||||
setPrompt("");
|
||||
if (promptInputRef.current) {
|
||||
promptInputRef.current.value = "";
|
||||
}
|
||||
}, 200);
|
||||
promptFn(prompt);
|
||||
}
|
||||
}}
|
||||
componentRef={promptInputRef}
|
||||
// autoFocus
|
||||
/>
|
||||
<AIPromptActionSection
|
||||
loading={loading}
|
||||
promptFn={promptFn}
|
||||
setStreamRes={setMdRes}
|
||||
streamRes={mdRes}
|
||||
history={history}
|
||||
prompt={prompt}
|
||||
setPrompt={setPrompt}
|
||||
currentPromptRef={currentPromptRef}
|
||||
promptInputRef={promptInputRef as any}
|
||||
/>
|
||||
</Stack>
|
||||
<AIPromptBlockInputSection
|
||||
{...{
|
||||
model,
|
||||
promptFn,
|
||||
history,
|
||||
loading,
|
||||
mdRes,
|
||||
setMdRes,
|
||||
placeholder,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import React from "react";
|
||||
import Stack from "../../layout/Stack";
|
||||
import LoadingOverlay from "../LoadingOverlay";
|
||||
import Textarea from "../../form/Textarea";
|
||||
import AIPromptActionSection from "./AIPromptActionSection";
|
||||
|
||||
type Props = {
|
||||
model?: string;
|
||||
promptFn: (prompt: string) => void;
|
||||
history?: ChatCompletionMessageParam[];
|
||||
loading?: boolean;
|
||||
mdRes?: string;
|
||||
setMdRes: React.Dispatch<React.SetStateAction<string>>;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export default function AIPromptBlockInputSection({
|
||||
model,
|
||||
promptFn,
|
||||
history = [],
|
||||
loading = false,
|
||||
mdRes = "",
|
||||
setMdRes,
|
||||
placeholder,
|
||||
}: Props) {
|
||||
const [prompt, setPrompt] = React.useState("");
|
||||
const currentPromptRef = React.useRef("");
|
||||
const promptInputRef = React.useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
return (
|
||||
<Stack className="w-full relative">
|
||||
{loading && <LoadingOverlay />}
|
||||
<Textarea
|
||||
placeholder={
|
||||
placeholder || (model ? `Prompt ${model}` : "Prompt AI")
|
||||
}
|
||||
wrapperProps={{ className: "outline-none" }}
|
||||
wrapperWrapperProps={{ className: "w-full" }}
|
||||
value={prompt}
|
||||
onChange={(e) => {
|
||||
setPrompt(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter" && !e.ctrlKey && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
currentPromptRef.current = prompt;
|
||||
setTimeout(() => {
|
||||
setPrompt("");
|
||||
if (promptInputRef.current) {
|
||||
promptInputRef.current.value = "";
|
||||
}
|
||||
}, 200);
|
||||
promptFn(prompt);
|
||||
}
|
||||
}}
|
||||
componentRef={promptInputRef}
|
||||
// autoFocus
|
||||
/>
|
||||
<AIPromptActionSection
|
||||
loading={loading}
|
||||
promptFn={promptFn}
|
||||
setStreamRes={setMdRes}
|
||||
streamRes={mdRes}
|
||||
history={history}
|
||||
prompt={prompt}
|
||||
setPrompt={setPrompt}
|
||||
currentPromptRef={currentPromptRef}
|
||||
promptInputRef={promptInputRef as any}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import type { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Bot, User } from "lucide-react";
|
||||
import { Bot, History, User } from "lucide-react";
|
||||
import Modal from "../Modal";
|
||||
import Button from "../../layout/Button";
|
||||
import Stack from "../../layout/Stack";
|
||||
@@ -11,13 +10,20 @@ import Divider from "../../layout/Divider";
|
||||
import Row from "../../layout/Row";
|
||||
import Card from "../Card";
|
||||
import Border from "../Border";
|
||||
import MarkdownEditorPreviewComponent from "../../mdx/markdown/MarkdownEditorPreviewComponent";
|
||||
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import Img from "../../layout/Img";
|
||||
|
||||
type Props = {
|
||||
history: ChatCompletionMessageParam[];
|
||||
user_image_url?: string;
|
||||
};
|
||||
|
||||
export default function AIPromptHistoryModal({ history }: Props) {
|
||||
export default function AIPromptHistoryModal({
|
||||
history,
|
||||
user_image_url,
|
||||
}: Props) {
|
||||
if (!history[0]) return null;
|
||||
|
||||
return (
|
||||
@@ -27,9 +33,10 @@ export default function AIPromptHistoryModal({ history }: Props) {
|
||||
title="View Chat History"
|
||||
size="smaller"
|
||||
color="gray"
|
||||
variant="outlined"
|
||||
variant="ghost"
|
||||
className="p-1!"
|
||||
>
|
||||
View History
|
||||
<History size={20} opacity={0.7} />
|
||||
</Button>
|
||||
}
|
||||
className="max-w-[900px] bg-slate-100 dark:bg-white/5 xl:p-8"
|
||||
@@ -49,22 +56,35 @@ export default function AIPromptHistoryModal({ history }: Props) {
|
||||
return (
|
||||
<Row
|
||||
key={index}
|
||||
className="w-full items-start justify-end"
|
||||
className="w-full items-start justify-end flex-nowrap"
|
||||
>
|
||||
<Card
|
||||
className={twMerge(
|
||||
"bg-background-dark text-foreground-dark dark:!bg-background-light dark:text-foreground-light"
|
||||
"bg-background-dark text-foreground-dark dark:!bg-background-light dark:text-foreground-light",
|
||||
"twui-llm-user-prompt-history-block",
|
||||
)}
|
||||
>
|
||||
{hst.content?.toString()}
|
||||
<Stack className="twui-llm-chat-block">
|
||||
{hst.content?.toString()}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Border className="w-10 h-10 rounded-full p-2 items-center justify-center">
|
||||
<User />
|
||||
</Border>
|
||||
{user_image_url ? (
|
||||
<Img
|
||||
alt="Prompting user"
|
||||
size={30}
|
||||
circle
|
||||
src={user_image_url}
|
||||
/>
|
||||
) : (
|
||||
<Border className="w-10 h-10 rounded-full p-2 items-center justify-center">
|
||||
<User />
|
||||
</Border>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row
|
||||
key={index}
|
||||
@@ -74,21 +94,18 @@ export default function AIPromptHistoryModal({ history }: Props) {
|
||||
<Border
|
||||
className={twMerge(
|
||||
"w-10 h-10 rounded-full items-center justify-center bg-white p-2",
|
||||
"dark:bg-background-dark"
|
||||
"dark:bg-background-dark",
|
||||
)}
|
||||
>
|
||||
<Bot />
|
||||
</Border>
|
||||
</Stack>
|
||||
<Card className="grow overflow-x-auto xl:p-8">
|
||||
<MarkdownEditorPreviewComponent
|
||||
value={hst.content?.toString() || ""}
|
||||
maxHeight="none"
|
||||
wrapperProps={{
|
||||
className:
|
||||
"border-none p-0 ai-response-content w-full",
|
||||
}}
|
||||
/>
|
||||
<Stack className="twui-llm-chat-block">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{hst.content?.toString()}
|
||||
</ReactMarkdown>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import Divider from "../../layout/Divider";
|
||||
import Stack from "../../layout/Stack";
|
||||
import React from "react";
|
||||
import MarkdownEditorPreviewComponent from "../../mdx/markdown/MarkdownEditorPreviewComponent";
|
||||
import { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
import type { ChatCompletionMessageParam } from "openai/resources/index";
|
||||
|
||||
type Props = {
|
||||
streamRes: string;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Speech, Voicemail, X } from "lucide-react";
|
||||
import Button from "../../layout/Button";
|
||||
import { ChatCompletionMessageParam } from "openai/resources";
|
||||
import Row from "../../layout/Row";
|
||||
import Loading from "../Loading";
|
||||
import Border from "../Border";
|
||||
|
||||
type Props = {
|
||||
voice_transcription_fn?: (params: { last_message?: string }) => void;
|
||||
transcribing?: boolean;
|
||||
history?: ChatCompletionMessageParam[];
|
||||
last_response?: string;
|
||||
cancel_voice_transcription_fn?: () => void;
|
||||
};
|
||||
|
||||
export default function AIVoiceTranscription({
|
||||
transcribing,
|
||||
voice_transcription_fn,
|
||||
history,
|
||||
last_response,
|
||||
cancel_voice_transcription_fn,
|
||||
}: Props) {
|
||||
if (!voice_transcription_fn) return null;
|
||||
|
||||
if (transcribing) {
|
||||
return (
|
||||
<Border className="px-1.5! py-0.5!">
|
||||
<Row className="gap-2!">
|
||||
<Loading size="small" />
|
||||
<Button
|
||||
title="Cancel Transcription"
|
||||
className="p-1!"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
cancel_voice_transcription_fn?.();
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<X size={20} opacity={0.7} />
|
||||
</Button>
|
||||
</Row>
|
||||
</Border>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
title="Listen to Speech"
|
||||
size="smaller"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
loading={transcribing}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const history_last_response = history?.find(
|
||||
(h) => h.role == "assistant",
|
||||
);
|
||||
voice_transcription_fn({
|
||||
last_message:
|
||||
last_response ||
|
||||
history_last_response?.content?.toString(),
|
||||
});
|
||||
}}
|
||||
className="p-1!"
|
||||
>
|
||||
<Speech size={20} opacity={0.7} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user