Updates
This commit is contained in:
@@ -3,13 +3,24 @@ import Link from "../layout/Link";
|
||||
import Divider from "../layout/Divider";
|
||||
import Row from "../layout/Row";
|
||||
import lowerToTitleCase from "../utils/lower-to-title-case";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type LinkObject = {
|
||||
title: string;
|
||||
path: string;
|
||||
};
|
||||
export default function Breadcrumbs() {
|
||||
type Props = {
|
||||
excludeRegexMatch?: RegExp;
|
||||
};
|
||||
|
||||
/**
|
||||
* # TWUI Breadcrumbs
|
||||
* @className `twui-current-breadcrumb-link`
|
||||
* @className `twui-current-breadcrumb-wrapper`
|
||||
*/
|
||||
export default function Breadcrumbs({ excludeRegexMatch }: Props) {
|
||||
const [links, setLinks] = React.useState<LinkObject[] | null>(null);
|
||||
const [current, setCurrent] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
let pathname = window.location.pathname;
|
||||
@@ -27,6 +38,8 @@ export default function Breadcrumbs() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (excludeRegexMatch && excludeRegexMatch.test(linkText)) return;
|
||||
|
||||
validPathLinks.push({
|
||||
title: lowerToTitleCase(linkText),
|
||||
path: (() => {
|
||||
@@ -56,30 +69,50 @@ export default function Breadcrumbs() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Row className="gap-4 flex-nowrap whitespace-nowrap overflow-x-auto w-full">
|
||||
{links.map((linkObject, index, array) => {
|
||||
if (index === links.length - 1) {
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
href={linkObject.path}
|
||||
className="text-slate-400 dark:text-slate-500 pointer-events-none text-xs"
|
||||
>
|
||||
{linkObject.title}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Link href={linkObject.path} className="text-xs">
|
||||
<div
|
||||
className={twMerge(
|
||||
"overflow-x-auto max-w-[70vw]",
|
||||
"twui-current-breadcrumb-wrapper"
|
||||
)}
|
||||
>
|
||||
<Row className="gap-4 flex-nowrap whitespace-nowrap overflow-x-auto w-full">
|
||||
{links.map((linkObject, index, array) => {
|
||||
const isTarget = array.length - 1 == index;
|
||||
|
||||
if (index === links.length - 1) {
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
href={linkObject.path}
|
||||
className={twMerge(
|
||||
"text-slate-400 dark:text-slate-500 pointer-events-none text-xs",
|
||||
isTarget ? "current" : "",
|
||||
"twui-current-breadcrumb-link"
|
||||
)}
|
||||
>
|
||||
{linkObject.title}
|
||||
</Link>
|
||||
<Divider vertical />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Link
|
||||
href={linkObject.path}
|
||||
className={twMerge(
|
||||
"text-xs",
|
||||
isTarget ? "current" : "",
|
||||
"twui-current-breadcrumb-link"
|
||||
)}
|
||||
>
|
||||
{linkObject.title}
|
||||
</Link>
|
||||
<Divider vertical />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
////////////////////////////////////////
|
||||
////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import React, {
|
||||
DetailedHTMLProps,
|
||||
HTMLAttributes,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Stack from "../layout/Stack";
|
||||
import Row from "../layout/Row";
|
||||
import Button from "../layout/Button";
|
||||
import Divider from "../layout/Divider";
|
||||
|
||||
export const TWUIPrismLanguages = ["shell", "javascript"] as const;
|
||||
|
||||
type Props = PropsWithChildren &
|
||||
DetailedHTMLProps<HTMLAttributes<HTMLPreElement>, HTMLPreElement> & {
|
||||
wrapperProps?: DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
"data-title"?: string;
|
||||
backgroundColor?: string;
|
||||
singleBlock?: boolean;
|
||||
language?: (typeof TWUIPrismLanguages)[number];
|
||||
};
|
||||
|
||||
/**
|
||||
* # CodeBlock
|
||||
*
|
||||
* @className `twui-code-block-wrapper`
|
||||
* @className `twui-code-pre-wrapper`
|
||||
* @className `twui-code-block-pre`
|
||||
* @className `twui-code-block-header`
|
||||
*/
|
||||
export default function CodeBlock({
|
||||
children,
|
||||
wrapperProps,
|
||||
backgroundColor,
|
||||
singleBlock,
|
||||
language,
|
||||
...props
|
||||
}: Props) {
|
||||
const codeRef = React.useRef<HTMLDivElement>();
|
||||
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
const title = props?.["data-title"];
|
||||
|
||||
const finalBackgroundColor = backgroundColor || "#28272b";
|
||||
|
||||
return (
|
||||
<div
|
||||
{...wrapperProps}
|
||||
className={twMerge(
|
||||
"outline outline-[1px] outline-slate-200 dark:outline-white/10",
|
||||
`rounded w-full transition-all items-start`,
|
||||
"relative",
|
||||
"twui-code-block-wrapper",
|
||||
wrapperProps?.className
|
||||
)}
|
||||
style={{
|
||||
boxShadow: copied
|
||||
? "0 0 10px 10px rgba(18, 139, 99, 0.2)"
|
||||
: undefined,
|
||||
maxWidth: "calc(100vw - 80px)",
|
||||
backgroundColor: finalBackgroundColor,
|
||||
...props.style,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
className={twMerge(
|
||||
"gap-0 w-full overflow-x-auto relative",
|
||||
"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"
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: finalBackgroundColor,
|
||||
}}
|
||||
>
|
||||
{title && <span className="text-white/70">{title}</span>}
|
||||
<div className="ml-auto">
|
||||
{copied ? (
|
||||
<Row>
|
||||
<span className="text-white text-xs twui-code-block-copied-text">
|
||||
Copied!
|
||||
</span>
|
||||
<div className="w-5 h-5 rounded-full bg-emerald-600 text-white flex items-center justify-center">
|
||||
<Check size={15} />
|
||||
</div>
|
||||
</Row>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
beforeIcon={<Copy size={17} color="white" />}
|
||||
className="!p-1 !bg-transparent"
|
||||
onClick={() => {
|
||||
const content =
|
||||
codeRef.current?.textContent;
|
||||
if (!content) {
|
||||
window.alert("No Content to copy");
|
||||
return;
|
||||
}
|
||||
|
||||
window.navigator.clipboard
|
||||
.writeText(content)
|
||||
.then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 3000);
|
||||
});
|
||||
}}
|
||||
title="Copy Code Snippet"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Row>
|
||||
{!singleBlock && (
|
||||
<Divider className="!border-white/10 sticky top-10" />
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
`p-1 w-full [&_pre]:!bg-transparent`,
|
||||
singleBlock ? "" : "-mt-1",
|
||||
"twui-code-pre-wrapper"
|
||||
)}
|
||||
ref={codeRef as any}
|
||||
>
|
||||
<pre
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"!my-0",
|
||||
language ? `language-${language}` : "",
|
||||
"twui-code-block-pre",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -37,6 +37,8 @@ let timeout: any;
|
||||
* @className_wrapper twui-dropdown-wrapper
|
||||
* @className_wrapper twui-dropdown-target
|
||||
* @className_wrapper twui-dropdown-content
|
||||
*
|
||||
* @note use the class `cancel-link` to prevent popup open on click
|
||||
*/
|
||||
export default function Dropdown({
|
||||
contentWrapperProps,
|
||||
@@ -102,7 +104,9 @@ export default function Dropdown({
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<div
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
const targetEl = e.target as HTMLElement | null;
|
||||
if (targetEl?.closest?.(".cancel-link")) return;
|
||||
externalSetOpen?.(!open);
|
||||
setOpen(!open);
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { DetailedHTMLProps, PropsWithChildren } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = PropsWithChildren &
|
||||
DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
||||
|
||||
/**
|
||||
* # Single Line CodeBlock
|
||||
*/
|
||||
export default function SingleLineCodeBlock({ children, ...props }: Props) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"[&_.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
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import Span from "../layout/Span";
|
||||
export const ToastStyles = ["normal", "success", "error"] as const;
|
||||
export const ToastColors = ToastStyles;
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
export type TWUIToastProps = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
@@ -18,6 +18,9 @@ type Props = DetailedHTMLProps<
|
||||
color?: (typeof ToastStyles)[number];
|
||||
};
|
||||
|
||||
let interval: any;
|
||||
let timeout: any;
|
||||
|
||||
/**
|
||||
* # Toast Component
|
||||
* @className twui-toast-root
|
||||
@@ -31,7 +34,7 @@ export default function Toast({
|
||||
closeDelay = 4000,
|
||||
color,
|
||||
...props
|
||||
}: Props) {
|
||||
}: TWUIToastProps) {
|
||||
if (!open) return null;
|
||||
|
||||
const toastEl = (
|
||||
@@ -47,10 +50,25 @@ export default function Toast({
|
||||
props.className,
|
||||
"twui-toast"
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
window.clearTimeout(timeout);
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const targetEl = e.target as HTMLElement;
|
||||
const rootWrapperEl = targetEl.closest(
|
||||
".twui-toast-root"
|
||||
) as HTMLDivElement | null;
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
closeToast({ wrapperEl: rootWrapperEl });
|
||||
setOpen?.(false);
|
||||
}, closeDelay);
|
||||
}}
|
||||
>
|
||||
<Span
|
||||
className={twMerge(
|
||||
"absolute top-2 right-2 z-[100] cursor-pointer"
|
||||
"absolute top-2 right-2 z-[100] cursor-pointer",
|
||||
"text-white"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
const targetEl = e.target as HTMLElement;
|
||||
@@ -64,7 +82,7 @@ export default function Toast({
|
||||
>
|
||||
<X size={15} />
|
||||
</Span>
|
||||
{props.children}
|
||||
<Span className={twMerge("text-white")}>{props.children}</Span>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -81,7 +99,7 @@ export default function Toast({
|
||||
const root = createRoot(wrapperEl);
|
||||
root.render(toastEl);
|
||||
|
||||
setTimeout(() => {
|
||||
timeout = setTimeout(() => {
|
||||
closeToast({ wrapperEl });
|
||||
setOpen?.(false);
|
||||
}, closeDelay);
|
||||
|
||||
Reference in New Issue
Block a user