Saltar al contenido principal

Biblioteca de componentes de UI

packages/ui (@repo/ui) es la biblioteca de componentes de React compartida de Closegram. Es la única fuente de verdad para la apariencia de botones, controles de formulario, avatares, badges, spinners y demás primitivas usadas por la app principal (apps/frontend-nextjs) y el panel de administración, de modo que ambos consumen los mismos componentes en lugar de redeclarar las mismas cadenas de Tailwind en cada archivo.

El paquete se desarrolla y previsualiza de forma aislada con Storybook (pnpm --filter @repo/ui dev, puerto 6006), lo que permite que cada componente se renderice sin depender de ninguna app. Los componentes se agrupan bajo los títulos Primitives/ y Foundations/ en Storybook.

Importación

El nombre del paquete es @repo/ui. Todo lo de la superficie pública se reexporta desde src/index.ts:

import { Button, Badge, Avatar, Spinner, Tooltip } from '@repo/ui';
import { fieldClass, TextInput, Switch, Select } from '@repo/ui';

Hay tres puntos de entrada definidos en package.json:

EntradaImportaciónContenido
Main@repo/uiTodos los componentes, las constantes de cadenas de clase y el helper cx
Icons@repo/ui/iconsEl conjunto completo de íconos (genéricos + de marca)
Styles@repo/ui/styles.cssHoja de estilos de Tailwind compilada (importar una sola vez en la raíz de la app)

react y react-dom (^18.3.1) son dependencias entre pares (peer dependencies) — la app consumidora las provee.

El helper cx

cx(...parts) es un pequeño combinador de className que descarta valores falsy, usado internamente por cada componente y reexportado para los sitios de llamada:

import { cx } from '@repo/ui';

cx('rounded-lg', isActive && 'bg-cg-blue', className);

Componentes

Button

Botón de acción canónico — un superconjunto de las variantes usadas en toda la app principal y el panel de administración. Extiende los atributos nativos de <button> (mediante React.ButtonHTMLAttributes) y reenvía una ref.

PropTipoPor defectoNotas
variant'primary' | 'secondary' | 'ghost' | 'danger' | 'success' | 'warning' | 'outline-danger' | 'outline-warning''secondary'Estilo visual
size'sm' | 'md''md'
iconReact.ReactNodeÍcono inicial opcional renderizado antes de children
...restoprops nativas de <button>onClick, disabled, type (por defecto 'button'), etc.
<Button variant="primary" size="md">Guardar</Button>

Para el puñado de páginas de configuración/pagos que aún declaran el estilo del botón en línea, las cadenas de clase sin procesar también se exportan: primaryButtonClass, secondaryButtonClass, dangerButtonClass, linkButtonClass.

IconButton

Botón de ícono redondo y ghost que consolida el patrón repetido w-9 h-9 flex items-center justify-center rounded-full hover:bg-gray-100 … usado en headers, modales y barras de herramientas. Extiende los atributos nativos de <button> y reenvía una ref.

PropTipoPor defecto
size'sm' | 'md' | 'lg' (w-8/9/10)'md'
...restoprops nativas de <button>
<IconButton size="md" aria-label="More"><MoreHorizontal /></IconButton>

Badge

Etiqueta/píldora pequeña. Superconjunto usado por la app principal y el panel de administración.

PropTipoPor defecto
tone'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger''neutral'
iconReact.ReactNode
childrenReact.ReactNode— (requerido)
classNamestring
<Badge tone="success">Verified</Badge>

Avatar

Avatar circular con un respaldo de inicial en gradiente morado→rosa cuando no hay imagen configurada.

PropTipoPor defectoNotas
srcstring | nullRecurre a una inicial cuando está ausente
altstring''
namestringPrimer carácter mostrado como la inicial de respaldo
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'
classNamestring
<Avatar src={user.photo} name={user.name} size="lg" />

StoryRingAvatar

Anillo de historia estilo Instagram alrededor de un avatar, coloreado según la audiencia/visibilidad de la historia. visibility proviene de User.activeStoryVisibility; null/undefined significa que no hay historia activa, lo que renderiza un avatar simple sin anillo.

PropTipoPor defectoNotas
srcstring | null
altstring''
visibilitystring | nullpublic, followers, close_friends, subscribers, private reciben cada uno un gradiente distinto
sizeClassstring— (requerido)Clases de tamaño de Tailwind para el avatar en sí, p. ej. "w-10 h-10"
fallbackSrcstring'/default-avatar.png'Imagen usada cuando src está vacío
classNamestring
<StoryRingAvatar src={user.photo} sizeClass="w-14 h-14" visibility="close_friends" />

El helper storyRingClass(visibility) también se exporta y devuelve las clases del gradiente del anillo (o null cuando no hay historia activa).

Spinner

Spinner de carga en línea que hereda el color de texto actual (border-current). Renderiza un elemento role="status" con aria-label="Loading".

PropTipoPor defecto
classNamestring'w-4 h-4'
<Spinner className="w-5 h-5" />

Skeleton

Bloque de skeleton base — un marcador de posición gris pulsante. Se compone el ancho/alto en el sitio de la llamada. Si el className ya contiene una utilidad rounded-*, se omite el rounded-md por defecto para que gane el rounded-full de quien llama.

PropTipoPor defecto
classNamestring''
<Skeleton className="h-3 w-32" />

Tooltip

Tooltip ligero: envuelve un elemento y muestra una etiqueta al pasar el mouse o al enfocar. No renderiza nada adicional cuando label está vacío.

PropTipoPor defecto
labelstring— (requerido)
side'top' | 'bottom' | 'left' | 'right''top'
childrenReact.ReactNode
classNamestring''
<Tooltip label="Copy link" side="bottom"><IconButton><Copy /></IconButton></Tooltip>

VerifiedBadge

Sello de verificación estilo Instagram/Twitter con una marca de verificación blanca. También se reexporta desde @repo/ui/icons.

PropTipoPor defecto
classNamestring'w-3.5 h-3.5 text-cg-blue flex-shrink-0'
<span>{user.name}{user.verified && <VerifiedBadge />}</span>

AnimatedCount

Contador animado que hace un pop y se desplaza hacia arriba/abajo cada vez que value cambia (usando la Web Animations API). Omite la animación en el primer montaje.

PropTipoPor defectoNotas
valuenumber— (requerido)
formatbooleantrueActiva/desactiva la abreviación compacta K/M
classNamestring
<AnimatedCount value={likeCount} />

El helper puro formatCount(n) (p. ej. 1200 → "1.2K", 3_400_000 → "3.4M") también se exporta.

RollingNumber

Rueda/cuenta desde su valor anterior hasta el nuevo, mostrando cada número intermedio mediante requestAnimationFrame. Omite el giro en el primer montaje y comienza cada giro desde lo que esté actualmente en pantalla para que los cambios rápidos se mantengan fluidos.

PropTipoPor defecto
valuenumber— (requerido)
classNamestring
<RollingNumber value={followerCount} />

DateTimePicker

Selector de fecha y hora personalizado sin calendario nativo del navegador y sin dependencia externa — una cuadrícula de mes más selectores de hora/minuto. Devuelve la misma cadena local 'YYYY-MM-DDTHH:mm' que usa un <input type="datetime-local">, de modo que quienes lo llaman no cambian. Marcado como 'use client'.

PropTipoPor defectoNotas
valuestring— (requerido)Cadena local 'YYYY-MM-DDTHH:mm', o ''
onChange(value: string) => void— (requerido)
minDateFecha y hora seleccionable más temprana
placeholderstring'Fecha y hora'
panelbooleanfalsePanel en línea siempre visible (la cuadrícula se desplaza, fila de hora + "Done" fijos) en lugar de un botón disparador + popover flotante
onDone() => voidLlamado por el botón "Done" del panel (p. ej. para cerrar un overlay)
classNamestring''
<DateTimePicker value={when} onChange={setWhen} min={new Date()} />

Primitivas de formulario

Form.tsx es la única fuente de verdad para la apariencia de inputs, textareas, selects, labels y switches. Exporta tanto componentes listos para usar como las constantes de cadenas de clase sin procesar a partir de las cuales se construyen.

Constantes de clase: fieldClass (campo con borde de ancho completo), labelClass, inlineInputClass, inlineSelectClass — importa estas en lugar de redeclarar las mismas cadenas.

Componentes:

ComponenteProps claveNotas
FieldLabelhtmlFor, className, children<label> estilizado con labelClass
TextInputprops nativas de <input> (ref reenviada)Estilizado con fieldClass
TextAreaprops nativas de <textarea>fieldClass + resize-none
SearchInputvalue, onChange(value), placeholder, onClear, inputClassName (SearchInputProps)Campo de búsqueda redondeado con ícono de lupa y un botón de limpiar opcional (se muestra cuando onClear está configurado y hay texto)
Switchchecked, onChange(), disabled, ariaLabelInterruptor; role="switch", API canónica checked/onChange
Selectvalue, onChange(value), options? (SelectOption[]), children?, variant ('field' | 'inline'), disabledPasar ya sea options o hijos <option>

SelectOption es { value: string; label: string }.

<FieldLabel>Idioma</FieldLabel>
<Select
value={lang}
onChange={setLang}
options={[
{ value: 'es', label: 'Español' },
{ value: 'en', label: 'English' },
]}
/>

<SearchInput value={q} onChange={setQ} onClear={() => setQ('')} placeholder="Buscar" />

<Switch checked={on} onChange={() => setOn((v) => !v)} ariaLabel="toggle" />

Estilos de callout / caja

styles/boxes.ts exporta cadenas de clase de callout en línea reutilizadas en los formularios de configuración y pagos, de modo que las mismas cadenas no se redeclaran en cada archivo:

  • successBoxClass (verde)
  • errorBoxClass (rojo)
  • neutralBoxClass (gris)
  • warningBoxClass (ámbar)
<div className={successBoxClass}>Saved.</div>

Íconos

Se importan desde @repo/ui/icons. Hay dos grupos.

Conjunto de íconos genéricos (icons/lucide.tsx)

Una biblioteca de íconos SVG personalizada generada automáticamente que es un reemplazo directo de lucide-react — los datos de trazado se derivan de lucide (licencia ISC) y se renderizan mediante un wrapper propio, de modo que la app ya no depende del paquete lucide-react. Cada ícono acepta IconProps (props nativas de SVG más):

PropTipoPor defecto
sizenumber | string24
strokeWidthnumber | string2
absoluteStrokeWidthboolean
colorstring'currentColor'
import { Bell, Heart, Search } from '@repo/ui/icons';

<Heart size={20} className="text-cg-red" />

El conjunto incluye (entre otros) Activity, Archive, ArrowLeft, Bell, Bookmark, Calendar, Check, CheckCircle2, ChevronDown/Left/Right/Up, Circle, Clapperboard, Clock, Coins, Compass, Copy, CreditCard, Crown, DollarSign, Download, ExternalLink, Eye, Facebook, FileText, Gift, Globe, Grid3x3, Hand, Hash, Heart, Image, Languages, Link, Link2, Loader2, Lock, LogOut, Mail, Maximize2, Megaphone, MapPin, MessageCircle, Mic, MicOff, Minus, Monitor, Moon, MoreHorizontal, Package, Pencil, Phone, Pin, Play, Plus, Radio, Receipt, Repeat2, RotateCcw, Search, Send, Settings, Share2, ShieldAlert/Check/Off, ShoppingBag, Smile, Square, SquarePen, Star, Store, Sun, Ticket, Trash2, TrendingUp, Trophy, Truck, Twitter, Upload, UploadCloud, UserPlus, UserSquare2, Users, Video, VideoOff, X, XCircle, además de un RepostSquare de esquinas cuadradas personalizado.

Íconos de marca

Íconos elaborados a mano que coinciden con el diseño de navegación y acciones de publicación de la app. La mayoría toman { className?: string; filled?: boolean } (una variante rellena para estados activos/seleccionados); unos pocos son solo de contorno (solo className):

ÍconoVariantes
HomeIconcontorno / filled
SearchIconcontorno / filled
ExploreIconcontorno / filled
ClipIconcontorno / filled
MessagesIconcontorno / filled
NotificationIconcontorno / filled
HeartIconcontorno / filled
ProfileIconcontorno / filled
CoinIconcontorno / filled
LanguageIconcontorno / filled
CommentIconsolo contorno
CreateIconsolo contorno
EditIconsolo contorno
RepostIconsolo contorno
ShareIconsolo contorno

VerifiedBadge también se reexporta desde @repo/ui/icons.

import { HomeIcon } from '@repo/ui/icons';

<HomeIcon filled={isActive} className="w-6 h-6" />

Tokens de diseño y Tailwind

La biblioteca incluye su propio tailwind.config.ts para que se renderice correctamente de forma aislada (Storybook) sin depender de ninguna app. El modo oscuro usa la estrategia darkMode: 'class' — cada componente está estilizado tanto para variantes claras como dark:. Consulta Theming para ver cómo se alterna la clase de tema en tiempo de ejecución.

La paleta está definida bajo el espacio de nombres de color cg (Closegram) y refleja los tokens usados por apps/frontend-nextjs. Tokens clave:

TokenValorUso
cg-blue#3797f0Azul de marca primario (+ -dark, -hover, -light, -action #0095f6)
cg-green#58c322Éxito
cg-red#ed4956Peligro
cg-bg#ffffff / dark #0c1014Fondo de página
cg-dark-*primary #121212, secondary, elevated #262626, modal, border, hover, input, …Superficies del modo oscuro
cg-text-*primary, secondary #a8a8a8, tertiary, placeholderColores de texto
cg-separator / cg-muted#262626 / #555555Divisores, elementos atenuados

El archivo de configuración señala una tarea pendiente para llevar esta paleta a un @repo/tailwind-config compartido, de modo que las apps y este paquete compartan una única fuente de verdad en lugar de duplicarla.

Las apps consumidoras que no compilan por sí mismas las clases de Tailwind del paquete pueden importar la hoja de estilos precompilada una sola vez en la raíz:

import '@repo/ui/styles.css';