import { clsx, type ClassValue } from "clsx"; import { extendTailwindMerge } from "tailwind-merge"; const SPACING_TOKENS = ["text-pair", "field", "stack", "section"]; /** * tailwind-merge only dedupes classes it can classify. Without these, a caller's * `gap-4` and the component's `gap-text-pair` both survive cn() and stylesheet * order silently decides the winner, so the override looks applied but is a no-op. */ const spacingClassGroups = Object.fromEntries( [ "gap", "gap-x", "gap-y", "space-x", "space-y", "m", "mx", "my", "ms", "me", "mt", "mr", "mb", "ml", "p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl", "inset", "inset-x", "inset-y", "top", "right", "bottom", "left", "start", "end", "w", "min-w", "max-w", "h", "min-h", "max-h", "size", "basis", "indent", "translate-x", "translate-y", ].map((group) => [group, [{ [group]: [...SPACING_TOKENS] }]]), ); /** * Custom twMerge that recognises WealthX typography tokens as font-size * utilities so they don't conflict with text-color utilities * (e.g. text-button vs text-primary-foreground). */ const twMerge = extendTailwindMerge({ extend: { classGroups: { ...spacingClassGroups, "font-size": [ { text: [ "display-large", "display-medium", "display-small", "h1", "h2", "h3", "h4", "h5", "h6", "body-large", "body-medium", "body-small", "label-large", "label-medium", "label-small", "button", "button-xs", "caption", "overline", "code", ], }, ], }, }, }); export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs)); } export function getInitials(name: string): string { if (!name.trim()) return "?"; return name .split(" ") .filter(Boolean) .map((word) => word[0]) .join("") .toUpperCase() .slice(0, 2); }