@illuma-ai/codeviz 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,7 @@
15
15
  * Uses the same imports as the template (React hooks, lucide-react icons).
16
16
  * Gets transpiled by the same Sucrase pipeline as user code.
17
17
  */
18
- export declare const BUILTIN_COMPONENTS_TSX = "\n// \u2500\u2500 Built-in UI Components (auto-injected by CodeViz) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Agent code can use these directly: <DataTable />, <MetricCard />, etc.\n// Do NOT remove or modify \u2014 these are managed by the platform.\n\n/**\n * DataTable \u2014 Searchable, sortable, paginated table.\n * Banking standard: right-aligned numerics, formatted values.\n *\n * @param columns - Array of { key, label, align?, render?, sortable? }\n * @param data - Array of row objects\n * @param searchable - Show search bar (default: true)\n * @param pageSize - Rows per page (default: 10)\n * @param onRowClick - Optional row click handler\n */\nfunction DataTable({ columns = [], data = [], searchable = true, pageSize = 10, onRowClick }) {\n const [search, setSearch] = useState('');\n const [sortKey, setSortKey] = useState(null);\n const [sortDir, setSortDir] = useState('asc');\n const [page, setPage] = useState(0);\n\n const filtered = useMemo(() => {\n if (!search.trim()) return data;\n const q = search.toLowerCase();\n return data.filter(row =>\n columns.some(col => String(row[col.key] ?? '').toLowerCase().includes(q))\n );\n }, [data, search, columns]);\n\n const sorted = useMemo(() => {\n if (!sortKey) return filtered;\n return [...filtered].sort((a, b) => {\n const av = a[sortKey] ?? '', bv = b[sortKey] ?? '';\n const cmp = typeof av === 'number' && typeof bv === 'number'\n ? av - bv\n : String(av).localeCompare(String(bv));\n return sortDir === 'asc' ? cmp : -cmp;\n });\n }, [filtered, sortKey, sortDir]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n const pageData = sorted.slice(page * pageSize, (page + 1) * pageSize);\n\n const handleSort = (key) => {\n if (sortKey === key) {\n setSortDir(d => d === 'asc' ? 'desc' : 'asc');\n } else {\n setSortKey(key);\n setSortDir('asc');\n }\n };\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg overflow-hidden\">\n {searchable && (\n <div className=\"px-4 py-3 border-b border-border-light\">\n <input\n type=\"text\"\n placeholder=\"Search...\"\n value={search}\n onChange={e => { setSearch(e.target.value); setPage(0); }}\n className=\"w-full px-3 py-1.5 text-sm rounded-md bg-surface-tertiary text-text-primary border border-border-light focus:outline-none focus:border-[#00C1D5] placeholder:text-text-tertiary\"\n />\n </div>\n )}\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-surface-tertiary\">\n {columns.map(col => (\n <th\n key={col.key}\n onClick={() => col.sortable !== false && handleSort(col.key)}\n className={cn(\n 'px-4 py-2.5 text-xs font-semibold text-text-secondary whitespace-nowrap',\n col.align === 'right' ? 'text-right' : 'text-left',\n col.sortable !== false && 'cursor-pointer hover:text-text-primary select-none'\n )}\n >\n <span className=\"inline-flex items-center gap-1\">\n {col.label}\n {sortKey === col.key && (\n <span className=\"text-[10px]\">{sortDir === 'asc' ? '\u25B2' : '\u25BC'}</span>\n )}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-light\">\n {pageData.map((row, idx) => (\n <tr\n key={idx}\n onClick={() => onRowClick && onRowClick(row, idx)}\n className={cn('transition-colors', onRowClick && 'cursor-pointer', 'hover:bg-surface-tertiary')}\n >\n {columns.map(col => (\n <td\n key={col.key}\n className={cn(\n 'px-4 py-2.5 text-sm',\n col.align === 'right' ? 'text-right tabular-nums' : 'text-left',\n 'text-text-primary'\n )}\n >\n {col.render ? col.render(row[col.key], row) : row[col.key]}\n </td>\n ))}\n </tr>\n ))}\n {pageData.length === 0 && (\n <tr>\n <td colSpan={columns.length} className=\"px-4 py-8 text-center text-text-tertiary text-sm\">\n {search ? 'No results found' : 'No data'}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n {totalPages > 1 && (\n <div className=\"flex items-center justify-between px-4 py-2.5 border-t border-border-light text-xs text-text-secondary\">\n <span>{sorted.length} row{sorted.length !== 1 ? 's' : ''}</span>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => setPage(p => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"px-2 py-1 rounded bg-surface-tertiary disabled:opacity-40 hover:bg-surface-primary\"\n >\n Prev\n </button>\n <span>{page + 1} / {totalPages}</span>\n <button\n onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))}\n disabled={page >= totalPages - 1}\n className=\"px-2 py-1 rounded bg-surface-tertiary disabled:opacity-40 hover:bg-surface-primary\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * MetricCard \u2014 Single KPI display with trend indicator.\n * Banking standard: formatted value, comparison period, trend arrow.\n *\n * @param label - Metric name (e.g., \"Total AUM\")\n * @param value - Formatted value (e.g., \"$1.2B\")\n * @param trend - Trend percentage (e.g., \"+3.2%\") \u2014 positive = green, negative = red\n * @param trendLabel - Comparison period (e.g., \"vs last quarter\")\n * @param icon - Optional lucide-react icon component\n * @param iconColor - Optional icon color (default: #00C1D5)\n */\nfunction MetricCard({ label, value, trend, trendLabel, icon, iconColor = '#00C1D5' }) {\n const isPositive = trend && !trend.startsWith('-');\n const isNegative = trend && trend.startsWith('-');\n const IconComp = icon;\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg p-3\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <p className=\"text-text-secondary text-xs font-medium uppercase tracking-wide\">{label}</p>\n {IconComp && <IconComp className=\"w-4 h-4\" style={{ color: iconColor }} />}\n </div>\n <p className=\"text-lg font-bold text-text-primary tabular-nums\">{value}</p>\n {trend && (\n <div className=\"flex items-center gap-1.5 mt-2\">\n <span className={cn('text-xs font-semibold', isPositive && 'text-status-positive', isNegative && 'text-status-negative')}>\n {isPositive && '\u25B2'}{isNegative && '\u25BC'} {trend}\n </span>\n {trendLabel && <span className=\"text-text-tertiary text-xs\">{trendLabel}</span>}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * StatusBadge \u2014 Colored pill for status display.\n * Banking standard: green (active/on-track), amber (watch), red (action needed).\n *\n * @param status - \"positive\" | \"negative\" | \"warning\" | \"neutral\"\n * @param label - Display text\n */\nfunction StatusBadge({ status = 'neutral', label }) {\n const styles = {\n positive: 'bg-[rgba(22,163,74,0.12)] text-status-positive',\n negative: 'bg-[rgba(220,38,38,0.12)] text-status-negative',\n warning: 'bg-[rgba(217,119,6,0.12)] text-status-warning',\n neutral: 'bg-surface-tertiary text-text-secondary',\n };\n\n return (\n <span className={cn('inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium', styles[status] || styles.neutral)}>\n {label}\n </span>\n );\n}\n\n/**\n * DetailCard \u2014 Entity header with key fields + optional tabbed content.\n * For: client profiles, account detail, loan summary.\n *\n * @param title - Entity name/title\n * @param subtitle - Secondary info (ID, type)\n * @param status - Optional StatusBadge props { status, label }\n * @param fields - Array of { label, value } for the key metrics row\n * @param tabs - Optional array of { id, label, content: ReactNode }\n * @param children - Content below the header (if no tabs)\n */\nfunction DetailCard({ title, subtitle, status, fields = [], tabs, children }) {\n const [activeTab, setActiveTab] = useState(tabs?.[0]?.id || '');\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg overflow-hidden\">\n <div className=\"p-3 border-b border-border-light\">\n <div className=\"flex items-start justify-between mb-2\">\n <div>\n <h2 className=\"text-base font-bold text-text-primary\">{title}</h2>\n {subtitle && <p className=\"text-text-secondary text-sm mt-0.5\">{subtitle}</p>}\n </div>\n {status && <StatusBadge status={status.status} label={status.label} />}\n </div>\n {fields.length > 0 && (\n <div className=\"flex flex-wrap gap-3 mt-2\">\n {fields.map((f, i) => (\n <div key={i}>\n <p className=\"text-text-tertiary text-xs uppercase tracking-wide\">{f.label}</p>\n <p className=\"text-text-primary font-semibold text-sm mt-0.5 tabular-nums\">{f.value}</p>\n </div>\n ))}\n </div>\n )}\n </div>\n {tabs && tabs.length > 0 && (\n <>\n <div className=\"flex border-b border-border-light\">\n {tabs.map(tab => (\n <button\n key={tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={cn(\n 'px-4 py-2.5 text-sm font-medium transition-colors',\n activeTab === tab.id\n ? 'text-[#00C1D5] border-b-2 border-[#00C1D5]'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n <div className=\"p-3\">\n {tabs.find(t => t.id === activeTab)?.content}\n </div>\n </>\n )}\n {!tabs && children && <div className=\"p-3\">{children}</div>}\n </div>\n );\n}\n\n/**\n * StepWizard \u2014 Multi-step form with progress indicator.\n * For: loan origination, account opening, KYC onboarding.\n *\n * @param steps - Array of { id, label, content: ReactNode, validate?: () => boolean }\n * @param onComplete - Called when final step is submitted\n */\nfunction StepWizard({ steps = [], onComplete }) {\n const [current, setCurrent] = useState(0);\n\n const next = () => {\n const step = steps[current];\n if (step.validate && !step.validate()) return;\n if (current < steps.length - 1) setCurrent(c => c + 1);\n else if (onComplete) onComplete();\n };\n\n const prev = () => setCurrent(c => Math.max(0, c - 1));\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg overflow-hidden\">\n <div className=\"flex items-center px-3 py-2.5 border-b border-border-light gap-2\">\n {steps.map((step, i) => (\n <div key={step.id} className=\"flex items-center gap-2\">\n <div className={cn(\n 'flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold',\n i < current && 'bg-[#00C1D5] text-white',\n i === current && 'bg-[#00C1D5] text-white ring-2 ring-[#00C1D5]/30',\n i > current && 'bg-surface-tertiary text-text-tertiary'\n )}>\n {i < current ? '\u2713' : i + 1}\n </div>\n <span className={cn('text-sm hidden sm:inline', i === current ? 'text-text-primary font-medium' : 'text-text-tertiary')}>\n {step.label}\n </span>\n {i < steps.length - 1 && <div className=\"w-8 h-px bg-border-light\" />}\n </div>\n ))}\n </div>\n <div className=\"p-3 min-h-[200px]\">\n {steps[current]?.content}\n </div>\n <div className=\"flex items-center justify-between px-3 py-2.5 border-t border-border-light\">\n <button\n onClick={prev}\n disabled={current === 0}\n className=\"px-3 py-1.5 text-xs rounded-lg bg-surface-tertiary text-text-secondary disabled:opacity-40 hover:bg-surface-primary\"\n >\n Back\n </button>\n <button\n onClick={next}\n className=\"px-3 py-1.5 text-xs rounded-lg bg-[#00C1D5] text-white hover:bg-[#00a8b9]\"\n >\n {current === steps.length - 1 ? 'Submit' : 'Next'}\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * Logo \u2014 Ranger/Illuma brand logo SVG.\n * Adapts to dark/light theme via the dark prop.\n * Templates use this as a one-liner: <Logo dark={dark} />\n *\n * @param dark - Whether to render in dark mode (white text) or light mode (navy text)\n */\nfunction Logo({ dark }) {\n const f = dark ? '#fff' : '#003';\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={140} height={43} viewBox=\"0 0 643.9 200\" className=\"flex-shrink-0\">\n <polygon fill={f} points=\"101.7,92.4 75.8,92.4 67.8,67.8 59.8,92.4 33.9,92.4 54.8,107.7 46.8,132.3 67.8,117.1 88.8,132.3 80.8,107.7\"/>\n <circle cx=\"67.5\" cy=\"100.3\" r=\"51.5\" fill=\"none\" stroke=\"#C00\" strokeWidth=\"4.4\"/>\n <path fill={f} d=\"M190.4,70.2h-53.1l-.5,19l1.5,.1c5.6-11.6,11-17,17.1-17h3.2v51.3c0,3.6-2.9,5.2-10,5.5v1.5h30.8V129c-7.1-.2-10-1.8-10-5.5V72.3h3.2c6,0,11.5,5.4,17.2,17l1.5-.1L190.4,70.2z\"/>\n <path fill={f} d=\"M222.8,120c-3.5,4.6-6.6,7.2-12,7.2c-8.3,0-13.3-6.3-13.3-16.9v-1.9h26.4c.4-5.4-1.1-10.3-4.2-13.6c-3-3.2-7.4-4.9-12.7-4.9c-11,0-19.6,9.3-19.6,21.2c0,11.9,8.1,20.2,19.6,20.2c7.4,0,12.8-3.2,17.1-10.1L222.8,120z M197.5,106.6c.6-9.5,3.7-14.7,8.7-14.7c2.2,0,4,.7,5.2,2.1c2.1,2.3,2.9,6.5,2.3,12.7H197.5z\"/>\n <path fill={f} d=\"M268.5,129.1c-3.6-.7-4.9-1.7-8.7-7l-10.1-14l6.6-8.2c4.5-5.5,6.9-7.2,11.7-8v-1.4h-17v1.4c2.3,.4,3.6,1.2,4.1,2.3c.5,1.4-.2,3.3-2.3,6l-4.7,6.1l-6.2-9.1c-1.2-1.8-1.8-3-1.5-3.8c.3-.7,1.5-1.2,3.8-1.5v-1.4h-20.9v1.4c4,.6,5.3,1.6,9.1,6.9l9.5,13.1l-7.7,9.4c-4.3,5.2-6.8,7-11,7.8v1.4h17.2v-1.4c-2.8-.4-4.4-1.2-4.9-2.4c-.5-1.3,.2-3.1,2.2-5.7l5.8-7.3l6.8,10c1.2,1.8,1.9,3,1.5,3.9c-.3,.8-1.7,1.3-4.3,1.6v1.4h20.9v-1.4z\"/>\n <path fill={f} d=\"M309.5,126.7c-2.3,1-3.9,1.1-4.9,.4c-.9-.6-1.4-2-1.4-4.2v-20c0-8.6-4.9-12.8-15.1-12.8c-9.7,0-16.2,3.6-16.2,9c0,3.1,2.2,5.3,5.2,5.3c3.3,0,5.4-2,5.4-5.3c0-1.6-.8-3.2-2.2-4.6c1.5-1.4,4.1-2.3,6.8-2.3c4.7,0,6.8,2.8,6.8,9.3v6.1c-.7,.3-2.2,.5-4.1,.9c-4,.7-10,1.7-14.2,4.1c-3.3,1.8-5.3,5.1-5.3,8.9c0,2.6,1,5,2.7,6.7c2,2,4.9,3,8.5,3c4.6,0,8.8-2.2,12.7-6.8h.1c.6,4.8,2.6,6.6,7.3,6.6c3.1,0,5.3-1,8.4-3l.1-.1l-.5-1.2z M293.9,109.5V122c-2.1,2.9-4.9,4.7-7.4,4.7c-4,0-6.2-2.4-6.2-6.8C280.4,114.5,282.3,111.8,293.9,109.5z\"/>\n <path fill={f} d=\"M329.6,105.4c-7.2-1.9-9.9-3.8-9.9-7.1c0-3.7,2.8-6.4,6.8-6.4c5.2,0,7.7,3.2,12.5,11.1h1.5V89.8h-1.2l-2.1,2.6c-2.8-1.6-6.4-2.6-9.7-2.6c-8.8,0-14.7,5.2-14.7,12.8c0,6.9,4.4,9.5,12.6,11.8c8.2,2.3,10.8,4.3,10.8,8.3c0,3.2-2.5,6.4-7.2,6.4c-5.9,0-9.1-2.7-14.8-12.5h-1.5v14.7h1.2l2.8-3c2.7,1.7,8.2,3,11.3,3c10.2,0,14.7-6.7,14.7-13.4C342.7,110.5,337.7,107.6,329.6,105.4z\"/>\n <path fill={f} d=\"M418.6,114c-4.2,9.2-11.5,14.3-20.4,14.3c-15.5,0-20.9-15.1-20.9-28c0-14.3,4.9-29.5,18.7-29.5c8.6,0,15,5.9,19.9,18.6h1.5V68.3h-1.1l-3.7,4c-4.6-2.3-9.8-3.5-15.4-3.5c-18,0-31.6,14-31.6,32.5c0,9.1,2.8,16.9,8.1,22.3c5.3,5.5,12.7,8.4,21.4,8.4c15.5,0,22.5-10.7,25.3-17l-1.8-1z\"/>\n <path fill={f} d=\"M464.7,127.8l-.5-1.2c-2.3,1-3.9,1.1-4.9,.4c-.9-.6-1.4-2-1.4-4.2v-20c0-8.6-4.9-12.8-15.1-12.8c-9.7,0-16.2,3.6-16.2,9c0,3.1,2.2,5.3,5.2,5.3c3.3,0,5.4-2,5.4-5.3c0-1.6-.8-3.2-2.2-4.6c1.5-1.4,4.1-2.3,6.8-2.3c4.7,0,6.8,2.8,6.8,9.3v6.1c-.7,.3-2.2,.5-4.1,.9c-4,.7-10,1.7-14.2,4.1c-3.3,1.8-5.3,5.1-5.3,8.9c0,2.6,1,5,2.7,6.7c2,2,4.9,3,8.5,3c4.6,0,8.8-2.2,12.7-6.8h.1c.6,4.8,2.6,6.6,7.3,6.6c3.1,0,5.3-1,8.4-3z M448.4,109.5V122c-2.1,2.9-4.9,4.7-7.4,4.7c-4,0-6.2-2.4-6.2-6.8C434.9,114.5,436.8,111.8,448.4,109.5z\"/>\n <path fill={f} d=\"M490.7,89.8c-4.6,0-8.8,1.9-12.9,5.9v-5.4L462.5,91v1.4c4.9,.1,5.9,1.7,5.9,4.1v46c0,2.5-.9,3-5.9,3.1v1.4h23.7v-1.4c-6.6-.1-8-.6-8-3.1v-13.6c3.3,1.7,6.2,2.4,10.1,2.4c10.8,0,18.4-8.7,18.4-21.3C506.6,97.7,500.4,89.8,490.7,89.8z M478,97.9c2.8-2.9,5.4-4.2,8.4-4.2c6.1,0,10,6.6,10,16.7c0,12.4-3.3,18.7-9.8,18.7c-4.2,0-7-2.1-8.7-6.4V97.9z\"/>\n <path fill={f} d=\"M520.2,83.4c3.5,0,5.9-2.4,5.9-6c0-3.6-2.3-5.9-5.9-5.9c-3.5,0-5.8,2.5-5.8,6.1C514.4,81.1,516.7,83.4,520.2,83.4z\"/>\n <path fill={f} d=\"M525.4,126V90.3L509.7,91v1.4c5.3,.1,6.1,2,6.1,4.1V126c0,2.4-1.1,3-6.1,3.1v1.4h22v-1.4C526.1,129,525.4,128.4,525.4,126z\"/>\n <path fill={f} d=\"M560.5,121.8c-2.1,3.8-4.4,5.7-7,5.7c-3.4,0-4.8-1.9-4.8-6.9V93.9h12.2v-3.1h-12.2V76.4l-9.4,3.4v10.9l-8,1.9V94h8v25.7c0,7.8,3.4,11.6,10.5,11.6c5.3,0,9.2-2.8,12.1-8.7l-1.4-.9z\"/>\n <path fill={f} d=\"M603.2,126.7c-2.3,1-3.9,1.1-4.9,.4c-.9-.6-1.4-2-1.4-4.2v-20c0-8.6-4.9-12.8-15.1-12.8c-9.7,0-16.2,3.6-16.2,9c0,3.1,2.2,5.3,5.2,5.3c3.3,0,5.4-2,5.4-5.3c0-1.6-.8-3.2-2.2-4.6c1.5-1.4,4.1-2.3,6.8-2.3c4.7,0,6.8,2.8,6.8,9.3v6.1c-.7,.3-2.2,.5-4.1,.9c-4,.7-10,1.7-14.2,4.1c-3.3,1.8-5.3,5.1-5.3,8.9c0,2.6,1,5,2.7,6.7c2,2,4.9,3,8.5,3c4.6,0,8.8-2.2,12.7-6.8h.1c.6,4.8,2.6,6.6,7.3,6.6c3.1,0,5.3-1,8.4-3l-.5-1.2z M587.6,109.5V122c-2.1,2.9-4.9,4.7-7.4,4.7c-4,0-6.2-2.4-6.2-6.8C574.1,114.5,576,111.8,587.6,109.5z\"/>\n <path fill={f} d=\"M620.9,126V68.5L604.8,69v1.4c6,.2,6.7,1.5,6.7,4.2V126c0,2.3-.8,3-6,3.1v1.4H627v-1.4C621.7,129,620.9,128.3,620.9,126z\"/>\n </svg>\n );\n}\n\n/**\n * Avatar \u2014 User avatar circle with initials.\n * Default initials \"RA\" for Ranger. Adapts to dark/light theme.\n *\n * @param initials - Two-letter initials to display (default: \"RA\")\n * @param dark - Whether to render in dark mode\n */\nfunction Avatar({ initials = 'RA', dark }) {\n return (\n <div className={cn(\n 'flex items-center justify-center w-7 h-7 rounded-full text-xs font-semibold flex-shrink-0',\n dark ? 'bg-[#00C1D5]/20 text-[#00C1D5]' : 'bg-[#000033]/10 text-[#000033]'\n )}>\n {initials}\n </div>\n );\n}\n\n// \u2500\u2500 End built-in components \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n";
18
+ export declare const BUILTIN_COMPONENTS_TSX = "\n// \u2500\u2500 Built-in UI Components (auto-injected by CodeViz) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Agent code can use these directly: <DataTable />, <MetricCard />, etc.\n// Do NOT remove or modify \u2014 these are managed by the platform.\n\n/**\n * DataTable \u2014 Searchable, sortable, paginated table.\n * Banking standard: right-aligned numerics, formatted values.\n *\n * @param columns - Array of { key, label, align?, render?, sortable? }\n * @param data - Array of row objects\n * @param searchable - Show search bar (default: true)\n * @param pageSize - Rows per page (default: 10)\n * @param onRowClick - Optional row click handler\n */\nfunction DataTable({ columns = [], data = [], searchable = true, pageSize = 10, onRowClick }) {\n const [search, setSearch] = useState('');\n const [sortKey, setSortKey] = useState(null);\n const [sortDir, setSortDir] = useState('asc');\n const [page, setPage] = useState(0);\n\n const filtered = useMemo(() => {\n if (!search.trim()) return data;\n const q = search.toLowerCase();\n return data.filter(row =>\n columns.some(col => String(row[col.key] ?? '').toLowerCase().includes(q))\n );\n }, [data, search, columns]);\n\n const sorted = useMemo(() => {\n if (!sortKey) return filtered;\n return [...filtered].sort((a, b) => {\n const av = a[sortKey] ?? '', bv = b[sortKey] ?? '';\n const cmp = typeof av === 'number' && typeof bv === 'number'\n ? av - bv\n : String(av).localeCompare(String(bv));\n return sortDir === 'asc' ? cmp : -cmp;\n });\n }, [filtered, sortKey, sortDir]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n const pageData = sorted.slice(page * pageSize, (page + 1) * pageSize);\n\n const handleSort = (key) => {\n if (sortKey === key) {\n setSortDir(d => d === 'asc' ? 'desc' : 'asc');\n } else {\n setSortKey(key);\n setSortDir('asc');\n }\n };\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg overflow-hidden\">\n {searchable && (\n <div className=\"px-4 py-3 border-b border-border-light\">\n <input\n type=\"text\"\n placeholder=\"Search...\"\n value={search}\n onChange={e => { setSearch(e.target.value); setPage(0); }}\n className=\"w-full px-3 py-1.5 text-sm rounded-md bg-surface-tertiary text-text-primary border border-border-light focus:outline-none focus:border-[#00C1D5] placeholder:text-text-tertiary\"\n />\n </div>\n )}\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-surface-tertiary\">\n {columns.map(col => (\n <th\n key={col.key}\n onClick={() => col.sortable !== false && handleSort(col.key)}\n className={cn(\n 'px-4 py-2.5 text-xs font-semibold text-text-secondary whitespace-nowrap',\n col.align === 'right' ? 'text-right' : 'text-left',\n col.sortable !== false && 'cursor-pointer hover:text-text-primary select-none'\n )}\n >\n <span className=\"inline-flex items-center gap-1\">\n {col.label}\n {sortKey === col.key && (\n <span className=\"text-[10px]\">{sortDir === 'asc' ? '\u25B2' : '\u25BC'}</span>\n )}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-light\">\n {pageData.map((row, idx) => (\n <tr\n key={idx}\n onClick={() => onRowClick && onRowClick(row, idx)}\n className={cn('transition-colors', onRowClick && 'cursor-pointer', 'hover:bg-surface-tertiary')}\n >\n {columns.map(col => (\n <td\n key={col.key}\n className={cn(\n 'px-4 py-2.5 text-sm',\n col.align === 'right' ? 'text-right tabular-nums' : 'text-left',\n 'text-text-primary'\n )}\n >\n {col.render ? col.render(row[col.key], row) : row[col.key]}\n </td>\n ))}\n </tr>\n ))}\n {pageData.length === 0 && (\n <tr>\n <td colSpan={columns.length} className=\"px-4 py-8 text-center text-text-tertiary text-sm\">\n {search ? 'No results found' : 'No data'}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n {totalPages > 1 && (\n <div className=\"flex items-center justify-between px-4 py-2.5 border-t border-border-light text-xs text-text-secondary\">\n <span>{sorted.length} row{sorted.length !== 1 ? 's' : ''}</span>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => setPage(p => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"px-2 py-1 rounded bg-surface-tertiary disabled:opacity-40 hover:bg-surface-primary\"\n >\n Prev\n </button>\n <span>{page + 1} / {totalPages}</span>\n <button\n onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))}\n disabled={page >= totalPages - 1}\n className=\"px-2 py-1 rounded bg-surface-tertiary disabled:opacity-40 hover:bg-surface-primary\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * MetricCard \u2014 Single KPI display with trend indicator.\n * Banking standard: formatted value, comparison period, trend arrow.\n *\n * @param label - Metric name (e.g., \"Total AUM\")\n * @param value - Formatted value (e.g., \"$1.2B\")\n * @param trend - Trend percentage (e.g., \"+3.2%\") \u2014 positive = green, negative = red\n * @param trendLabel - Comparison period (e.g., \"vs last quarter\")\n * @param icon - Optional lucide-react icon component\n * @param iconColor - Optional icon color (default: #00C1D5)\n */\nfunction MetricCard({ label, value, trend, trendLabel, icon, iconColor = '#00C1D5' }) {\n const isPositive = trend && !trend.startsWith('-');\n const isNegative = trend && trend.startsWith('-');\n const IconComp = icon;\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg p-3\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <p className=\"text-text-secondary text-xs font-medium uppercase tracking-wide\">{label}</p>\n {IconComp && <IconComp className=\"w-4 h-4\" style={{ color: iconColor }} />}\n </div>\n <p className=\"text-xl font-bold text-text-primary tabular-nums\">{value}</p>\n {trend && (\n <div className=\"flex items-center gap-1.5 mt-2\">\n <span className={cn('text-xs font-semibold', isPositive && 'text-status-positive', isNegative && 'text-status-negative')}>\n {isPositive && '\u25B2'}{isNegative && '\u25BC'} {trend}\n </span>\n {trendLabel && <span className=\"text-text-tertiary text-xs\">{trendLabel}</span>}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * StatusBadge \u2014 Colored pill for status display.\n * Banking standard: green (active/on-track), amber (watch), red (action needed).\n *\n * @param status - \"positive\" | \"negative\" | \"warning\" | \"neutral\"\n * @param label - Display text\n */\nfunction StatusBadge({ status = 'neutral', label }) {\n const styles = {\n positive: 'bg-[rgba(22,163,74,0.12)] text-status-positive',\n negative: 'bg-[rgba(220,38,38,0.12)] text-status-negative',\n warning: 'bg-[rgba(217,119,6,0.12)] text-status-warning',\n neutral: 'bg-surface-tertiary text-text-secondary',\n };\n\n return (\n <span className={cn('inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium', styles[status] || styles.neutral)}>\n {label}\n </span>\n );\n}\n\n/**\n * DetailCard \u2014 Entity header with key fields + optional tabbed content.\n * For: client profiles, account detail, loan summary.\n *\n * @param title - Entity name/title\n * @param subtitle - Secondary info (ID, type)\n * @param status - Optional StatusBadge props { status, label }\n * @param fields - Array of { label, value } for the key metrics row\n * @param tabs - Optional array of { id, label, content: ReactNode }\n * @param children - Content below the header (if no tabs)\n */\nfunction DetailCard({ title, subtitle, status, fields = [], tabs, children }) {\n const [activeTab, setActiveTab] = useState(tabs?.[0]?.id || '');\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg overflow-hidden\">\n <div className=\"p-3 border-b border-border-light\">\n <div className=\"flex items-start justify-between mb-2\">\n <div>\n <h2 className=\"text-base font-bold text-text-primary\">{title}</h2>\n {subtitle && <p className=\"text-text-secondary text-sm mt-0.5\">{subtitle}</p>}\n </div>\n {status && <StatusBadge status={status.status} label={status.label} />}\n </div>\n {fields.length > 0 && (\n <div className=\"flex flex-wrap gap-3 mt-2\">\n {fields.map((f, i) => (\n <div key={i}>\n <p className=\"text-text-tertiary text-xs uppercase tracking-wide\">{f.label}</p>\n <p className=\"text-text-primary font-semibold text-sm mt-0.5 tabular-nums\">{f.value}</p>\n </div>\n ))}\n </div>\n )}\n </div>\n {tabs && tabs.length > 0 && (\n <>\n <div className=\"flex border-b border-border-light\">\n {tabs.map(tab => (\n <button\n key={tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={cn(\n 'px-4 py-2.5 text-sm font-medium transition-colors',\n activeTab === tab.id\n ? 'text-[#00C1D5] border-b-2 border-[#00C1D5]'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n <div className=\"p-3\">\n {tabs.find(t => t.id === activeTab)?.content}\n </div>\n </>\n )}\n {!tabs && children && <div className=\"p-3\">{children}</div>}\n </div>\n );\n}\n\n/**\n * StepWizard \u2014 Multi-step form with progress indicator.\n * For: loan origination, account opening, KYC onboarding.\n *\n * @param steps - Array of { id, label, content: ReactNode, validate?: () => boolean }\n * @param onComplete - Called when final step is submitted\n */\nfunction StepWizard({ steps = [], onComplete }) {\n const [current, setCurrent] = useState(0);\n\n const next = () => {\n const step = steps[current];\n if (step.validate && !step.validate()) return;\n if (current < steps.length - 1) setCurrent(c => c + 1);\n else if (onComplete) onComplete();\n };\n\n const prev = () => setCurrent(c => Math.max(0, c - 1));\n\n return (\n <div className=\"bg-surface-secondary border border-border-light rounded-lg overflow-hidden\">\n <div className=\"flex items-center px-3 py-2.5 border-b border-border-light gap-2\">\n {steps.map((step, i) => (\n <div key={step.id} className=\"flex items-center gap-2\">\n <div className={cn(\n 'flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold',\n i < current && 'bg-[#00C1D5] text-white',\n i === current && 'bg-[#00C1D5] text-white ring-2 ring-[#00C1D5]/30',\n i > current && 'bg-surface-tertiary text-text-tertiary'\n )}>\n {i < current ? '\u2713' : i + 1}\n </div>\n <span className={cn('text-sm hidden sm:inline', i === current ? 'text-text-primary font-medium' : 'text-text-tertiary')}>\n {step.label}\n </span>\n {i < steps.length - 1 && <div className=\"w-8 h-px bg-border-light\" />}\n </div>\n ))}\n </div>\n <div className=\"p-3 min-h-[200px]\">\n {steps[current]?.content}\n </div>\n <div className=\"flex items-center justify-between px-3 py-2.5 border-t border-border-light\">\n <button\n onClick={prev}\n disabled={current === 0}\n className=\"px-3 py-1.5 text-xs rounded-lg bg-surface-tertiary text-text-secondary disabled:opacity-40 hover:bg-surface-primary\"\n >\n Back\n </button>\n <button\n onClick={next}\n className=\"px-3 py-1.5 text-xs rounded-lg bg-[#00C1D5] text-white hover:bg-[#00a8b9]\"\n >\n {current === steps.length - 1 ? 'Submit' : 'Next'}\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * Logo \u2014 Ranger/Illuma brand logo SVG.\n * Adapts to dark/light theme via the dark prop.\n * Templates use this as a one-liner: <Logo dark={dark} />\n *\n * @param dark - Whether to render in dark mode (white text) or light mode (navy text)\n */\nfunction Logo({ dark }) {\n const f = dark ? '#fff' : '#003';\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={140} height={43} viewBox=\"0 0 643.9 200\" className=\"flex-shrink-0\">\n <polygon fill={f} points=\"101.7,92.4 75.8,92.4 67.8,67.8 59.8,92.4 33.9,92.4 54.8,107.7 46.8,132.3 67.8,117.1 88.8,132.3 80.8,107.7\"/>\n <circle cx=\"67.5\" cy=\"100.3\" r=\"51.5\" fill=\"none\" stroke=\"#C00\" strokeWidth=\"4.4\"/>\n <path fill={f} d=\"M190.4,70.2h-53.1l-.5,19l1.5,.1c5.6-11.6,11-17,17.1-17h3.2v51.3c0,3.6-2.9,5.2-10,5.5v1.5h30.8V129c-7.1-.2-10-1.8-10-5.5V72.3h3.2c6,0,11.5,5.4,17.2,17l1.5-.1L190.4,70.2z\"/>\n <path fill={f} d=\"M222.8,120c-3.5,4.6-6.6,7.2-12,7.2c-8.3,0-13.3-6.3-13.3-16.9v-1.9h26.4c.4-5.4-1.1-10.3-4.2-13.6c-3-3.2-7.4-4.9-12.7-4.9c-11,0-19.6,9.3-19.6,21.2c0,11.9,8.1,20.2,19.6,20.2c7.4,0,12.8-3.2,17.1-10.1L222.8,120z M197.5,106.6c.6-9.5,3.7-14.7,8.7-14.7c2.2,0,4,.7,5.2,2.1c2.1,2.3,2.9,6.5,2.3,12.7H197.5z\"/>\n <path fill={f} d=\"M268.5,129.1c-3.6-.7-4.9-1.7-8.7-7l-10.1-14l6.6-8.2c4.5-5.5,6.9-7.2,11.7-8v-1.4h-17v1.4c2.3,.4,3.6,1.2,4.1,2.3c.5,1.4-.2,3.3-2.3,6l-4.7,6.1l-6.2-9.1c-1.2-1.8-1.8-3-1.5-3.8c.3-.7,1.5-1.2,3.8-1.5v-1.4h-20.9v1.4c4,.6,5.3,1.6,9.1,6.9l9.5,13.1l-7.7,9.4c-4.3,5.2-6.8,7-11,7.8v1.4h17.2v-1.4c-2.8-.4-4.4-1.2-4.9-2.4c-.5-1.3,.2-3.1,2.2-5.7l5.8-7.3l6.8,10c1.2,1.8,1.9,3,1.5,3.9c-.3,.8-1.7,1.3-4.3,1.6v1.4h20.9v-1.4z\"/>\n <path fill={f} d=\"M309.5,126.7c-2.3,1-3.9,1.1-4.9,.4c-.9-.6-1.4-2-1.4-4.2v-20c0-8.6-4.9-12.8-15.1-12.8c-9.7,0-16.2,3.6-16.2,9c0,3.1,2.2,5.3,5.2,5.3c3.3,0,5.4-2,5.4-5.3c0-1.6-.8-3.2-2.2-4.6c1.5-1.4,4.1-2.3,6.8-2.3c4.7,0,6.8,2.8,6.8,9.3v6.1c-.7,.3-2.2,.5-4.1,.9c-4,.7-10,1.7-14.2,4.1c-3.3,1.8-5.3,5.1-5.3,8.9c0,2.6,1,5,2.7,6.7c2,2,4.9,3,8.5,3c4.6,0,8.8-2.2,12.7-6.8h.1c.6,4.8,2.6,6.6,7.3,6.6c3.1,0,5.3-1,8.4-3l.1-.1l-.5-1.2z M293.9,109.5V122c-2.1,2.9-4.9,4.7-7.4,4.7c-4,0-6.2-2.4-6.2-6.8C280.4,114.5,282.3,111.8,293.9,109.5z\"/>\n <path fill={f} d=\"M329.6,105.4c-7.2-1.9-9.9-3.8-9.9-7.1c0-3.7,2.8-6.4,6.8-6.4c5.2,0,7.7,3.2,12.5,11.1h1.5V89.8h-1.2l-2.1,2.6c-2.8-1.6-6.4-2.6-9.7-2.6c-8.8,0-14.7,5.2-14.7,12.8c0,6.9,4.4,9.5,12.6,11.8c8.2,2.3,10.8,4.3,10.8,8.3c0,3.2-2.5,6.4-7.2,6.4c-5.9,0-9.1-2.7-14.8-12.5h-1.5v14.7h1.2l2.8-3c2.7,1.7,8.2,3,11.3,3c10.2,0,14.7-6.7,14.7-13.4C342.7,110.5,337.7,107.6,329.6,105.4z\"/>\n <path fill={f} d=\"M418.6,114c-4.2,9.2-11.5,14.3-20.4,14.3c-15.5,0-20.9-15.1-20.9-28c0-14.3,4.9-29.5,18.7-29.5c8.6,0,15,5.9,19.9,18.6h1.5V68.3h-1.1l-3.7,4c-4.6-2.3-9.8-3.5-15.4-3.5c-18,0-31.6,14-31.6,32.5c0,9.1,2.8,16.9,8.1,22.3c5.3,5.5,12.7,8.4,21.4,8.4c15.5,0,22.5-10.7,25.3-17l-1.8-1z\"/>\n <path fill={f} d=\"M464.7,127.8l-.5-1.2c-2.3,1-3.9,1.1-4.9,.4c-.9-.6-1.4-2-1.4-4.2v-20c0-8.6-4.9-12.8-15.1-12.8c-9.7,0-16.2,3.6-16.2,9c0,3.1,2.2,5.3,5.2,5.3c3.3,0,5.4-2,5.4-5.3c0-1.6-.8-3.2-2.2-4.6c1.5-1.4,4.1-2.3,6.8-2.3c4.7,0,6.8,2.8,6.8,9.3v6.1c-.7,.3-2.2,.5-4.1,.9c-4,.7-10,1.7-14.2,4.1c-3.3,1.8-5.3,5.1-5.3,8.9c0,2.6,1,5,2.7,6.7c2,2,4.9,3,8.5,3c4.6,0,8.8-2.2,12.7-6.8h.1c.6,4.8,2.6,6.6,7.3,6.6c3.1,0,5.3-1,8.4-3z M448.4,109.5V122c-2.1,2.9-4.9,4.7-7.4,4.7c-4,0-6.2-2.4-6.2-6.8C434.9,114.5,436.8,111.8,448.4,109.5z\"/>\n <path fill={f} d=\"M490.7,89.8c-4.6,0-8.8,1.9-12.9,5.9v-5.4L462.5,91v1.4c4.9,.1,5.9,1.7,5.9,4.1v46c0,2.5-.9,3-5.9,3.1v1.4h23.7v-1.4c-6.6-.1-8-.6-8-3.1v-13.6c3.3,1.7,6.2,2.4,10.1,2.4c10.8,0,18.4-8.7,18.4-21.3C506.6,97.7,500.4,89.8,490.7,89.8z M478,97.9c2.8-2.9,5.4-4.2,8.4-4.2c6.1,0,10,6.6,10,16.7c0,12.4-3.3,18.7-9.8,18.7c-4.2,0-7-2.1-8.7-6.4V97.9z\"/>\n <path fill={f} d=\"M520.2,83.4c3.5,0,5.9-2.4,5.9-6c0-3.6-2.3-5.9-5.9-5.9c-3.5,0-5.8,2.5-5.8,6.1C514.4,81.1,516.7,83.4,520.2,83.4z\"/>\n <path fill={f} d=\"M525.4,126V90.3L509.7,91v1.4c5.3,.1,6.1,2,6.1,4.1V126c0,2.4-1.1,3-6.1,3.1v1.4h22v-1.4C526.1,129,525.4,128.4,525.4,126z\"/>\n <path fill={f} d=\"M560.5,121.8c-2.1,3.8-4.4,5.7-7,5.7c-3.4,0-4.8-1.9-4.8-6.9V93.9h12.2v-3.1h-12.2V76.4l-9.4,3.4v10.9l-8,1.9V94h8v25.7c0,7.8,3.4,11.6,10.5,11.6c5.3,0,9.2-2.8,12.1-8.7l-1.4-.9z\"/>\n <path fill={f} d=\"M603.2,126.7c-2.3,1-3.9,1.1-4.9,.4c-.9-.6-1.4-2-1.4-4.2v-20c0-8.6-4.9-12.8-15.1-12.8c-9.7,0-16.2,3.6-16.2,9c0,3.1,2.2,5.3,5.2,5.3c3.3,0,5.4-2,5.4-5.3c0-1.6-.8-3.2-2.2-4.6c1.5-1.4,4.1-2.3,6.8-2.3c4.7,0,6.8,2.8,6.8,9.3v6.1c-.7,.3-2.2,.5-4.1,.9c-4,.7-10,1.7-14.2,4.1c-3.3,1.8-5.3,5.1-5.3,8.9c0,2.6,1,5,2.7,6.7c2,2,4.9,3,8.5,3c4.6,0,8.8-2.2,12.7-6.8h.1c.6,4.8,2.6,6.6,7.3,6.6c3.1,0,5.3-1,8.4-3l-.5-1.2z M587.6,109.5V122c-2.1,2.9-4.9,4.7-7.4,4.7c-4,0-6.2-2.4-6.2-6.8C574.1,114.5,576,111.8,587.6,109.5z\"/>\n <path fill={f} d=\"M620.9,126V68.5L604.8,69v1.4c6,.2,6.7,1.5,6.7,4.2V126c0,2.3-.8,3-6,3.1v1.4H627v-1.4C621.7,129,620.9,128.3,620.9,126z\"/>\n </svg>\n );\n}\n\n/**\n * Avatar \u2014 User avatar circle with initials.\n * Default initials \"RA\" for Ranger. Adapts to dark/light theme.\n *\n * @param initials - Two-letter initials to display (default: \"RA\")\n * @param dark - Whether to render in dark mode\n */\nfunction Avatar({ initials = 'RA', dark }) {\n return (\n <div className={cn(\n 'flex items-center justify-center w-7 h-7 rounded-full text-xs font-semibold flex-shrink-0',\n dark ? 'bg-[#00C1D5]/20 text-[#00C1D5]' : 'bg-[#000033]/10 text-[#000033]'\n )}>\n {initials}\n </div>\n );\n}\n\n// \u2500\u2500 End built-in components \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n";
19
19
  /** List of component names — used to add to BUILTIN_GLOBALS in the transpiler */
20
20
  export declare const BUILTIN_COMPONENT_NAMES: readonly ["DataTable", "MetricCard", "StatusBadge", "DetailCard", "StepWizard", "Logo", "Avatar"];
21
21
  //# sourceMappingURL=components.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/codeviz",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Multi-format code & content preview — Monaco editor, live React/HTML/Markdown/SVG/CSV/JSON rendering, streaming API, zoom/pan, download",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",