diff --git a/src/components/extenders/Modal.tsx b/src/components/extenders/Modal.tsx index 2b44e36c..e591d53f 100644 --- a/src/components/extenders/Modal.tsx +++ b/src/components/extenders/Modal.tsx @@ -28,7 +28,7 @@ export default function Modal({ state, title, description, modal, children, clas - + {title && ( {typeof title === "string" ? {title} : title} diff --git a/src/components/primitives/Box.tsx b/src/components/primitives/Box.tsx index f1361fc7..f59ca4c9 100644 --- a/src/components/primitives/Box.tsx +++ b/src/components/primitives/Box.tsx @@ -1,64 +1,85 @@ +/** + * Box Component + * + * A flexible container component that serves as a fundamental building block for layouts. + * It provides various visual styles and sizing options through variants. + * + * @variants + * content: + * - xs to xl: Content size variants + * + * container: + * - true/false: Controls container behavior + * + * @compoundVariants + * The component uses compound variants to determine border radius (rounded corners): + * - When container=true: Uses both size and content values (rounded-${size}+${content}) + * - When container=false: Uses only size value (rounded-${size}) + * + * @example + * + * Content goes here + * + */ + import { tv } from "tailwind-variants"; import useThemedVariables from "../../hooks/theming/useThemedVariables"; import { mergeClass } from "../../utils/css"; import { sizeScale } from "../../utils/tailwind"; import type { Component, Styled, Themable } from "../../utils/types"; -export const boxStyles = tv( - { - base: "flex flex-col border-1", - variants: { - look: { - soft: "bg-main-1 border-main-0", - base: "bg-main-3 border-main-0 text-main-12", - bold: "bg-main-2 border-main-6 text-main-12", - tint: "bg-accent-4 border-main-0 text-main-12", - hype: "bg-accent-4 border-accent-6 text-main-12", - }, - size: { - xs: "p-xs gap-xs", - sm: "p-sm gap-sm", - md: "p-md gap-md", - lg: "p-lg gap-md", - xl: "p-xl gap-xl", - }, - container: { - true: "", - false: "", - }, - content: { - xs: "", - sm: "", - md: "", - lg: "", - xl: "", - }, +export const boxStyles = tv({ + base: "flex flex-col border-1", + variants: { + look: { + soft: "bg-main-1 border-main-0", + base: "bg-main-3 border-main-0 text-main-12", + bold: "bg-main-2 border-main-6 text-main-12", + tint: "bg-accent-4 border-main-0 text-main-12", + hype: "bg-accent-4 border-accent-6 text-main-12", }, - defaultVariants: { - size: "md", - content: "md", - look: "base", - container: true, + size: { + xs: "p-xs gap-xs", + sm: "p-sm gap-sm", + md: "p-md gap-md", + lg: "p-lg gap-md", + xl: "p-xl gap-xl", }, - compoundVariants: sizeScale.flatMap(size => - sizeScale.flatMap(content => [ - { - size, - content, - container: true as const, - class: `rounded-${size}+${content}` as `rounded-${typeof size}+${typeof content}`, - }, - { - size, - content, - container: false as const, - class: `rounded-${size}` as `rounded-${typeof size}`, - }, - ]), - ), + container: { + true: "", + false: "", + }, + content: { + xs: "", + sm: "", + md: "", + lg: "", + xl: "", + }, + }, + defaultVariants: { + size: "md", + content: "md", + look: "base", + container: true, }, - { twMerge: false }, -); + compoundVariants: sizeScale.flatMap(size => + sizeScale.flatMap(content => [ + { + size, + content, + container: true as const, + class: `rounded-${size}+${content}` as `rounded-${typeof size}+${typeof content}`, + }, + { + size, + content, + container: false as const, + class: `rounded-${size}` as `rounded-${typeof size}`, + }, + ]), + ), +}); export type BoxProps = Component & Themable>; diff --git a/src/components/primitives/Icon.tsx b/src/components/primitives/Icon.tsx index fe28ef0e..516a10c9 100644 --- a/src/components/primitives/Icon.tsx +++ b/src/components/primitives/Icon.tsx @@ -2,11 +2,12 @@ import * as RemixIcon from "@remixicon/react"; import { type ReactElement, useMemo } from "react"; import { tv } from "tailwind-variants"; import { mergeClass } from "../.."; -import type { Component, Styled } from "../.."; +import type { Component, Styled, Themable } from "../.."; +import useThemableProps from "../../hooks/theming/useThemableProps"; import Image from "./Image"; export const iconStyles = tv({ - base: "flex flex-col border-0 overflow-hidden self-center rounded-sm w-[1em] h-[1em]", + base: "flex flex-col border-0 overflow-hidden self-center rounded-sm w-[1em] h-[1em]", variants: { size: { xs: "", @@ -26,14 +27,16 @@ export const iconStyles = tv({ }); export type IconProps = Component< - Styled & { - src?: string; - remix?: keyof typeof RemixIcon; - }, + Styled & + Themable & { + src?: string; + remix?: keyof typeof RemixIcon; + }, HTMLImageElement >; export default function Icon({ rounded, remix, size, src, alt, className, ...props }: IconProps) { + const themeVars = useThemableProps(props); const styles = useMemo(() => iconStyles({ rounded, size }), [rounded, size]); const Component = useMemo(() => { @@ -41,5 +44,5 @@ export default function Icon({ rounded, remix, size, src, alt, className, ...pro return (imageProps: Component) => {alt}; }, [remix, alt, src]); - return ; + return ; } diff --git a/src/components/primitives/Input.tsx b/src/components/primitives/Input.tsx index 2c7c3bc4..df3457ae 100644 --- a/src/components/primitives/Input.tsx +++ b/src/components/primitives/Input.tsx @@ -7,22 +7,22 @@ import type { Component, GetSet, Styled } from "../../utils/types"; import Group from "../extenders/Group"; export const inputStyles = tv({ - base: "text-main-12 flex items-center text-nowrap font-text", + base: "flex items-center placeholder:text-main-11 text-nowrap font-text", variants: { look: { - none: "bg-main-0 border-0", - soft: "bg-main-2 border-main-2 hover:border-main-4 active:border-main-7 hover:text-main-12 focus-within:outline focus-within:outline-main-12", - base: "bg-main-6 focus-within:outline focus-within:outline-main-12", - bold: "bg-main-3 border-main-4 hover:border-main-4 active:border-main-7 hover:text-main-12 focus-within:outline focus-within:outline-main-12", - tint: "bg-main-1 border-accent-6 hover:border-accent-8 active:bg-main-2 text-main-12 focus-within:outline focus-within:outline-main-12", - hype: "bg-main-0 border-accent-8 hover:border-accent-10 active:border-accent-8 hover:text-main-12 focus-within:outline focus-within:outline-main-12", + none: "text-main-12 bg-main-0 border-0", + soft: "text-main-11 bg-main-0 border-main-9 border-1 active:border-main-7 focus-within:outline focus-within:outline-main-12", + base: "text-main-12 bg-main-6 focus-within:outline focus-within:outline-main-12", + bold: "text-main-12 bg-main-3 border-main-4 hover:border-main-4 active:border-main-7 hover:text-main-12 focus-within:outline focus-within:outline-main-12", + tint: "text-main-12 bg-main-1 border-accent-6 hover:border-accent-8 active:bg-main-2 focus-within:outline focus-within:outline-main-12", + hype: "text-main-12 bg-main-0 border-accent-8 hover:border-accent-10 active:border-accent-8 hover:text-main-12 focus-within:outline focus-within:outline-main-12", }, size: { xs: "px-xs py-xs text-xs rounded-xs", sm: "px-sm py-sm text-sm rounded-sm", - md: "px-md py-md text-md rounded-md", - lg: "px-lg py-lg text-lg rounded-lg", - xl: "px-xl py-xl text-3xl rounded-xl", + md: "px-md py-sm text-md rounded-md", + lg: "px-lg py-md text-lg rounded-lg", + xl: "px-xl py-lg text-3xl rounded-xl", }, }, defaultVariants: { diff --git a/src/components/primitives/Table.tsx b/src/components/primitives/Table.tsx index a98e7402..c48cd155 100644 --- a/src/components/primitives/Table.tsx +++ b/src/components/primitives/Table.tsx @@ -233,7 +233,7 @@ export function Table({ export function createTable(columns: T) { const TemplateTable = (props: Omit, "columns"> & ListProps) => ( // biome-ignore lint/suspicious/noExplicitAny: no reasons for it to have type errors - +
); // biome-ignore lint/suspicious/noExplicitAny: no reasons for it to have type errors diff --git a/src/components/primitives/Tabs.tsx b/src/components/primitives/Tabs.tsx index 6c10178d..be4fa7b3 100644 --- a/src/components/primitives/Tabs.tsx +++ b/src/components/primitives/Tabs.tsx @@ -1,3 +1,36 @@ +/** + * @component Tabs + * @description A flexible and customizable tabs component that supports different visual styles and navigation. + * + * @features + * - Multiple visual styles (soft, base, bold, tint, hype) + * - Responsive design with Tailwind CSS + * - Built-in navigation support using Remix's Link component + * - Customizable themes + * - Accessibility features including keyboard navigation + * + * @usage + * ```tsx + * + * ``` + * + * @props + * - tabs: Array of tab items with label, link, and key + * - look: Visual style variant (soft, base, bold, tint, hype) + * - size: Size variant + * - theme: Custom theme override + * - className: Additional CSS classes + * - disabled: Whether the tabs are disabled + * - to: Default navigation path + */ + import { Link, useLocation } from "@remix-run/react"; import type { ReactNode } from "react"; diff --git a/src/components/primitives/Text.tsx b/src/components/primitives/Text.tsx index 1134fa8b..963b8b74 100644 --- a/src/components/primitives/Text.tsx +++ b/src/components/primitives/Text.tsx @@ -2,55 +2,52 @@ import { tv } from "tailwind-variants"; import { mergeClass } from "../../utils/css"; import type { Component, Styled } from "../../utils/types"; -export const textStyles = tv( - { - base: "text-main-11 font-text text-[clamp(15px,0.4167vw+0.78125rem,20px)]", - variants: { - look: { - base: "text-main-11", - soft: "text-main-11", - bold: "text-main-12", - tint: "text-accent-12", - hype: "text-accent-11", - }, - size: { - xs: "text-xs", - sm: "text-sm", - md: "text-base", - lg: "text-lg", - xl: "text-xl", - display1: "font-title font-bold leading-tight italic uppercase !text-[clamp(44px,5vw+0.875rem,104px)]", - 1: "font-title !text-3xl", - 2: "font-title font-bold leading-none italic !text-[clamp(38px,0.667vw+2.125rem,46px)]", - 3: "font-title font-bold leading-none italic !text-[clamp(26px,0.667vw+1.375rem,34px)]", - 4: "font-title font-bold leading-[1.18] !text-[clamp(18px,0.667vw+0.875rem,26px)]", - 5: "font-title font-bold leading-normal !text-[clamp(15px,0.25vw+0.84375rem,18px)] uppercase tracking-[1.6px] ", - 6: "font-title !text-sm", - }, - interactable: { - true: "cursor-pointer select-none", - false: "", - }, +export const textStyles = tv({ + base: "font-text text-[clamp(15px,0.4167vw+0.78125rem,20px)]", + variants: { + look: { + base: "text-main-11", + soft: "text-main-11", + bold: "text-main-12", + tint: "text-accent-12", + hype: "text-accent-11", }, - defaultVariants: { - size: "md", - look: "base", - interactable: false, + size: { + xs: "text-xs", + sm: "text-sm", + md: "text-base", + lg: "text-lg", + xl: "text-xl", + display1: "font-title font-bold leading-tight italic uppercase !text-[clamp(44px,5vw+0.875rem,104px)]", + 1: "font-title !text-3xl", + 2: "font-title font-bold leading-none italic !text-[clamp(38px,0.667vw+2.125rem,46px)]", + 3: "font-title font-bold leading-none italic !text-[clamp(26px,0.667vw+1.375rem,34px)]", + 4: "font-title font-bold leading-[1.18] !text-[clamp(18px,0.667vw+0.875rem,26px)]", + 5: "font-title font-bold leading-none !text-[clamp(15px,0.25vw+0.84375rem,18px)] uppercase tracking-[0.8px] ", + 6: "font-title !text-sm", + }, + interactable: { + true: "cursor-pointer select-none", + false: "", }, - compoundVariants: [ - { look: "soft", interactable: true, class: "hover:text-main-12" }, - { - look: "base", - interactable: true, - class: "hover:text-main-12 active:text-main-11", - }, - { look: "bold", interactable: true, class: "hover:text-main-12" }, - { look: "tint", interactable: true, class: "hover:text-main-12" }, - { look: "hype", interactable: true, class: "hover:text-main-12" }, - ], }, - { twMerge: false }, -); + defaultVariants: { + size: "md", + look: "base", + interactable: false, + }, + compoundVariants: [ + { look: "soft", interactable: true, class: "hover:text-main-12" }, + { + look: "base", + interactable: true, + class: "hover:text-main-12 active:text-main-11", + }, + { look: "bold", interactable: true, class: "hover:text-main-12" }, + { look: "tint", interactable: true, class: "hover:text-main-12" }, + { look: "hype", interactable: true, class: "hover:text-main-12" }, + ], +}); export type TextProps = Component & { bold?: boolean }, HTMLParagraphElement>; diff --git a/src/components/primitives/Title.tsx b/src/components/primitives/Title.tsx index 2880c70d..a57ac2e9 100644 --- a/src/components/primitives/Title.tsx +++ b/src/components/primitives/Title.tsx @@ -6,7 +6,7 @@ import { textStyles } from "./Text"; export const titleStyles = tv( { extend: textStyles, - base: "text-main-12 font-title font-bold", + base: "font-title font-bold", variants: { look: { base: "text-main-12", diff --git a/src/utils/css.ts b/src/utils/css.ts index 396ab454..8abd137d 100644 --- a/src/utils/css.ts +++ b/src/utils/css.ts @@ -1,5 +1,110 @@ import clsx, { type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { extendTailwindMerge } from "tailwind-merge"; + +const twMerge = extendTailwindMerge({ + extend: { + classGroups: { + p: [ + "p-sm/2", + "p-sm/4", + "p-sm*2", + "p-sm*4", + "p-md/2", + "p-md/4", + "p-md*2", + "p-md*4", + "p-lg/2", + "p-lg/4", + "p-lg*2", + "p-lg*4", + ], + px: [ + "px-sm/2", + "px-sm/4", + "px-sm*2", + "px-sm*4", + "px-md/2", + "px-md/4", + "px-md*2", + "px-md*4", + "px-lg/2", + "px-lg/4", + "px-lg*2", + "px-lg*4", + ], + py: [ + "py-sm/2", + "py-sm/4", + "py-sm*2", + "py-sm*4", + "py-md/2", + "py-md/4", + "py-md*2", + "py-md*4", + "py-lg/2", + "py-lg/4", + "py-lg*2", + "py-lg*4", + ], + m: [ + "m-sm/2", + "m-sm/4", + "m-sm*2", + "m-sm*4", + "m-md/2", + "m-md/4", + "m-md*2", + "m-md*4", + "m-lg/2", + "m-lg/4", + "m-lg*2", + "m-lg*4", + ], + mx: [ + "mx-sm/2", + "mx-sm/4", + "mx-sm*2", + "mx-sm*4", + "mx-md/2", + "mx-md/4", + "mx-md*2", + "mx-md*4", + "mx-lg/2", + "mx-lg/4", + "mx-lg*2", + "mx-lg*4", + ], + my: [ + "my-sm/2", + "my-sm/4", + "my-sm*2", + "my-sm*4", + "my-md/2", + "my-md/4", + "my-md*2", + "my-md*4", + "my-lg/2", + "my-lg/4", + "my-lg*2", + "my-lg*4", + ], + gap: [ + "gap-sm/2", + "gap-sm/4", + "gap-sm*2", + "gap-sm*4", + "gap-md/2", + "gap-md/4", + "gap-md*2", + "gap-md*4", + "gap-lg/2", + "gap-lg/4", + "gap-lg*2", + "gap-lg*4", + ], + }, + }, +}); /** * Merges classes together with the most compatibility possible