{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "categorycard", "type": "registry:component", "title": "CategoryCard", "description": "Componente Concorde CategoryCard — un solo archivo, CategoryCard.tsx.", "dependencies": [ "react" ], "registryDependencies": [], "files": [ { "path": "components/CategoryCard.tsx", "type": "registry:file", "target": "components/CategoryCard.tsx", "content": "\"use client\";\r\n\r\n/**\r\n * CategoryCard — Generado por Concorde\r\n * Fuente: https://voyager-ds.vercel.app/preview/components/button-primary\r\n * Generado: 2026-05-28\r\n * EDITAR LIBREMENTE después de generar\r\n */\r\n\r\nimport { useId } from \"react\";\r\nimport type { JSX } from \"react\";\r\n\r\n// ─── Types ────────────────────────────────────────────────────────────────────\r\n\r\nexport type CategoryCardCategory =\r\n | \"vehicular\"\r\n | \"maquinaria\"\r\n | \"equipos-diversos\"\r\n | \"articulos-diversos\";\r\n\r\nexport interface CategoryCardProps {\r\n /** Categoría — determina ícono y label por defecto */\r\n category: CategoryCardCategory;\r\n /** Override del label (default: nombre de categoría) */\r\n label?: string;\r\n /** Handler de click */\r\n onClick?: () => void;\r\n /** Aria label accesible */\r\n \"aria-label\"?: string;\r\n className?: string;\r\n}\r\n\r\n// ─── Label defaults ───────────────────────────────────────────────────────────\r\n\r\nconst LABEL_DEFAULTS: Record = {\r\n \"vehicular\": \"VEHICULAR\",\r\n \"maquinaria\": \"MAQUINARIA\",\r\n \"equipos-diversos\": \"EQUIPOS\\nDIVERSOS\",\r\n \"articulos-diversos\":\"ARTÍCULOS\\nDIVERSOS\",\r\n};\r\n\r\n// ─── Self-contained CSS ───────────────────────────────────────────────────────\r\n\r\nconst STYLE_ID = \"concorde-pcatcard-styles\";\r\n\r\nconst PCATCARD_STYLES = `\r\n/* Card — sync EXACTO SVG (1807:15166 / 15183 / 1230:3327 / 3616):\r\n bg blanco→#F4F5F9 · borde gradiente lila · sombra morada #200068 */\r\n.pcatcard {\r\n width: 92px;\r\n height: 95px;\r\n border-radius: var(--vmc-radius-md, 8px);\r\n border: 1px solid transparent;\r\n background-image:\r\n linear-gradient(160deg, #ffffff 0%, #f4f5f9 100%),\r\n linear-gradient(135deg, #9c96f8 0%, rgba(255,255,255,0.65) 38%, #7364de 70%, #9c96f8 100%);\r\n background-origin: padding-box, border-box;\r\n background-clip: padding-box, border-box;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 0 8px;\r\n gap: 8px;\r\n cursor: pointer;\r\n position: relative;\r\n overflow: hidden;\r\n box-shadow:\r\n rgba(32, 0, 104, 0.08) 0px 2px 10px,\r\n rgba(32, 0, 104, 0.05) 0px 1px 3px;\r\n transition:\r\n transform 0.2s cubic-bezier(0.25, 0.8, 0.25, 1),\r\n box-shadow 0.22s,\r\n background-image 0.22s;\r\n transform: translateZ(0);\r\n outline: none;\r\n background-color: transparent;\r\n border-style: solid;\r\n text-align: center;\r\n}\r\n\r\n/* Glass highlight */\r\n.pcatcard::before {\r\n content: \"\";\r\n position: absolute;\r\n inset: 0;\r\n border-radius: inherit;\r\n background: linear-gradient(160deg, rgba(255,255,255,0.55) 0%, transparent 50%);\r\n pointer-events: none;\r\n z-index: 1;\r\n}\r\n\r\n/* Radial glow below card */\r\n.pcatcard::after {\r\n content: \"\";\r\n position: absolute;\r\n inset: -6px;\r\n border-radius: inherit;\r\n background: radial-gradient(at 50% 60%, oklch(0.4 0.2 285 / 0.22) 0%, transparent 70%);\r\n filter: blur(8px);\r\n opacity: 0;\r\n z-index: -1;\r\n transition: opacity 0.28s;\r\n}\r\n\r\n/* Icon wrapper */\r\n.pcatcard-icon-wrap {\r\n width: 36px;\r\n height: 36px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n color: var(--vmc-color-vault, oklch(0.22 0.18 285));\r\n position: relative;\r\n z-index: 2;\r\n}\r\n\r\n/* Label */\r\n.pcatcard-label {\r\n font-family: var(--vmc-font-display, 'Plus Jakarta Sans', system-ui, sans-serif);\r\n font-size: 9px;\r\n font-weight: 700;\r\n text-transform: uppercase;\r\n letter-spacing: 0.06em;\r\n color: #3b1782;\r\n text-align: center;\r\n line-height: 1.35;\r\n position: relative;\r\n z-index: 2;\r\n white-space: pre-line;\r\n}\r\n\r\n/* Hover */\r\n.pcatcard:hover {\r\n transform: translateY(-4px);\r\n box-shadow:\r\n oklch(0.22 0.18 285 / 0.14) 0px 12px 20px,\r\n oklch(0.22 0.18 285 / 0.1) 0px 4px 8px,\r\n oklch(0.22 0.18 285 / 0.06) 0px 1px 2px;\r\n background-image:\r\n linear-gradient(160deg, oklch(0.97 0.01 285) 0%, oklch(0.99 0.004 285) 100%),\r\n linear-gradient(135deg,\r\n oklch(0.52 0.22 285) 0%,\r\n oklch(1 0 0 / 0.8) 38%,\r\n oklch(0.4 0.24 285) 72%,\r\n oklch(0.52 0.22 285) 100%\r\n );\r\n}\r\n\r\n.pcatcard:hover::after {\r\n opacity: 0.3;\r\n}\r\n\r\n/* Focus / pressed */\r\n.pcatcard:active {\r\n opacity: 0.58;\r\n transform: scale(0.96) !important;\r\n background-image:\r\n linear-gradient(160deg, oklch(0.94 0.012 285) 0%, oklch(0.9 0.018 285) 100%),\r\n linear-gradient(135deg,\r\n oklch(0.72 0.14 285) 0%,\r\n oklch(1 0 0 / 0.65) 38%,\r\n oklch(0.58 0.18 285) 70%,\r\n oklch(0.72 0.14 285) 100%\r\n ) !important;\r\n box-shadow:\r\n oklch(0.22 0.18 285 / 0.12) 0px 1px 5px,\r\n oklch(0.22 0.18 285 / 0.08) 0px 1px 3px inset !important;\r\n}\r\n\r\n/* Keyboard focus ring */\r\n.pcatcard:focus-visible {\r\n outline: 2px solid var(--vmc-color-vault-400, oklch(0.62 0.20 285));\r\n outline-offset: 3px;\r\n}\r\n\r\n/* Reduced motion */\r\n@media (prefers-reduced-motion: reduce) {\r\n .pcatcard,\r\n .pcatcard::after { transition: none; }\r\n}\r\n`;\r\n\r\nlet _stylesInjected = false;\r\n\r\n// ─── Icon components (gradient stroke, unique IDs per instance) ───────────────\r\n\r\ninterface IconProps { gradId: string; }\r\n\r\n// Íconos RELLENOS — paths exactos del SVG export de Figma (gradiente morado #653DE9→#0F003B).\r\n// Mapeo (confirmar con diseño): car→vehicular · grúa→maquinaria · medidor→equipos · monitor→artículos.\r\n\r\nfunction IconVehicular({ gradId }: IconProps): JSX.Element {\r\n // SVG 1230:3327 — carro (cabina + 2 ruedas)\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nfunction IconMaquinaria({ gradId }: IconProps): JSX.Element {\r\n // SVG 1230:3616 — grúa / estructura\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nfunction IconEquiposDiversos({ gradId }: IconProps): JSX.Element {\r\n // SVG 1807:15166 — medidor / herramienta\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nfunction IconArticulosDiversos({ gradId }: IconProps): JSX.Element {\r\n // SVG 1807:15183 — monitor / pantalla\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nconst ICON_MAP: Record JSX.Element> = {\r\n \"vehicular\": IconVehicular,\r\n \"maquinaria\": IconMaquinaria,\r\n \"equipos-diversos\": IconEquiposDiversos,\r\n \"articulos-diversos\": IconArticulosDiversos,\r\n};\r\n\r\n// ─── Component ────────────────────────────────────────────────────────────────\r\n\r\nexport default function CategoryCard({\r\n category,\r\n label,\r\n onClick,\r\n \"aria-label\": ariaLabel,\r\n className = \"\",\r\n}: CategoryCardProps): JSX.Element {\r\n // Unique gradient ID per instance — avoids SVG defs collision\r\n const uid = useId().replace(/:/g, \"-\");\r\n const gradId = `vg-${category}-${uid}`;\r\n\r\n if (typeof document !== \"undefined\" && !_stylesInjected) {\r\n if (!document.getElementById(STYLE_ID)) {\r\n const el = document.createElement(\"style\");\r\n el.id = STYLE_ID;\r\n el.textContent = PCATCARD_STYLES;\r\n document.head.appendChild(el);\r\n }\r\n _stylesInjected = true;\r\n }\r\n\r\n const displayLabel = label ?? LABEL_DEFAULTS[category];\r\n const IconComponent = ICON_MAP[category];\r\n\r\n const classes = [\"pcatcard\", className].filter(Boolean).join(\" \");\r\n\r\n return (\r\n <>\r\n \r\n \r\n
\r\n \r\n
\r\n {displayLabel}\r\n \r\n \r\n );\r\n}\r\n" } ] }