Updates
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { ComponentProps, ReactNode } from "react";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { TWUI_LINK_LIST_LINK_OBJECT } from "../elements/LinkList";
|
||||
import Link from "./Link";
|
||||
import Row from "./Row";
|
||||
|
||||
type Props = ComponentProps<typeof Link> & {
|
||||
link: TWUI_LINK_LIST_LINK_OBJECT;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Link With an Arrow
|
||||
* @className twui-arrowed-link
|
||||
*/
|
||||
export default function ArrowedLink({ link, icon, ...props }: Props) {
|
||||
return (
|
||||
<Link href={link.url} {...props} {...link.linkProps}>
|
||||
<Row>
|
||||
<span>{link.title}</span>
|
||||
{icon || <ArrowUpRight size={17} />}
|
||||
</Row>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
+230
-52
@@ -1,11 +1,48 @@
|
||||
import {
|
||||
import React, {
|
||||
AnchorHTMLAttributes,
|
||||
ButtonHTMLAttributes,
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
HTMLAttributeAnchorTarget,
|
||||
HTMLAttributes,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Loading from "../elements/Loading";
|
||||
import LucideIcon, { TWUILucideIconName } from "../elements/lucide-icon";
|
||||
|
||||
export type TWUIButtonProps = DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & {
|
||||
title: string;
|
||||
variant?: "normal" | "ghost" | "outlined";
|
||||
color?:
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "text"
|
||||
| "white"
|
||||
| "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?: TWUILucideIconName | React.JSX.Element;
|
||||
afterIcon?: TWUILucideIconName | React.JSX.Element;
|
||||
buttonContentProps?: DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
loadingProps?: ComponentProps<typeof Loading>;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Buttons
|
||||
@@ -17,106 +54,196 @@ import Loading from "../elements/Loading";
|
||||
* @className twui-button-secondary
|
||||
* @className twui-button-secondary-outlined
|
||||
* @className twui-button-secondary-ghost
|
||||
* @className twui-button-white
|
||||
* @className twui-button-white-outlined
|
||||
* @className twui-button-white-ghost
|
||||
* @className twui-button-accent
|
||||
* @className twui-button-accent-outlined
|
||||
* @className twui-button-accent-ghost
|
||||
* @className twui-button-gray
|
||||
* @className twui-button-gray-outlined
|
||||
* @className twui-button-gray-ghost
|
||||
*
|
||||
* @example
|
||||
```css
|
||||
CSS directive:
|
||||
|
||||
//@theme inline {
|
||||
--breakpoint-xs: 350px;
|
||||
--color-primary: #000000;
|
||||
--color-primary-hover: #29292b;
|
||||
--color-primary-outline: #29292b;
|
||||
--color-primary-text: #29292b;
|
||||
--color-primary-dark: #29292b;
|
||||
--color-primary-dark-hover: #4b4b4b;
|
||||
--color-primary-dark-outline: #4b4b4b;
|
||||
--color-primary-dark-text: #4b4b4b;
|
||||
--color-secondary: #000000;
|
||||
--color-secondary-hover: #dddddd;
|
||||
--color-secondary-outline: #dddddd;
|
||||
--color-secondary-text: #dddddd;
|
||||
--color-secondary-dark: #000000;
|
||||
--color-secondary-dark-hover: #dddddd;
|
||||
--color-secondary-dark-outline: #dddddd;
|
||||
--color-secondary-dark-text: #dddddd;
|
||||
--color-accent: #000000;
|
||||
--color-accent-hover: #dddddd;
|
||||
--color-accent-outline: #dddddd;
|
||||
--color-accent-text: #dddddd;
|
||||
--color-accent-dark: #000000;
|
||||
--color-accent-dark-hover: #dddddd;
|
||||
--color-accent-dark-outline: #dddddd;
|
||||
--color-accent-dark-text: #dddddd;
|
||||
}
|
||||
```
|
||||
*/
|
||||
export default function Button({
|
||||
href,
|
||||
target,
|
||||
variant,
|
||||
color,
|
||||
size,
|
||||
buttonContentProps,
|
||||
linkProps,
|
||||
beforeIcon,
|
||||
afterIcon,
|
||||
loading,
|
||||
loadingIconSize,
|
||||
loadingProps,
|
||||
...props
|
||||
}: DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & {
|
||||
variant?: "normal" | "ghost" | "outlined";
|
||||
color?: "primary" | "secondary" | "accent" | "gray";
|
||||
href?: string;
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
loading?: boolean;
|
||||
linkProps?: DetailedHTMLProps<
|
||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
HTMLAnchorElement
|
||||
>;
|
||||
}) {
|
||||
}: TWUIButtonProps) {
|
||||
const finalClassName: string = (() => {
|
||||
if (variant == "normal" || !variant) {
|
||||
if (color == "primary" || !color)
|
||||
return twMerge(
|
||||
"bg-blue-500 hover:bg-blue-600 text-white",
|
||||
"twui-button-primary"
|
||||
"bg-primary hover:bg-primary-hover text-white",
|
||||
"dark:bg-primary-dark hover:dark:bg-primary-dark-hover text-white",
|
||||
"twui-button-primary",
|
||||
);
|
||||
if (color == "secondary")
|
||||
return twMerge(
|
||||
"bg-emerald-500 hover:bg-emerald-600 text-white",
|
||||
"twui-button-secondary"
|
||||
"bg-secondary hover:bg-secondary-hover text-white",
|
||||
"twui-button-secondary",
|
||||
);
|
||||
if (color == "white")
|
||||
return twMerge(
|
||||
"!bg-white hover:!bg-slate-200 !text-slate-800",
|
||||
"twui-button-white",
|
||||
);
|
||||
if (color == "accent")
|
||||
return twMerge(
|
||||
"bg-violet-500 hover:bg-violet-600 text-white",
|
||||
"twui-button-accent"
|
||||
"bg-accent hover:bg-accent-hover text-white",
|
||||
"twui-button-accent",
|
||||
);
|
||||
if (color == "gray")
|
||||
return twMerge(
|
||||
"bg-slate-300 hover:bg-slate-200 text-slate-800",
|
||||
"twui-button-gray"
|
||||
"bg-gray hover:bg-gray-hover text-foreground-light",
|
||||
"dark:bg-gray-dark hover:dark:bg-gray-dark-hover dark:text-foreground-dark",
|
||||
"twui-button-gray",
|
||||
);
|
||||
if (color == "success")
|
||||
return twMerge(
|
||||
"bg-success hover:bg-success-hover text-white",
|
||||
"dark:bg-success hover:dark:bg-success-hover text-white",
|
||||
"twui-button-success",
|
||||
);
|
||||
if (color == "error")
|
||||
return twMerge(
|
||||
"bg-error hover:bg-error-hover text-white",
|
||||
"dark:bg-error hover:dark:bg-error-hover text-white",
|
||||
"twui-button-error",
|
||||
);
|
||||
} else if (variant == "outlined") {
|
||||
if (color == "primary" || !color)
|
||||
return twMerge(
|
||||
"bg-transparent outline outline-1 outline-blue-500",
|
||||
"text-blue-500",
|
||||
"twui-button-primary-outlined"
|
||||
"bg-transparent outline outline-1 outline-primary",
|
||||
"text-primary-text dark:text-primary-dark-text dark:outline-primary-dark-outline",
|
||||
"twui-button-primary-outlined",
|
||||
);
|
||||
if (color == "secondary")
|
||||
return twMerge(
|
||||
"bg-transparent outline outline-1 outline-emerald-500",
|
||||
"text-emerald-500",
|
||||
"twui-button-secondary-outlined"
|
||||
"bg-transparent outline outline-1 outline-secondary",
|
||||
"text-secondary",
|
||||
"twui-button-secondary-outlined",
|
||||
);
|
||||
if (color == "accent")
|
||||
return twMerge(
|
||||
"bg-transparent outline outline-1 outline-violet-500",
|
||||
"text-violet-500",
|
||||
"twui-button-accent-outlined"
|
||||
"bg-transparent outline outline-1 outline-accent",
|
||||
"text-accent",
|
||||
"twui-button-accent-outlined",
|
||||
);
|
||||
if (color == "gray")
|
||||
return twMerge(
|
||||
"bg-transparent outline outline-1 outline-slate-300",
|
||||
"text-slate-600",
|
||||
"twui-button-gray-outlined"
|
||||
"text-slate-600 dark:text-white/60 dark:outline-white/30",
|
||||
"twui-button-gray-outlined",
|
||||
);
|
||||
if (color == "white")
|
||||
return twMerge(
|
||||
"bg-transparent outline outline-1 outline-white/50",
|
||||
"text-white",
|
||||
"twui-button-white-outlined",
|
||||
);
|
||||
if (color == "error")
|
||||
return twMerge(
|
||||
"bg-transparent outline outline-1 outline-error text-error",
|
||||
"dark:outline-error dark:text-error-dark",
|
||||
"twui-button-error-outlined",
|
||||
);
|
||||
} else if (variant == "ghost") {
|
||||
if (color == "primary" || !color)
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-blue-500",
|
||||
"twui-button-primary-ghost"
|
||||
"bg-transparent dark:bg-transparent outline-none p-2",
|
||||
"text-primary-text dark:text-primary-dark-text hover:bg-transparent dark:hover:bg-transparent",
|
||||
"twui-button-primary-ghost",
|
||||
);
|
||||
if (color == "secondary")
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-emerald-500",
|
||||
"twui-button-secondary-ghost"
|
||||
"bg-transparent dark:bg-transparent outline-none p-2",
|
||||
"text-secondary hover:bg-transparent dark:hover:bg-transparent",
|
||||
"twui-button-secondary-ghost",
|
||||
);
|
||||
if (color == "text")
|
||||
return twMerge(
|
||||
"bg-transparent dark:bg-transparent outline-none p-2 dark:text-foreground-dark",
|
||||
"text-foreground-light hover:bg-transparent dark:hover:bg-transparent",
|
||||
"twui-button-secondary-ghost",
|
||||
);
|
||||
if (color == "accent")
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-violet-500",
|
||||
"twui-button-accent-ghost"
|
||||
"bg-transparent dark:bg-transparent outline-none p-2",
|
||||
"text-accent hover:bg-transparent dark:hover:bg-transparent",
|
||||
"twui-button-accent-ghost",
|
||||
);
|
||||
if (color == "gray")
|
||||
return twMerge(
|
||||
"bg-transparent dark:bg-transparent outline-none p-2 hover:bg-transparent dark:hover:bg-transparent",
|
||||
"text-slate-600 dark:text-white/70 hover:opacity-80",
|
||||
"twui-button-gray-ghost",
|
||||
);
|
||||
if (color == "error")
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-slate-600",
|
||||
"twui-button-gray-ghost"
|
||||
"text-red-600 dark:text-red-400",
|
||||
"twui-button-error-ghost",
|
||||
);
|
||||
if (color == "warning")
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-yellow-600",
|
||||
"twui-button-warning-ghost",
|
||||
);
|
||||
if (color == "success")
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-success",
|
||||
"twui-button-success-ghost",
|
||||
);
|
||||
if (color == "white")
|
||||
return twMerge(
|
||||
"bg-transparent outline-none p-2",
|
||||
"text-white",
|
||||
"twui-button-white-ghost",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,30 +254,81 @@ export default function Button({
|
||||
<button
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"bg-blue-600 text-white text-base font-medium px-4 py-2 rounded",
|
||||
"flex items-center justify-center relative transition-all",
|
||||
"bg-primary text-white font-medium px-4 py-2 rounded-default",
|
||||
"flex items-center justify-center relative transition-all cursor-pointer",
|
||||
props.disabled ? "opacity-40 cursor-not-allowed" : "",
|
||||
"twui-button-general",
|
||||
size == "small"
|
||||
? "px-3 py-1.5 twui-button-small text-sm"
|
||||
: size == "smaller"
|
||||
? "px-2 py-1 text-xs twui-button-smaller"
|
||||
: size == "large"
|
||||
? "text-lg twui-button-large"
|
||||
: size == "larger"
|
||||
? "px-5 py-3 text-xl twui-button-larger"
|
||||
: "twui-button-base",
|
||||
finalClassName,
|
||||
loading ? "pointer-events-none opacity-80" : "",
|
||||
props.className,
|
||||
loading ? "pointer-events-none opacity-80" : "l"
|
||||
)}
|
||||
aria-label={props.title}
|
||||
>
|
||||
<div
|
||||
{...buttonContentProps}
|
||||
className={twMerge(
|
||||
"flex items-center gap-2",
|
||||
"flex flex-row items-center gap-2 whitespace-nowrap",
|
||||
loading ? "opacity-0" : "",
|
||||
"twui-button-content-wrapper"
|
||||
"twui-button-content-wrapper",
|
||||
buttonContentProps?.className,
|
||||
)}
|
||||
>
|
||||
{beforeIcon ? (
|
||||
typeof beforeIcon == "string" ? (
|
||||
<LucideIcon name={beforeIcon as TWUILucideIconName} />
|
||||
) : (
|
||||
beforeIcon
|
||||
)
|
||||
) : null}
|
||||
{props.children}
|
||||
{afterIcon ? (
|
||||
typeof afterIcon == "string" ? (
|
||||
<LucideIcon name={afterIcon as TWUILucideIconName} />
|
||||
) : (
|
||||
afterIcon
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
{loading && <Loading className="absolute" />}
|
||||
|
||||
{loading && (
|
||||
<Loading
|
||||
size={(() => {
|
||||
if (loadingIconSize) return loadingIconSize;
|
||||
switch (size) {
|
||||
case "small":
|
||||
return "small";
|
||||
case "smaller":
|
||||
return "smaller";
|
||||
|
||||
default:
|
||||
return "normal";
|
||||
}
|
||||
})()}
|
||||
{...loadingProps}
|
||||
className={twMerge("absolute", loadingProps?.className)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (href)
|
||||
return (
|
||||
<a {...linkProps} href={href} target={target}>
|
||||
<a
|
||||
{...linkProps}
|
||||
href={href}
|
||||
target={target}
|
||||
title={props.title}
|
||||
aria-label={props.title}
|
||||
>
|
||||
{buttonComponent}
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
/**
|
||||
* # Flexbox Column
|
||||
* @className twui-center
|
||||
*/
|
||||
export default function Center({
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"flex flex-col items-center justify-center gap-4 p-2 w-full",
|
||||
"h-full twui-center",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,8 +12,8 @@ export default function Container({
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"flex items-center w-full max-w-[1200px] gap-4 justify-between",
|
||||
"flex-wrap flex-col xl:flex-row",
|
||||
"flex w-full max-w-container gap-4 justify-between",
|
||||
"flex-wrap flex-col xl:flex-row items-start xl:items-center",
|
||||
"twui-container",
|
||||
props.className
|
||||
)}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
vertical?: boolean;
|
||||
dashed?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Vertical and Horizontal Divider
|
||||
* @className twui-divider
|
||||
* @className twui-divider-horizontal
|
||||
* @className twui-divider-vertical
|
||||
*/
|
||||
export default function Divider({
|
||||
vertical,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
|
||||
vertical?: boolean;
|
||||
}) {
|
||||
export default function Divider({ vertical, dashed, ...props }: Props) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@@ -20,6 +25,8 @@ export default function Divider({
|
||||
? "border-0 border-l h-full min-h-5"
|
||||
: "border-0 border-t w-full",
|
||||
"twui-divider",
|
||||
vertical ? "twui-divider-vertical" : "twui-divider-horizontal",
|
||||
dashed ? "border-dashed" : "border-solid",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Img, { TWUIImageProps } from "./Img";
|
||||
import Border from "../elements/Border";
|
||||
|
||||
export default function DocsImg({ ...props }: TWUIImageProps) {
|
||||
return (
|
||||
<Border className={twMerge("p-0 mb-10")}>
|
||||
<Img
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"w-full h-auto rounded-default overflow-hidden",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
</Border>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,12 @@ export default function H1({
|
||||
return (
|
||||
<h1
|
||||
{...props}
|
||||
className={twMerge("text-5xl mb-4", "twui-h1", props.className)}
|
||||
className={twMerge(
|
||||
"text-4xl md:text-5xl mb-4",
|
||||
"twui-headings twui-heading",
|
||||
"twui-h1",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</h1>
|
||||
|
||||
@@ -11,7 +11,12 @@ export default function H2({
|
||||
return (
|
||||
<h2
|
||||
{...props}
|
||||
className={twMerge("text-3xl mb-4", "twui-h2", props.className)}
|
||||
className={twMerge(
|
||||
"text-2xl md:text-3xl mb-4",
|
||||
"twui-headings twui-heading",
|
||||
"twui-h2",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</h2>
|
||||
|
||||
@@ -11,7 +11,12 @@ export default function H3({
|
||||
return (
|
||||
<h3
|
||||
{...props}
|
||||
className={twMerge("text-xl mb-4", "twui-h3", props.className)}
|
||||
className={twMerge(
|
||||
"text-xl mb-4",
|
||||
"twui-headings twui-heading",
|
||||
"twui-h3",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</h3>
|
||||
|
||||
@@ -11,7 +11,12 @@ export default function H4({
|
||||
return (
|
||||
<h4
|
||||
{...props}
|
||||
className={twMerge("text-base mb-4", "twui-h4", props.className)}
|
||||
className={twMerge(
|
||||
"text-base mb-4",
|
||||
"twui-headings twui-heading",
|
||||
"twui-h4",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</h4>
|
||||
|
||||
@@ -11,7 +11,12 @@ export default function H5({
|
||||
return (
|
||||
<h5
|
||||
{...props}
|
||||
className={twMerge("text-sm mb-4", "twui-h5", props.className)}
|
||||
className={twMerge(
|
||||
"mb-4",
|
||||
"twui-headings twui-heading",
|
||||
"twui-h5",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</h5>
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function HR({
|
||||
<hr
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"border-slate-200 dark:border-white/20 w-full my-4",
|
||||
"border-slate-200 dark:border-white/10 w-full my-4",
|
||||
"twui-hr",
|
||||
props.className
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ComponentProps, ReactNode } from "react";
|
||||
import { TWUI_LINK_LIST_LINK_OBJECT } from "../elements/LinkList";
|
||||
import Link from "./Link";
|
||||
import Row from "./Row";
|
||||
|
||||
type Props = ComponentProps<typeof Link> & {
|
||||
link: TWUI_LINK_LIST_LINK_OBJECT;
|
||||
icon: ReactNode;
|
||||
iconPosition?: "before" | "after";
|
||||
};
|
||||
|
||||
/**
|
||||
* # Link With an Icon
|
||||
* @className twui-arrowed-link
|
||||
*/
|
||||
export default function IconLink({
|
||||
link,
|
||||
iconPosition,
|
||||
icon,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<Link
|
||||
href={link.url}
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
link.onClick?.(e);
|
||||
props.onClick?.(e);
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
{iconPosition == "before" || !iconPosition ? icon : null}
|
||||
<span>{link.title}</span>
|
||||
{iconPosition == "after" ? icon : null}
|
||||
</Row>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import _ from "lodash";
|
||||
import React, { DetailedHTMLProps, ImgHTMLAttributes, ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TWUIImageProps = DetailedHTMLProps<
|
||||
ImgHTMLAttributes<HTMLImageElement>,
|
||||
HTMLImageElement
|
||||
> & {
|
||||
alt: string;
|
||||
size?: number;
|
||||
circle?: boolean;
|
||||
bgImg?: boolean;
|
||||
backgroundImage?: boolean;
|
||||
fallbackImageSrc?: string;
|
||||
srcLight?: string;
|
||||
srcDark?: string;
|
||||
imgErrSrc?: string;
|
||||
imgErrComp?: ReactNode;
|
||||
imgErrSrcLight?: string;
|
||||
imgErrSrcDark?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Image Component
|
||||
* @className twui-img
|
||||
*/
|
||||
export default function Img({
|
||||
imgErrSrc,
|
||||
imgErrComp,
|
||||
imgErrSrcDark,
|
||||
imgErrSrcLight,
|
||||
...props
|
||||
}: TWUIImageProps) {
|
||||
const width = props.size || props.width;
|
||||
const height = props.size || props.height;
|
||||
const sizeRatio = width && height ? Number(width) / Number(height) : 1;
|
||||
|
||||
const [imageError, setImageError] = React.useState(false);
|
||||
|
||||
const finalProps = _.omit(props, [
|
||||
"size",
|
||||
"circle",
|
||||
"bgImg",
|
||||
"backgroundImage",
|
||||
"fallbackImageSrc",
|
||||
"srcLight",
|
||||
"srcDark",
|
||||
]);
|
||||
|
||||
const interpolatedProps: typeof props = {
|
||||
...finalProps,
|
||||
width: width,
|
||||
height: height,
|
||||
className: twMerge(
|
||||
"object-cover",
|
||||
props.circle && "rounded-full",
|
||||
props.bgImg || props.backgroundImage
|
||||
? "absolute top-0 left-0 w-full h-full object-cover z-0"
|
||||
: "",
|
||||
"twui-img",
|
||||
props.className
|
||||
),
|
||||
onError: (e) => {
|
||||
if (props.fallbackImageSrc) {
|
||||
e.currentTarget.src = props.fallbackImageSrc;
|
||||
}
|
||||
props.onError?.(e);
|
||||
},
|
||||
style: {
|
||||
...(props.size
|
||||
? {
|
||||
width: `${props.size}px`,
|
||||
minWidth: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
}
|
||||
: {}),
|
||||
...props.style,
|
||||
},
|
||||
};
|
||||
|
||||
if (imageError) {
|
||||
return (
|
||||
imgErrComp || (
|
||||
<img
|
||||
loading="lazy"
|
||||
{...interpolatedProps}
|
||||
src={
|
||||
imgErrSrc ||
|
||||
"https://static.datasquirel.com/images/user-images/user-2/castcord-image-preset_thumbnail.jpg"
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (props.srcDark && props.srcLight) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<img
|
||||
loading="lazy"
|
||||
{...interpolatedProps}
|
||||
className={twMerge(
|
||||
"hidden dark:block",
|
||||
interpolatedProps.className
|
||||
)}
|
||||
src={props.srcDark}
|
||||
onError={(e) => {
|
||||
setImageError(true);
|
||||
props.onError?.(e);
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
loading="lazy"
|
||||
{...interpolatedProps}
|
||||
className={twMerge(
|
||||
"block dark:hidden",
|
||||
interpolatedProps.className
|
||||
)}
|
||||
src={props.srcLight}
|
||||
onError={(e) => {
|
||||
setImageError(true);
|
||||
props.onError?.(e);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
{...interpolatedProps}
|
||||
onError={(e) => {
|
||||
setImageError(true);
|
||||
props.onError?.(e);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +1,61 @@
|
||||
import { AnchorHTMLAttributes, DetailedHTMLProps } from "react";
|
||||
import { AnchorHTMLAttributes, DetailedHTMLProps, RefAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ArrowUpRight, LucideProps } from "lucide-react";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
HTMLAnchorElement
|
||||
> & {
|
||||
showArrow?: boolean;
|
||||
arrowSize?: number;
|
||||
arrowProps?: Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>;
|
||||
strict?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # General Anchor Elements
|
||||
* @className twui-a | twui-anchor
|
||||
* @info use `cancel-link` class name to prevent triggering this link from a child element
|
||||
*/
|
||||
export default function Link({
|
||||
showArrow,
|
||||
arrowSize = 20,
|
||||
arrowProps,
|
||||
strict,
|
||||
...props
|
||||
}: DetailedHTMLProps<
|
||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
HTMLAnchorElement
|
||||
>) {
|
||||
}: Props) {
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"text-base text-link-500 no-underline hover:text-link-500/50",
|
||||
"text-blue-600 dark:text-blue-400",
|
||||
"border-0 border-b border-blue-300 border-solid leading-4",
|
||||
"text-link-500 no-underline hover:text-link-500/50",
|
||||
"text-link dark:text-link-dark hover:opacity-80 transition-all",
|
||||
"border-0 border-b border-link dark:border-link-dark border-solid leading-4",
|
||||
"twui-anchor",
|
||||
"twui-a",
|
||||
props.className
|
||||
)}
|
||||
onClick={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
if (target.closest(".cancel-link")) {
|
||||
e.preventDefault();
|
||||
}
|
||||
props?.onClick?.(e);
|
||||
}}
|
||||
data-strict={strict ? "yes" : undefined}
|
||||
>
|
||||
{props.children}
|
||||
{showArrow && (
|
||||
<ArrowUpRight
|
||||
size={arrowSize}
|
||||
{...arrowProps}
|
||||
className={twMerge(
|
||||
"inline-block ml-1 -mt-[1px]",
|
||||
arrowProps?.className
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type LoadingRectangleBlockProps = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
|
||||
/**
|
||||
* # A loading Rectangle block
|
||||
* @className twui-loading-rectangle-block
|
||||
* @className twui-loading-block
|
||||
*/
|
||||
export default function LoadingRectangleBlock({
|
||||
...props
|
||||
}: LoadingRectangleBlockProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"flex items-center w-full h-10 animate-pulse bg-slate-200 rounded",
|
||||
"dark:bg-slate-800",
|
||||
"twui-loading-rectangle-block twui-loading-block",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+10
-4
@@ -1,18 +1,24 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLHeadingElement>,
|
||||
HTMLHeadingElement
|
||||
> & {
|
||||
noMargin?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Paragraph Tag
|
||||
* @className twui-p | twui-paragraph
|
||||
*/
|
||||
export default function P({
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>) {
|
||||
export default function P({ noMargin, ...props }: Props) {
|
||||
return (
|
||||
<p
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"text-base py-4",
|
||||
"py-4",
|
||||
noMargin ? "!m-0 p-0" : "",
|
||||
"twui-p",
|
||||
"twui-paragraph",
|
||||
props.className
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
noWrap?: boolean;
|
||||
itemsStart?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Flexbox Row
|
||||
* @className twui-row
|
||||
*/
|
||||
export default function Row({
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) {
|
||||
export default function Row({ noWrap, itemsStart, ...props }: Props) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"flex flex-row items-center gap-2 flex-wrap",
|
||||
"flex flex-row gap-2",
|
||||
noWrap ? "xl:flex-nowrap" : "flex-wrap",
|
||||
itemsStart ? "items-start" : "items-center",
|
||||
"twui-row",
|
||||
props.className
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import _ from "lodash";
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
horizontal?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Space Component
|
||||
* @className twui-spacer
|
||||
*/
|
||||
export default function Spacer({ horizontal, ...props }: Props) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge(
|
||||
"",
|
||||
horizontal ? "w-10" : "w-full h-10",
|
||||
"twui-spacer",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,29 @@ import { twMerge } from "tailwind-merge";
|
||||
* @className twui-span
|
||||
*/
|
||||
export default function Span({
|
||||
size,
|
||||
variant,
|
||||
truncate,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>) {
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement> & {
|
||||
size?: "normal" | "small" | "smaller" | "large" | "larger";
|
||||
variant?: "normal" | "faded";
|
||||
truncate?: { lines?: number; width?: number };
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
className={twMerge("text-base", "twui-span", props.className)}
|
||||
className={twMerge(
|
||||
"",
|
||||
size == "small" && "text-sm",
|
||||
size == "smaller" && "text-xs",
|
||||
size == "large" && "text-lg",
|
||||
size == "larger" && "text-xl",
|
||||
variant == "faded" && "opacity-50",
|
||||
truncate ? `` : ``,
|
||||
"twui-span",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import _ from "lodash";
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
center?: boolean;
|
||||
gap?: number | string;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Flexbox Column
|
||||
* @className twui-stack
|
||||
*/
|
||||
export default function Stack({ gap, ...props }: Props) {
|
||||
const finalProps = _.omit(props, "center");
|
||||
return (
|
||||
<div
|
||||
{...finalProps}
|
||||
className={twMerge(
|
||||
"flex flex-col items-start gap-4",
|
||||
props.center && "items-center",
|
||||
gap
|
||||
? typeof gap == "string"
|
||||
? `gap-[${gap}]`
|
||||
: `gap-${gap}`
|
||||
: "",
|
||||
"twui-stack",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,37 @@
|
||||
import _ from "lodash";
|
||||
import { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
center?: boolean;
|
||||
gap?: number | string;
|
||||
componentRef?: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Flexbox Column
|
||||
* @className twui-stack
|
||||
*/
|
||||
export default function Stack({
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) {
|
||||
export default function Stack({ gap, componentRef, ...props }: Props) {
|
||||
const finalProps = _.omit(props, "center");
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...finalProps}
|
||||
className={twMerge(
|
||||
"flex flex-col items-start",
|
||||
"flex flex-col items-start gap-4",
|
||||
props.center && "items-center",
|
||||
gap
|
||||
? typeof gap == "string"
|
||||
? `gap-[${gap}]`
|
||||
: `gap-${gap}`
|
||||
: "",
|
||||
"twui-stack",
|
||||
props.className
|
||||
)}
|
||||
ref={componentRef}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user