@keyfunc/keybricks-ds 2.4.7 → 2.4.10
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.
- package/DEPLOY.md +9 -6
- package/DESIGN.md +4 -4
- package/INTEGRATION.md +27 -2
- package/_adherence.oxlintrc.json +4 -4
- package/assets/fonts/inter/InterVariable-Italic.woff2 +0 -0
- package/assets/fonts/inter/InterVariable.woff2 +0 -0
- package/assets/fonts/inter/LICENSE.txt +92 -0
- package/assets/fonts/inter/README.md +13 -0
- package/assets/icons/icon-index.js +1 -1
- package/assets/icons/icon-index.json +1 -1
- package/changelog.js +91 -0
- package/components/_cards/segmented-control.card.html +1 -1
- package/components/_cards/table.card.html +139 -6
- package/components/_cards/toggles.card.html +27 -27
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-display.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-icons.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/data/SmartTable.d.ts +9 -1
- package/components/data/SmartTable.jsx +24 -4
- package/components/data/SmartTable.vue +18 -5
- package/components/data/Spreadsheet.d.ts +5 -1
- package/components/data/Spreadsheet.jsx +23 -3
- package/components/data/Spreadsheet.vue +18 -5
- package/components/data/Table.d.ts +25 -4
- package/components/data/Table.jsx +74 -20
- package/components/data/Table.vue +56 -14
- package/components/display/Tag.d.ts +1 -1
- package/components/display/Tag.jsx +1 -1
- package/components/display/Tag.vue +1 -1
- package/components/forms/Switch.d.ts +2 -2
- package/components/forms/Switch.jsx +10 -8
- package/components/forms/Switch.vue +5 -4
- package/components/forms/forms.prompt.md +1 -1
- package/components/icons/Icon.jsx +2 -1
- package/components/icons/icon-paths.js +2 -1
- package/components/navigation/Breadcrumb.jsx +9 -7
- package/components/navigation/Breadcrumb.vue +16 -14
- package/components/tabs/CountBadge.vue +1 -1
- package/components/tabs/Tabs.d.ts +1 -1
- package/components/tabs/Tabs.jsx +1 -1
- package/covers.js +104 -0
- package/gallery.html +133 -0
- package/guidelines/_color-lab.js +46 -0
- package/guidelines/brand-logo.html +9 -0
- package/guidelines/colors-list.html +86 -0
- package/guidelines/colors-palette.html +85 -0
- package/guidelines/colors-schemes.html +68 -0
- package/guidelines/colors-transparency.html +59 -0
- package/guidelines/foundations-borders.html +10 -0
- package/guidelines/foundations-chrome.html +17 -0
- package/guidelines/foundations-motion.html +17 -0
- package/guidelines/foundations-radii.html +14 -0
- package/guidelines/foundations-size-tiers.html +88 -0
- package/guidelines/foundations-spacing.html +19 -0
- package/guidelines/interaction-states.html +25 -0
- package/guidelines/type-extended.html +11 -0
- package/guidelines/type-mono.html +10 -0
- package/guidelines/type-scale.html +15 -0
- package/guidelines/type-weights.html +12 -0
- package/index.html +597 -0
- package/package.json +8 -2
- package/readme.md +1 -1
- package/readme.zh-CN.md +1 -1
- package/site-nav.js +1 -0
- package/tokens/fonts.css +19 -4
- package/tokens/layout.css +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"display/Avatar.jsx":"import React from \"react\";\n\nconst palette = [\"var(--cat-join)\", \"var(--cat-source)\", \"var(--cat-transform)\", \"var(--cat-merge)\", \"var(--cat-output)\"];\nfunction initials(name = \"\") {\n const parts = name.trim().split(/\\s+/);\n if (!parts[0]) return \"?\";\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2);\n return (parts[0][0] + (parts[1] ? parts[1][0] : \"\")).toUpperCase();\n}\n\n/** Initials avatar — the source file ships no avatar imagery, so people render as tinted initials. */\nexport function Avatar({ name = \"\", src, size = 28, style }) {\n const hue = palette[[...name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length];\n if (src) return <img src={src} alt={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", objectFit: \"cover\", flexShrink: 0, ...style }} />;\n return (\n <span title={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-hover)\", color: hue, display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: `max(${Math.round(size * .4)}px, var(--text-glyph-min))`, letterSpacing: 0, flexShrink: 0, boxSizing: \"border-box\", border: \"1px solid var(--border-card)\", ...style }}>\n {initials(name)}\n </span>\n );\n}\n\n/** Overlapping avatar row (assignees on a task). */\nexport function AvatarGroup({ names = [], size = 24, max = 4, style }) {\n const shown = names.slice(0, max), rest = names.length - shown.length;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", ...style }}>\n {shown.map((n, i) => <Avatar key={n + i} name={n} size={size} style={{ marginLeft: i ? -8 : 0, boxShadow: \"var(--ring-surface)\" }} />)}\n {rest > 0 ? <span style={{ marginLeft: -8, width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-active)\", color: \"var(--text-secondary)\", boxShadow: \"var(--ring-surface)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontSize: `max(${Math.round(size * .38)}px, var(--text-glyph-min))` }}>+{rest}</span> : null}\n </div>\n );\n}\n","display/Avatar.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nconst palette = ['var(--cat-join)', 'var(--cat-source)', 'var(--cat-transform)', 'var(--cat-merge)', 'var(--cat-output)']\nfunction initials(name = '') {\n const parts = name.trim().split(/\\s+/)\n if (!parts[0]) return '?'\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2)\n return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\n// Initials avatar — the source file ships no avatar imagery, so people render as tinted initials.\nconst props = defineProps({ name: { type: String, default: '' }, src: String, size: { type: Number, default: 28 }, style: Object })\nconst hue = computed(() => palette[[...props.name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length])\n</script>\n<template>\n <img v-if=\"src\" :src=\"src\" :alt=\"name\" :style=\"{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }\" />\n <span v-else :title=\"name\" :style=\"{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-hover)', color: hue, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: `max(${Math.round(size * .4)}px, var(--text-glyph-min))`, letterSpacing: 0, flexShrink: 0, boxSizing: 'border-box', border: '1px solid var(--border-card)', ...style }\">{{ initials(name) }}</span>\n</template>\n","display/Badge.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst badgeTones = {\n success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\",\n pending: \"var(--dot-neutral)\", info: \"var(--action-primary)\",\n};\n\n/** Circular status badge with white ring (node error/warning dots, step icons). */\nexport function StatusBadge({ tone = \"danger\", icon, size = 20, ring = true, style }) {\n return (\n <span style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: badgeTones[tone], boxShadow: ring ? \"var(--ring-surface)\" : \"none\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: \"var(--on-accent)\", flexShrink: 0, ...style }}>\n {icon ? <Icon name={icon} size={Math.round(size * .6)} strokeWidth={2.5} /> : null}\n </span>\n );\n}\n\n/** Inline tinted text badge (table statuses: Completed / Failed / Partial). */\nexport function Badge({ tone = \"neutral\", children, style }) {\n const tones = {\n success: { background: \"var(--success-bg)\", color: \"var(--success-text)\" },\n danger: { background: \"var(--danger-bg)\", color: \"var(--danger-text)\" },\n warning: { background: \"var(--warning-bg)\", color: \"var(--warning-text)\" },\n info: { background: \"var(--info-bg)\", color: \"var(--action-primary)\" },\n neutral: { background: \"var(--bg-hover)\", color: \"var(--text-body)\" },\n };\n return <span style={{ display: \"inline-flex\", alignItems: \"center\", height: 20, padding: \"0 8px\", borderRadius: \"var(--radius-full)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", lineHeight: 1, letterSpacing: \"var(--tracking-xs)\", ...tones[tone], ...style }}>{children}</span>;\n}\n\n/** Category icon tile — the signature tinted square. kind: palette 40/r14 · node 48/r16 · row 32/r8.\n * A row tile is a hairline square unless you pass `hue`, then it is tinted like the others. hue \"neutral\" is the grey tile (folders, containers).\n * iconStyle is forwarded to the Icon (Table's two-line cell sizes it from the tier tokens). */\nexport function IconTile({ icon, hue, kind = \"palette\", style, iconStyle }) {\n const dims = { palette: { box: 40, r: \"var(--radius-tile)\", icon: 18 }, node: { box: 48, r: \"var(--radius-node)\", icon: 22 }, row: { box: 32, r: \"var(--radius-md)\", icon: 16 } };\n const t = dims[kind];\n const h = hue || \"source\";\n const tinted = kind !== \"row\" || !!hue;\n const bg = !tinted ? \"var(--bg-surface)\" : h === \"neutral\" ? \"var(--bg-hover)\" : `var(--cat-${h}-bg)`;\n const fg = !tinted ? \"var(--text-body)\" : h === \"neutral\" ? \"var(--text-secondary)\" : `var(--cat-${h})`;\n return (\n <span style={{ width: t.box, height: t.box, borderRadius: t.r, background: bg, border: tinted ? \"none\" : \"1px solid var(--border-control)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: fg, flexShrink: 0, boxSizing: \"border-box\", ...style }}>\n <Icon name={icon} size={t.icon} strokeWidth={2} style={iconStyle} />\n </span>\n );\n}\n","display/Badge.vue":"<script setup>\nconst tones = {\n success: { background: 'var(--success-bg)', color: 'var(--success-text)' },\n danger: { background: 'var(--danger-bg)', color: 'var(--danger-text)' },\n warning: { background: 'var(--warning-bg)', color: 'var(--warning-text)' },\n info: { background: 'var(--info-bg)', color: 'var(--action-primary)' },\n neutral: { background: 'var(--bg-hover)', color: 'var(--text-body)' },\n}\ndefineProps({ tone: { type: String, default: 'neutral' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', height: '20px', padding: '0 8px', borderRadius: 'var(--radius-full)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', lineHeight: 1, letterSpacing: 'var(--tracking-xs)', ...tones[tone], ...style }\"><slot /></span>\n</template>\n","display/Card.jsx":"import React from \"react\";\n\n/** White surface card — r10, hairline rgba(229,229,229,.6) border, flat. */\nexport function Card({ title, action, dense, pad = true, children, style }) {\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxSizing: \"border-box\", padding: pad ? (dense ? \"var(--card-pad-sm)\" : \"var(--card-pad)\") : 0, ...style }}>\n {title ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: 16 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {action}\n </div>\n ) : null}\n {children}\n </div>\n );\n}\n\n/** Dashboard stat card: muted 13px label + light 24px number + optional chart slot. */\nexport function StatCard({ label, value, dense, children, style }) {\n return (\n <Card dense={dense} style={{ display: \"flex\", flexDirection: \"column\", gap: 12, ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\" }}>{label}</span>\n {children}\n {value != null ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-num)\", lineHeight: \"var(--leading-num)\", letterSpacing: \"var(--tracking-num)\", color: \"var(--text-primary)\" }}>{value}</span> : null}\n </Card>\n );\n}\n","display/Card.vue":"<script setup>\n// White surface card — r10, hairline rgba(229,229,229,.6) border, flat.\ndefineProps({ title: String, dense: Boolean, pad: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxSizing: 'border-box', padding: pad ? (dense ? 'var(--card-pad-sm)' : 'var(--card-pad)') : 0, ...style }\">\n <div v-if=\"title\" style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <slot name=\"action\" />\n </div>\n <slot />\n </div>\n</template>\n","display/Divider.jsx":"import React from \"react\";\n\n/** Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule. */\nexport function Divider({ label, vertical, style }) {\n if (vertical) return <span style={{ width: 1, alignSelf: \"stretch\", background: \"var(--border-subtle)\", ...style }} />;\n if (!label) return <div style={{ height: 1, background: \"var(--border-subtle)\", ...style }} />;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, ...style }}>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\", whiteSpace: \"nowrap\" }}>{label}</span>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n </div>\n );\n}\n","display/Divider.vue":"<script setup>\n// Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule.\ndefineProps({ label: String, vertical: Boolean, style: Object })\n</script>\n<template>\n <span v-if=\"vertical\" :style=\"{ width: '1px', alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }\" />\n <div v-else-if=\"!label\" :style=\"{ height: '1px', background: 'var(--border-subtle)', ...style }\" />\n <div v-else :style=\"{ display: 'flex', alignItems: 'center', gap: '12px', ...style }\">\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted);white-space:nowrap\">{{ label }}</span>\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n </div>\n</template>\n","display/Kbd.jsx":"import React from \"react\";\n\n/** Keyboard shortcut hint, e.g. <Kbd keys={[\"⌘\",\"K\"]}/>. */\nexport function Kbd({ keys = [], style }) {\n return (\n <span style={{ display: \"inline-flex\", gap: 3, ...style }}>\n {keys.map((k, i) => (\n <span key={i} style={{ display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", minWidth: 20, height: 20, padding: \"0 5px\", borderRadius: \"var(--radius-sm)\", background: \"var(--bg-inset)\", border: \"1px solid var(--border-control)\", boxSizing: \"border-box\", fontFamily: \"var(--font-sans)\", fontVariantNumeric: \"tabular-nums\", fontSize: \"var(--text-xs)\", color: \"var(--text-secondary)\" }}>{k}</span>\n ))}\n </span>\n );\n}\n","display/Kbd.vue":"<script setup>\n// Keyboard shortcut hint, e.g. <Kbd :keys=\"['⌘','K']\"/>.\ndefineProps({ keys: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', gap: '3px', ...style }\">\n <span v-for=\"(k, i) in keys\" :key=\"i\" style=\"display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 5px;border-radius:var(--radius-sm);background:var(--bg-inset);border:1px solid var(--border-control);box-sizing:border-box;font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-secondary)\">{{ k }}</span>\n </span>\n</template>\n","display/ProgressBar.jsx":"import React from \"react\";\n\n/** Labeled horizontal progress bar (Expense Distribution rows). */\nexport function ProgressBar({ label, value = 50, color = \"var(--chart-blue)\", showValue = true, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, ...style }}>\n {label != null ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\" }}>\n <span style={{ color: \"var(--text-body)\" }}>{label}</span>\n {showValue ? <span style={{ color: \"var(--text-heading)\" }}>{value}%</span> : null}\n </div>\n ) : null}\n <div style={{ height: 6, borderRadius: \"var(--radius-full)\", background: \"var(--track)\", overflow: \"hidden\" }}>\n <div style={{ width: `${value}%`, height: \"100%\", borderRadius: \"var(--radius-full)\", background: color, transition: \"width .3s ease\" }} />\n </div>\n </div>\n );\n}\n\n/** Ring/donut chart. segments: [{value, color}]; centered light numeral. */\nexport function Donut({ size = 128, thickness = 7.68, segments, value = 75, label, style }) {\n const r = (size - thickness) / 2 - (size * .1 - thickness / 2 > 0 ? size * .1 - thickness / 2 : 0);\n const R = size / 2 - thickness / 2 - size * .02;\n const C = 2 * Math.PI * R;\n const segs = segments || [{ value, color: \"var(--chart-blue)\" }];\n let offset = 0;\n return (\n <div style={{ position: \"relative\", width: size, height: size, ...style }}>\n <svg width={size} height={size} style={{ transform: \"rotate(-90deg)\" }}>\n <circle cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke=\"var(--track)\" strokeWidth={thickness} />\n {segs.map((s, i) => {\n const len = (s.value / 100) * C;\n const el = <circle key={i} cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke={s.color} strokeWidth={thickness} strokeDasharray={`${len} ${C - len}`} strokeDashoffset={-offset} strokeLinecap=\"butt\" />;\n offset += len;\n return el;\n })}\n </svg>\n <div style={{ position: \"absolute\", inset: 0, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-lg)\", letterSpacing: \"var(--tracking-lg)\", color: \"var(--text-primary)\" }}>{label != null ? label : `${value}%`}</div>\n </div>\n );\n}\n\n/** Area line chart (Revenue Trend): 1.22px blue line + fading gradient fill. points: 0–100 values. */\nexport function AreaChart({ points = [65, 75, 60, 85, 70, 90, 85, 95, 100], width = 350, height = 128, color = \"var(--chart-blue)\", style }) {\n const step = width / (points.length - 1);\n const ys = points.map((p) => height - (p / 100) * height * .48 - height * .5);\n const line = ys.map((y, i) => `${i ? \"L\" : \"M\"} ${i * step} ${y}`).join(\" \");\n const id = React.useId ? React.useId().replace(/:/g, \"\") : \"g\" + Math.round(Math.random() * 1e6);\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n <defs><linearGradient id={id} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stopColor={color} stopOpacity=\".3\" /><stop offset=\"1\" stopColor={color} stopOpacity=\"0\" /></linearGradient></defs>\n <path d={`${line} L ${width} ${height} L 0 ${height} Z`} fill={`url(#${id})`} />\n <path d={line} fill=\"none\" stroke={color} strokeWidth=\"1.22\" />\n </svg>\n );\n}\n\n/** Mini bar chart used in stat cards. bars: 0–100 values. */\nexport function MiniBars({ bars = [40, 65, 50, 80, 60, 90, 70], width = 248, height = 96, color = \"var(--chart-blue)\", style }) {\n const gap = 6;\n const bw = (width - gap * (bars.length - 1)) / bars.length;\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n {bars.map((b, i) => {\n const h = (b / 100) * height;\n return <rect key={i} x={i * (bw + gap)} y={height - h} width={bw} height={h} rx={2} fill={color} opacity={.85} />;\n })}\n </svg>\n );\n}\n","display/ProgressBar.vue":"<script setup>\n// Labeled horizontal progress bar (Expense Distribution rows).\ndefineProps({ label: String, value: { type: Number, default: 50 }, color: { type: String, default: 'var(--chart-blue)' }, showValue: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }\">\n <div v-if=\"label != null\" style=\"display:flex;justify-content:space-between;font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base)\">\n <span style=\"color:var(--text-body)\">{{ label }}</span>\n <span v-if=\"showValue\" style=\"color:var(--text-heading)\">{{ value }}%</span>\n </div>\n <div style=\"height:6px;border-radius:var(--radius-full);background:var(--track);overflow:hidden\">\n <div :style=\"{ width: value + '%', height: '100%', borderRadius: 'var(--radius-full)', background: color, transition: 'width .3s ease' }\" />\n </div>\n </div>\n</template>\n","display/Tag.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tones = { neutral: { bg: \"var(--bg-hover)\", fg: \"var(--text-body)\" }, info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\" } };\n\n/** Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md 24 · lg 28, --tag-h); text stays 12px. */\nexport function Tag({ children, onRemove, tone = \"neutral\", size, style }) {\n const t = tones[tone] || tones.neutral;\n return (\n <span data-size={size} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 4, height: \"var(--tag-h)\", padding: onRemove ? \"0 6px 0 10px\" : \"0 10px\", borderRadius: \"var(--radius-full)\", background: t.bg, color: t.fg, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\", whiteSpace: \"nowrap\", ...style }}>\n {children}\n {onRemove ? <button type=\"button\" onClick={onRemove} aria-label=\"Remove\" data-ink=\"\" style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", opacity: \"var(--ink-op, .7)\", display: \"flex\", padding: 0 }}><Icon name=\"x\" size={12} /></button> : null}\n </span>\n );\n}\n","display/Tag.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst tones = { neutral: { bg: 'var(--bg-hover)', fg: 'var(--text-body)' }, info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)' } }\n// Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md 24 · lg 28, --tag-h); text stays 12px.\nconst props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, size: String, style: Object })\ndefineEmits(['remove'])\nconst t = computed(() => tones[props.tone] || tones.neutral)\n</script>\n<template>\n <span :data-size=\"size\" :style=\"{ display: 'inline-flex', alignItems: 'center', gap: '4px', height: 'var(--tag-h)', padding: removable ? '0 6px 0 10px' : '0 10px', borderRadius: 'var(--radius-full)', background: t.bg, color: t.fg, fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', whiteSpace: 'nowrap', ...style }\">\n <slot />\n <button v-if=\"removable\" type=\"button\" aria-label=\"Remove\" data-ink=\"\" @click=\"$emit('remove')\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;opacity:var(--ink-op, .7);display:flex;padding:0\"><Icon name=\"x\" :size=\"12\" /></button>\n </span>\n</template>\n","display/AreaChart.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ points: { type: Array, default: () => [65, 75, 60, 85, 70, 90, 85, 95, 100] }, width: { type: Number, default: 350 }, height: { type: Number, default: 128 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gid = 'g' + Math.round(Math.random() * 1e6)\nconst step = computed(() => props.width / (props.points.length - 1))\nconst line = computed(() => props.points.map((p, i) => `${i ? 'L' : 'M'} ${i * step.value} ${props.height - (p / 100) * props.height * .48 - props.height * .5}`).join(' '))\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <defs><linearGradient :id=\"gid\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" :stop-color=\"color\" stop-opacity=\".3\" /><stop offset=\"1\" :stop-color=\"color\" stop-opacity=\"0\" /></linearGradient></defs>\n <path :d=\"`${line} L ${width} ${height} L 0 ${height} Z`\" :fill=\"`url(#${gid})`\" />\n <path :d=\"line\" fill=\"none\" :stroke=\"color\" stroke-width=\"1.22\" />\n </svg>\n</template>\n","display/AvatarGroup.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nimport Avatar from './Avatar.vue'\nconst props = defineProps({ names: { type: Array, default: () => [] }, size: { type: Number, default: 24 }, max: { type: Number, default: 4 }, style: Object })\nconst shown = computed(() => props.names.slice(0, props.max))\nconst rest = computed(() => props.names.length - shown.value.length)\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', ...style }\">\n <Avatar v-for=\"(n, i) in shown\" :key=\"n + i\" :name=\"n\" :size=\"size\" :style=\"{ marginLeft: i ? '-8px' : 0, boxShadow: 'var(--ring-surface)' }\" />\n <span v-if=\"rest > 0\" :style=\"{ marginLeft: '-8px', width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-active)', color: 'var(--text-secondary)', boxShadow: 'var(--ring-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontSize: `max(${Math.round(size * .38)}px, var(--text-glyph-min))` }\">+{{ rest }}</span>\n </div>\n</template>\n","display/Donut.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nconst props = defineProps({ size: { type: Number, default: 128 }, thickness: { type: Number, default: 7.68 }, segments: Array, value: { type: Number, default: 75 }, label: [String, Number], style: Object })\nconst R = computed(() => props.size / 2 - props.thickness / 2 - props.size * .02)\nconst C = computed(() => 2 * Math.PI * R.value)\nconst segs = computed(() => props.segments || [{ value: props.value, color: 'var(--chart-blue)' }])\nconst arcs = computed(() => { let offset = 0; return segs.value.map(s => { const len = (s.value / 100) * C.value; const a = { ...s, len, offset }; offset += len; return a }) })\n</script>\n<template>\n <div :style=\"{ position: 'relative', width: px(size), height: px(size), ...style }\">\n <svg :width=\"size\" :height=\"size\" style=\"transform:rotate(-90deg)\">\n <circle :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" stroke=\"var(--track)\" :stroke-width=\"thickness\" />\n <circle v-for=\"(s, i) in arcs\" :key=\"i\" :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" :stroke=\"s.color\" :stroke-width=\"thickness\" :stroke-dasharray=\"`${s.len} ${C - s.len}`\" :stroke-dashoffset=\"-s.offset\" stroke-linecap=\"butt\" />\n </svg>\n <div style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)\">{{ label != null ? label : `${value}%` }}</div>\n </div>\n</template>\n","display/IconTile.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Category icon tile. kind: palette 40/r14 · node 48/r16 · row 32/r8. A row tile is a hairline square unless `hue` is set, then it is tinted.\n// hue \"neutral\" is the grey tile (folders, containers). iconStyle is forwarded to the Icon (Table's two-line cell sizes it from the tier tokens).\nconst dims = { palette: { box: 40, r: 'var(--radius-tile)', icon: 18 }, node: { box: 48, r: 'var(--radius-node)', icon: 22 }, row: { box: 32, r: 'var(--radius-md)', icon: 16 } }\nconst props = defineProps({ icon: String, hue: String, kind: { type: String, default: 'palette' }, style: Object, iconStyle: Object })\nconst t = computed(() => dims[props.kind])\nconst h = computed(() => props.hue || 'source')\nconst tinted = computed(() => props.kind !== 'row' || !!props.hue)\nconst bg = computed(() => !tinted.value ? 'var(--bg-surface)' : h.value === 'neutral' ? 'var(--bg-hover)' : `var(--cat-${h.value}-bg)`)\nconst fg = computed(() => !tinted.value ? 'var(--text-body)' : h.value === 'neutral' ? 'var(--text-secondary)' : `var(--cat-${h.value})`)\n</script>\n<template>\n <span :style=\"{ width: px(t.box), height: px(t.box), borderRadius: px(t.r), background: bg, border: tinted ? 'none' : '1px solid var(--border-control)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: fg, flexShrink: 0, boxSizing: 'border-box', ...style }\">\n <Icon :name=\"icon\" :size=\"t.icon\" :stroke-width=\"2\" :style=\"iconStyle\" />\n </span>\n</template>\n","display/MiniBars.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ bars: { type: Array, default: () => [40, 65, 50, 80, 60, 90, 70] }, width: { type: Number, default: 248 }, height: { type: Number, default: 96 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gap = 6\nconst bw = computed(() => (props.width - gap * (props.bars.length - 1)) / props.bars.length)\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <rect v-for=\"(b, i) in bars\" :key=\"i\" :x=\"i * (bw + gap)\" :y=\"height - (b / 100) * height\" :width=\"bw\" :height=\"(b / 100) * height\" rx=\"2\" :fill=\"color\" opacity=\".85\" />\n </svg>\n</template>\n","display/StatCard.vue":"<script setup>\nimport Card from './Card.vue'\ndefineProps({ label: String, value: [String, Number], dense: Boolean, style: Object })\n</script>\n<template>\n <Card :dense=\"dense\" :style=\"{ display: 'flex', flexDirection: 'column', gap: '12px', ...style }\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-secondary)\">{{ label }}</span>\n <slot />\n <span v-if=\"value != null\" style=\"font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-num);line-height:var(--leading-num);letter-spacing:var(--tracking-num);color:var(--text-primary)\">{{ value }}</span>\n </Card>\n</template>\n","display/StatusBadge.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Circular status badge with white ring (node error/warning dots, step icons).\nconst badgeTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', pending: 'var(--dot-neutral)', info: 'var(--action-primary)' }\nconst props = defineProps({ tone: { type: String, default: 'danger' }, icon: String, size: { type: Number, default: 20 }, ring: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <span :style=\"{ width: size + 'px', height: size + 'px', borderRadius: 'var(--radius-full)', background: badgeTones[tone], boxShadow: ring ? 'var(--ring-surface)' : 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--on-accent)', flexShrink: 0, ...style }\">\n <Icon v-if=\"icon\" :name=\"icon\" :size=\"Math.round(size * .6)\" :stroke-width=\"2.5\" />\n </span>\n</template>\n"});
|
|
1
|
+
window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"display/Avatar.jsx":"import React from \"react\";\n\nconst palette = [\"var(--cat-join)\", \"var(--cat-source)\", \"var(--cat-transform)\", \"var(--cat-merge)\", \"var(--cat-output)\"];\nfunction initials(name = \"\") {\n const parts = name.trim().split(/\\s+/);\n if (!parts[0]) return \"?\";\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2);\n return (parts[0][0] + (parts[1] ? parts[1][0] : \"\")).toUpperCase();\n}\n\n/** Initials avatar — the source file ships no avatar imagery, so people render as tinted initials. */\nexport function Avatar({ name = \"\", src, size = 28, style }) {\n const hue = palette[[...name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length];\n if (src) return <img src={src} alt={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", objectFit: \"cover\", flexShrink: 0, ...style }} />;\n return (\n <span title={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-hover)\", color: hue, display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: `max(${Math.round(size * .4)}px, var(--text-glyph-min))`, letterSpacing: 0, flexShrink: 0, boxSizing: \"border-box\", border: \"1px solid var(--border-card)\", ...style }}>\n {initials(name)}\n </span>\n );\n}\n\n/** Overlapping avatar row (assignees on a task). */\nexport function AvatarGroup({ names = [], size = 24, max = 4, style }) {\n const shown = names.slice(0, max), rest = names.length - shown.length;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", ...style }}>\n {shown.map((n, i) => <Avatar key={n + i} name={n} size={size} style={{ marginLeft: i ? -8 : 0, boxShadow: \"var(--ring-surface)\" }} />)}\n {rest > 0 ? <span style={{ marginLeft: -8, width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-active)\", color: \"var(--text-secondary)\", boxShadow: \"var(--ring-surface)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontSize: `max(${Math.round(size * .38)}px, var(--text-glyph-min))` }}>+{rest}</span> : null}\n </div>\n );\n}\n","display/Avatar.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nconst palette = ['var(--cat-join)', 'var(--cat-source)', 'var(--cat-transform)', 'var(--cat-merge)', 'var(--cat-output)']\nfunction initials(name = '') {\n const parts = name.trim().split(/\\s+/)\n if (!parts[0]) return '?'\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2)\n return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\n// Initials avatar — the source file ships no avatar imagery, so people render as tinted initials.\nconst props = defineProps({ name: { type: String, default: '' }, src: String, size: { type: Number, default: 28 }, style: Object })\nconst hue = computed(() => palette[[...props.name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length])\n</script>\n<template>\n <img v-if=\"src\" :src=\"src\" :alt=\"name\" :style=\"{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }\" />\n <span v-else :title=\"name\" :style=\"{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-hover)', color: hue, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: `max(${Math.round(size * .4)}px, var(--text-glyph-min))`, letterSpacing: 0, flexShrink: 0, boxSizing: 'border-box', border: '1px solid var(--border-card)', ...style }\">{{ initials(name) }}</span>\n</template>\n","display/Badge.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst badgeTones = {\n success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\",\n pending: \"var(--dot-neutral)\", info: \"var(--action-primary)\",\n};\n\n/** Circular status badge with white ring (node error/warning dots, step icons). */\nexport function StatusBadge({ tone = \"danger\", icon, size = 20, ring = true, style }) {\n return (\n <span style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: badgeTones[tone], boxShadow: ring ? \"var(--ring-surface)\" : \"none\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: \"var(--on-accent)\", flexShrink: 0, ...style }}>\n {icon ? <Icon name={icon} size={Math.round(size * .6)} strokeWidth={2.5} /> : null}\n </span>\n );\n}\n\n/** Inline tinted text badge (table statuses: Completed / Failed / Partial). */\nexport function Badge({ tone = \"neutral\", children, style }) {\n const tones = {\n success: { background: \"var(--success-bg)\", color: \"var(--success-text)\" },\n danger: { background: \"var(--danger-bg)\", color: \"var(--danger-text)\" },\n warning: { background: \"var(--warning-bg)\", color: \"var(--warning-text)\" },\n info: { background: \"var(--info-bg)\", color: \"var(--action-primary)\" },\n neutral: { background: \"var(--bg-hover)\", color: \"var(--text-body)\" },\n };\n return <span style={{ display: \"inline-flex\", alignItems: \"center\", height: 20, padding: \"0 8px\", borderRadius: \"var(--radius-full)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", lineHeight: 1, letterSpacing: \"var(--tracking-xs)\", ...tones[tone], ...style }}>{children}</span>;\n}\n\n/** Category icon tile — the signature tinted square. kind: palette 40/r14 · node 48/r16 · row 32/r8.\n * A row tile is a hairline square unless you pass `hue`, then it is tinted like the others. hue \"neutral\" is the grey tile (folders, containers).\n * iconStyle is forwarded to the Icon (Table's two-line cell sizes it from the tier tokens). */\nexport function IconTile({ icon, hue, kind = \"palette\", style, iconStyle }) {\n const dims = { palette: { box: 40, r: \"var(--radius-tile)\", icon: 18 }, node: { box: 48, r: \"var(--radius-node)\", icon: 22 }, row: { box: 32, r: \"var(--radius-md)\", icon: 16 } };\n const t = dims[kind];\n const h = hue || \"source\";\n const tinted = kind !== \"row\" || !!hue;\n const bg = !tinted ? \"var(--bg-surface)\" : h === \"neutral\" ? \"var(--bg-hover)\" : `var(--cat-${h}-bg)`;\n const fg = !tinted ? \"var(--text-body)\" : h === \"neutral\" ? \"var(--text-secondary)\" : `var(--cat-${h})`;\n return (\n <span style={{ width: t.box, height: t.box, borderRadius: t.r, background: bg, border: tinted ? \"none\" : \"1px solid var(--border-control)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: fg, flexShrink: 0, boxSizing: \"border-box\", ...style }}>\n <Icon name={icon} size={t.icon} strokeWidth={2} style={iconStyle} />\n </span>\n );\n}\n","display/Badge.vue":"<script setup>\nconst tones = {\n success: { background: 'var(--success-bg)', color: 'var(--success-text)' },\n danger: { background: 'var(--danger-bg)', color: 'var(--danger-text)' },\n warning: { background: 'var(--warning-bg)', color: 'var(--warning-text)' },\n info: { background: 'var(--info-bg)', color: 'var(--action-primary)' },\n neutral: { background: 'var(--bg-hover)', color: 'var(--text-body)' },\n}\ndefineProps({ tone: { type: String, default: 'neutral' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', height: '20px', padding: '0 8px', borderRadius: 'var(--radius-full)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', lineHeight: 1, letterSpacing: 'var(--tracking-xs)', ...tones[tone], ...style }\"><slot /></span>\n</template>\n","display/Card.jsx":"import React from \"react\";\n\n/** White surface card — r10, hairline rgba(229,229,229,.6) border, flat. */\nexport function Card({ title, action, dense, pad = true, children, style }) {\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxSizing: \"border-box\", padding: pad ? (dense ? \"var(--card-pad-sm)\" : \"var(--card-pad)\") : 0, ...style }}>\n {title ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: 16 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {action}\n </div>\n ) : null}\n {children}\n </div>\n );\n}\n\n/** Dashboard stat card: muted 13px label + light 24px number + optional chart slot. */\nexport function StatCard({ label, value, dense, children, style }) {\n return (\n <Card dense={dense} style={{ display: \"flex\", flexDirection: \"column\", gap: 12, ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\" }}>{label}</span>\n {children}\n {value != null ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-num)\", lineHeight: \"var(--leading-num)\", letterSpacing: \"var(--tracking-num)\", color: \"var(--text-primary)\" }}>{value}</span> : null}\n </Card>\n );\n}\n","display/Card.vue":"<script setup>\n// White surface card — r10, hairline rgba(229,229,229,.6) border, flat.\ndefineProps({ title: String, dense: Boolean, pad: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxSizing: 'border-box', padding: pad ? (dense ? 'var(--card-pad-sm)' : 'var(--card-pad)') : 0, ...style }\">\n <div v-if=\"title\" style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <slot name=\"action\" />\n </div>\n <slot />\n </div>\n</template>\n","display/Divider.jsx":"import React from \"react\";\n\n/** Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule. */\nexport function Divider({ label, vertical, style }) {\n if (vertical) return <span style={{ width: 1, alignSelf: \"stretch\", background: \"var(--border-subtle)\", ...style }} />;\n if (!label) return <div style={{ height: 1, background: \"var(--border-subtle)\", ...style }} />;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, ...style }}>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\", whiteSpace: \"nowrap\" }}>{label}</span>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n </div>\n );\n}\n","display/Divider.vue":"<script setup>\n// Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule.\ndefineProps({ label: String, vertical: Boolean, style: Object })\n</script>\n<template>\n <span v-if=\"vertical\" :style=\"{ width: '1px', alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }\" />\n <div v-else-if=\"!label\" :style=\"{ height: '1px', background: 'var(--border-subtle)', ...style }\" />\n <div v-else :style=\"{ display: 'flex', alignItems: 'center', gap: '12px', ...style }\">\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted);white-space:nowrap\">{{ label }}</span>\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n </div>\n</template>\n","display/Kbd.jsx":"import React from \"react\";\n\n/** Keyboard shortcut hint, e.g. <Kbd keys={[\"⌘\",\"K\"]}/>. */\nexport function Kbd({ keys = [], style }) {\n return (\n <span style={{ display: \"inline-flex\", gap: 3, ...style }}>\n {keys.map((k, i) => (\n <span key={i} style={{ display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", minWidth: 20, height: 20, padding: \"0 5px\", borderRadius: \"var(--radius-sm)\", background: \"var(--bg-inset)\", border: \"1px solid var(--border-control)\", boxSizing: \"border-box\", fontFamily: \"var(--font-sans)\", fontVariantNumeric: \"tabular-nums\", fontSize: \"var(--text-xs)\", color: \"var(--text-secondary)\" }}>{k}</span>\n ))}\n </span>\n );\n}\n","display/Kbd.vue":"<script setup>\n// Keyboard shortcut hint, e.g. <Kbd :keys=\"['⌘','K']\"/>.\ndefineProps({ keys: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', gap: '3px', ...style }\">\n <span v-for=\"(k, i) in keys\" :key=\"i\" style=\"display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 5px;border-radius:var(--radius-sm);background:var(--bg-inset);border:1px solid var(--border-control);box-sizing:border-box;font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-secondary)\">{{ k }}</span>\n </span>\n</template>\n","display/ProgressBar.jsx":"import React from \"react\";\n\n/** Labeled horizontal progress bar (Expense Distribution rows). */\nexport function ProgressBar({ label, value = 50, color = \"var(--chart-blue)\", showValue = true, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, ...style }}>\n {label != null ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\" }}>\n <span style={{ color: \"var(--text-body)\" }}>{label}</span>\n {showValue ? <span style={{ color: \"var(--text-heading)\" }}>{value}%</span> : null}\n </div>\n ) : null}\n <div style={{ height: 6, borderRadius: \"var(--radius-full)\", background: \"var(--track)\", overflow: \"hidden\" }}>\n <div style={{ width: `${value}%`, height: \"100%\", borderRadius: \"var(--radius-full)\", background: color, transition: \"width .3s ease\" }} />\n </div>\n </div>\n );\n}\n\n/** Ring/donut chart. segments: [{value, color}]; centered light numeral. */\nexport function Donut({ size = 128, thickness = 7.68, segments, value = 75, label, style }) {\n const r = (size - thickness) / 2 - (size * .1 - thickness / 2 > 0 ? size * .1 - thickness / 2 : 0);\n const R = size / 2 - thickness / 2 - size * .02;\n const C = 2 * Math.PI * R;\n const segs = segments || [{ value, color: \"var(--chart-blue)\" }];\n let offset = 0;\n return (\n <div style={{ position: \"relative\", width: size, height: size, ...style }}>\n <svg width={size} height={size} style={{ transform: \"rotate(-90deg)\" }}>\n <circle cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke=\"var(--track)\" strokeWidth={thickness} />\n {segs.map((s, i) => {\n const len = (s.value / 100) * C;\n const el = <circle key={i} cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke={s.color} strokeWidth={thickness} strokeDasharray={`${len} ${C - len}`} strokeDashoffset={-offset} strokeLinecap=\"butt\" />;\n offset += len;\n return el;\n })}\n </svg>\n <div style={{ position: \"absolute\", inset: 0, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-lg)\", letterSpacing: \"var(--tracking-lg)\", color: \"var(--text-primary)\" }}>{label != null ? label : `${value}%`}</div>\n </div>\n );\n}\n\n/** Area line chart (Revenue Trend): 1.22px blue line + fading gradient fill. points: 0–100 values. */\nexport function AreaChart({ points = [65, 75, 60, 85, 70, 90, 85, 95, 100], width = 350, height = 128, color = \"var(--chart-blue)\", style }) {\n const step = width / (points.length - 1);\n const ys = points.map((p) => height - (p / 100) * height * .48 - height * .5);\n const line = ys.map((y, i) => `${i ? \"L\" : \"M\"} ${i * step} ${y}`).join(\" \");\n const id = React.useId ? React.useId().replace(/:/g, \"\") : \"g\" + Math.round(Math.random() * 1e6);\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n <defs><linearGradient id={id} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stopColor={color} stopOpacity=\".3\" /><stop offset=\"1\" stopColor={color} stopOpacity=\"0\" /></linearGradient></defs>\n <path d={`${line} L ${width} ${height} L 0 ${height} Z`} fill={`url(#${id})`} />\n <path d={line} fill=\"none\" stroke={color} strokeWidth=\"1.22\" />\n </svg>\n );\n}\n\n/** Mini bar chart used in stat cards. bars: 0–100 values. */\nexport function MiniBars({ bars = [40, 65, 50, 80, 60, 90, 70], width = 248, height = 96, color = \"var(--chart-blue)\", style }) {\n const gap = 6;\n const bw = (width - gap * (bars.length - 1)) / bars.length;\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n {bars.map((b, i) => {\n const h = (b / 100) * height;\n return <rect key={i} x={i * (bw + gap)} y={height - h} width={bw} height={h} rx={2} fill={color} opacity={.85} />;\n })}\n </svg>\n );\n}\n","display/ProgressBar.vue":"<script setup>\n// Labeled horizontal progress bar (Expense Distribution rows).\ndefineProps({ label: String, value: { type: Number, default: 50 }, color: { type: String, default: 'var(--chart-blue)' }, showValue: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }\">\n <div v-if=\"label != null\" style=\"display:flex;justify-content:space-between;font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base)\">\n <span style=\"color:var(--text-body)\">{{ label }}</span>\n <span v-if=\"showValue\" style=\"color:var(--text-heading)\">{{ value }}%</span>\n </div>\n <div style=\"height:6px;border-radius:var(--radius-full);background:var(--track);overflow:hidden\">\n <div :style=\"{ width: value + '%', height: '100%', borderRadius: 'var(--radius-full)', background: color, transition: 'width .3s ease' }\" />\n </div>\n </div>\n</template>\n","display/Tag.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tones = { neutral: { bg: \"var(--bg-hover)\", fg: \"var(--text-body)\" }, info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\" } };\n\n/** Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md and lg 24, --tag-h); text stays 12px. */\nexport function Tag({ children, onRemove, tone = \"neutral\", size, style }) {\n const t = tones[tone] || tones.neutral;\n return (\n <span data-size={size} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 4, height: \"var(--tag-h)\", padding: onRemove ? \"0 6px 0 10px\" : \"0 10px\", borderRadius: \"var(--radius-full)\", background: t.bg, color: t.fg, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\", whiteSpace: \"nowrap\", ...style }}>\n {children}\n {onRemove ? <button type=\"button\" onClick={onRemove} aria-label=\"Remove\" data-ink=\"\" style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", opacity: \"var(--ink-op, .7)\", display: \"flex\", padding: 0 }}><Icon name=\"x\" size={12} /></button> : null}\n </span>\n );\n}\n","display/Tag.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst tones = { neutral: { bg: 'var(--bg-hover)', fg: 'var(--text-body)' }, info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)' } }\n// Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md and lg 24, --tag-h); text stays 12px.\nconst props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, size: String, style: Object })\ndefineEmits(['remove'])\nconst t = computed(() => tones[props.tone] || tones.neutral)\n</script>\n<template>\n <span :data-size=\"size\" :style=\"{ display: 'inline-flex', alignItems: 'center', gap: '4px', height: 'var(--tag-h)', padding: removable ? '0 6px 0 10px' : '0 10px', borderRadius: 'var(--radius-full)', background: t.bg, color: t.fg, fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', whiteSpace: 'nowrap', ...style }\">\n <slot />\n <button v-if=\"removable\" type=\"button\" aria-label=\"Remove\" data-ink=\"\" @click=\"$emit('remove')\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;opacity:var(--ink-op, .7);display:flex;padding:0\"><Icon name=\"x\" :size=\"12\" /></button>\n </span>\n</template>\n","display/AreaChart.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ points: { type: Array, default: () => [65, 75, 60, 85, 70, 90, 85, 95, 100] }, width: { type: Number, default: 350 }, height: { type: Number, default: 128 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gid = 'g' + Math.round(Math.random() * 1e6)\nconst step = computed(() => props.width / (props.points.length - 1))\nconst line = computed(() => props.points.map((p, i) => `${i ? 'L' : 'M'} ${i * step.value} ${props.height - (p / 100) * props.height * .48 - props.height * .5}`).join(' '))\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <defs><linearGradient :id=\"gid\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" :stop-color=\"color\" stop-opacity=\".3\" /><stop offset=\"1\" :stop-color=\"color\" stop-opacity=\"0\" /></linearGradient></defs>\n <path :d=\"`${line} L ${width} ${height} L 0 ${height} Z`\" :fill=\"`url(#${gid})`\" />\n <path :d=\"line\" fill=\"none\" :stroke=\"color\" stroke-width=\"1.22\" />\n </svg>\n</template>\n","display/AvatarGroup.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nimport Avatar from './Avatar.vue'\nconst props = defineProps({ names: { type: Array, default: () => [] }, size: { type: Number, default: 24 }, max: { type: Number, default: 4 }, style: Object })\nconst shown = computed(() => props.names.slice(0, props.max))\nconst rest = computed(() => props.names.length - shown.value.length)\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', ...style }\">\n <Avatar v-for=\"(n, i) in shown\" :key=\"n + i\" :name=\"n\" :size=\"size\" :style=\"{ marginLeft: i ? '-8px' : 0, boxShadow: 'var(--ring-surface)' }\" />\n <span v-if=\"rest > 0\" :style=\"{ marginLeft: '-8px', width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-active)', color: 'var(--text-secondary)', boxShadow: 'var(--ring-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontSize: `max(${Math.round(size * .38)}px, var(--text-glyph-min))` }\">+{{ rest }}</span>\n </div>\n</template>\n","display/Donut.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nconst props = defineProps({ size: { type: Number, default: 128 }, thickness: { type: Number, default: 7.68 }, segments: Array, value: { type: Number, default: 75 }, label: [String, Number], style: Object })\nconst R = computed(() => props.size / 2 - props.thickness / 2 - props.size * .02)\nconst C = computed(() => 2 * Math.PI * R.value)\nconst segs = computed(() => props.segments || [{ value: props.value, color: 'var(--chart-blue)' }])\nconst arcs = computed(() => { let offset = 0; return segs.value.map(s => { const len = (s.value / 100) * C.value; const a = { ...s, len, offset }; offset += len; return a }) })\n</script>\n<template>\n <div :style=\"{ position: 'relative', width: px(size), height: px(size), ...style }\">\n <svg :width=\"size\" :height=\"size\" style=\"transform:rotate(-90deg)\">\n <circle :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" stroke=\"var(--track)\" :stroke-width=\"thickness\" />\n <circle v-for=\"(s, i) in arcs\" :key=\"i\" :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" :stroke=\"s.color\" :stroke-width=\"thickness\" :stroke-dasharray=\"`${s.len} ${C - s.len}`\" :stroke-dashoffset=\"-s.offset\" stroke-linecap=\"butt\" />\n </svg>\n <div style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)\">{{ label != null ? label : `${value}%` }}</div>\n </div>\n</template>\n","display/IconTile.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Category icon tile. kind: palette 40/r14 · node 48/r16 · row 32/r8. A row tile is a hairline square unless `hue` is set, then it is tinted.\n// hue \"neutral\" is the grey tile (folders, containers). iconStyle is forwarded to the Icon (Table's two-line cell sizes it from the tier tokens).\nconst dims = { palette: { box: 40, r: 'var(--radius-tile)', icon: 18 }, node: { box: 48, r: 'var(--radius-node)', icon: 22 }, row: { box: 32, r: 'var(--radius-md)', icon: 16 } }\nconst props = defineProps({ icon: String, hue: String, kind: { type: String, default: 'palette' }, style: Object, iconStyle: Object })\nconst t = computed(() => dims[props.kind])\nconst h = computed(() => props.hue || 'source')\nconst tinted = computed(() => props.kind !== 'row' || !!props.hue)\nconst bg = computed(() => !tinted.value ? 'var(--bg-surface)' : h.value === 'neutral' ? 'var(--bg-hover)' : `var(--cat-${h.value}-bg)`)\nconst fg = computed(() => !tinted.value ? 'var(--text-body)' : h.value === 'neutral' ? 'var(--text-secondary)' : `var(--cat-${h.value})`)\n</script>\n<template>\n <span :style=\"{ width: px(t.box), height: px(t.box), borderRadius: px(t.r), background: bg, border: tinted ? 'none' : '1px solid var(--border-control)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: fg, flexShrink: 0, boxSizing: 'border-box', ...style }\">\n <Icon :name=\"icon\" :size=\"t.icon\" :stroke-width=\"2\" :style=\"iconStyle\" />\n </span>\n</template>\n","display/MiniBars.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ bars: { type: Array, default: () => [40, 65, 50, 80, 60, 90, 70] }, width: { type: Number, default: 248 }, height: { type: Number, default: 96 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gap = 6\nconst bw = computed(() => (props.width - gap * (props.bars.length - 1)) / props.bars.length)\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <rect v-for=\"(b, i) in bars\" :key=\"i\" :x=\"i * (bw + gap)\" :y=\"height - (b / 100) * height\" :width=\"bw\" :height=\"(b / 100) * height\" rx=\"2\" :fill=\"color\" opacity=\".85\" />\n </svg>\n</template>\n","display/StatCard.vue":"<script setup>\nimport Card from './Card.vue'\ndefineProps({ label: String, value: [String, Number], dense: Boolean, style: Object })\n</script>\n<template>\n <Card :dense=\"dense\" :style=\"{ display: 'flex', flexDirection: 'column', gap: '12px', ...style }\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-secondary)\">{{ label }}</span>\n <slot />\n <span v-if=\"value != null\" style=\"font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-num);line-height:var(--leading-num);letter-spacing:var(--tracking-num);color:var(--text-primary)\">{{ value }}</span>\n </Card>\n</template>\n","display/StatusBadge.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Circular status badge with white ring (node error/warning dots, step icons).\nconst badgeTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', pending: 'var(--dot-neutral)', info: 'var(--action-primary)' }\nconst props = defineProps({ tone: { type: String, default: 'danger' }, icon: String, size: { type: Number, default: 20 }, ring: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <span :style=\"{ width: size + 'px', height: size + 'px', borderRadius: 'var(--radius-full)', background: badgeTones[tone], boxShadow: ring ? 'var(--ring-surface)' : 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--on-accent)', flexShrink: 0, ...style }\">\n <Icon v-if=\"icon\" :name=\"icon\" :size=\"Math.round(size * .6)\" :stroke-width=\"2.5\" />\n </span>\n</template>\n"});
|