infinity-ui-elements
Version:
A React TypeScript component library with Tailwind CSS design system
1,280 lines (1,269 loc) • 176 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { cva } from 'class-variance-authority';
import { Slot } from '@radix-ui/react-slot';
import { PulseLoader, ClipLoader } from 'react-spinners';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { ExternalLink, Loader2, Search, ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';
import { createPortal } from 'react-dom';
import { flexRender } from '@tanstack/react-table';
/**
* ==============================================
* HOW TO ADD A NEW ICON:
* ==============================================
*
* 1. Add your SVG file to: src/assets/icons/{iconName}.svg
*
* 2. Copy the SVG content from the file
*
* 3. Add it to the iconRegistry below:
* iconName: `<svg>...</svg>`,
*
* 4. Use it anywhere in your app:
* <Icon name="iconName" size={24} />
*
* The Icon component will automatically:
* - Replace hardcoded colors with currentColor
* - Allow you to control color via className or style
* - Resize based on the size prop
* ==============================================
*/
/**
* Icon registry - maps icon names to their SVG content
* Add your icons here by copying the SVG content from your icon files
*/
const iconRegistry = {
// Tick/Check icon
tick: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.364 15.1924L19.5564 6L20.9706 7.41421L10.364 18.0208L4 11.6569L5.41422 10.2427L10.364 15.1924Z" fill="#081416"/>
</svg>`,
// Alias: check points to the same icon as tick
check: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.364 15.1924L19.5564 6L20.9706 7.41421L10.364 18.0208L4 11.6569L5.41422 10.2427L10.364 15.1924Z" fill="#081416"/>
</svg>`,
add: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12.9 11.0999L21 11.0997V12.8997L12.9 12.8999V21H11.1V12.8999L3.00004 12.9001L3 11.1001L11.1 11.0999L11.0999 3.00001L12.8999 3L12.9 11.0999Z" fill="#081416"/>
</svg>`,
// Info icon
info: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22.5C6.20101 22.5 1.5 17.7989 1.5 12C1.5 6.20101 6.20101 1.5 12 1.5C17.7989 1.5 22.5 6.20101 22.5 12C22.5 17.7989 17.7989 22.5 12 22.5ZM12 20.4C16.6392 20.4 20.4 16.6392 20.4 12C20.4 7.36081 16.6392 3.6 12 3.6C7.36081 3.6 3.6 7.36081 3.6 12C3.6 16.6392 7.36081 20.4 12 20.4ZM10.95 6.75H13.05V8.85H10.95V6.75ZM10.95 10.95H13.05V17.25H10.95V10.95Z" fill="#081416"/>
</svg>
`,
// Exclamation/Warning icon
exclamation: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM13 17H11V15H13V17ZM13 13H11V7H13V13Z" fill="#081416"/>
</svg>`,
// Close/X icon
close: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="#081416" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
};
const Icon = ({ name, size = 24, className = "", style = {}, ...props }) => {
const svgContent = iconRegistry[name];
if (!svgContent) {
console.warn(`Icon "${String(name)}" not found in registry.\n` +
`Available icons: ${Object.keys(iconRegistry).join(", ")}`);
return null;
}
// Parse the SVG content
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgContent, "image/svg+xml");
const svgElement = svgDoc.querySelector("svg");
if (!svgElement) {
console.error(`Invalid SVG content for icon "${String(name)}"`);
return null;
}
// Get the viewBox for proper scaling
const viewBox = svgElement.getAttribute("viewBox") || "0 0 24 24";
// Get all SVG content as string and replace hardcoded colors with currentColor
let innerSVG = svgElement.innerHTML;
// Replace common hardcoded colors with currentColor
// This allows the icon to inherit text color from parent
innerSVG = innerSVG
.replace(/fill="[^"]*"/g, 'fill="currentColor"')
.replace(/stroke="[^"]*"/g, 'stroke="currentColor"');
return (jsx("svg", { width: size, height: size, viewBox: viewBox, className: className, style: style, xmlns: "http://www.w3.org/2000/svg", ...props, dangerouslySetInnerHTML: { __html: innerSVG } }));
};
/**
* Get all available icon names from the registry
* @returns Array of registered icon names
*
* @example
* ```tsx
* const icons = getAvailableIcons();
* console.log(icons); // ['tick', 'check', ...]
* ```
*/
function getAvailableIcons() {
return Object.keys(iconRegistry);
}
/**
* Check if an icon exists in the registry
* @param name - Icon name to check
* @returns true if the icon exists
*
* @example
* ```tsx
* if (hasIcon('tick')) {
* // Icon exists
* }
* ```
*/
function hasIcon(name) {
return name in iconRegistry;
}
// Define patterns for custom classes that should be preserved
// This approach is more scalable than hardcoding individual class names
const CUSTOM_CLASS_PATTERNS = [
// Custom font classes
/^font-(functional|display)$/,
// Custom font-size classes
/^font-size-/,
// Custom leading (line-height) classes
/^leading-(00|25|50|75|100|200|300|400|500|600|700|800|900|1000|1100)$/,
// Custom text utility classes (text-display, text-heading, text-body, text-caption variants)
/^text-(display|heading|body|caption)(-\w+)?(-\w+)?$/,
// Text weight classes
/^text-weight-/,
// Text color classes
/^text-color-/,
/^outline-width-/,
/^border-width-/,
// Custom spacing classes (example)
// /^spacing-(xs|sm|md|lg|xl)$/,
// Custom color classes (example)
// /^color-(primary|secondary|accent)$/,
// Any class that starts with 'custom-'
/^custom-/,
];
function isCustomClass(className) {
return CUSTOM_CLASS_PATTERNS.some((pattern) => pattern.test(className));
}
function cn(...inputs) {
// Use clsx first to combine classes, then use twMerge for standard Tailwind classes
const combined = clsx(inputs);
// Split classes and filter out our custom classes before merging
const classes = combined.split(" ");
const customClasses = classes.filter((cls) => isCustomClass(cls));
const standardClasses = classes.filter((cls) => !isCustomClass(cls));
// Merge standard classes and add back custom classes
const mergedStandard = twMerge(standardClasses.join(" "));
return clsx(mergedStandard, customClasses);
}
const buttonVariants = cva("items-center gap-3 justify-center whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none", {
variants: {
variant: {
primary: "bg-action-fill-primary-default text-action-ink-on-primary-normal hover:bg-action-fill-primary-hover",
secondary: "border",
tertiary: "hover:bg-accent hover:text-accent-foreground",
},
color: {
primary: "",
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
white: "",
},
size: {
xsmall: "md:h-[28px] px-3 rounded-medium text-body-small-medium",
small: "md:h-[32px] px-4 rounded-medium text-body-small-medium",
medium: "md:h-[36px] px-5 py-2 rounded-large text-body-medium-medium",
large: "md:h-[44px] px-6 rounded-large text-body-large-medium",
},
isIconOnly: {
true: "aspect-square p-0",
false: "",
},
isLoading: {
true: "cursor-not-allowed",
false: "",
},
isDisabled: {
true: "cursor-not-allowed",
false: "cursor-pointer",
},
isFullWidth: {
true: "flex w-full",
false: "flex w-fit",
},
},
compoundVariants: [
// Primary variant colors
{
variant: "primary",
color: "primary",
class: `bg-action-fill-primary-default text-action-ink-on-primary-normal
hover:bg-action-fill-primary-hover
disabled:bg-action-fill-primary-disabled
disabled:text-action-ink-primary-disabled,
active:bg-action-fill-primary-activated
`,
},
{
variant: "primary",
color: "positive",
class: `bg-action-fill-positive-default text-action-ink-on-primary-normal
hover:bg-action-fill-positive-hover
disabled:bg-action-fill-primary-disabled
disabled:text-action-ink-primary-disabled
active:bg-action-fill-positive-activated
`,
},
{
variant: "primary",
color: "negative",
class: `bg-action-fill-negative-default text-action-ink-on-primary-normal
hover:bg-action-fill-negative-hover
disabled:bg-action-fill-negative-disabled
disabled:text-action-ink-negative-disabled
active:bg-action-fill-negative-activated
`,
},
{
variant: "primary",
color: "notice",
class: `bg-action-fill-notice-default text-action-ink-on-primary-normal
hover:bg-action-fill-notice-hover
disabled:bg-action-fill-notice-disabled
disabled:text-action-ink-notice-disabled
active:bg-action-fill-notice-activated
`,
},
{
variant: "primary",
color: "info",
class: `bg-action-fill-info-default text-action-ink-on-primary-normal
hover:bg-action-fill-info-hover
disabled:bg-action-fill-info-disabled
disabled:text-action-ink-info-disabled
active:bg-action-fill-info-activated
`,
},
{
variant: "primary",
color: "neutral",
class: `bg-action-fill-neutral-default text-action-ink-on-primary-normal
hover:bg-action-fill-neutral-hover
disabled:bg-action-fill-neutral-disabled
disabled:text-action-ink-neutral-disabled
active:bg-action-fill-neutral-activated
`,
},
{
variant: "primary",
color: "white",
class: `bg-action-fill-white-default text-action-ink-neutral-subtle
hover:bg-action-fill-white-hover
disabled:bg-action-fill-white-disabled
disabled:text-action-ink-neutral-disabled
active:bg-action-fill-white-activated
`,
},
// Secondary variant colors
{
variant: "secondary",
color: "primary",
class: `
border-action-outline-primary-faded
text-action-ink-primary-normal
hover:border-action-outline-primary-faded-hover
hover:bg-action-fill-primary-faded-hover
disabled:bg-action-outline-info-disabled
disabled:text-action-ink-primary-disabled
disabled:border-action-outline-primary-disabled
active:border-action-outline-primary-faded-activated
active:bg-action-fill-primary-faded-activated
`,
},
{
variant: "secondary",
color: "positive",
class: `
border-action-outline-positive-faded
text-action-ink-positive-normal
hover:border-action-outline-positive-faded-hover
hover:bg-action-fill-positive-faded-hover
disabled:bg-action-outline-positive-disabled
disabled:text-action-ink-positive-disabled
disabled:border-action-outline-positive-disabled
active:border-action-outline-positive-faded-activated
active:bg-action-fill-positive-faded-activated
`,
},
{
variant: "secondary",
color: "negative",
class: `
border-action-outline-negative-faded
text-action-ink-negative-normal
hover:border-action-outline-negative-faded-hover
hover:bg-action-fill-negative-faded-hover
disabled:bg-action-outline-negative-disabled
disabled:text-action-ink-negative-disabled
disabled:border-action-outline-negative-disabled
active:border-action-outline-negative-faded-activated
active:bg-action-fill-negative-faded-activated
`,
},
{
variant: "secondary",
color: "notice",
class: `
border-action-outline-notice-faded
text-action-ink-notice-normal
hover:border-action-outline-notice-faded-hover
hover:bg-action-fill-notice-faded-hover
disabled:bg-action-outline-notice-disabled
disabled:text-action-ink-notice-disabled
disabled:border-action-outline-notice-disabled
active:border-action-outline-notice-faded-activated
active:bg-action-fill-notice-faded-activated
`,
},
{
variant: "secondary",
color: "info",
class: `border-action-outline-info-faded
text-action-ink-info-normal
hover:border-action-outline-info-faded-hover
hover:bg-action-fill-info-faded-hover
disabled:bg-action-outline-info-disabled
disabled:text-action-ink-info-disabled
disabled:border-action-outline-info-disabled
active:border-action-outline-info-faded-activated
active:bg-action-fill-info-faded-activated
`,
},
{
variant: "secondary",
color: "neutral",
class: `border-action-outline-neutral-faded
text-action-ink-neutral-normal
hover:bg-action-outline-neutral-faded-hover
hover:bg-action-fill-neutral-faded-hover
disabled:text-action-ink-neutral-disabled
disabled:border-action-outline-neutral-disabled
active:border-action-outline-neutral-faded-activated
active:bg-action-fill-neutral-faded-activated
`,
},
{
variant: "secondary",
color: "white",
class: `border-action-outline-white-faded
text-action-ink-on-primary-subtle
hover:border-action-outline-white-faded-hover
hover:bg-action-fill-white-faded-hover
disabled:text-surface-ink-white-disabled
disabled:border-action-outline-white-disabled
active:border-action-outline-white-faded-activated
active:bg-action-fill-white-faded-activated
`,
},
// Tertiary variant colors
{
variant: "tertiary",
color: "primary",
class: `text-action-ink-primary-normal
hover:bg-action-fill-primary-faded
disabled:text-action-ink-on-primary-muted
active:bg-action-fill-primary-faded-activated
`,
},
{
variant: "tertiary",
color: "positive",
class: `text-action-ink-positive-normal
hover:bg-action-fill-positive-faded
disabled:text-action-ink-on-positive-muted
active:bg-action-fill-positive-faded-activated
`,
},
{
variant: "tertiary",
color: "negative",
class: `text-action-ink-negative-normal
hover:bg-action-fill-negative-faded
disabled:text-action-ink-on-negative-muted
active:bg-action-fill-negative-faded-activated
`,
},
{
variant: "tertiary",
color: "notice",
class: `text-action-ink-notice-normal
hover:bg-action-fill-notice-faded
disabled:text-action-ink-on-notice-muted
active:bg-action-fill-notice-faded-activated
`,
},
{
variant: "tertiary",
color: "info",
class: `text-action-ink-info-normal
hover:bg-action-fill-info-faded
disabled:text-action-ink-on-notice-muted
active:bg-action-fill-info-faded-activated
`,
},
{
variant: "tertiary",
color: "neutral",
class: `text-action-ink-neutral-normal
hover:bg-action-fill-neutral-faded
disabled:text-action-ink-on-notice-muted
active:bg-action-fill-neutral-faded-activated
`,
},
{
variant: "tertiary",
color: "white",
class: `text-action-ink-white-subtle
hover:bg-action-white-faded-hover
disabled:text-action-ink-white-disabled
disabled:bg-action-fill-white-disabled
active:bg-action-white-faded-activated
`,
},
// Icon only sizing
{
isIconOnly: true,
size: "xsmall",
class: "h-7 w-7",
},
{
isIconOnly: true,
size: "small",
class: "h-8 w-8",
},
{
isIconOnly: true,
size: "medium",
class: "h-9 w-9",
},
{
isIconOnly: true,
size: "large",
class: "h-10 w-10",
},
],
defaultVariants: {
variant: "primary",
color: "primary",
size: "medium",
isIconOnly: false,
isLoading: false,
isFullWidth: false,
},
});
const Button = React.forwardRef(({ className, variant = "primary", color = "primary", size, isIconOnly, isLoading, asChild = false, leadingIcon, trailingIcon, isFullWidth = false, children, disabled, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
const isDisabled = disabled || isLoading || false;
const getLoaderColor = () => {
if (color === "white") {
return "var(--color-surface-ink-neutral-normal)";
}
return `var(--color-action-ink-${color}-normal)`;
};
const buttonContent = (jsxs(Fragment, { children: [isLoading && !isIconOnly && (jsx(PulseLoader, { color: getLoaderColor(), size: 10 })), isLoading && isIconOnly && (jsx(ClipLoader, { color: getLoaderColor(), size: 20 })), !isLoading && leadingIcon && (jsx("span", { className: "mr-2", children: leadingIcon })), !isIconOnly && !isLoading && children, isIconOnly && !isLoading && children, !isLoading && trailingIcon && jsx("span", { children: trailingIcon })] }));
return (jsx(Comp, { className: cn(buttonVariants({
variant,
color,
size,
isIconOnly,
isLoading,
isDisabled,
isFullWidth,
}), className), ref: ref, disabled: isDisabled, ...props, children: buttonContent }));
});
Button.displayName = "Button";
const iconButtonVariants = cva("inline-flex items-center justify-center transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none p-0 border-0 bg-transparent", {
variants: {
color: {
primary: "",
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
white: "",
},
size: {
xsmall: "",
small: "",
medium: "",
large: "",
},
isLoading: {
true: "cursor-not-allowed",
false: "",
},
isDisabled: {
true: "cursor-not-allowed",
false: "cursor-pointer",
},
},
defaultVariants: {
color: "primary",
size: "medium",
isLoading: false,
},
});
const IconButton = React.forwardRef(({ className, color = "primary", size = "medium", isLoading, asChild = false, icon, iconElement, disabled, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
const isDisabled = disabled || isLoading || false;
const getLoaderColor = () => {
if (color === "white") {
return "var(--color-surface-ink-neutral-normal)";
}
return `var(--color-action-ink-${color}-normal)`;
};
const getIconSize = () => {
switch (size) {
case "xsmall":
return 16;
case "small":
return 18;
case "medium":
return 20;
case "large":
return 24;
default:
return 20;
}
};
const getIconColorClass = () => {
// Color classes with normal, hover, focus, and disabled states
const colorMap = {
primary: "text-action-ink-primary-normal hover:text-action-ink-primary-subtle focus-visible:text-action-ink-primary-subtle disabled:text-action-ink-primary-muted",
positive: "text-action-ink-positive-normal hover:text-action-ink-positive-subtle focus-visible:text-action-ink-positive-subtle disabled:text-action-ink-positive-muted",
negative: "text-action-ink-negative-normal hover:text-action-ink-negative-subtle focus-visible:text-action-ink-negative-subtle disabled:text-action-ink-negative-muted",
notice: "text-action-ink-notice-normal hover:text-action-ink-notice-subtle focus-visible:text-action-ink-notice-subtle disabled:text-action-ink-notice-muted",
info: "text-action-ink-info-normal hover:text-action-ink-info-subtle focus-visible:text-action-ink-info-subtle disabled:text-action-ink-info-muted",
neutral: "text-action-ink-neutral-normal hover:text-action-ink-neutral-subtle focus-visible:text-action-ink-neutral-subtle disabled:text-action-ink-neutral-muted",
white: "text-surface-ink-white-subtle hover:text-surface-ink-white-normal focus-visible:text-surface-ink-white-normal disabled:text-surface-ink-white-muted",
};
return colorMap[color] || colorMap.primary;
};
const buttonContent = (jsx(Fragment, { children: isLoading ? (jsx(ClipLoader, { color: getLoaderColor(), size: getIconSize() })) : icon ? (jsx(Icon, { name: icon, size: getIconSize(), className: getIconColorClass() })) : iconElement ? (jsx("span", { className: getIconColorClass(), children: iconElement })) : null }));
return (jsx(Comp, { className: cn(iconButtonVariants({
color,
size,
isLoading,
isDisabled,
}), className), ref: ref, disabled: isDisabled, ...props, children: buttonContent }));
});
IconButton.displayName = "IconButton";
const linkVariants = cva("inline-flex items-center gap-1 whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none decoration-1 underline-offset-4", {
variants: {
type: {
anchor: "hover:underline",
action: "no-underline cursor-pointer",
},
color: {
primary: "",
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
white: "",
},
size: {
xsmall: "text-body-xsmall-medium gap-1",
small: "text-body-small-medium gap-1",
medium: "text-body-medium-medium gap-1.5",
large: "text-body-large-medium gap-1.5",
},
isIconOnly: {
true: "no-underline",
false: "",
},
isDisabled: {
true: "cursor-not-allowed opacity-50",
false: "cursor-pointer",
},
},
compoundVariants: [
// Primary color variants
{
color: "primary",
class: `text-action-ink-primary-normal
hover:text-action-ink-primary-subtle
hover:decoration-action-outline-primary-hover
disabled:text-action-ink-primary-disabled
focus:text-action-ink-primary-hover
`,
},
// Positive color variants
{
color: "positive",
class: `text-action-ink-positive-normal
hover:text-action-ink-positive-subtle
hover:decoration-action-outline-positive-hover
hover:text-action-ink-positive-hover
disabled:text-action-ink-positive-disabled
focus:text-action-ink-positive-hover
`,
},
// Negative color variants
{
color: "negative",
class: `text-action-ink-negative-normal
hover:text-action-ink-negative-subtle
hover:decoration-action-outline-negative-hover
hover:text-action-ink-negative-hover
disabled:text-action-ink-negative-disabled
focus:text-action-ink-negative-hover
`,
},
// Notice color variants
{
color: "notice",
class: `text-action-ink-notice-normal
hover:text-action-ink-notice-subtle
hover:decoration-action-outline-notice-hover
hover:text-action-ink-notice-hover
disabled:text-action-ink-notice-disabled
focus:text-action-ink-notice-hover
`,
},
// Info color variants
{
color: "info",
class: `text-action-ink-info-normal
hover:text-action-ink-info-subtle
hover:decoration-action-outline-info-hover
hover:text-action-ink-info-hover
disabled:text-action-ink-info-disabled
focus:text-action-ink-info-hover
`,
},
// Neutral color variants
{
color: "neutral",
class: `text-action-ink-neutral-normal
hover:text-action-ink-neutral-subtle
hover:decoration-action-outline-neutral-hover
hover:text-action-ink-neutral-hover
disabled:text-action-ink-neutral-disabled
focus:text-action-ink-neutral-hover
`,
},
{
color: "white",
class: `text-surface-ink-white-normal
hover:text-surface-ink-white-subtle
hover:text-action-ink-white-hover
disabled:text-action-ink-white-muted
focus:text-action-ink-white-hover
`,
},
],
defaultVariants: {
type: "anchor",
color: "primary",
size: "medium",
isIconOnly: false,
isDisabled: false,
},
});
const Link = React.forwardRef(({ className, type = "anchor", color = "primary", size = "medium", isIconOnly = false, isDisabled = false, asChild = false, showIcon = false, icon, leadingIcon, trailingIcon, children, onClick, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
const handleClick = (e) => {
if (isDisabled) {
e.preventDefault();
return;
}
onClick?.(e);
};
// Icon size based on link size
const iconSize = {
xsmall: 12,
small: 14,
medium: 16,
large: 18,
}[size];
// Determine what to show as trailing icon
// Priority: trailingIcon > (showIcon && icon) > (showIcon && default ExternalLink)
const finalTrailingIcon = trailingIcon || (showIcon && (icon || jsx(ExternalLink, { size: iconSize })));
const linkContent = (jsxs(Fragment, { children: [leadingIcon && !isIconOnly && (jsx("span", { className: "inline-flex items-center", children: leadingIcon })), !isIconOnly && children, isIconOnly && children, finalTrailingIcon && !isIconOnly && (jsx("span", { className: "inline-flex items-center", children: finalTrailingIcon })), isIconOnly &&
(leadingIcon || finalTrailingIcon || (jsx(ExternalLink, { size: iconSize })))] }));
return (jsx(Comp, { className: cn(linkVariants({
type,
color,
size,
isIconOnly,
isDisabled,
}), className), ref: ref, onClick: handleClick, "aria-disabled": isDisabled, tabIndex: isDisabled ? -1 : undefined, ...props, children: linkContent }));
});
Link.displayName = "Link";
const alertVariants = cva("relative flex gap-4 p-4 rounded-large transition-colors", {
variants: {
emphasis: {
subtle: "",
intense: "",
},
intent: {
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
},
},
compoundVariants: [
// Subtle emphasis
{
emphasis: "subtle",
intent: "positive",
class: "bg-feedback-fill-positive-subtle",
},
{
emphasis: "subtle",
intent: "negative",
class: "bg-feedback-fill-negative-subtle",
},
{
emphasis: "subtle",
intent: "notice",
class: "bg-feedback-fill-notice-subtle",
},
{
emphasis: "subtle",
intent: "info",
class: "bg-feedback-fill-info-subtle",
},
{
emphasis: "subtle",
intent: "neutral",
class: "bg-feedback-fill-neutral-subtle",
},
// Intense emphasis
{
emphasis: "intense",
intent: "positive",
class: "bg-feedback-fill-positive-intense",
},
{
emphasis: "intense",
intent: "negative",
class: "bg-feedback-fill-negative-intense",
},
{
emphasis: "intense",
intent: "notice",
class: "bg-feedback-fill-notice-intense",
},
{
emphasis: "intense",
intent: "info",
class: "bg-feedback-fill-info-intense",
},
{
emphasis: "intense",
intent: "neutral",
class: "bg-feedback-fill-neutral-intense",
},
],
defaultVariants: {
emphasis: "subtle",
intent: "info",
},
});
const alertIconVariants = cva("flex-shrink-0 rounded-full flex items-center justify-center mt-1", {
variants: {
emphasis: {
subtle: "",
intense: "text-white",
},
intent: {
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
},
},
compoundVariants: [
// Subtle emphasis - colored backgrounds with white icons
{
emphasis: "subtle",
intent: "positive",
class: "text-action-ink-positive-normal",
},
{
emphasis: "subtle",
intent: "negative",
class: " text-action-ink-negative-normal",
},
{
emphasis: "subtle",
intent: "notice",
class: " text-action-ink-notice-normal",
},
{
emphasis: "subtle",
intent: "info",
class: " text-action-ink-info-normal",
},
{
emphasis: "subtle",
intent: "neutral",
class: " text-action-ink-neutral-normal",
},
// Intense emphasis - white backgrounds with colored icons
{
emphasis: "intense",
intent: "positive",
class: " text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "negative",
class: " text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "notice",
class: " text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "info",
class: " text-action-fill-info-default",
},
{
emphasis: "intense",
intent: "neutral",
class: " text-action-fill-neutral-default",
},
],
defaultVariants: {
emphasis: "subtle",
intent: "info",
},
});
const alertTextVariants = cva("", {
variants: {
emphasis: {
subtle: "",
intense: "",
},
intent: {
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
},
},
compoundVariants: [
// Subtle emphasis - dark text
{
emphasis: "subtle",
intent: "positive",
class: "text-surface-ink-neutral-normal",
},
{
emphasis: "subtle",
intent: "negative",
class: "text-surface-ink-neutral-normal",
},
{
emphasis: "subtle",
intent: "notice",
class: "text-surface-ink-neutral-normal",
},
{
emphasis: "subtle",
intent: "info",
class: "text-surface-ink-neutral-normal",
},
{
emphasis: "subtle",
intent: "neutral",
class: "text-surface-ink-neutral-normal",
},
// Intense emphasis - white text
{
emphasis: "intense",
intent: "positive",
class: "text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "negative",
class: "text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "notice",
class: "text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "info",
class: "text-action-ink-on-primary-normal",
},
{
emphasis: "intense",
intent: "neutral",
class: "text-action-ink-on-primary-normal",
},
],
defaultVariants: {
emphasis: "subtle",
intent: "info",
},
});
const getDefaultIcon = (intent, emphasis) => {
const iconSize = 16;
switch (emphasis) {
case "intense":
return (jsx(Icon, { name: "info", size: iconSize, className: "text-white", "aria-hidden": "true" }));
default:
return jsx(Icon, { name: "info", size: iconSize, "aria-hidden": "true" });
}
};
const getButtonColor = (intent) => {
switch (intent) {
case "positive":
return "positive";
case "negative":
return "negative";
case "notice":
return "notice";
case "info":
return "info";
case "neutral":
return "neutral";
default:
return "neutral";
}
};
const getActionButton = (actionButtonText, onActionButtonClick, emphasis, intent) => {
if (!actionButtonText)
return null;
// For intense emphasis: secondary variant with white border and white text
// For subtle emphasis: secondary variant with colored border and colored text (default behavior)
if (emphasis === "intense") {
return (jsx(Button, { variant: "secondary", color: "white", size: "small", onClick: onActionButtonClick, children: actionButtonText }));
}
// Subtle emphasis: use default secondary variant styling
return (jsx(Button, { variant: "secondary", color: intent, size: "small", onClick: onActionButtonClick, children: actionButtonText }));
};
const getActionLink = (actionButtonText, onActionLinkClick, emphasis, intent) => {
if (!actionButtonText)
return null;
// For intense emphasis: secondary variant with white border and white text
// For subtle emphasis: secondary variant with colored border and colored text (default behavior)
if (emphasis === "intense") {
return (jsx(Link, { type: "action", color: "white", size: "small", onClick: onActionLinkClick, children: actionButtonText }));
}
// Subtle emphasis: use default secondary variant styling
return (jsx(Link, { type: "action", color: intent, size: "small", onClick: onActionLinkClick, children: actionButtonText }));
};
const Alert = React.forwardRef(({ className, emphasis = "subtle", intent = "info", title, children, isFullWidth = false, onClose, icon, actionButtonText, onActionButtonClick, actionLinkText, onActionLinkClick, ...props }, ref) => {
const displayIcon = icon ?? getDefaultIcon(intent, emphasis);
const actionButton = getActionButton(actionButtonText, onActionButtonClick, emphasis, intent);
const actionLink = getActionLink(actionLinkText, onActionLinkClick, emphasis, intent);
const hasActions = actionButton || actionLink;
return (jsxs("div", { ref: ref, className: cn(alertVariants({ emphasis, intent }), className), role: "alert", ...props, children: [jsx("div", { className: cn(alertIconVariants({ emphasis, intent }), "h-5 w-5 shrink-0"), children: displayIcon }), isFullWidth ? (
/* Full Width Layout: Icon, Content, Actions/Close in one row */
jsxs("div", { className: "flex-1 min-w-0 flex items-center justify-between gap-4", children: [jsxs("div", { className: "flex-1 min-w-0 flex flex-col gap-1", children: [title && (jsx("div", { className: cn("text-body-medium-semibold", alertTextVariants({ emphasis, intent })), children: title })), jsx("div", { className: cn("text-body-small-regular", alertTextVariants({ emphasis, intent })), children: children })] }), jsxs("div", { className: "flex items-center gap-4 shrink-0", children: [hasActions && (jsxs(Fragment, { children: [actionButton, actionLink] })), onClose && (jsx(IconButton, { icon: "close", color: emphasis === "intense" ? "white" : getButtonColor(intent), size: "xsmall", onClick: onClose, className: cn("shrink-0 h-5 w-5", emphasis === "intense"
? "hover:bg-white/20"
: "hover:bg-black/10"), "aria-label": "Close alert" }))] })] })) : (
/* Not Full Width Layout: Icon and Content in row, Actions below, Close top-right */
jsxs("div", { className: "flex-1 min-w-0 relative", children: [onClose && (jsx("div", { className: "absolute top-0 right-0", children: jsx(IconButton, { icon: "close", color: emphasis === "intense" ? "white" : getButtonColor(intent), size: "xsmall", onClick: onClose, className: cn("shrink-0 h-5 w-5", emphasis === "intense"
? "hover:bg-white/20"
: "hover:bg-black/10"), "aria-label": "Close alert" }) })), jsxs("div", { className: "flex flex-col gap-3 pr-8", children: [title && (jsx("div", { className: cn("text-body-medium-semibold", alertTextVariants({ emphasis, intent })), children: title })), jsx("div", { className: cn("text-body-small-regular", alertTextVariants({ emphasis, intent })), children: children }), hasActions && (jsxs("div", { className: "flex items-center gap-4", children: [actionButton, actionLink] }))] })] }))] }));
});
Alert.displayName = "Alert";
// Helper function to get the text utility class name
function getTextClassName(variant = "body", size = "medium", weight = "regular", color = "default") {
// Build the base class name
let baseClass = `text-${variant}`;
// Add size
if (size) {
baseClass += `-${size}`;
}
// Add weight
if (weight) {
baseClass += `-${weight}`;
}
// Add color class separately
const colorClass = `text-color-${color}`;
return `${baseClass} ${colorClass}`;
}
const Text = React.forwardRef(({ className, variant = "body", size = "medium", weight = "regular", color = "default", as = "p", children, ...props }, ref) => {
const Component = as;
const textClass = getTextClassName(variant, size, weight, color);
return React.createElement(Component, {
className: cn(textClass, className),
ref,
...props,
}, children);
});
Text.displayName = "Text";
const inverseColorClasses = {
a1: "bg-avatar-fill-a1-on-bg text-avatar-fill-a1-bg",
a2: "bg-avatar-fill-a2-on-bg text-avatar-fill-a2-bg",
a3: "bg-avatar-fill-a3-on-bg text-avatar-fill-a3-bg",
a4: "bg-avatar-fill-a4-on-bg text-avatar-fill-a4-bg",
a5: "bg-avatar-fill-a5-on-bg text-avatar-fill-a5-bg",
};
const avatarVariants = cva("inline-flex items-center justify-center font-medium text-center select-none", {
variants: {
color: {
a1: "bg-avatar-fill-a1-bg text-avatar-fill-a1-on-bg",
a2: "bg-avatar-fill-a2-bg text-avatar-fill-a2-on-bg",
a3: "bg-avatar-fill-a3-bg text-avatar-fill-a3-on-bg",
a4: "bg-avatar-fill-a4-bg text-avatar-fill-a4-on-bg",
a5: "bg-avatar-fill-a5-bg text-avatar-fill-a5-on-bg",
},
size: {
small: "h-[24px] w-[24px] text-body-medium-regular rounded-large",
medium: "h-[32px] w-[32px] text-body-medium-regular rounded-xlarge",
xlarge: "h-[36px] w-[36px] text-body-medium-regular rounded-xlarge",
},
},
defaultVariants: {
color: "a1",
size: "medium",
},
});
const statusVariants = cva("absolute flex items-center justify-center rounded-full border-2 border-surface-fill-neutral-intense", {
variants: {
size: {
small: "h-5 w-5 -bottom-0.5 -right-0.5",
medium: "h-6 w-6 -bottom-1 -right-1",
xlarge: "h-6 w-6 -bottom-1 -right-1",
},
statusColor: {
positive: "bg-action-fill-positive-default",
negative: "bg-action-fill-negative-default",
notice: "bg-action-fill-notice-default",
info: "bg-action-fill-info-default",
neutral: "bg-action-fill-neutral-default",
},
},
defaultVariants: {
size: "medium",
statusColor: "notice",
},
});
const Avatar = React.forwardRef(({ className, appearance = "default", color, size, children, src, alt, showStatus = false, statusColor = "notice", statusIcon, label, trailingComponent, containerClassName, ...props }, ref) => {
const [imageError, setImageError] = React.useState(false);
const handleImageError = () => {
setImageError(true);
};
const getStatusIconSize = () => {
switch (size) {
case "small":
return "h-3.5 w-3.5";
case "medium":
return "h-4 w-4";
default:
return "h-4 w-4";
}
};
const getTextSize = () => {
switch (size) {
case "small":
return "small";
case "medium":
return "medium";
default:
return "medium";
}
};
const resolvedColor = color ?? "a1";
const avatarElement = (jsxs("div", { className: "relative inline-block", children: [jsx("div", { className: cn(avatarVariants({ color: resolvedColor, size }), appearance === "inverse"
? inverseColorClasses[resolvedColor]
: undefined, className), ...props, children: src && !imageError ? (jsx("img", { src: src, alt: alt || "Avatar", className: cn("h-full w-full object-cover", size === "small" ? "rounded-large" : "rounded-xlarge"), onError: handleImageError })) : (children) }), showStatus && (jsx("div", { className: cn(statusVariants({ size, statusColor })), children: statusIcon && (jsx("span", { className: cn("text-action-ink-on-primary-normal", getStatusIconSize()), children: statusIcon })) }))] }));
// If no label or trailing component, return just the avatar
if (!label && !trailingComponent) {
return jsx("div", { ref: ref, children: avatarElement });
}
// Otherwise, return avatar with label and/or trailing component
return (jsxs("div", { ref: ref, className: cn("inline-flex items-center gap-3", containerClassName), children: [avatarElement, label && (jsx(Text, { variant: "body", size: getTextSize(), weight: "medium", color: "default", as: "span", children: label })), trailingComponent && (jsx("span", { className: "ml-auto", children: trailingComponent }))] }));
});
Avatar.displayName = "Avatar";
const badgeVariants = cva("inline-flex items-center whitespace-nowrap transition-colors", {
variants: {
variant: {
light: "",
filled: "",
},
color: {
primary: "",
positive: "",
negative: "",
notice: "",
info: "",
neutral: "",
},
size: {
small: "px-2 h-[var(--size-20)] gap-2 rounded-large text-body-small-medium",
medium: "px-3 h-[var(--size-24)] gap-3 rounded-large text-body-medium-medium",
large: "px-4 h-[var(--size-28)] gap-3 rounded-xlarge text-body-large-medium",
},
},
compoundVariants: [
// Light variant colors
{
variant: "light",
color: "primary",
class: "bg-action-fill-primary-faded text-action-ink-primary-normal",
},
{
variant: "light",
color: "positive",
class: "bg-action-fill-positive-faded text-action-ink-positive-normal",
},
{
variant: "light",
color: "negative",
class: "bg-action-fill-negative-faded text-action-ink-negative-normal",
},
{
variant: "light",
color: "notice",
class: "bg-action-fill-notice-faded text-action-ink-notice-normal",
},
{
variant: "light",
color: "info",
class: "bg-action-fill-info-faded text-action-ink-info-normal",
},
{
variant: "light",
color: "neutral",
class: "bg-action-fill-neutral-faded text-action-ink-neutral-normal",
},
// Filled variant colors
{
variant: "filled",
color: "primary",
class: "bg-action-fill-primary-default text-action-ink-on-primary-normal",
},
{
variant: "filled",
color: "positive",
class: "bg-action-fill-positive-default text-action-ink-on-primary-normal",
},
{
variant: "filled",
color: "negative",
class: "bg-action-fill-negative-default text-action-ink-on-primary-normal",
},
{
variant: "filled",
color: "notice",
class: "bg-action-fill-notice-default text-action-ink-on-primary-normal",
},
{
variant: "filled",
color: "info",
class: "bg-action-fill-info-default text-action-ink-on-primary-normal",
},
{
variant: "filled",
color: "neutral",
class: "bg-action-fill-neutral-default text-action-ink-on-primary-normal",
},
],
defaultVariants: {
variant: "light",
color: "info",
size: "medium",
},
});
const Badge = React.forwardRef(({ className, variant, size, color, showDot = false, children, ...props }, ref) => {
const getDotColor = () => {
if (variant === "filled") {
return "bg-action-ink-on-primary-normal";
}
// Light variant - use the corresponding action color
switch (color) {
case "primary":
return "bg-action-fill-primary-default";
case "positive":
return "bg-action-fill-positive-default";
case "negative":
return "bg-action-fill-negative-default";
case "notice":
return "bg-action-fill-notice-default";
case "info":
return "bg-action-fill-info-default";
case "neutral":
return "bg-action-fill-neutral-default";
default:
return "bg-action-fill-info-default";
}
};
const getDotSize = () => {
if (size === "small") {
return "h-2 w-2";
}
if (size === "medium") {
return "h-[6px] w-[6px]";
}
return "h-3 w-3";
};
return (jsxs("div", { ref: ref, className: cn(badgeVariants({ variant, size, color }), className), ...props, children: [showDot && (jsx("span", { className: cn("rounded-full", getDotColor(), getDotSize()), "aria-hidden": "true" })), children] }));
});
Badge.displayName = "Badge";
const buttonGroupVariants = cva("inline-flex", {
variants: {
variant: {
attached: "",
separated: "",
},
orientation: {
horizontal: "flex-row",
vertical: "flex-col",
},
size: {
xsmall: "",
small: "",
medium: "",
large: "",
},
isFullWidth: {
true: "w-full",
false: "w-fit",
},
isDisabled: {
true: "pointer-events-none opacity-50",
false: "",
},
},
compoundVariants: [
{
variant: "separated",
orientation: "horizontal",
class: "gap-2",
},
{
variant: "separated",
orientation: "vertical",
class: "gap-2",
},
],
defaultVariants: {
variant: "attached",
orientation: "horizontal",
size: "medium",
isFullWidth: false,
isDisabled: false,
},
});
const ButtonGroup = React.forwardRef(({ className, variant = "attached", orientation = "horizontal", size = "medium", isDisabled = false, isFullWidth = false, value, onChange, children, ...props }, ref) => {
const childrenArray = React.Children.toArray(children);
const isControlled = value !== undefined && onChange !== undefined;
return (jsx("div", { ref: ref, className: cn(buttonGroupVariants({