@keyfunc/keybricks-ds 1.0.2 → 1.0.3
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/INTEGRATION.md +18 -0
- package/components/_shared/src-buttons.js +1 -1
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-display.js +1 -1
- package/components/_shared/src-feedback.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_shared/src-pipeline.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/_vue/index.mjs +21 -0
- package/components/buttons/Button.vue +0 -20
- package/components/buttons/ButtonGroup.vue +16 -0
- package/components/data/KeyValue.vue +0 -16
- package/components/data/KeyValueList.vue +12 -0
- package/components/data/ListRow.vue +1 -1
- package/components/display/AreaChart.vue +14 -0
- package/components/display/Avatar.vue +0 -17
- package/components/display/AvatarGroup.vue +13 -0
- package/components/display/Badge.vue +0 -34
- package/components/display/Card.vue +0 -15
- package/components/display/Donut.vue +17 -0
- package/components/display/IconTile.vue +13 -0
- package/components/display/MiniBars.vue +11 -0
- package/components/display/ProgressBar.vue +0 -54
- package/components/display/StatCard.vue +11 -0
- package/components/display/StatusBadge.vue +11 -0
- package/components/feedback/ActivityItem.vue +1 -32
- package/components/feedback/Alert.vue +0 -36
- package/components/feedback/DotActivity.vue +15 -0
- package/components/feedback/EmptyState.vue +12 -0
- package/components/feedback/LogViewer.vue +8 -0
- package/components/feedback/Modal.vue +0 -45
- package/components/feedback/Skeleton.vue +6 -0
- package/components/feedback/Spinner.vue +6 -0
- package/components/feedback/Toast.vue +19 -0
- package/components/forms/Combobox.vue +1 -1
- package/components/forms/DatePicker.vue +1 -1
- package/components/forms/Input.jsx +2 -1
- package/components/forms/Input.vue +2 -24
- package/components/forms/Textarea.vue +20 -0
- package/components/forms/input-base.js +3 -0
- package/components/navigation/Breadcrumb.vue +0 -22
- package/components/navigation/PageHeading.vue +9 -0
- package/components/navigation/PageToolbar.vue +15 -0
- package/components/navigation/ThemeToggle.vue +18 -0
- package/components/navigation/TopBar.vue +0 -32
- package/components/pipeline/NodeCard.vue +2 -1
- package/components/pipeline/PaletteItem.vue +1 -1
- package/components/pipeline/PalettePanel.jsx +2 -8
- package/components/pipeline/PalettePanel.vue +1 -8
- package/components/pipeline/pipeline-catalog.js +8 -0
- package/components/tabs/PanelTabs.vue +10 -0
- package/components/tabs/PillTabs.vue +10 -0
- package/components/tabs/Tabs.vue +0 -28
- package/package.json +1 -1
- package/readme.md +1 -0
- package/tokens/tokens.json +1 -1
|
@@ -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: Math.max(9, Math.round(size * .4)), 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: Math.max(9, Math.round(size * .38)) }}>+{rest}</span> : null}\n </div>\n );\n}\n","display/Avatar.vue":"<script setup>\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: size, height: size, borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }\" />\n <span v-else :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: Math.max(9, Math.round(size * .4)), letterSpacing: 0, flexShrink: 0, boxSizing: 'border-box', border: '1px solid var(--border-card)', ...style }\">{{ initials(name) }}</span>\n</template>\n\n<!-- AvatarGroup — overlapping avatar row (assignees on a task). -->\n<!--\n<script setup name=\"AvatarGroup\">\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 ? -8 : 0, boxShadow: 'var(--ring-surface)' }\" />\n <span v-if=\"rest > 0\" :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: Math.max(9, Math.round(size * .38)) }\">+{{ rest }}</span>\n </div>\n</template>\n-->\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 (hairline). */\nexport function IconTile({ icon, hue = \"source\", kind = \"palette\", style }) {\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 tinted = kind !== \"row\";\n return (\n <span style={{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : \"var(--bg-surface)\", border: tinted ? \"none\" : \"1px solid var(--border-control)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: tinted ? `var(--cat-${hue})` : \"var(--text-body)\", flexShrink: 0, boxSizing: \"border-box\", ...style }}>\n <Icon name={icon} size={t.icon} strokeWidth={2} />\n </span>\n );\n}\n","display/Badge.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst badgeTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', pending: 'var(--dot-neutral)', info: 'var(--action-primary)' }\n// StatusBadge — circular status badge with white ring (node error/warning dots, step icons).\nconst props = defineProps({ tone: { type: String, default: 'danger' }, icon: String, size: { type: Number, default: 20 }, ring: { type: Boolean, default: true }, style: Object })\nconst iconSize = computed(() => Math.round(props.size * .6))\n</script>\n<template>\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 v-if=\"icon\" :name=\"icon\" :size=\"iconSize\" :stroke-width=\"2.5\" />\n </span>\n</template>\n\n<!-- Badge — inline tinted text badge (table statuses: Completed / Failed / Partial). -->\n<!--\n<script setup name=\"Badge\">\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: 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 }\"><slot /></span>\n</template>\n-->\n\n<!-- IconTile — category icon tile — the signature tinted square. kind: palette 40/r14 · node 48/r16 · row 32/r8 (hairline). -->\n<!--\n<script setup name=\"IconTile\">\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\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: { type: String, default: 'source' }, kind: { type: String, default: 'palette' }, style: Object })\nconst t = computed(() => dims[props.kind])\nconst tinted = computed(() => props.kind !== 'row')\n</script>\n<template>\n <span :style=\"{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : 'var(--bg-surface)', border: tinted ? 'none' : '1px solid var(--border-control)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: tinted ? `var(--cat-${hue})` : 'var(--text-body)', flexShrink: 0, boxSizing: 'border-box', ...style }\">\n <Icon :name=\"icon\" :size=\"t.icon\" :stroke-width=\"2\" />\n </span>\n</template>\n-->\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\n<!-- StatCard — dashboard stat card: muted 13px label + light 24px number + optional chart slot. -->\n<!--\n<script setup name=\"StatCard\">\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: 12, ...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-->\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: 1, alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }\" />\n <div v-else-if=\"!label\" :style=\"{ height: 1, background: 'var(--border-subtle)', ...style }\" />\n <div v-else :style=\"{ display: 'flex', alignItems: 'center', gap: 12, ...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-mono)\", 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: 3, ...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-mono);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: 20, letterSpacing: \"var(--tracking-xl)\", 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: 4, ...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\n<!-- Donut — ring/donut chart. segments: [{value, color}]; centered light numeral. -->\n<!--\n<script setup name=\"Donut\">\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: 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)\" :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:20px;letter-spacing:var(--tracking-xl);color:var(--text-primary)\">{{ label != null ? label : `${value}%` }}</div>\n </div>\n</template>\n-->\n\n<!-- AreaChart — area line chart (Revenue Trend): 1.22px blue line + fading gradient fill. points: 0–100 values. -->\n<!--\n<script setup name=\"AreaChart\">\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-->\n\n<!-- MiniBars — mini bar chart used in stat cards. bars: 0–100 values. -->\n<!--\n<script setup name=\"MiniBars\">\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-->\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). */\nexport function Tag({ children, onRemove, tone = \"neutral\", style }) {\n const t = tones[tone] || tones.neutral;\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 4, height: 22, 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)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\", ...style }}>\n {children}\n {onRemove ? <button type=\"button\" onClick={onRemove} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", opacity: .7, display: \"flex\", padding: 0 }}><Icon name=\"x\" size={11} /></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).\nconst props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, style: Object })\ndefineEmits(['remove'])\nconst t = computed(() => tones[props.tone] || tones.neutral)\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', gap: 4, height: 22, 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)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', ...style }\">\n <slot />\n <button v-if=\"removable\" type=\"button\" @click=\"$emit('remove')\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;opacity:.7;display:flex;padding:0\"><Icon name=\"x\" :size=\"11\" /></button>\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: Math.max(9, Math.round(size * .4)), 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: Math.max(9, Math.round(size * .38)) }}>+{rest}</span> : null}\n </div>\n );\n}\n","display/Avatar.vue":"<script setup>\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: size, height: size, borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }\" />\n <span v-else :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: Math.max(9, Math.round(size * .4)), 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 (hairline). */\nexport function IconTile({ icon, hue = \"source\", kind = \"palette\", style }) {\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 tinted = kind !== \"row\";\n return (\n <span style={{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : \"var(--bg-surface)\", border: tinted ? \"none\" : \"1px solid var(--border-control)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: tinted ? `var(--cat-${hue})` : \"var(--text-body)\", flexShrink: 0, boxSizing: \"border-box\", ...style }}>\n <Icon name={icon} size={t.icon} strokeWidth={2} />\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: 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 }\"><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: 1, alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }\" />\n <div v-else-if=\"!label\" :style=\"{ height: 1, background: 'var(--border-subtle)', ...style }\" />\n <div v-else :style=\"{ display: 'flex', alignItems: 'center', gap: 12, ...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-mono)\", 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: 3, ...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-mono);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: 20, letterSpacing: \"var(--tracking-xl)\", 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: 4, ...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). */\nexport function Tag({ children, onRemove, tone = \"neutral\", style }) {\n const t = tones[tone] || tones.neutral;\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 4, height: 22, 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)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\", ...style }}>\n {children}\n {onRemove ? <button type=\"button\" onClick={onRemove} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", opacity: .7, display: \"flex\", padding: 0 }}><Icon name=\"x\" size={11} /></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).\nconst props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, style: Object })\ndefineEmits(['remove'])\nconst t = computed(() => tones[props.tone] || tones.neutral)\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', gap: 4, height: 22, 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)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', ...style }\">\n <slot />\n <button v-if=\"removable\" type=\"button\" @click=\"$emit('remove')\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;opacity:.7;display:flex;padding:0\"><Icon name=\"x\" :size=\"11\" /></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 { 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 ? -8 : 0, boxShadow: 'var(--ring-surface)' }\" />\n <span v-if=\"rest > 0\" :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: Math.max(9, Math.round(size * .38)) }\">+{{ rest }}</span>\n </div>\n</template>\n","display/Donut.vue":"<script setup>\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: 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)\" :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:20px;letter-spacing:var(--tracking-xl);color:var(--text-primary)\">{{ label != null ? label : `${value}%` }}</div>\n </div>\n</template>\n","display/IconTile.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\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: { type: String, default: 'source' }, kind: { type: String, default: 'palette' }, style: Object })\nconst t = computed(() => dims[props.kind])\nconst tinted = computed(() => props.kind !== 'row')\n</script>\n<template>\n <span :style=\"{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : 'var(--bg-surface)', border: tinted ? 'none' : '1px solid var(--border-control)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: tinted ? `var(--cat-${hue})` : 'var(--text-body)', flexShrink: 0, boxSizing: 'border-box', ...style }\">\n <Icon :name=\"icon\" :size=\"t.icon\" :stroke-width=\"2\" />\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: 12, ...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 +1 @@
|
|
|
1
|
-
window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"feedback/Accordion.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open`+`onToggle`, or uncontrolled via `defaultOpen`. */\nexport function Accordion({ title, subtitle, defaultOpen, open: openProp, onToggle, children, style }) {\n const [openState, setOpenState] = React.useState(!!defaultOpen);\n const open = openProp != null ? openProp : openState;\n const toggle = () => (onToggle ? onToggle(!open) : setOpenState(!open));\n return (\n <div style={{ borderBottom: \"1px solid var(--border-card)\", ...style }}>\n <div onClick={toggle} style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"14px 4px\", cursor: \"pointer\" }}>\n <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\", transform: open ? \"rotate(90deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-right\" size={14} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n </div>\n {open ? <div style={{ padding: \"0 4px 16px 28px\" }}>{children}</div> : null}\n </div>\n );\n}\n","feedback/Accordion.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open` prop + `toggle` emit, or uncontrolled via `defaultOpen`.\nconst props = defineProps({ title: String, subtitle: String, defaultOpen: Boolean, open: { type: Boolean, default: null }, style: Object })\nconst emit = defineEmits(['toggle'])\nconst openState = ref(!!props.defaultOpen)\nconst open = computed(() => props.open != null ? props.open : openState.value)\nfunction toggle() { if (props.open != null) emit('toggle', !open.value); else openState.value = !openState.value }\n</script>\n<template>\n <div :style=\"{ borderBottom: '1px solid var(--border-card)', ...style }\">\n <div @click=\"toggle\" style=\"display:flex;align-items:center;gap:10px;padding:14px 4px;cursor:pointer\">\n <span :style=\"{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-right\" :size=\"14\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n </div>\n <div v-if=\"open\" style=\"padding:0 4px 16px 28px\"><slot /></div>\n </div>\n</template>\n","feedback/ActivityItem.jsx":"import React from \"react\";\nimport { IconTile } from \"../display/Badge.jsx\";\n\n/** Activity feed row: 32px icon tile + 13px/500 title + 12px meta + optional right action. */\nexport function ActivityItem({ icon = \"file-text\", title, meta, action, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"12px 16px\", ...style }}>\n <IconTile icon={icon} kind=\"row\" style={{ background: \"var(--info-bg)\", border: \"none\", color: \"var(--info-fg)\" }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\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 {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {action ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\", cursor: \"pointer\" }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Compact dot activity row (Recent Activities card): 8px status dot + 12px text + 11px time. */\nexport function DotActivity({ tone = \"success\", text, time, style }) {\n const tones = { success: \"var(--success)\", warning: \"var(--warning)\", danger: \"var(--danger)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n return (\n <div style={{ display: \"flex\", gap: 8, alignItems: \"flex-start\", ...style }}>\n <span style={{ width: 8, height: 8, borderRadius: \"var(--radius-full)\", background: tones[tone], marginTop: 5, flexShrink: 0 }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-heading)\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>{text}</span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{time}</span>\n </div>\n </div>\n );\n}\n\n/** Mono log lines (\"[14:30] Task started: Import\") — Menlo 11px on inset panel. */\nexport function LogViewer({ lines = [], height, style }) {\n return (\n <div style={{ background: \"var(--bg-inset)\", borderRadius: \"var(--radius-md)\", border: \"1px solid var(--border-subtle)\", padding: \"10px 12px\", display: \"flex\", flexDirection: \"column\", gap: 6, overflowY: \"auto\", height, boxSizing: \"border-box\", ...style }}>\n {lines.map((l, i) => <span key={i} style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: typeof l === \"object\" ? l.color || \"var(--text-body)\" : \"var(--text-body)\", whiteSpace: \"pre\" }}>{typeof l === \"object\" ? l.text : l}</span>)}\n </div>\n );\n}\n","feedback/ActivityItem.vue":"<script setup>\nimport { IconTile } from '../display/Badge.vue'\n// Activity feed row: 32px icon tile + 13px/500 title + 12px meta + optional right action.\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, action: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', ...style }\">\n <IconTile :icon=\"icon\" kind=\"row\" style=\"background:var(--info-bg);border:none;color:var(--info-fg)\" />\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\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 <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <span v-if=\"action\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-secondary);cursor:pointer\">{{ action }}</span>\n </div>\n</template>\n\n<!-- DotActivity — compact dot activity row (Recent Activities card): 8px status dot + 12px text + 11px time. -->\n<!--\n<script setup name=\"DotActivity\">\nimport { computed } from 'vue'\nconst tonesMap = { success: 'var(--success)', warning: 'var(--warning)', danger: 'var(--danger)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\nconst props = defineProps({ tone: { type: String, default: 'success' }, text: String, time: String, style: Object })\nconst dot = computed(() => tonesMap[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: 8, alignItems: 'flex-start', ...style }\">\n <span :style=\"{ width: 8, height: 8, borderRadius: 'var(--radius-full)', background: dot, marginTop: 5, flexShrink: 0 }\" />\n <div style=\"display:flex;flex-direction:column;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-heading);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">{{ text }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted)\">{{ time }}</span>\n </div>\n </div>\n</template>\n-->\n\n<!-- LogViewer — mono log lines (\"[14:30] Task started: Import\") — Menlo 11px on inset panel. -->\n<!--\n<script setup name=\"LogViewer\">\ndefineProps({ lines: { type: Array, default: () => [] }, height: [Number, String], style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-inset)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-subtle)', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 6, overflowY: 'auto', height, boxSizing: 'border-box', ...style }\">\n <span v-for=\"(l, i) in lines\" :key=\"i\" :style=\"{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', color: typeof l === 'object' ? (l.color || 'var(--text-body)') : 'var(--text-body)', whiteSpace: 'pre' }\">{{ typeof l === 'object' ? l.text : l }}</span>\n </div>\n</template>\n-->\n","feedback/Alert.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst alertTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Inline banner — tinted, no border, 13px title + 12px body. */\nexport function Alert({ tone = \"info\", title, children, action, style }) {\n const t = alertTones[tone];\n return (\n <div style={{ display: \"flex\", gap: 10, alignItems: \"flex-start\", background: t.bg, borderRadius: \"var(--radius-md)\", padding: \"10px 12px\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", marginTop: 1 }}><Icon name={t.icon} size={15} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, flex: 1, minWidth: 0 }}>\n {title ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: t.fg }}>{title}</span> : null}\n {children ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{children}</span> : null}\n </div>\n {action}\n </div>\n );\n}\n\n/** Empty state — 22px muted glyph, 13px/500 title, 12px hint, optional action. */\nexport function EmptyState({ icon = \"file-text\", title, hint, action, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 6, padding: \"40px 24px\", textAlign: \"center\", ...style }}>\n <span style={{ color: \"var(--text-muted)\", marginBottom: 6 }}><Icon name={icon} size={22} strokeWidth={1.5} /></span>\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 {hint ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\", maxWidth: 320 }}>{hint}</span> : null}\n {action ? <span style={{ paddingTop: 10 }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Loading placeholder block. */\nexport function Skeleton({ width = \"100%\", height = 12, radius = \"var(--radius-sm)\", style }) {\n return <span style={{ display: \"block\", width, height, borderRadius: radius, background: \"var(--skeleton)\", animation: \"kb-pulse 1.4s var(--ease-in-out) infinite\", ...style }} />;\n}\n\n/** Indeterminate spinner (task-running affordance). */\nexport function Spinner({ size = 16, color = \"var(--action-primary)\", style }) {\n return (\n <span style={{ display: \"inline-block\", width: size, height: size, borderRadius: \"var(--radius-full)\", border: `2px solid var(--track)`, borderTopColor: color, animation: \"kb-spin .7s linear infinite\", ...style }} />\n );\n}\n","feedback/Alert.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst alertTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Alert — inline banner — tinted, no border, 13px title + 12px body.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, style: Object })\nconst t = computed(() => alertTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: 10, alignItems: 'flex-start', background: t.bg, borderRadius: 'var(--radius-md)', padding: '10px 12px', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', marginTop: 1 }\"><Icon :name=\"t.icon\" :size=\"15\" /></span>\n <div style=\"display:flex;flex-direction:column;gap:2px;flex:1;min-width:0\">\n <span v-if=\"title\" :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: t.fg }\">{{ title }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\"><slot /></span>\n </div>\n <slot name=\"action\" />\n </div>\n</template>\n\n<!-- EmptyState — 22px muted glyph, 13px/500 title, 12px hint, optional action. -->\n<!--\n<script setup name=\"EmptyState\">\nimport Icon from '../icons/Icon.vue'\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, hint: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 6, padding: '40px 24px', textAlign: 'center', ...style }\">\n <span style=\"color:var(--text-muted);margin-bottom:6px\"><Icon :name=\"icon\" :size=\"22\" :stroke-width=\"1.5\" /></span>\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 <span v-if=\"hint\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary);max-width:320px\">{{ hint }}</span>\n <span v-if=\"$slots.action\" style=\"padding-top:10px\"><slot name=\"action\" /></span>\n </div>\n</template>\n-->\n\n<!-- Skeleton — loading placeholder block. -->\n<!--\n<script setup name=\"Skeleton\">\ndefineProps({ width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: 12 }, radius: { type: String, default: 'var(--radius-sm)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'block', width, height, borderRadius: radius, background: 'var(--skeleton)', animation: 'kb-pulse 1.4s var(--ease-in-out) infinite', ...style }\" />\n</template>\n-->\n\n<!-- Spinner — indeterminate spinner (task-running affordance). -->\n<!--\n<script setup name=\"Spinner\">\ndefineProps({ size: { type: Number, default: 16 }, color: { type: String, default: 'var(--action-primary)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-block', width: size, height: size, borderRadius: 'var(--radius-full)', border: '2px solid var(--track)', borderTopColor: color, animation: 'kb-spin .7s linear infinite', ...style }\" />\n</template>\n-->\n","feedback/Banner.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\nconst bannerTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Page-level notification banner (Carbon/SLDS scoped notification): full-width tinted strip with icon, message, optional inline action link and dismiss ×. Distinct from Alert (inline, in-card) and Toast (floating). */\nexport function Banner({ tone = \"info\", title, action, onAction, dismissible, onDismiss, children, style }) {\n const t = bannerTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: \"10px 12px 10px 14px\", boxSizing: \"border-box\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", flexShrink: 0 }}><Icon name={t.icon} size={16} /></span>\n <span style={{ flex: 1, minWidth: 0, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>\n {title && <strong style={{ fontWeight: \"var(--weight-medium)\", color: t.fg, paddingRight: 6 }}>{title}</strong>}{children}\n </span>\n {action && <button type=\"button\" onClick={onAction} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: 0, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", color: t.fg, textDecoration: \"underline\", whiteSpace: \"nowrap\", flexShrink: 0 }}>{action}</button>}\n {dismissible && <IconButton icon=\"x\" size=\"xs\" muted label=\"Dismiss\" onClick={onDismiss} />}\n </div>\n );\n}\n","feedback/Banner.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst bannerTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Page-level notification banner: tinted full-width strip, left accent bar, inline action link, dismiss ×.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, action: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['action', 'dismiss'])\nconst t = computed(() => bannerTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: '10px', background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: '10px 12px 10px 14px', boxSizing: 'border-box', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', flexShrink: 0 }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <span style=\"flex:1;min-width:0;font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\">\n <strong v-if=\"title\" :style=\"{ fontWeight: 'var(--weight-medium)', color: t.fg, paddingRight: '6px' }\">{{ title }}</strong><slot />\n </span>\n <button v-if=\"action\" type=\"button\" @click=\"emit('action')\" :style=\"{ border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-sm)', color: t.fg, textDecoration: 'underline', whiteSpace: 'nowrap', flexShrink: 0 }\">{{ action }}</button>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"xs\" muted label=\"Dismiss\" @click=\"emit('dismiss')\" />\n </div>\n</template>\n","feedback/ContextMenu.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. */\nexport function ContextMenu({ items = [], onSelect, style }) {\n const [hover, setHover] = React.useState(-1);\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-menu)\", padding: 4, minWidth: 148, display: \"inline-flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => it.divider ? <div key={i} style={{ height: 1, background: \"var(--border-subtle)\", margin: \"4px 0\" }} /> : (\n <button key={i} type=\"button\" onClick={() => onSelect && onSelect(it.label)}\n onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}\n style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"7px 10px\", borderRadius: \"var(--radius-md)\", border: \"none\", cursor: \"pointer\", background: hover === i ? \"var(--bg-hover)\" : \"transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: it.danger ? \"var(--danger)\" : \"var(--text-heading)\", textAlign: \"left\" }}>\n {it.icon ? <Icon name={it.icon} size={15} /> : null}{it.label}\n </button>\n ))}\n </div>\n );\n}\n","feedback/ContextMenu.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\nconst emit = defineEmits(['select'])\nconst hover = ref(-1)\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-menu)', padding: 4, minWidth: 148, display: 'inline-flex', flexDirection: 'column', ...style }\">\n <template v-for=\"(it, i) in items\" :key=\"i\">\n <div v-if=\"it.divider\" style=\"height:1px;background:var(--border-subtle);margin:4px 0\" />\n <button v-else type=\"button\" @click=\"emit('select', it.label)\" @mouseenter=\"hover = i\" @mouseleave=\"hover = -1\"\n :style=\"{ display: 'flex', alignItems: 'center', gap: 10, padding: '7px 10px', borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', background: hover === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: it.danger ? 'var(--danger)' : 'var(--text-heading)', textAlign: 'left' }\">\n <Icon v-if=\"it.icon\" :name=\"it.icon\" :size=\"15\" />{{ it.label }}\n </button>\n </template>\n </div>\n</template>\n","feedback/Drawer.jsx":"import React from \"react\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Right slide-out panel over a scrim: 17.5px title + subtitle, scrollable body, right-aligned footer slot. inline=true renders it in-flow for embedding in cards/demos. */\nexport function Drawer({ open = true, title, subtitle, width = 400, inline, height, footer, onClose, children, style }) {\n if (!open) return null;\n const panel = (\n <div style={{ width, maxWidth: \"100%\", height: inline ? (height || 420) : \"100%\", background: \"var(--bg-raised)\", borderLeft: \"1px solid var(--border-card)\", boxShadow: inline ? \"var(--shadow-menu)\" : \"-8px 0 24px rgba(0,0,0,.12)\", display: \"flex\", flexDirection: \"column\", boxSizing: \"border-box\", ...(inline ? { border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\" } : null), ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"16px 16px 12px\", borderBottom: \"1px solid var(--border-card)\", flexShrink: 0 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span>}\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" onClick={onClose} />\n </div>\n <div style={{ flex: 1, overflowY: \"auto\", padding: 16 }}>{children}</div>\n {footer && <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, padding: \"12px 16px\", borderTop: \"1px solid var(--border-card)\", flexShrink: 0 }}>{footer}</div>}\n </div>\n );\n if (inline) return panel;\n return (\n <div onClick={onClose} style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", justifyContent: \"flex-end\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }}>\n <div onClick={(e) => e.stopPropagation()} style={{ height: \"100%\", animation: \"kb-slide-in var(--dur) var(--ease-out)\" }}>{panel}</div>\n </div>\n );\n}\n","feedback/Drawer.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\n// Right slide-out panel over a scrim; inline=true renders in-flow for demos. Header, scrollable body, footer slot.\ndefineProps({ open: { type: Boolean, default: true }, title: String, subtitle: String, width: { type: Number, default: 400 }, inline: Boolean, height: { type: [Number, String], default: 420 }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <component :is=\"inline ? 'div' : 'teleport'\" v-if=\"open\" v-bind=\"inline ? {} : { to: 'body' }\">\n <div v-if=\"!inline\" @click=\"emit('close')\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;justify-content:flex-end;animation:kb-fade-in var(--dur-fast) var(--ease-out)\">\n <div @click.stop style=\"height:100%;animation:kb-slide-in var(--dur) var(--ease-out)\"><DrawerPanel /></div>\n </div>\n <div :style=\"{ width: width + 'px', height: inline ? (typeof height === 'number' ? height + 'px' : height) : '100%', background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: inline ? 'var(--radius-lg)' : 0, boxShadow: 'var(--shadow-menu)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:16px 16px 12px;border-bottom:1px solid var(--border-card);flex-shrink:0\">\n <div style=\"display:flex;flex-direction:column;gap:3px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" @click=\"emit('close')\" />\n </div>\n <div style=\"flex:1;overflow-y:auto;padding:16px\"><slot /></div>\n <div v-if=\"$slots.footer\" style=\"display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border-card);flex-shrink:0\"><slot name=\"footer\" /></div>\n </div>\n </component>\n</template>\n","feedback/Menu.jsx":"import React from \"react\";\nimport { ContextMenu } from \"./ContextMenu.jsx\";\n\n/** Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click. */\nexport function Menu({ trigger, items = [], onSelect, align = \"start\", style }) {\n const [open, setOpen] = React.useState(false);\n const ref = React.useRef(null);\n React.useEffect(() => {\n if (!open) return;\n const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };\n document.addEventListener(\"mousedown\", onDoc);\n return () => document.removeEventListener(\"mousedown\", onDoc);\n }, [open]);\n return (\n <div ref={ref} style={{ position: \"relative\", display: \"inline-flex\", ...style }}>\n <span onClick={() => setOpen((o) => !o)}>{trigger}</span>\n {open ? (\n <div style={{ position: \"absolute\", top: \"calc(100% + 6px)\", [align === \"end\" ? \"right\" : \"left\"]: 0, zIndex: \"var(--z-menu)\" }}>\n <ContextMenu items={items} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />\n </div>\n ) : null}\n </div>\n );\n}\n","feedback/Menu.vue":"<script setup>\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport ContextMenu from './ContextMenu.vue'\n// Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click.\nconst props = defineProps({ items: { type: Array, default: () => [] }, align: { type: String, default: 'start' }, style: Object })\nconst emit = defineEmits(['select'])\nconst open = ref(false)\nconst root = ref(null)\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\nfunction select(l) { open.value = false; emit('select', l) }\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', display: 'inline-flex', ...style }\">\n <span @click=\"open = !open\"><slot name=\"trigger\" /></span>\n <div v-if=\"open\" :style=\"{ position: 'absolute', top: 'calc(100% + 6px)', [align === 'end' ? 'right' : 'left']: 0, zIndex: 'var(--z-menu)' }\">\n <ContextMenu :items=\"items\" @select=\"select\" />\n </div>\n </div>\n</template>\n","feedback/Modal.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Hover tooltip — 11px, inverted surface, 6px offset. Wraps its children. */\nexport function Tooltip({ label, placement = \"top\", children, style }) {\n const [show, setShow] = React.useState(false);\n const pos = {\n top: { bottom: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginBottom: 6 },\n bottom: { top: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginTop: 6 },\n left: { right: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginRight: 6 },\n right: { left: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginLeft: 6 },\n }[placement];\n return (\n <span style={{ position: \"relative\", display: \"inline-flex\", ...style }} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n {show ? (\n <span style={{ position: \"absolute\", zIndex: \"var(--z-tooltip)\", whiteSpace: \"nowrap\", background: \"var(--tooltip-bg)\", color: \"var(--tooltip-fg)\", borderRadius: \"var(--radius-md)\", padding: \"5px 8px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", boxShadow: \"var(--shadow-menu)\", pointerEvents: \"none\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\", ...pos }}>{label}</span>\n ) : null}\n </span>\n );\n}\n\n/** Centered modal dialog with scrim. width defaults to 440. */\nexport function Modal({ open, title, subtitle, onClose, footer, width = 440, children, style }) {\n if (!open) return null;\n return (\n <div style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }} onClick={onClose}>\n <div onClick={(e) => e.stopPropagation()}\n style={{ width, maxWidth: \"92vw\", background: \"var(--bg-raised)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-modal)\", animation: \"kb-rise var(--dur) var(--ease-out)\", ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"17px 17px 0\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n <IconButton icon=\"x\" size=\"md\" onClick={onClose} label=\"Close\" />\n </div>\n {children ? <div style={{ padding: \"16px 17px 0\" }}>{children}</div> : null}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, padding: 17 }}>{footer}</div>\n </div>\n </div>\n );\n}\n\nconst toastTones = { success: { icon: \"circle-check\", color: \"var(--success-strong)\" }, danger: { icon: \"triangle-alert\", color: \"var(--danger)\" }, warning: { icon: \"triangle-alert\", color: \"var(--warning)\" }, info: { icon: \"info\", color: \"var(--action-primary)\" } };\n\n/** Single toast row. */\nexport function Toast({ tone = \"success\", title, meta, onClose, style }) {\n const t = toastTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", gap: 10, minWidth: 280, maxWidth: 380, background: \"var(--bg-raised)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\", boxShadow: \"var(--shadow-menu)\", padding: \"12px 12px 12px 14px\", animation: \"kb-toast-in var(--dur) var(--ease-out)\", ...style }}>\n <span style={{ color: t.color, display: \"inline-flex\", marginTop: 1 }}><Icon name={t.icon} size={16} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\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 {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {onClose ? <IconButton icon=\"x\" size=\"xs\" muted onClick={onClose} label=\"Dismiss\" /> : null}\n </div>\n );\n}\n\n/** Fixed bottom-right stack of toasts. */\nexport function ToastStack({ toasts = [], onDismiss, style }) {\n return (\n <div style={{ position: \"fixed\", right: 24, bottom: 24, zIndex: \"var(--z-toast)\", display: \"flex\", flexDirection: \"column\", gap: 8, ...style }}>\n {toasts.map((t) => <Toast key={t.id} tone={t.tone} title={t.title} meta={t.meta} onClose={() => onDismiss && onDismiss(t.id)} />)}\n </div>\n );\n}\n","feedback/Modal.vue":"<script setup>\nimport { ref, computed } from 'vue'\n// Tooltip — hover tooltip — 11px, inverted surface, 6px offset. Wraps its children.\nconst props = defineProps({ label: String, placement: { type: String, default: 'top' }, style: Object })\nconst show = ref(false)\nconst pos = computed(() => ({\n top: { bottom: '100%', left: '50%', transform: 'translateX(-50%)', marginBottom: 6 },\n bottom: { top: '100%', left: '50%', transform: 'translateX(-50%)', marginTop: 6 },\n left: { right: '100%', top: '50%', transform: 'translateY(-50%)', marginRight: 6 },\n right: { left: '100%', top: '50%', transform: 'translateY(-50%)', marginLeft: 6 },\n}[props.placement]))\n</script>\n<template>\n <span :style=\"{ position: 'relative', display: 'inline-flex', ...style }\" @mouseenter=\"show = true\" @mouseleave=\"show = false\">\n <slot />\n <span v-if=\"show\" :style=\"{ position: 'absolute', zIndex: 'var(--z-tooltip)', whiteSpace: 'nowrap', background: 'var(--tooltip-bg)', color: 'var(--tooltip-fg)', borderRadius: 'var(--radius-md)', padding: '5px 8px', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', boxShadow: 'var(--shadow-menu)', pointerEvents: 'none', animation: 'kb-fade-in var(--dur-fast) var(--ease-out)', ...pos }\">{{ label }}</span>\n </span>\n</template>\n\n<!-- Modal — centered modal dialog with scrim. width defaults to 440. -->\n<!--\n<script setup name=\"Modal\">\nimport IconButton from '../buttons/IconButton.vue'\ndefineProps({ open: Boolean, title: String, subtitle: String, width: { type: Number, default: 440 }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <div v-if=\"open\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;align-items:center;justify-content:center;animation:kb-fade-in var(--dur-fast) var(--ease-out)\" @click=\"emit('close')\">\n <div @click.stop :style=\"{ width, maxWidth: '92vw', background: 'var(--bg-raised)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-modal)', animation: 'kb-rise var(--dur) var(--ease-out)', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:17px 17px 0\">\n <div style=\"display:flex;flex-direction:column;gap:4px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" @click=\"emit('close')\" />\n </div>\n <div v-if=\"$slots.default\" style=\"padding:16px 17px 0\"><slot /></div>\n <div style=\"display:flex;justify-content:flex-end;gap:8px;padding:17px\"><slot name=\"footer\" /></div>\n </div>\n </div>\n</template>\n-->\n\n<!-- Toast — single toast row. -->\n<!--\n<script setup name=\"Toast\">\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst toastTones = { success: { icon: 'circle-check', color: 'var(--success-strong)' }, danger: { icon: 'triangle-alert', color: 'var(--danger)' }, warning: { icon: 'triangle-alert', color: 'var(--warning)' }, info: { icon: 'info', color: 'var(--action-primary)' } }\nconst props = defineProps({ tone: { type: String, default: 'success' }, title: String, meta: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['close'])\nconst t = computed(() => toastTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', gap: 10, minWidth: 280, maxWidth: 380, background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-menu)', padding: '12px 12px 12px 14px', animation: 'kb-toast-in var(--dur) var(--ease-out)', ...style }\">\n <span :style=\"{ color: t.color, display: 'inline-flex', marginTop: 1 }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\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 <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"xs\" muted label=\"Dismiss\" @click=\"emit('close')\" />\n </div>\n</template>\n-->\n","feedback/Stepper.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline #e5e5e5. */\nexport function Stepper({ steps = [], current = 0, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", ...style }}>\n {steps.map((s, i) => {\n const done = i < current, is = i === current;\n return (\n <React.Fragment key={s.label}>\n {i > 0 ? <div style={{ flex: 1, height: 2, background: \"var(--track)\", marginTop: 15.25, marginLeft: 12, marginRight: 12, minWidth: 24 }} /> : null}\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8, flexShrink: 0 }}>\n <span style={{ width: 32, height: 32, borderRadius: \"var(--radius-full)\", boxSizing: \"border-box\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", background: done ? \"var(--success)\" : is ? \"var(--action-primary)\" : \"var(--bg-surface)\", border: done || is ? \"none\" : \"2px solid var(--track)\", color: done || is ? \"var(--on-accent)\" : \"var(--text-muted)\" }}>\n <Icon name={done ? \"check\" : s.icon || \"file\"} size={16} />\n </span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", fontWeight: is ? \"var(--weight-medium)\" : \"var(--weight-regular)\", color: is ? \"var(--text-heading)\" : \"var(--text-secondary)\", whiteSpace: \"nowrap\" }}>{s.label}</span>\n </div>\n </React.Fragment>\n );\n })}\n </div>\n );\n}\n","feedback/Stepper.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline.\ndefineProps({ steps: { type: Array, default: () => [] }, current: { type: Number, default: 0 }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', ...style }\">\n <template v-for=\"(s, i) in steps\" :key=\"s.label\">\n <div v-if=\"i > 0\" style=\"flex:1;height:2px;background:var(--track);margin-top:15.25px;margin-left:12px;margin-right:12px;min-width:24px\" />\n <div style=\"display:flex;flex-direction:column;align-items:center;gap:8px;flex-shrink:0\">\n <span :style=\"{ width: 32, height: 32, borderRadius: 'var(--radius-full)', boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: i < current ? 'var(--success)' : i === current ? 'var(--action-primary)' : 'var(--bg-surface)', border: (i < current || i === current) ? 'none' : '2px solid var(--track)', color: (i < current || i === current) ? 'var(--on-accent)' : 'var(--text-muted)' }\">\n <Icon :name=\"i < current ? 'check' : (s.icon || 'file')\" :size=\"16\" />\n </span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', fontWeight: i === current ? 'var(--weight-medium)' : 'var(--weight-regular)', color: i === current ? 'var(--text-heading)' : 'var(--text-secondary)', whiteSpace: 'nowrap' }\">{{ s.label }}</span>\n </div>\n </template>\n </div>\n</template>\n","feedback/Timeline.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tlTones = { success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n\n/** Vertical activity timeline (SLDS pattern): icon nodes on a hairline spine, 13px/500 title + 12px meta + optional body, newest first. items: [{icon,tone,title,meta,body}]. */\nexport function Timeline({ items = [], style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => (\n <div key={i} style={{ display: \"flex\", gap: 12 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", flexShrink: 0 }}>\n <span style={{ width: 26, height: 26, borderRadius: \"var(--radius-full)\", background: \"var(--bg-surface)\", border: `1.5px solid ${tlTones[it.tone || \"neutral\"]}`, color: tlTones[it.tone || \"neutral\"], display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", boxSizing: \"border-box\", flexShrink: 0 }}>\n <Icon name={it.icon || \"clock\"} size={13} />\n </span>\n {i < items.length - 1 && <span style={{ width: 1.5, flex: 1, minHeight: 14, background: \"var(--border-control)\" }}></span>}\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, paddingBottom: i < items.length - 1 ? 18 : 0, minWidth: 0, paddingTop: 3 }}>\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)\" }}>{it.title}</span>\n {it.meta && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{it.meta}</span>}\n {it.body && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\", paddingTop: 4 }}>{it.body}</span>}\n </div>\n </div>\n ))}\n </div>\n );\n}\n","feedback/Timeline.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\nconst tlTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\n// Vertical activity timeline: outlined icon nodes on a hairline spine. items: [{icon,tone,title,meta,body}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', ...style }\">\n <div v-for=\"(it, i) in items\" :key=\"i\" style=\"display:flex;gap:12px\">\n <div style=\"display:flex;flex-direction:column;align-items:center;flex-shrink:0\">\n <span :style=\"{ width: '26px', height: '26px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', border: `1.5px solid ${tlTones[it.tone || 'neutral']}`, color: tlTones[it.tone || 'neutral'], display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxSizing: 'border-box' }\"><Icon :name=\"it.icon || 'clock'\" :size=\"13\" /></span>\n <span v-if=\"i < items.length - 1\" style=\"width:1.5px;flex:1;min-height:14px;background:var(--border-control)\" />\n </div>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '2px', paddingBottom: i < items.length - 1 ? '18px' : 0, minWidth: 0, paddingTop: '3px' }\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)\">{{ it.title }}</span>\n <span v-if=\"it.meta\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)\">{{ it.meta }}</span>\n <span v-if=\"it.body\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body);padding-top:4px\">{{ it.body }}</span>\n </div>\n </div>\n </div>\n</template>\n"});
|
|
1
|
+
window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"feedback/Accordion.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open`+`onToggle`, or uncontrolled via `defaultOpen`. */\nexport function Accordion({ title, subtitle, defaultOpen, open: openProp, onToggle, children, style }) {\n const [openState, setOpenState] = React.useState(!!defaultOpen);\n const open = openProp != null ? openProp : openState;\n const toggle = () => (onToggle ? onToggle(!open) : setOpenState(!open));\n return (\n <div style={{ borderBottom: \"1px solid var(--border-card)\", ...style }}>\n <div onClick={toggle} style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"14px 4px\", cursor: \"pointer\" }}>\n <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\", transform: open ? \"rotate(90deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-right\" size={14} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n </div>\n {open ? <div style={{ padding: \"0 4px 16px 28px\" }}>{children}</div> : null}\n </div>\n );\n}\n","feedback/Accordion.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open` prop + `toggle` emit, or uncontrolled via `defaultOpen`.\nconst props = defineProps({ title: String, subtitle: String, defaultOpen: Boolean, open: { type: Boolean, default: null }, style: Object })\nconst emit = defineEmits(['toggle'])\nconst openState = ref(!!props.defaultOpen)\nconst open = computed(() => props.open != null ? props.open : openState.value)\nfunction toggle() { if (props.open != null) emit('toggle', !open.value); else openState.value = !openState.value }\n</script>\n<template>\n <div :style=\"{ borderBottom: '1px solid var(--border-card)', ...style }\">\n <div @click=\"toggle\" style=\"display:flex;align-items:center;gap:10px;padding:14px 4px;cursor:pointer\">\n <span :style=\"{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-right\" :size=\"14\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n </div>\n <div v-if=\"open\" style=\"padding:0 4px 16px 28px\"><slot /></div>\n </div>\n</template>\n","feedback/ActivityItem.jsx":"import React from \"react\";\nimport { IconTile } from \"../display/Badge.jsx\";\n\n/** Activity feed row: 32px icon tile + 13px/500 title + 12px meta + optional right action. */\nexport function ActivityItem({ icon = \"file-text\", title, meta, action, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"12px 16px\", ...style }}>\n <IconTile icon={icon} kind=\"row\" style={{ background: \"var(--info-bg)\", border: \"none\", color: \"var(--info-fg)\" }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\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 {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {action ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\", cursor: \"pointer\" }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Compact dot activity row (Recent Activities card): 8px status dot + 12px text + 11px time. */\nexport function DotActivity({ tone = \"success\", text, time, style }) {\n const tones = { success: \"var(--success)\", warning: \"var(--warning)\", danger: \"var(--danger)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n return (\n <div style={{ display: \"flex\", gap: 8, alignItems: \"flex-start\", ...style }}>\n <span style={{ width: 8, height: 8, borderRadius: \"var(--radius-full)\", background: tones[tone], marginTop: 5, flexShrink: 0 }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-heading)\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>{text}</span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{time}</span>\n </div>\n </div>\n );\n}\n\n/** Mono log lines (\"[14:30] Task started: Import\") — Menlo 11px on inset panel. */\nexport function LogViewer({ lines = [], height, style }) {\n return (\n <div style={{ background: \"var(--bg-inset)\", borderRadius: \"var(--radius-md)\", border: \"1px solid var(--border-subtle)\", padding: \"10px 12px\", display: \"flex\", flexDirection: \"column\", gap: 6, overflowY: \"auto\", height, boxSizing: \"border-box\", ...style }}>\n {lines.map((l, i) => <span key={i} style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: typeof l === \"object\" ? l.color || \"var(--text-body)\" : \"var(--text-body)\", whiteSpace: \"pre\" }}>{typeof l === \"object\" ? l.text : l}</span>)}\n </div>\n );\n}\n","feedback/ActivityItem.vue":"<script setup>\nimport IconTile from '../display/Badge.vue'\n// Activity feed row: 32px icon tile + 13px/500 title + 12px meta + optional right action.\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, action: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', ...style }\">\n <IconTile :icon=\"icon\" kind=\"row\" style=\"background:var(--info-bg);border:none;color:var(--info-fg)\" />\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\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 <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <span v-if=\"action\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-secondary);cursor:pointer\">{{ action }}</span>\n </div>\n</template>\n","feedback/Alert.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst alertTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Inline banner — tinted, no border, 13px title + 12px body. */\nexport function Alert({ tone = \"info\", title, children, action, style }) {\n const t = alertTones[tone];\n return (\n <div style={{ display: \"flex\", gap: 10, alignItems: \"flex-start\", background: t.bg, borderRadius: \"var(--radius-md)\", padding: \"10px 12px\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", marginTop: 1 }}><Icon name={t.icon} size={15} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, flex: 1, minWidth: 0 }}>\n {title ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: t.fg }}>{title}</span> : null}\n {children ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{children}</span> : null}\n </div>\n {action}\n </div>\n );\n}\n\n/** Empty state — 22px muted glyph, 13px/500 title, 12px hint, optional action. */\nexport function EmptyState({ icon = \"file-text\", title, hint, action, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 6, padding: \"40px 24px\", textAlign: \"center\", ...style }}>\n <span style={{ color: \"var(--text-muted)\", marginBottom: 6 }}><Icon name={icon} size={22} strokeWidth={1.5} /></span>\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 {hint ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\", maxWidth: 320 }}>{hint}</span> : null}\n {action ? <span style={{ paddingTop: 10 }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Loading placeholder block. */\nexport function Skeleton({ width = \"100%\", height = 12, radius = \"var(--radius-sm)\", style }) {\n return <span style={{ display: \"block\", width, height, borderRadius: radius, background: \"var(--skeleton)\", animation: \"kb-pulse 1.4s var(--ease-in-out) infinite\", ...style }} />;\n}\n\n/** Indeterminate spinner (task-running affordance). */\nexport function Spinner({ size = 16, color = \"var(--action-primary)\", style }) {\n return (\n <span style={{ display: \"inline-block\", width: size, height: size, borderRadius: \"var(--radius-full)\", border: `2px solid var(--track)`, borderTopColor: color, animation: \"kb-spin .7s linear infinite\", ...style }} />\n );\n}\n","feedback/Alert.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst alertTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Alert — inline banner — tinted, no border, 13px title + 12px body.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, style: Object })\nconst t = computed(() => alertTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: 10, alignItems: 'flex-start', background: t.bg, borderRadius: 'var(--radius-md)', padding: '10px 12px', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', marginTop: 1 }\"><Icon :name=\"t.icon\" :size=\"15\" /></span>\n <div style=\"display:flex;flex-direction:column;gap:2px;flex:1;min-width:0\">\n <span v-if=\"title\" :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: t.fg }\">{{ title }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\"><slot /></span>\n </div>\n <slot name=\"action\" />\n </div>\n</template>\n","feedback/Banner.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\nconst bannerTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Page-level notification banner (Carbon/SLDS scoped notification): full-width tinted strip with icon, message, optional inline action link and dismiss ×. Distinct from Alert (inline, in-card) and Toast (floating). */\nexport function Banner({ tone = \"info\", title, action, onAction, dismissible, onDismiss, children, style }) {\n const t = bannerTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: \"10px 12px 10px 14px\", boxSizing: \"border-box\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", flexShrink: 0 }}><Icon name={t.icon} size={16} /></span>\n <span style={{ flex: 1, minWidth: 0, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>\n {title && <strong style={{ fontWeight: \"var(--weight-medium)\", color: t.fg, paddingRight: 6 }}>{title}</strong>}{children}\n </span>\n {action && <button type=\"button\" onClick={onAction} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: 0, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", color: t.fg, textDecoration: \"underline\", whiteSpace: \"nowrap\", flexShrink: 0 }}>{action}</button>}\n {dismissible && <IconButton icon=\"x\" size=\"xs\" muted label=\"Dismiss\" onClick={onDismiss} />}\n </div>\n );\n}\n","feedback/Banner.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst bannerTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Page-level notification banner: tinted full-width strip, left accent bar, inline action link, dismiss ×.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, action: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['action', 'dismiss'])\nconst t = computed(() => bannerTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: '10px', background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: '10px 12px 10px 14px', boxSizing: 'border-box', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', flexShrink: 0 }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <span style=\"flex:1;min-width:0;font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\">\n <strong v-if=\"title\" :style=\"{ fontWeight: 'var(--weight-medium)', color: t.fg, paddingRight: '6px' }\">{{ title }}</strong><slot />\n </span>\n <button v-if=\"action\" type=\"button\" @click=\"emit('action')\" :style=\"{ border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-sm)', color: t.fg, textDecoration: 'underline', whiteSpace: 'nowrap', flexShrink: 0 }\">{{ action }}</button>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"xs\" muted label=\"Dismiss\" @click=\"emit('dismiss')\" />\n </div>\n</template>\n","feedback/ContextMenu.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. */\nexport function ContextMenu({ items = [], onSelect, style }) {\n const [hover, setHover] = React.useState(-1);\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-menu)\", padding: 4, minWidth: 148, display: \"inline-flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => it.divider ? <div key={i} style={{ height: 1, background: \"var(--border-subtle)\", margin: \"4px 0\" }} /> : (\n <button key={i} type=\"button\" onClick={() => onSelect && onSelect(it.label)}\n onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}\n style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"7px 10px\", borderRadius: \"var(--radius-md)\", border: \"none\", cursor: \"pointer\", background: hover === i ? \"var(--bg-hover)\" : \"transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: it.danger ? \"var(--danger)\" : \"var(--text-heading)\", textAlign: \"left\" }}>\n {it.icon ? <Icon name={it.icon} size={15} /> : null}{it.label}\n </button>\n ))}\n </div>\n );\n}\n","feedback/ContextMenu.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\nconst emit = defineEmits(['select'])\nconst hover = ref(-1)\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-menu)', padding: 4, minWidth: 148, display: 'inline-flex', flexDirection: 'column', ...style }\">\n <template v-for=\"(it, i) in items\" :key=\"i\">\n <div v-if=\"it.divider\" style=\"height:1px;background:var(--border-subtle);margin:4px 0\" />\n <button v-else type=\"button\" @click=\"emit('select', it.label)\" @mouseenter=\"hover = i\" @mouseleave=\"hover = -1\"\n :style=\"{ display: 'flex', alignItems: 'center', gap: 10, padding: '7px 10px', borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', background: hover === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: it.danger ? 'var(--danger)' : 'var(--text-heading)', textAlign: 'left' }\">\n <Icon v-if=\"it.icon\" :name=\"it.icon\" :size=\"15\" />{{ it.label }}\n </button>\n </template>\n </div>\n</template>\n","feedback/Drawer.jsx":"import React from \"react\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Right slide-out panel over a scrim: 17.5px title + subtitle, scrollable body, right-aligned footer slot. inline=true renders it in-flow for embedding in cards/demos. */\nexport function Drawer({ open = true, title, subtitle, width = 400, inline, height, footer, onClose, children, style }) {\n if (!open) return null;\n const panel = (\n <div style={{ width, maxWidth: \"100%\", height: inline ? (height || 420) : \"100%\", background: \"var(--bg-raised)\", borderLeft: \"1px solid var(--border-card)\", boxShadow: inline ? \"var(--shadow-menu)\" : \"-8px 0 24px rgba(0,0,0,.12)\", display: \"flex\", flexDirection: \"column\", boxSizing: \"border-box\", ...(inline ? { border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\" } : null), ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"16px 16px 12px\", borderBottom: \"1px solid var(--border-card)\", flexShrink: 0 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span>}\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" onClick={onClose} />\n </div>\n <div style={{ flex: 1, overflowY: \"auto\", padding: 16 }}>{children}</div>\n {footer && <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, padding: \"12px 16px\", borderTop: \"1px solid var(--border-card)\", flexShrink: 0 }}>{footer}</div>}\n </div>\n );\n if (inline) return panel;\n return (\n <div onClick={onClose} style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", justifyContent: \"flex-end\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }}>\n <div onClick={(e) => e.stopPropagation()} style={{ height: \"100%\", animation: \"kb-slide-in var(--dur) var(--ease-out)\" }}>{panel}</div>\n </div>\n );\n}\n","feedback/Drawer.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\n// Right slide-out panel over a scrim; inline=true renders in-flow for demos. Header, scrollable body, footer slot.\ndefineProps({ open: { type: Boolean, default: true }, title: String, subtitle: String, width: { type: Number, default: 400 }, inline: Boolean, height: { type: [Number, String], default: 420 }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <component :is=\"inline ? 'div' : 'teleport'\" v-if=\"open\" v-bind=\"inline ? {} : { to: 'body' }\">\n <div v-if=\"!inline\" @click=\"emit('close')\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;justify-content:flex-end;animation:kb-fade-in var(--dur-fast) var(--ease-out)\">\n <div @click.stop style=\"height:100%;animation:kb-slide-in var(--dur) var(--ease-out)\"><DrawerPanel /></div>\n </div>\n <div :style=\"{ width: width + 'px', height: inline ? (typeof height === 'number' ? height + 'px' : height) : '100%', background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: inline ? 'var(--radius-lg)' : 0, boxShadow: 'var(--shadow-menu)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:16px 16px 12px;border-bottom:1px solid var(--border-card);flex-shrink:0\">\n <div style=\"display:flex;flex-direction:column;gap:3px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" @click=\"emit('close')\" />\n </div>\n <div style=\"flex:1;overflow-y:auto;padding:16px\"><slot /></div>\n <div v-if=\"$slots.footer\" style=\"display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border-card);flex-shrink:0\"><slot name=\"footer\" /></div>\n </div>\n </component>\n</template>\n","feedback/Menu.jsx":"import React from \"react\";\nimport { ContextMenu } from \"./ContextMenu.jsx\";\n\n/** Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click. */\nexport function Menu({ trigger, items = [], onSelect, align = \"start\", style }) {\n const [open, setOpen] = React.useState(false);\n const ref = React.useRef(null);\n React.useEffect(() => {\n if (!open) return;\n const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };\n document.addEventListener(\"mousedown\", onDoc);\n return () => document.removeEventListener(\"mousedown\", onDoc);\n }, [open]);\n return (\n <div ref={ref} style={{ position: \"relative\", display: \"inline-flex\", ...style }}>\n <span onClick={() => setOpen((o) => !o)}>{trigger}</span>\n {open ? (\n <div style={{ position: \"absolute\", top: \"calc(100% + 6px)\", [align === \"end\" ? \"right\" : \"left\"]: 0, zIndex: \"var(--z-menu)\" }}>\n <ContextMenu items={items} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />\n </div>\n ) : null}\n </div>\n );\n}\n","feedback/Menu.vue":"<script setup>\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport ContextMenu from './ContextMenu.vue'\n// Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click.\nconst props = defineProps({ items: { type: Array, default: () => [] }, align: { type: String, default: 'start' }, style: Object })\nconst emit = defineEmits(['select'])\nconst open = ref(false)\nconst root = ref(null)\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\nfunction select(l) { open.value = false; emit('select', l) }\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', display: 'inline-flex', ...style }\">\n <span @click=\"open = !open\"><slot name=\"trigger\" /></span>\n <div v-if=\"open\" :style=\"{ position: 'absolute', top: 'calc(100% + 6px)', [align === 'end' ? 'right' : 'left']: 0, zIndex: 'var(--z-menu)' }\">\n <ContextMenu :items=\"items\" @select=\"select\" />\n </div>\n </div>\n</template>\n","feedback/Modal.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Hover tooltip — 11px, inverted surface, 6px offset. Wraps its children. */\nexport function Tooltip({ label, placement = \"top\", children, style }) {\n const [show, setShow] = React.useState(false);\n const pos = {\n top: { bottom: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginBottom: 6 },\n bottom: { top: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginTop: 6 },\n left: { right: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginRight: 6 },\n right: { left: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginLeft: 6 },\n }[placement];\n return (\n <span style={{ position: \"relative\", display: \"inline-flex\", ...style }} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n {show ? (\n <span style={{ position: \"absolute\", zIndex: \"var(--z-tooltip)\", whiteSpace: \"nowrap\", background: \"var(--tooltip-bg)\", color: \"var(--tooltip-fg)\", borderRadius: \"var(--radius-md)\", padding: \"5px 8px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", boxShadow: \"var(--shadow-menu)\", pointerEvents: \"none\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\", ...pos }}>{label}</span>\n ) : null}\n </span>\n );\n}\n\n/** Centered modal dialog with scrim. width defaults to 440. */\nexport function Modal({ open, title, subtitle, onClose, footer, width = 440, children, style }) {\n if (!open) return null;\n return (\n <div style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }} onClick={onClose}>\n <div onClick={(e) => e.stopPropagation()}\n style={{ width, maxWidth: \"92vw\", background: \"var(--bg-raised)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-modal)\", animation: \"kb-rise var(--dur) var(--ease-out)\", ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"17px 17px 0\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n <IconButton icon=\"x\" size=\"md\" onClick={onClose} label=\"Close\" />\n </div>\n {children ? <div style={{ padding: \"16px 17px 0\" }}>{children}</div> : null}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, padding: 17 }}>{footer}</div>\n </div>\n </div>\n );\n}\n\nconst toastTones = { success: { icon: \"circle-check\", color: \"var(--success-strong)\" }, danger: { icon: \"triangle-alert\", color: \"var(--danger)\" }, warning: { icon: \"triangle-alert\", color: \"var(--warning)\" }, info: { icon: \"info\", color: \"var(--action-primary)\" } };\n\n/** Single toast row. */\nexport function Toast({ tone = \"success\", title, meta, onClose, style }) {\n const t = toastTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", gap: 10, minWidth: 280, maxWidth: 380, background: \"var(--bg-raised)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\", boxShadow: \"var(--shadow-menu)\", padding: \"12px 12px 12px 14px\", animation: \"kb-toast-in var(--dur) var(--ease-out)\", ...style }}>\n <span style={{ color: t.color, display: \"inline-flex\", marginTop: 1 }}><Icon name={t.icon} size={16} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\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 {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {onClose ? <IconButton icon=\"x\" size=\"xs\" muted onClick={onClose} label=\"Dismiss\" /> : null}\n </div>\n );\n}\n\n/** Fixed bottom-right stack of toasts. */\nexport function ToastStack({ toasts = [], onDismiss, style }) {\n return (\n <div style={{ position: \"fixed\", right: 24, bottom: 24, zIndex: \"var(--z-toast)\", display: \"flex\", flexDirection: \"column\", gap: 8, ...style }}>\n {toasts.map((t) => <Toast key={t.id} tone={t.tone} title={t.title} meta={t.meta} onClose={() => onDismiss && onDismiss(t.id)} />)}\n </div>\n );\n}\n","feedback/Modal.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\ndefineProps({ open: Boolean, title: String, subtitle: String, width: { type: Number, default: 440 }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <div v-if=\"open\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;align-items:center;justify-content:center;animation:kb-fade-in var(--dur-fast) var(--ease-out)\" @click=\"emit('close')\">\n <div @click.stop :style=\"{ width, maxWidth: '92vw', background: 'var(--bg-raised)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-modal)', animation: 'kb-rise var(--dur) var(--ease-out)', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:17px 17px 0\">\n <div style=\"display:flex;flex-direction:column;gap:4px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" @click=\"emit('close')\" />\n </div>\n <div v-if=\"$slots.default\" style=\"padding:16px 17px 0\"><slot /></div>\n <div style=\"display:flex;justify-content:flex-end;gap:8px;padding:17px\"><slot name=\"footer\" /></div>\n </div>\n </div>\n</template>\n","feedback/Stepper.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline #e5e5e5. */\nexport function Stepper({ steps = [], current = 0, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", ...style }}>\n {steps.map((s, i) => {\n const done = i < current, is = i === current;\n return (\n <React.Fragment key={s.label}>\n {i > 0 ? <div style={{ flex: 1, height: 2, background: \"var(--track)\", marginTop: 15.25, marginLeft: 12, marginRight: 12, minWidth: 24 }} /> : null}\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8, flexShrink: 0 }}>\n <span style={{ width: 32, height: 32, borderRadius: \"var(--radius-full)\", boxSizing: \"border-box\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", background: done ? \"var(--success)\" : is ? \"var(--action-primary)\" : \"var(--bg-surface)\", border: done || is ? \"none\" : \"2px solid var(--track)\", color: done || is ? \"var(--on-accent)\" : \"var(--text-muted)\" }}>\n <Icon name={done ? \"check\" : s.icon || \"file\"} size={16} />\n </span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", fontWeight: is ? \"var(--weight-medium)\" : \"var(--weight-regular)\", color: is ? \"var(--text-heading)\" : \"var(--text-secondary)\", whiteSpace: \"nowrap\" }}>{s.label}</span>\n </div>\n </React.Fragment>\n );\n })}\n </div>\n );\n}\n","feedback/Stepper.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline.\ndefineProps({ steps: { type: Array, default: () => [] }, current: { type: Number, default: 0 }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', ...style }\">\n <template v-for=\"(s, i) in steps\" :key=\"s.label\">\n <div v-if=\"i > 0\" style=\"flex:1;height:2px;background:var(--track);margin-top:15.25px;margin-left:12px;margin-right:12px;min-width:24px\" />\n <div style=\"display:flex;flex-direction:column;align-items:center;gap:8px;flex-shrink:0\">\n <span :style=\"{ width: 32, height: 32, borderRadius: 'var(--radius-full)', boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: i < current ? 'var(--success)' : i === current ? 'var(--action-primary)' : 'var(--bg-surface)', border: (i < current || i === current) ? 'none' : '2px solid var(--track)', color: (i < current || i === current) ? 'var(--on-accent)' : 'var(--text-muted)' }\">\n <Icon :name=\"i < current ? 'check' : (s.icon || 'file')\" :size=\"16\" />\n </span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', fontWeight: i === current ? 'var(--weight-medium)' : 'var(--weight-regular)', color: i === current ? 'var(--text-heading)' : 'var(--text-secondary)', whiteSpace: 'nowrap' }\">{{ s.label }}</span>\n </div>\n </template>\n </div>\n</template>\n","feedback/Timeline.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tlTones = { success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n\n/** Vertical activity timeline (SLDS pattern): icon nodes on a hairline spine, 13px/500 title + 12px meta + optional body, newest first. items: [{icon,tone,title,meta,body}]. */\nexport function Timeline({ items = [], style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => (\n <div key={i} style={{ display: \"flex\", gap: 12 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", flexShrink: 0 }}>\n <span style={{ width: 26, height: 26, borderRadius: \"var(--radius-full)\", background: \"var(--bg-surface)\", border: `1.5px solid ${tlTones[it.tone || \"neutral\"]}`, color: tlTones[it.tone || \"neutral\"], display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", boxSizing: \"border-box\", flexShrink: 0 }}>\n <Icon name={it.icon || \"clock\"} size={13} />\n </span>\n {i < items.length - 1 && <span style={{ width: 1.5, flex: 1, minHeight: 14, background: \"var(--border-control)\" }}></span>}\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, paddingBottom: i < items.length - 1 ? 18 : 0, minWidth: 0, paddingTop: 3 }}>\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)\" }}>{it.title}</span>\n {it.meta && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{it.meta}</span>}\n {it.body && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\", paddingTop: 4 }}>{it.body}</span>}\n </div>\n </div>\n ))}\n </div>\n );\n}\n","feedback/Timeline.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\nconst tlTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\n// Vertical activity timeline: outlined icon nodes on a hairline spine. items: [{icon,tone,title,meta,body}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', ...style }\">\n <div v-for=\"(it, i) in items\" :key=\"i\" style=\"display:flex;gap:12px\">\n <div style=\"display:flex;flex-direction:column;align-items:center;flex-shrink:0\">\n <span :style=\"{ width: '26px', height: '26px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', border: `1.5px solid ${tlTones[it.tone || 'neutral']}`, color: tlTones[it.tone || 'neutral'], display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxSizing: 'border-box' }\"><Icon :name=\"it.icon || 'clock'\" :size=\"13\" /></span>\n <span v-if=\"i < items.length - 1\" style=\"width:1.5px;flex:1;min-height:14px;background:var(--border-control)\" />\n </div>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '2px', paddingBottom: i < items.length - 1 ? '18px' : 0, minWidth: 0, paddingTop: '3px' }\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)\">{{ it.title }}</span>\n <span v-if=\"it.meta\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)\">{{ it.meta }}</span>\n <span v-if=\"it.body\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body);padding-top:4px\">{{ it.body }}</span>\n </div>\n </div>\n </div>\n</template>\n","feedback/DotActivity.vue":"<script setup>\nimport { computed } from 'vue'\nconst tonesMap = { success: 'var(--success)', warning: 'var(--warning)', danger: 'var(--danger)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\nconst props = defineProps({ tone: { type: String, default: 'success' }, text: String, time: String, style: Object })\nconst dot = computed(() => tonesMap[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: 8, alignItems: 'flex-start', ...style }\">\n <span :style=\"{ width: 8, height: 8, borderRadius: 'var(--radius-full)', background: dot, marginTop: 5, flexShrink: 0 }\" />\n <div style=\"display:flex;flex-direction:column;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-heading);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">{{ text }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted)\">{{ time }}</span>\n </div>\n </div>\n</template>\n","feedback/EmptyState.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, hint: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 6, padding: '40px 24px', textAlign: 'center', ...style }\">\n <span style=\"color:var(--text-muted);margin-bottom:6px\"><Icon :name=\"icon\" :size=\"22\" :stroke-width=\"1.5\" /></span>\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 <span v-if=\"hint\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary);max-width:320px\">{{ hint }}</span>\n <span v-if=\"$slots.action\" style=\"padding-top:10px\"><slot name=\"action\" /></span>\n </div>\n</template>\n","feedback/LogViewer.vue":"<script setup>\ndefineProps({ lines: { type: Array, default: () => [] }, height: [Number, String], style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-inset)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-subtle)', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 6, overflowY: 'auto', height, boxSizing: 'border-box', ...style }\">\n <span v-for=\"(l, i) in lines\" :key=\"i\" :style=\"{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', color: typeof l === 'object' ? (l.color || 'var(--text-body)') : 'var(--text-body)', whiteSpace: 'pre' }\">{{ typeof l === 'object' ? l.text : l }}</span>\n </div>\n</template>\n","feedback/Skeleton.vue":"<script setup>\ndefineProps({ width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: 12 }, radius: { type: String, default: 'var(--radius-sm)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'block', width, height, borderRadius: radius, background: 'var(--skeleton)', animation: 'kb-pulse 1.4s var(--ease-in-out) infinite', ...style }\" />\n</template>\n","feedback/Spinner.vue":"<script setup>\ndefineProps({ size: { type: Number, default: 16 }, color: { type: String, default: 'var(--action-primary)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-block', width: size, height: size, borderRadius: 'var(--radius-full)', border: '2px solid var(--track)', borderTopColor: color, animation: 'kb-spin .7s linear infinite', ...style }\" />\n</template>\n","feedback/Toast.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst toastTones = { success: { icon: 'circle-check', color: 'var(--success-strong)' }, danger: { icon: 'triangle-alert', color: 'var(--danger)' }, warning: { icon: 'triangle-alert', color: 'var(--warning)' }, info: { icon: 'info', color: 'var(--action-primary)' } }\nconst props = defineProps({ tone: { type: String, default: 'success' }, title: String, meta: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['close'])\nconst t = computed(() => toastTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', gap: 10, minWidth: 280, maxWidth: 380, background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-menu)', padding: '12px 12px 12px 14px', animation: 'kb-toast-in var(--dur) var(--ease-out)', ...style }\">\n <span :style=\"{ color: t.color, display: 'inline-flex', marginTop: 1 }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\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 <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"xs\" muted label=\"Dismiss\" @click=\"emit('close')\" />\n </div>\n</template>\n"});
|