This commit is contained in:
2026-10-01 08:02:47 +01:00
parent 6392457ffb
commit 3dc9f12ad4
104 changed files with 6536 additions and 1037 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
import { ComponentProps, ReactNode } from "react";
import type { ComponentProps, ReactNode } from "react";
import { ArrowUpRight } from "lucide-react";
import { TWUI_LINK_LIST_LINK_OBJECT } from "../elements/LinkList";
import type { TWUI_LINK_LIST_LINK_OBJECT } from "../elements/LinkList";
import Link from "./Link";
import Row from "./Row";
+65 -26
View File
@@ -1,32 +1,50 @@
import React, {
AnchorHTMLAttributes,
ButtonHTMLAttributes,
ComponentProps,
DetailedHTMLProps,
HTMLAttributeAnchorTarget,
HTMLAttributes,
type AnchorHTMLAttributes,
type ButtonHTMLAttributes,
type ComponentProps,
type DetailedHTMLProps,
type HTMLAttributeAnchorTarget,
type HTMLAttributes,
} from "react";
import { twMerge } from "tailwind-merge";
import Loading from "../elements/Loading";
import LucideIcon, { TWUILucideIconName } from "../elements/lucide-icon";
import LucideIcon, { type TWUILucideIconName } from "../elements/lucide-icon";
import type { LucideProps } from "lucide-react";
export const TWUIButtonColors = [
"primary",
"secondary",
"text",
"white",
"accent",
"gray",
"error",
"warning",
"success",
] as const;
export const TWUIButtonSizes = [
"small",
"smaller",
"normal",
"large",
"larger",
] as const;
export const TWUIButtonVariants = ["normal", "ghost", "outlined"] as const;
export type TWUIButtonColorType = (typeof TWUIButtonColors)[number];
export type TWUIButtonSizeType = (typeof TWUIButtonSizes)[number];
export type TWUIButtonVariantType = (typeof TWUIButtonVariants)[number];
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";
variant?: TWUIButtonVariantType;
color?: TWUIButtonColorType;
size?: TWUIButtonSizeType;
loadingIconSize?: React.ComponentProps<typeof Loading>["size"];
href?: string;
target?: HTMLAttributeAnchorTarget;
@@ -37,6 +55,10 @@ export type TWUIButtonProps = DetailedHTMLProps<
>;
beforeIcon?: TWUILucideIconName | React.JSX.Element;
afterIcon?: TWUILucideIconName | React.JSX.Element;
before_icon_name?: TWUILucideIconName;
after_icon_name?: TWUILucideIconName;
before_icon_props?: LucideProps;
after_icon_props?: LucideProps;
buttonContentProps?: DetailedHTMLProps<
HTMLAttributes<HTMLDivElement>,
HTMLDivElement
@@ -44,7 +66,7 @@ export type TWUIButtonProps = DetailedHTMLProps<
loadingProps?: ComponentProps<typeof Loading>;
};
const buttonClasses: Record<string, Record<string, string>> = {
export const ButtonClasses: Record<string, Record<string, string>> = {
normal: {
primary: twMerge(
"bg-primary hover:bg-primary-hover text-white",
@@ -96,8 +118,8 @@ const buttonClasses: Record<string, Record<string, string>> = {
"twui-button-accent-outlined",
),
gray: twMerge(
"bg-transparent outline outline-1 outline-slate-300",
"text-slate-600 dark:text-white/60 dark:outline-white/30",
"bg-transparent outline outline-1 outline-gray",
"text-dark dark:text-white/60 dark:outline-white/30",
"twui-button-gray-outlined",
),
white: twMerge(
@@ -110,6 +132,11 @@ const buttonClasses: Record<string, Record<string, string>> = {
"dark:outline-error dark:text-error-dark",
"twui-button-error-outlined",
),
warning: twMerge(
"bg-transparent outline outline-1 outline-warning text-warning",
"dark:outline-warning dark:text-warning-dark",
"twui-button-warning-outlined",
),
},
ghost: {
primary: twMerge(
@@ -226,9 +253,14 @@ export default function Button({
loading,
loadingIconSize,
loadingProps,
before_icon_name,
after_icon_name,
before_icon_props,
after_icon_props,
...props
}: TWUIButtonProps) {
const finalClassName = buttonClasses[variant || "normal"]?.[color || "primary"] ?? "";
const finalClassName =
ButtonClasses[variant || "normal"]?.[color || "primary"] ?? "";
const buttonComponent = (
<button
@@ -246,7 +278,7 @@ export default function Button({
? "text-lg twui-button-large"
: size == "larger"
? "px-5 py-3 text-xl twui-button-larger"
: "twui-button-base",
: "text-sm twui-button-base",
finalClassName,
loading ? "pointer-events-none opacity-80" : "",
props.className,
@@ -262,7 +294,12 @@ export default function Button({
buttonContentProps?.className,
)}
>
{beforeIcon ? (
{before_icon_name ? (
<LucideIcon
{...before_icon_props}
name={before_icon_name}
/>
) : beforeIcon ? (
typeof beforeIcon == "string" ? (
<LucideIcon name={beforeIcon as TWUILucideIconName} />
) : (
@@ -270,7 +307,9 @@ export default function Button({
)
) : null}
{props.children}
{afterIcon ? (
{after_icon_name ? (
<LucideIcon {...after_icon_props} name={after_icon_name} />
) : afterIcon ? (
typeof afterIcon == "string" ? (
<LucideIcon name={afterIcon as TWUILucideIconName} />
) : (
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -14,7 +14,7 @@ export default function Center({
className={twMerge(
"flex flex-col items-center justify-center gap-4 p-2 w-full",
"h-full twui-center",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function Container({
"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
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
type Props = DetailedHTMLProps<
@@ -27,7 +27,7 @@ export default function Divider({ vertical, dashed, ...props }: Props) {
"twui-divider",
vertical ? "twui-divider-vertical" : "twui-divider-horizontal",
dashed ? "border-dashed" : "border-solid",
props.className
props.className,
)}
/>
);
+2 -2
View File
@@ -1,5 +1,5 @@
import { twMerge } from "tailwind-merge";
import Img, { TWUIImageProps } from "./Img";
import Img, { type TWUIImageProps } from "./Img";
import Border from "../elements/Border";
export default function DocsImg({ ...props }: TWUIImageProps) {
@@ -9,7 +9,7 @@ export default function DocsImg({ ...props }: TWUIImageProps) {
{...props}
className={twMerge(
"w-full h-auto rounded-default overflow-hidden",
props.className
props.className,
)}
/>
</Border>
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -16,7 +16,7 @@ export default function Footer({
"px-4 sm:px-10 py-2",
"border-0 border-b border-slate-200 border-solid",
"twui-footer",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function H1({
"text-4xl md:text-5xl mb-4",
"twui-headings twui-heading",
"twui-h1",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function H2({
"text-2xl md:text-3xl mb-4",
"twui-headings twui-heading",
"twui-h2",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function H3({
"text-xl mb-4",
"twui-headings twui-heading",
"twui-h3",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function H4({
"text-base mb-4",
"twui-headings twui-heading",
"twui-h4",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function H5({
"mb-4",
"twui-headings twui-heading",
"twui-h5",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -14,7 +14,7 @@ export default function HR({
className={twMerge(
"border-slate-200 dark:border-white/10 w-full my-4",
"twui-hr",
props.className
props.className,
)}
/>
);
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -16,7 +16,7 @@ export default function Header({
"px-4 sm:px-10 py-3 flex-wrap",
"border-0 border-b border-slate-200 dark:border-white/10 border-solid",
"twui-header",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,5 +1,5 @@
import { ComponentProps, ReactNode } from "react";
import { TWUI_LINK_LIST_LINK_OBJECT } from "../elements/LinkList";
import type { ComponentProps, ReactNode } from "react";
import type { TWUI_LINK_LIST_LINK_OBJECT } from "../elements/LinkList";
import Link from "./Link";
import Row from "./Row";
+22 -4
View File
@@ -1,5 +1,10 @@
import _ from "lodash";
import React, { DetailedHTMLProps, ImgHTMLAttributes, ReactNode } from "react";
import React, {
type DetailedHTMLProps,
type ImgHTMLAttributes,
type ReactNode,
useEffect,
} from "react";
import { twMerge } from "tailwind-merge";
export type TWUIImageProps = DetailedHTMLProps<
@@ -18,6 +23,7 @@ export type TWUIImageProps = DetailedHTMLProps<
imgErrComp?: ReactNode;
imgErrSrcLight?: string;
imgErrSrcDark?: string;
checkImageFn?: () => Promise<boolean> | boolean;
};
/**
@@ -29,6 +35,7 @@ export default function Img({
imgErrComp,
imgErrSrcDark,
imgErrSrcLight,
checkImageFn,
...props
}: TWUIImageProps) {
const width = props.size || props.width;
@@ -58,7 +65,7 @@ export default function Img({
? "absolute top-0 left-0 w-full h-full object-cover z-0"
: "",
"twui-img",
props.className
props.className,
),
onError: (e) => {
if (props.fallbackImageSrc) {
@@ -78,6 +85,17 @@ export default function Img({
},
};
useEffect(() => {
if (checkImageFn) {
(async () => {
const does_image_exist = await checkImageFn();
if (!does_image_exist) {
setImageError(true);
}
})();
}
}, []);
if (imageError) {
return (
imgErrComp || (
@@ -101,7 +119,7 @@ export default function Img({
{...interpolatedProps}
className={twMerge(
"hidden dark:block",
interpolatedProps.className
interpolatedProps.className,
)}
src={props.srcDark}
onError={(e) => {
@@ -114,7 +132,7 @@ export default function Img({
{...interpolatedProps}
className={twMerge(
"block dark:hidden",
interpolatedProps.className
interpolatedProps.className,
)}
src={props.srcLight}
onError={(e) => {
+8 -4
View File
@@ -1,6 +1,10 @@
import { AnchorHTMLAttributes, DetailedHTMLProps, RefAttributes } from "react";
import type {
AnchorHTMLAttributes,
DetailedHTMLProps,
RefAttributes,
} from "react";
import { twMerge } from "tailwind-merge";
import { ArrowUpRight, LucideProps } from "lucide-react";
import { ArrowUpRight, type LucideProps } from "lucide-react";
type Props = DetailedHTMLProps<
AnchorHTMLAttributes<HTMLAnchorElement>,
@@ -33,7 +37,7 @@ export default function Link({
"border-0 border-b border-link dark:border-link-dark border-solid leading-4",
"twui-anchor",
"twui-a",
props.className
props.className,
)}
onClick={(e) => {
const target = e.target as HTMLElement;
@@ -52,7 +56,7 @@ export default function Link({
{...arrowProps}
className={twMerge(
"inline-block ml-1 -mt-[1px]",
arrowProps?.className
arrowProps?.className,
)}
/>
)}
+1 -1
View File
@@ -1,4 +1,4 @@
import {
import type {
AnchorHTMLAttributes,
DetailedHTMLProps,
HTMLAttributes,
+1 -1
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
export type LoadingRectangleBlockProps = DetailedHTMLProps<
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -14,7 +14,7 @@ export default function Main({
className={twMerge(
"flex flex-col items-center w-full",
"twui-main",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
type Props = DetailedHTMLProps<
@@ -21,7 +21,7 @@ export default function P({ noMargin, ...props }: Props) {
noMargin ? "!m-0 p-0" : "",
"twui-p",
"twui-paragraph",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
type Props = DetailedHTMLProps<
@@ -22,7 +22,7 @@ export default function Row({ noWrap, itemsStart, ...props }: Props) {
noWrap ? "xl:flex-nowrap" : "flex-wrap",
itemsStart ? "items-start" : "items-center",
"twui-row",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
@@ -15,7 +15,7 @@ export default function Section({
"flex flex-col items-center w-full",
"px-4 sm:px-10 py-10",
"twui-section",
props.className
props.className,
)}
>
{props.children}
+2 -2
View File
@@ -1,5 +1,5 @@
import _ from "lodash";
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
type Props = DetailedHTMLProps<
@@ -21,7 +21,7 @@ export default function Spacer({ horizontal, ...props }: Props) {
"",
horizontal ? "w-10" : "w-full h-10",
"twui-spacer",
props.className
props.className,
)}
>
{props.children}
+1 -1
View File
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
/**
-37
View File
@@ -1,37 +0,0 @@
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>
);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import _ from "lodash";
import { DetailedHTMLProps, HTMLAttributes } from "react";
import type { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
type Props = DetailedHTMLProps<
@@ -29,7 +29,7 @@ export default function Stack({ gap, componentRef, ...props }: Props) {
: `gap-${gap}`
: "",
"twui-stack",
props.className
props.className,
)}
ref={componentRef}
>