@keyfunc/keybricks-ds 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPLOY.md +204 -0
- package/DESIGN.md +269 -0
- package/INTEGRATION.md +417 -0
- package/SKILL.md +32 -0
- package/_adherence.oxlintrc.json +1469 -0
- package/_scripts/build-index.mjs +80 -0
- package/_scripts/build-tokens.mjs +135 -0
- package/assets/icons/icon-index.js +1 -0
- package/assets/icons/icon-index.json +1 -0
- package/assets/icons/unicons-index.js +1 -0
- package/assets/icons/unicons-index.json +1 -0
- package/assets/keybricks-logo.png +0 -0
- package/components/_cards/accordion.card.html +32 -0
- package/components/_cards/action-chip.card.html +22 -0
- package/components/_cards/activity.card.html +34 -0
- package/components/_cards/alert.card.html +32 -0
- package/components/_cards/app-chrome.card.html +36 -0
- package/components/_cards/banner.card.html +21 -0
- package/components/_cards/brand-visual.card.html +43 -0
- package/components/_cards/breadcrumb.card.html +23 -0
- package/components/_cards/button-1-base.card.html +39 -0
- package/components/_cards/button-2-states.card.html +56 -0
- package/components/_cards/button-3-sizes-icons.card.html +32 -0
- package/components/_cards/button-4-group.card.html +47 -0
- package/components/_cards/canvas-zoom-controls.card.html +19 -0
- package/components/_cards/cards-indicators.card.html +59 -0
- package/components/_cards/charts-1-sparkline.card.html +55 -0
- package/components/_cards/charts-10-gauge.card.html +31 -0
- package/components/_cards/charts-11-funnel.card.html +28 -0
- package/components/_cards/charts-12-treemap.card.html +23 -0
- package/components/_cards/charts-14-radar.card.html +31 -0
- package/components/_cards/charts-15-distribution.card.html +32 -0
- package/components/_cards/charts-16-pareto.card.html +25 -0
- package/components/_cards/charts-17-candlestick.card.html +34 -0
- package/components/_cards/charts-18-sankey.card.html +29 -0
- package/components/_cards/charts-19-chord.card.html +31 -0
- package/components/_cards/charts-2-line.card.html +46 -0
- package/components/_cards/charts-20-sunburst.card.html +38 -0
- package/components/_cards/charts-21-worldmap.card.html +33 -0
- package/components/_cards/charts-22-gantt.card.html +37 -0
- package/components/_cards/charts-23-bar3d.card.html +26 -0
- package/components/_cards/charts-24-pie3d.card.html +32 -0
- package/components/_cards/charts-25-surface3d.card.html +24 -0
- package/components/_cards/charts-26-scatter3d.card.html +24 -0
- package/components/_cards/charts-27-ribbon3d.card.html +23 -0
- package/components/_cards/charts-3-bar.card.html +71 -0
- package/components/_cards/charts-4-barlist.card.html +30 -0
- package/components/_cards/charts-5-donut.card.html +35 -0
- package/components/_cards/charts-6-scatter.card.html +26 -0
- package/components/_cards/charts-7-heatmap.card.html +28 -0
- package/components/_cards/charts-8-waterfall.card.html +25 -0
- package/components/_cards/charts-9-bullet.card.html +31 -0
- package/components/_cards/charts-99-fullpage.card.html +194 -0
- package/components/_cards/checkbox.card.html +34 -0
- package/components/_cards/codeblock.card.html +19 -0
- package/components/_cards/combobox.card.html +25 -0
- package/components/_cards/context-menu.card.html +21 -0
- package/components/_cards/datatable.card.html +48 -0
- package/components/_cards/date-picker.card.html +31 -0
- package/components/_cards/diffvalue.card.html +21 -0
- package/components/_cards/divider.card.html +24 -0
- package/components/_cards/drawer.card.html +30 -0
- package/components/_cards/dropdown-menu.card.html +38 -0
- package/components/_cards/dropzone.card.html +22 -0
- package/components/_cards/empty-state.card.html +29 -0
- package/components/_cards/filter-bar.card.html +34 -0
- package/components/_cards/icon-button.card.html +27 -0
- package/components/_cards/icons-1-core.card.html +49 -0
- package/components/_cards/icons-2-feather.card.html +39 -0
- package/components/_cards/icons-3-unicons.card.html +44 -0
- package/components/_cards/input.card.html +25 -0
- package/components/_cards/kbd.card.html +20 -0
- package/components/_cards/keyvalue.card.html +23 -0
- package/components/_cards/listrow.card.html +30 -0
- package/components/_cards/loading.card.html +33 -0
- package/components/_cards/log-viewer.card.html +23 -0
- package/components/_cards/modal.card.html +35 -0
- package/components/_cards/multi-select.card.html +26 -0
- package/components/_cards/number-stepper.card.html +24 -0
- package/components/_cards/pagination.card.html +25 -0
- package/components/_cards/people.card.html +46 -0
- package/components/_cards/pipeline-node.card.html +76 -0
- package/components/_cards/pipeline-palette.card.html +54 -0
- package/components/_cards/search.card.html +23 -0
- package/components/_cards/select.card.html +23 -0
- package/components/_cards/slider.card.html +25 -0
- package/components/_cards/smart-table.card.html +37 -0
- package/components/_cards/spreadsheet.card.html +31 -0
- package/components/_cards/stepper.card.html +25 -0
- package/components/_cards/tabs.card.html +25 -0
- package/components/_cards/tag.card.html +26 -0
- package/components/_cards/textarea.card.html +27 -0
- package/components/_cards/timeline.card.html +32 -0
- package/components/_cards/toast.card.html +33 -0
- package/components/_cards/toggles.card.html +68 -0
- package/components/_cards/tooltip.card.html +22 -0
- package/components/_cards/tree.card.html +150 -0
- package/components/_shared/frame-theme.js +16 -0
- package/components/_shared/src-brand.js +1 -0
- package/components/_shared/src-buttons.js +1 -0
- package/components/_shared/src-charts.js +1 -0
- package/components/_shared/src-data.js +1 -0
- package/components/_shared/src-display.js +1 -0
- package/components/_shared/src-feedback.js +1 -0
- package/components/_shared/src-forms.js +1 -0
- package/components/_shared/src-icons.js +1 -0
- package/components/_shared/src-navigation.js +1 -0
- package/components/_shared/src-pipeline.js +1 -0
- package/components/_shared/src-tabs.js +1 -0
- package/components/_shared/svg-download.js +19 -0
- package/components/_shared/view-source.js +185 -0
- package/components/_vue/index.mjs +114 -0
- package/components/brand/BrandVisual.d.ts +17 -0
- package/components/brand/BrandVisual.jsx +29 -0
- package/components/brand/BrandVisual.vue +34 -0
- package/components/brand/_dir.card.html +13 -0
- package/components/brand/brandMotifs.js +210 -0
- package/components/buttons/ActionChip.d.ts +10 -0
- package/components/buttons/ActionChip.jsx +20 -0
- package/components/buttons/ActionChip.vue +18 -0
- package/components/buttons/Button.d.ts +40 -0
- package/components/buttons/Button.jsx +101 -0
- package/components/buttons/Button.vue +87 -0
- package/components/buttons/IconButton.d.ts +26 -0
- package/components/buttons/IconButton.jsx +28 -0
- package/components/buttons/IconButton.vue +21 -0
- package/components/buttons/_dir.card.html +13 -0
- package/components/buttons/buttons.prompt.md +19 -0
- package/components/charts/Bar3D.d.ts +15 -0
- package/components/charts/Bar3D.jsx +93 -0
- package/components/charts/Bar3D.vue +79 -0
- package/components/charts/BarChart.d.ts +23 -0
- package/components/charts/BarChart.jsx +135 -0
- package/components/charts/BarChart.vue +139 -0
- package/components/charts/BarList.d.ts +13 -0
- package/components/charts/BarList.jsx +31 -0
- package/components/charts/BarList.vue +25 -0
- package/components/charts/BoxPlot.d.ts +18 -0
- package/components/charts/BoxPlot.jsx +62 -0
- package/components/charts/BoxPlot.vue +58 -0
- package/components/charts/BulletChart.d.ts +12 -0
- package/components/charts/BulletChart.jsx +41 -0
- package/components/charts/BulletChart.vue +33 -0
- package/components/charts/CandlestickChart.d.ts +15 -0
- package/components/charts/CandlestickChart.jsx +63 -0
- package/components/charts/CandlestickChart.vue +62 -0
- package/components/charts/ChartKit.d.ts +8 -0
- package/components/charts/ChartKit.jsx +76 -0
- package/components/charts/ChartKit.vue +47 -0
- package/components/charts/ChordChart.d.ts +13 -0
- package/components/charts/ChordChart.jsx +55 -0
- package/components/charts/ChordChart.vue +54 -0
- package/components/charts/DonutChart.d.ts +15 -0
- package/components/charts/DonutChart.jsx +73 -0
- package/components/charts/DonutChart.vue +61 -0
- package/components/charts/FunnelChart.d.ts +11 -0
- package/components/charts/FunnelChart.jsx +37 -0
- package/components/charts/FunnelChart.vue +29 -0
- package/components/charts/GanttChart.d.ts +20 -0
- package/components/charts/GanttChart.jsx +80 -0
- package/components/charts/GanttChart.vue +60 -0
- package/components/charts/GaugeChart.d.ts +13 -0
- package/components/charts/GaugeChart.jsx +33 -0
- package/components/charts/GaugeChart.vue +29 -0
- package/components/charts/HeatmapChart.d.ts +13 -0
- package/components/charts/HeatmapChart.jsx +62 -0
- package/components/charts/HeatmapChart.vue +38 -0
- package/components/charts/Histogram.d.ts +16 -0
- package/components/charts/Histogram.jsx +56 -0
- package/components/charts/Histogram.vue +53 -0
- package/components/charts/LineChart.d.ts +17 -0
- package/components/charts/LineChart.jsx +84 -0
- package/components/charts/LineChart.vue +86 -0
- package/components/charts/ParetoChart.d.ts +13 -0
- package/components/charts/ParetoChart.jsx +54 -0
- package/components/charts/ParetoChart.vue +55 -0
- package/components/charts/Pie3D.d.ts +17 -0
- package/components/charts/Pie3D.jsx +145 -0
- package/components/charts/Pie3D.vue +115 -0
- package/components/charts/RadarChart.d.ts +14 -0
- package/components/charts/RadarChart.jsx +79 -0
- package/components/charts/RadarChart.vue +73 -0
- package/components/charts/Ribbon3D.d.ts +13 -0
- package/components/charts/Ribbon3D.jsx +89 -0
- package/components/charts/Ribbon3D.vue +79 -0
- package/components/charts/SankeyChart.d.ts +15 -0
- package/components/charts/SankeyChart.jsx +59 -0
- package/components/charts/SankeyChart.vue +53 -0
- package/components/charts/Scatter3D.d.ts +12 -0
- package/components/charts/Scatter3D.jsx +69 -0
- package/components/charts/Scatter3D.vue +59 -0
- package/components/charts/ScatterChart.d.ts +12 -0
- package/components/charts/ScatterChart.jsx +53 -0
- package/components/charts/ScatterChart.vue +57 -0
- package/components/charts/Sparkline.d.ts +19 -0
- package/components/charts/Sparkline.jsx +63 -0
- package/components/charts/Sparkline.vue +54 -0
- package/components/charts/SunburstChart.d.ts +12 -0
- package/components/charts/SunburstChart.jsx +61 -0
- package/components/charts/SunburstChart.vue +60 -0
- package/components/charts/Surface3D.d.ts +13 -0
- package/components/charts/Surface3D.jsx +70 -0
- package/components/charts/Surface3D.vue +63 -0
- package/components/charts/TreemapChart.d.ts +11 -0
- package/components/charts/TreemapChart.jsx +35 -0
- package/components/charts/TreemapChart.vue +27 -0
- package/components/charts/WaterfallChart.d.ts +11 -0
- package/components/charts/WaterfallChart.jsx +59 -0
- package/components/charts/WaterfallChart.vue +46 -0
- package/components/charts/WorldMap.d.ts +21 -0
- package/components/charts/WorldMap.jsx +107 -0
- package/components/charts/WorldMap.vue +73 -0
- package/components/charts/_dir.card.html +7 -0
- package/components/charts/chart-3d.js +84 -0
- package/components/charts/chart-geometry.js +223 -0
- package/components/charts/charts.prompt.md +24 -0
- package/components/data/CodeBlock.d.ts +9 -0
- package/components/data/CodeBlock.jsx +19 -0
- package/components/data/CodeBlock.vue +22 -0
- package/components/data/DataTable.d.ts +28 -0
- package/components/data/DataTable.jsx +32 -0
- package/components/data/DataTable.vue +25 -0
- package/components/data/DiffValue.d.ts +11 -0
- package/components/data/DiffValue.jsx +15 -0
- package/components/data/DiffValue.vue +13 -0
- package/components/data/FilterBar.d.ts +16 -0
- package/components/data/FilterBar.jsx +22 -0
- package/components/data/FilterBar.vue +23 -0
- package/components/data/KeyValue.d.ts +16 -0
- package/components/data/KeyValue.jsx +25 -0
- package/components/data/KeyValue.vue +26 -0
- package/components/data/ListRow.d.ts +14 -0
- package/components/data/ListRow.jsx +25 -0
- package/components/data/ListRow.vue +25 -0
- package/components/data/Pagination.d.ts +12 -0
- package/components/data/Pagination.jsx +27 -0
- package/components/data/Pagination.vue +24 -0
- package/components/data/SmartTable.d.ts +30 -0
- package/components/data/SmartTable.jsx +73 -0
- package/components/data/SmartTable.vue +74 -0
- package/components/data/Spreadsheet.d.ts +33 -0
- package/components/data/Spreadsheet.jsx +110 -0
- package/components/data/Spreadsheet.vue +95 -0
- package/components/data/_dir.card.html +13 -0
- package/components/data/data.prompt.md +33 -0
- package/components/display/Avatar.d.ts +21 -0
- package/components/display/Avatar.jsx +31 -0
- package/components/display/Avatar.vue +34 -0
- package/components/display/Badge.d.ts +27 -0
- package/components/display/Badge.jsx +40 -0
- package/components/display/Badge.vue +47 -0
- package/components/display/Card.d.ts +27 -0
- package/components/display/Card.jsx +27 -0
- package/components/display/Card.vue +28 -0
- package/components/display/Divider.d.ts +8 -0
- package/components/display/Divider.jsx +14 -0
- package/components/display/Divider.vue +13 -0
- package/components/display/Kbd.d.ts +7 -0
- package/components/display/Kbd.jsx +12 -0
- package/components/display/Kbd.vue +9 -0
- package/components/display/ProgressBar.d.ts +42 -0
- package/components/display/ProgressBar.jsx +70 -0
- package/components/display/ProgressBar.vue +69 -0
- package/components/display/Tag.d.ts +9 -0
- package/components/display/Tag.jsx +15 -0
- package/components/display/Tag.vue +15 -0
- package/components/display/_dir.card.html +13 -0
- package/components/display/display.prompt.md +30 -0
- package/components/feedback/Accordion.d.ts +13 -0
- package/components/feedback/Accordion.jsx +21 -0
- package/components/feedback/Accordion.vue +22 -0
- package/components/feedback/ActivityItem.d.ts +25 -0
- package/components/feedback/ActivityItem.jsx +39 -0
- package/components/feedback/ActivityItem.vue +46 -0
- package/components/feedback/Alert.d.ts +36 -0
- package/components/feedback/Alert.jsx +48 -0
- package/components/feedback/Alert.vue +59 -0
- package/components/feedback/Banner.d.ts +15 -0
- package/components/feedback/Banner.jsx +25 -0
- package/components/feedback/Banner.vue +25 -0
- package/components/feedback/ContextMenu.d.ts +8 -0
- package/components/feedback/ContextMenu.jsx +18 -0
- package/components/feedback/ContextMenu.vue +19 -0
- package/components/feedback/Drawer.d.ts +18 -0
- package/components/feedback/Drawer.jsx +26 -0
- package/components/feedback/Drawer.vue +24 -0
- package/components/feedback/Menu.d.ts +10 -0
- package/components/feedback/Menu.jsx +24 -0
- package/components/feedback/Menu.vue +21 -0
- package/components/feedback/Modal.d.ts +38 -0
- package/components/feedback/Modal.jsx +69 -0
- package/components/feedback/Modal.vue +65 -0
- package/components/feedback/Stepper.d.ts +9 -0
- package/components/feedback/Stepper.jsx +24 -0
- package/components/feedback/Stepper.vue +18 -0
- package/components/feedback/Timeline.d.ts +15 -0
- package/components/feedback/Timeline.jsx +27 -0
- package/components/feedback/Timeline.vue +21 -0
- package/components/feedback/_dir.card.html +13 -0
- package/components/feedback/feedback.prompt.md +35 -0
- package/components/forms/Checkbox.d.ts +13 -0
- package/components/forms/Checkbox.jsx +17 -0
- package/components/forms/Checkbox.vue +18 -0
- package/components/forms/Combobox.d.ts +13 -0
- package/components/forms/Combobox.jsx +34 -0
- package/components/forms/Combobox.vue +35 -0
- package/components/forms/DatePicker.d.ts +12 -0
- package/components/forms/DatePicker.jsx +48 -0
- package/components/forms/DatePicker.vue +46 -0
- package/components/forms/Dropzone.d.ts +20 -0
- package/components/forms/Dropzone.jsx +36 -0
- package/components/forms/Dropzone.vue +28 -0
- package/components/forms/Field.d.ts +13 -0
- package/components/forms/Field.jsx +13 -0
- package/components/forms/Field.vue +12 -0
- package/components/forms/Input.d.ts +18 -0
- package/components/forms/Input.jsx +35 -0
- package/components/forms/Input.vue +38 -0
- package/components/forms/MultiSelect.d.ts +14 -0
- package/components/forms/MultiSelect.jsx +33 -0
- package/components/forms/MultiSelect.vue +36 -0
- package/components/forms/NumberStepper.d.ts +13 -0
- package/components/forms/NumberStepper.jsx +15 -0
- package/components/forms/NumberStepper.vue +17 -0
- package/components/forms/Radio.vue +16 -0
- package/components/forms/RadioGroup.vue +11 -0
- package/components/forms/SearchInput.d.ts +12 -0
- package/components/forms/SearchInput.jsx +17 -0
- package/components/forms/SearchInput.vue +14 -0
- package/components/forms/Select.d.ts +14 -0
- package/components/forms/Select.jsx +17 -0
- package/components/forms/Select.vue +16 -0
- package/components/forms/Slider.d.ts +15 -0
- package/components/forms/Slider.jsx +21 -0
- package/components/forms/Slider.vue +23 -0
- package/components/forms/Switch.d.ts +31 -0
- package/components/forms/Switch.jsx +40 -0
- package/components/forms/Switch.vue +16 -0
- package/components/forms/_dir.card.html +13 -0
- package/components/forms/forms.prompt.md +23 -0
- package/components/icons/Icon.d.ts +19 -0
- package/components/icons/Icon.jsx +93 -0
- package/components/icons/Icon.prompt.md +8 -0
- package/components/icons/Icon.vue +12 -0
- package/components/icons/Unicon.d.ts +12 -0
- package/components/icons/Unicon.jsx +16 -0
- package/components/icons/Unicon.vue +15 -0
- package/components/icons/_dir.card.html +13 -0
- package/components/icons/feather-paths.js +2 -0
- package/components/icons/icon-paths.js +73 -0
- package/components/index.d.ts +87 -0
- package/components/index.js +110 -0
- package/components/navigation/Breadcrumb.d.ts +24 -0
- package/components/navigation/Breadcrumb.jsx +120 -0
- package/components/navigation/Breadcrumb.vue +94 -0
- package/components/navigation/DocTree.d.ts +18 -0
- package/components/navigation/DocTree.jsx +37 -0
- package/components/navigation/DocTree.vue +25 -0
- package/components/navigation/Sidebar.d.ts +29 -0
- package/components/navigation/Sidebar.jsx +75 -0
- package/components/navigation/Sidebar.vue +54 -0
- package/components/navigation/TopBar.d.ts +27 -0
- package/components/navigation/TopBar.jsx +44 -0
- package/components/navigation/TopBar.vue +51 -0
- package/components/navigation/Tree.d.ts +83 -0
- package/components/navigation/Tree.jsx +347 -0
- package/components/navigation/Tree.vue +258 -0
- package/components/navigation/_dir.card.html +13 -0
- package/components/navigation/navigation.prompt.md +22 -0
- package/components/pipeline/ConfigPanel.d.ts +18 -0
- package/components/pipeline/ConfigPanel.jsx +30 -0
- package/components/pipeline/ConfigPanel.vue +32 -0
- package/components/pipeline/DataPreview.d.ts +14 -0
- package/components/pipeline/DataPreview.jsx +24 -0
- package/components/pipeline/DataPreview.vue +21 -0
- package/components/pipeline/NodeCard.d.ts +47 -0
- package/components/pipeline/NodeCard.jsx +97 -0
- package/components/pipeline/NodeCard.vue +75 -0
- package/components/pipeline/PaletteItem.vue +15 -0
- package/components/pipeline/PaletteListItem.vue +15 -0
- package/components/pipeline/PalettePanel.d.ts +20 -0
- package/components/pipeline/PalettePanel.jsx +74 -0
- package/components/pipeline/PalettePanel.vue +45 -0
- package/components/pipeline/PipelineCanvas.d.ts +25 -0
- package/components/pipeline/PipelineCanvas.jsx +94 -0
- package/components/pipeline/PipelineCanvas.vue +73 -0
- package/components/pipeline/ZoomControls.d.ts +9 -0
- package/components/pipeline/ZoomControls.jsx +15 -0
- package/components/pipeline/ZoomControls.vue +15 -0
- package/components/pipeline/_dir.card.html +13 -0
- package/components/pipeline/pipeline.prompt.md +22 -0
- package/components/tabs/SegmentedControl.d.ts +11 -0
- package/components/tabs/SegmentedControl.jsx +21 -0
- package/components/tabs/SegmentedControl.vue +15 -0
- package/components/tabs/Tabs.d.ts +13 -0
- package/components/tabs/Tabs.jsx +52 -0
- package/components/tabs/Tabs.vue +39 -0
- package/components/tabs/ViewToggle.d.ts +8 -0
- package/components/tabs/ViewToggle.jsx +12 -0
- package/components/tabs/ViewToggle.vue +12 -0
- package/components/tabs/_dir.card.html +13 -0
- package/components/tabs/tabs.prompt.md +14 -0
- package/package.json +76 -0
- package/readme.md +150 -0
- package/styles.css +10 -0
- package/tokens/alpha.css +27 -0
- package/tokens/charts.css +55 -0
- package/tokens/colors-extended.css +40 -0
- package/tokens/colors.css +39 -0
- package/tokens/dark.css +96 -0
- package/tokens/fonts.css +4 -0
- package/tokens/interaction.css +48 -0
- package/tokens/layout.css +30 -0
- package/tokens/motion.css +22 -0
- package/tokens/tokens.d.ts +680 -0
- package/tokens/tokens.js +1018 -0
- package/tokens/tokens.json +2928 -0
- package/tokens/typography.css +22 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"data/CodeBlock.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Monospace code/JSON block with a language label and copy-to-clipboard button (SQL Preview, export JSON). */\nexport function CodeBlock({ code = \"\", language, height, style }) {\n const [copied, setCopied] = React.useState(false);\n const copy = () => { try { navigator.clipboard && navigator.clipboard.writeText(code); } catch (e) {} setCopied(true); setTimeout(() => setCopied(false), 1400); };\n return (\n <div style={{ background: \"var(--bg-inset)\", border: \"1px solid var(--border-subtle)\", borderRadius: \"var(--radius-md)\", ...style }}>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", padding: \"7px 10px\", borderBottom: \"1px solid var(--border-subtle)\" }}>\n <span style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: \"var(--text-muted)\" }}>{language || \"code\"}</span>\n <button type=\"button\" onClick={copy} style={{ display: \"flex\", alignItems: \"center\", gap: 4, border: \"none\", background: \"transparent\", cursor: \"pointer\", color: copied ? \"var(--success-strong)\" : \"var(--text-secondary)\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", padding: 0 }}>\n <Icon name={copied ? \"check\" : \"copy\"} size={12} />{copied ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n <pre style={{ margin: 0, padding: \"10px 12px\", overflow: \"auto\", height, fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--text-body)\" }}>{code}</pre>\n </div>\n );\n}\n","data/CodeBlock.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Monospace code/JSON block with a language label and copy-to-clipboard button (SQL Preview, export JSON).\nconst props = defineProps({ code: { type: String, default: '' }, language: String, height: [Number, String], style: Object })\nconst copied = ref(false)\nfunction copy() {\n try { navigator.clipboard && navigator.clipboard.writeText(props.code) } catch (e) {}\n copied.value = true; setTimeout(() => copied.value = false, 1400)\n}\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-inset)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', ...style }\">\n <div style=\"display:flex;justify-content:space-between;align-items:center;padding:7px 10px;border-bottom:1px solid var(--border-subtle)\">\n <span style=\"font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-muted)\">{{ language || 'code' }}</span>\n <button type=\"button\" @click=\"copy\" :style=\"{ display: 'flex', alignItems: 'center', gap: 4, border: 'none', background: 'transparent', cursor: 'pointer', color: copied ? 'var(--success-strong)' : 'var(--text-secondary)', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', padding: 0 }\">\n <Icon :name=\"copied ? 'check' : 'copy'\" :size=\"12\" />{{ copied ? 'Copied' : 'Copy' }}\n </button>\n </div>\n <pre :style=\"{ margin: 0, padding: '10px 12px', overflow: 'auto', height, fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', color: 'var(--text-body)' }\">{{ code }}</pre>\n </div>\n</template>\n","data/DataTable.jsx":"import React from \"react\";\nimport { Checkbox } from \"../forms/Checkbox.jsx\";\n\n/** Keybricks data table. columns: [{key,label,width,mono,render}]. dense = 29px preview-drawer rows; default = 51.5px config rows. */\nexport function DataTable({ columns = [], rows = [], dense, selectable, selected = [], onToggle, headerBorder = true, style }) {\n const rowH = dense ? 29.5 : 51.5;\n const cellFont = (c) => c.mono\n ? { fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-heading)\" }\n : { fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-primary)\" };\n return (\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", ...style }}>\n <thead>\n <tr style={{ background: \"var(--bg-inset)\", borderTop: headerBorder && !dense ? \"1px solid var(--border-control)\" : \"none\", borderBottom: headerBorder ? \"1px solid var(--border-control)\" : \"none\", height: dense ? 29 : 48.5 }}>\n {selectable ? <th style={{ width: 40, padding: \"0 0 0 16px\", textAlign: \"left\" }}><Checkbox size=\"sm\" checked={rows.length > 0 && selected.length === rows.length} onChange={(v) => onToggle && onToggle(\"all\", v)} /></th> : null}\n {columns.map((c) => (\n <th key={c.key} style={{ textAlign: c.align || \"left\", padding: \"0 12px\", width: c.width, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: dense ? \"var(--tracking-xs)\" : \".3px\", textTransform: dense ? \"none\" : \"uppercase\", color: \"var(--text-secondary)\", whiteSpace: \"nowrap\" }}>{c.label}</th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((r, ri) => (\n <tr key={ri} style={{ height: rowH, borderBottom: \"1px solid var(--border-subtle)\" }}>\n {selectable ? <td style={{ padding: \"0 0 0 16px\" }}><Checkbox size=\"sm\" checked={selected.includes(ri)} onChange={(v) => onToggle && onToggle(ri, v)} /></td> : null}\n {columns.map((c) => (\n <td key={c.key} style={{ padding: \"0 12px\", textAlign: c.align || \"left\", whiteSpace: \"nowrap\", ...cellFont(c) }}>{c.render ? c.render(r[c.key], r, ri) : r[c.key]}</td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n","data/DataTable.vue":"<script setup>\nimport { computed } from 'vue'\nimport Checkbox from '../forms/Checkbox.vue'\n// Keybricks data table. columns: [{key,label,width,mono,render}]. dense = 29px preview-drawer rows; default = 51.5px config rows.\nconst props = defineProps({ columns: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, dense: Boolean, selectable: Boolean, selected: { type: Array, default: () => [] }, headerBorder: { type: Boolean, default: true }, style: Object })\nconst emit = defineEmits(['toggle'])\nconst rowH = computed(() => props.dense ? 29.5 : 51.5)\nfunction cellFont(c) { return c.mono ? { fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', letterSpacing: 'var(--tracking-xs)', color: 'var(--text-heading)' } : { fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', color: 'var(--text-primary)' } }\n</script>\n<template>\n <table :style=\"{ width: '100%', borderCollapse: 'collapse', ...style }\">\n <thead>\n <tr :style=\"{ background: 'var(--bg-inset)', borderTop: (headerBorder && !dense) ? '1px solid var(--border-control)' : 'none', borderBottom: headerBorder ? '1px solid var(--border-control)' : 'none', height: dense ? 29 : 48.5 }\">\n <th v-if=\"selectable\" style=\"width:40px;padding:0 0 0 16px;text-align:left\"><Checkbox size=\"sm\" :checked=\"rows.length > 0 && selected.length === rows.length\" @change=\"v => emit('toggle', 'all', v)\" /></th>\n <th v-for=\"c in columns\" :key=\"c.key\" :style=\"{ textAlign: c.align || 'left', padding: '0 12px', width: c.width, fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: dense ? 'var(--tracking-xs)' : '.3px', textTransform: dense ? 'none' : 'uppercase', color: 'var(--text-secondary)', whiteSpace: 'nowrap' }\">{{ c.label }}</th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(r, ri) in rows\" :key=\"ri\" :style=\"{ height: rowH, borderBottom: '1px solid var(--border-subtle)' }\">\n <td v-if=\"selectable\" style=\"padding:0 0 0 16px\"><Checkbox size=\"sm\" :checked=\"selected.includes(ri)\" @change=\"v => emit('toggle', ri, v)\" /></td>\n <td v-for=\"c in columns\" :key=\"c.key\" :style=\"{ padding: '0 12px', textAlign: c.align || 'left', whiteSpace: 'nowrap', ...cellFont(c) }\">{{ c.render ? c.render(r[c.key], r, ri) : r[c.key] }}</td>\n </tr>\n </tbody>\n </table>\n</template>\n","data/DiffValue.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst toneColor = { neutral: \"var(--text-heading)\", warning: \"var(--warning-text)\", success: \"var(--success-text)\", danger: \"var(--danger-text)\" };\n\n/** Before → after value change (\"USD → EUR\"). tone colors the new value; from renders muted + struck through when both sides are given. */\nexport function DiffValue({ from, to, tone = \"warning\", mono = true, style }) {\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, fontFamily: mono ? \"var(--font-mono)\" : \"var(--font-sans)\", fontSize: \"var(--text-sm)\", ...style }}>\n {from != null ? <span style={{ color: \"var(--text-muted)\", textDecoration: \"line-through\" }}>{from}</span> : null}\n <Icon name=\"arrow-right\" size={12} style={{ color: \"var(--text-muted)\" }} />\n <span style={{ color: toneColor[tone], fontWeight: \"var(--weight-medium)\" }}>{to}</span>\n </span>\n );\n}\n","data/DiffValue.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\nconst toneColor = { neutral: 'var(--text-heading)', warning: 'var(--warning-text)', success: 'var(--success-text)', danger: 'var(--danger-text)' }\n// Before → after value change (\"USD → EUR\"). tone colors the new value; from renders muted + struck through when both sides are given.\ndefineProps({ from: [String, Number], to: [String, Number], tone: { type: String, default: 'warning' }, mono: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--text-sm)', ...style }\">\n <span v-if=\"from != null\" style=\"color:var(--text-muted);text-decoration:line-through\">{{ from }}</span>\n <Icon name=\"arrow-right\" :size=\"12\" style=\"color:var(--text-muted)\" />\n <span :style=\"{ color: toneColor[tone], fontWeight: 'var(--weight-medium)' }\">{{ to }}</span>\n </span>\n</template>\n","data/FilterBar.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { Button } from \"../buttons/Button.jsx\";\n\n/** SAP Fiori-style list-report filter bar: variant title row with expand/collapse, a wrapping row of labeled filter fields (pass Field-wrapped controls as children), and Adapt Filters (n) + Go actions. */\nexport function FilterBar({ variant = \"Standard\", modified, adaptCount, defaultExpanded = true, onGo, onAdapt, children, style }) {\n const [open, setOpen] = React.useState(defaultExpanded);\n return (\n <div style={{ background: \"var(--bg-surface)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\", padding: \"10px 16px 0\", boxSizing: \"border-box\", ...style }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 6, paddingBottom: 10 }}>\n <button type=\"button\" onClick={() => setOpen(!open)} style={{ display: \"flex\", alignItems: \"center\", gap: 6, border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: 0, color: \"var(--text-heading)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\" }}>\n {variant}{modified && <span style={{ color: \"var(--text-muted)\", fontWeight: \"var(--weight-regular)\" }}>*</span>}\n <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\", transform: open ? \"rotate(180deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-down\" size={14} /></span>\n </button>\n <span style={{ flex: 1 }}></span>\n <Button variant=\"text\" size=\"sm\" onClick={onAdapt}>Adapt Filters{adaptCount ? ` (${adaptCount})` : \"\"}</Button>\n <Button size=\"sm\" onClick={onGo}>Go</Button>\n </div>\n {open && <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"12px 16px\", paddingBottom: 14, borderTop: \"1px solid var(--border-subtle)\", paddingTop: 12 }}>{children}</div>}\n </div>\n );\n}\n","data/FilterBar.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport Button from '../buttons/Button.vue'\n// SAP Fiori-style list-report filter bar: variant title + expand/collapse, wrapping labeled filter fields (slot), Adapt Filters (n) + Go.\nconst props = defineProps({ variant: { type: String, default: 'Standard' }, modified: Boolean, adaptCount: Number, defaultExpanded: { type: Boolean, default: true }, style: Object })\nconst emit = defineEmits(['go', 'adapt'])\nconst open = ref(props.defaultExpanded)\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', padding: '10px 16px 0', boxSizing: 'border-box', ...style }\">\n <div style=\"display:flex;align-items:center;gap:6px;padding-bottom:10px\">\n <button type=\"button\" @click=\"open = !open\" style=\"display:flex;align-items:center;gap:6px;border:none;background:transparent;cursor:pointer;padding:0;color:var(--text-heading);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base)\">\n {{ variant }}<span v-if=\"modified\" style=\"color:var(--text-muted);font-weight:var(--weight-regular)\">*</span>\n <span :style=\"{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-down\" :size=\"14\" /></span>\n </button>\n <span style=\"flex:1\" />\n <Button variant=\"text\" size=\"sm\" @click=\"emit('adapt')\">Adapt Filters{{ adaptCount ? ` (${adaptCount})` : '' }}</Button>\n <Button size=\"sm\" @click=\"emit('go')\">Go</Button>\n </div>\n <div v-if=\"open\" style=\"display:flex;flex-wrap:wrap;gap:12px 16px;padding-bottom:14px;border-top:1px solid var(--border-subtle);padding-top:12px\"><slot /></div>\n </div>\n</template>\n","data/KeyValue.jsx":"import React from \"react\";\n\n/** Label/value row (config summaries, confirmation steps). mono renders the value in Menlo. */\nexport function KeyValue({ label, value, mono, style }) {\n return (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", gap: 12, padding: \"7px 0\", ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{label}</span>\n <span style={{ fontFamily: mono ? \"var(--font-mono)\" : \"var(--font-sans)\", fontSize: mono ? \"var(--text-xs)\" : \"var(--text-sm)\", letterSpacing: mono ? \"var(--tracking-xs)\" : 0, color: \"var(--text-heading)\", textAlign: \"right\" }}>{value}</span>\n </div>\n );\n}\n\n/** Stack of KeyValue rows with hairline separators. rows: [{label,value,mono}]. */\nexport function KeyValueList({ rows = [], style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", ...style }}>\n {rows.map((r, i) => (\n <React.Fragment key={r.label}>\n <KeyValue {...r} />\n {i < rows.length - 1 ? <div style={{ height: 1, background: \"var(--border-subtle)\" }} /> : null}\n </React.Fragment>\n ))}\n </div>\n );\n}\n","data/KeyValue.vue":"<script setup>\n// Label/value row (config summaries, confirmation steps). mono renders the value in Menlo.\ndefineProps({ label: String, value: [String, Number], mono: Boolean, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', justifyContent: 'space-between', gap: 12, padding: '7px 0', ...style }\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ label }}</span>\n <span :style=\"{ fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: mono ? 'var(--text-xs)' : 'var(--text-sm)', letterSpacing: mono ? 'var(--tracking-xs)' : 0, color: 'var(--text-heading)', textAlign: 'right' }\">{{ value }}</span>\n </div>\n</template>\n\n<!-- KeyValueList — stack of KeyValue rows with hairline separators. rows: [{label,value,mono}]. -->\n<!--\n<script setup name=\"KeyValueList\">\nimport KeyValue from './KeyValue.vue'\ndefineProps({ rows: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', ...style }\">\n <template v-for=\"(r, i) in rows\" :key=\"r.label\">\n <KeyValue v-bind=\"r\" />\n <div v-if=\"i < rows.length - 1\" style=\"height:1px;background:var(--border-subtle)\" />\n </template>\n </div>\n</template>\n-->\n","data/ListRow.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconTile } from \"../display/Badge.jsx\";\n\n/** Expandable workflow list row: chevron + bordered 32px icon tile + title/meta + right count. */\nexport function ListRow({ icon = \"file-text\", title, meta, trailing, expanded, onClick, children, style }) {\n const [hover, setHover] = React.useState(false);\n return (\n <div style={{ borderBottom: \"1px solid var(--border-card)\", ...style }}>\n <div onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", padding: \"12px 16px\", cursor: \"pointer\", background: hover ? \"var(--bg-inset)\" : \"transparent\", transition: \"var(--t-hover)\" }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12 }}>\n <span style={{ color: \"var(--text-muted)\", transform: expanded ? \"rotate(90deg)\" : \"none\", transition: \"transform .15s ease\", display: \"inline-flex\" }}><Icon name=\"chevron-right\" size={16} /></span>\n <IconTile icon={icon} kind=\"row\" />\n <div style={{ display: \"flex\", flexDirection: \"column\" }}>\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)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n </div>\n {trailing ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{trailing}</span> : null}\n </div>\n {expanded && children ? <div style={{ padding: \"0 16px 12px 72px\" }}>{children}</div> : null}\n </div>\n );\n}\n","data/ListRow.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport { IconTile } from '../display/Badge.vue'\n// Expandable workflow list row: chevron + bordered 32px icon tile + title/meta + right count.\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, trailing: [String, Number], expanded: Boolean, style: Object })\ndefineEmits(['click'])\nconst hover = ref(false)\n</script>\n<template>\n <div :style=\"{ borderBottom: '1px solid var(--border-card)', ...style }\">\n <div @click=\"$emit('click')\" @mouseenter=\"hover = true\" @mouseleave=\"hover = false\" :style=\"{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', cursor: 'pointer', background: hover ? 'var(--bg-inset)' : 'transparent', transition: 'var(--t-hover)' }\">\n <div style=\"display:flex;align-items:center;gap:12px\">\n <span :style=\"{ color: 'var(--text-muted)', transform: expanded ? 'rotate(90deg)' : 'none', transition: 'transform .15s ease', display: 'inline-flex' }\"><Icon name=\"chevron-right\" :size=\"16\" /></span>\n <IconTile :icon=\"icon\" kind=\"row\" />\n <div style=\"display:flex;flex-direction:column\">\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-weight:var(--weight-medium);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n </div>\n <span v-if=\"trailing\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ trailing }}</span>\n </div>\n <div v-if=\"expanded\" style=\"padding:0 16px 12px 72px\"><slot /></div>\n </div>\n</template>\n","data/Pagination.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Table pagination — 12px range label + prev/next + numbered pages. */\nexport function Pagination({ page = 1, pageCount = 1, total, pageSize = 20, onChange, style }) {\n const btn = (content, disabled, onClick, active) => (\n <button type=\"button\" disabled={disabled} onClick={onClick}\n style={{ minWidth: 26, height: 26, padding: \"0 6px\", borderRadius: \"var(--radius-sm)\", border: `1px solid ${active ? \"var(--action-primary)\" : \"var(--border-control)\"}`, background: active ? \"var(--action-primary)\" : \"var(--bg-surface)\", color: active ? \"var(--on-accent)\" : \"var(--text-body)\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", cursor: disabled ? \"default\" : \"pointer\", opacity: disabled ? .45 : 1, display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", transition: \"var(--t-hover)\" }}>\n {content}\n </button>\n );\n const pages = [];\n const from = Math.max(1, Math.min(page - 1, pageCount - 2));\n for (let i = from; i < from + 3 && i <= pageCount; i++) pages.push(i);\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: 12, ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>\n {total != null ? `${(page - 1) * pageSize + 1}–${Math.min(page * pageSize, total)} of ${total}` : `Page ${page} of ${pageCount}`}\n </span>\n <div style={{ display: \"flex\", gap: 4 }}>\n {btn(<Icon name=\"chevron-left\" size={13} />, page <= 1, () => onChange && onChange(page - 1))}\n {pages.map((p) => <React.Fragment key={p}>{btn(p, false, () => onChange && onChange(p), p === page)}</React.Fragment>)}\n {btn(<Icon name=\"chevron-right\" size={13} />, page >= pageCount, () => onChange && onChange(page + 1))}\n </div>\n </div>\n );\n}\n","data/Pagination.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Table pagination — 12px range label + prev/next + numbered pages.\nconst props = defineProps({ page: { type: Number, default: 1 }, pageCount: { type: Number, default: 1 }, total: Number, pageSize: { type: Number, default: 20 }, style: Object })\nconst emit = defineEmits(['change'])\nconst pages = computed(() => {\n const from = Math.max(1, Math.min(props.page - 1, props.pageCount - 2)), out = []\n for (let i = from; i < from + 3 && i <= props.pageCount; i++) out.push(i)\n return out\n})\nconst label = computed(() => props.total != null ? `${(props.page - 1) * props.pageSize + 1}–${Math.min(props.page * props.pageSize, props.total)} of ${props.total}` : `Page ${props.page} of ${props.pageCount}`)\nfunction btnStyle(active, disabled) { return { minWidth: 26, height: 26, padding: '0 6px', borderRadius: 'var(--radius-sm)', border: `1px solid ${active ? 'var(--action-primary)' : 'var(--border-control)'}`, background: active ? 'var(--action-primary)' : 'var(--bg-surface)', color: active ? 'var(--on-accent)' : 'var(--text-body)', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? .45 : 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', transition: 'var(--t-hover)' } }\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, ...style }\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ label }}</span>\n <div style=\"display:flex;gap:4px\">\n <button type=\"button\" :disabled=\"page <= 1\" @click=\"emit('change', page - 1)\" :style=\"btnStyle(false, page <= 1)\"><Icon name=\"chevron-left\" :size=\"13\" /></button>\n <button v-for=\"p in pages\" :key=\"p\" type=\"button\" @click=\"emit('change', p)\" :style=\"btnStyle(p === page, false)\">{{ p }}</button>\n <button type=\"button\" :disabled=\"page >= pageCount\" @click=\"emit('change', page + 1)\" :style=\"btnStyle(false, page >= pageCount)\"><Icon name=\"chevron-right\" :size=\"13\" /></button>\n </div>\n </div>\n</template>\n","data/SmartTable.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\nimport { Checkbox } from \"../forms/Checkbox.jsx\";\nimport { Avatar } from \"../display/Avatar.jsx\";\nimport { SearchInput } from \"../forms/SearchInput.jsx\";\n\nconst typeIcon = { text: \"type\", longText: \"align-left\", number: \"hash\", select: \"chevron-down\", multiSelect: \"tag\", date: \"calendar\", person: \"user\", checkbox: \"check-square\", url: \"link\", progress: \"bar-chart-2\" };\nconst Pill = ({ hue = \"gray\", children }) => <span style={{ display: \"inline-flex\", alignItems: \"center\", height: 20, padding: \"0 8px\", borderRadius: \"var(--radius-full)\", background: `var(--hue-${hue}-bg)`, color: `var(--hue-${hue}-fg)`, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", lineHeight: 1, whiteSpace: \"nowrap\" }}>{children}</span>;\n\n/** Notion/Airtable-style database grid (\"smart table\"): field-type icons in headers, colored select pills, person/date/checkbox/url/progress cells, row hover + expand, inline text/number editing, + New row, add-field column, per-column footer aggregates, and a view toolbar (Hide fields · Filter · Group · Sort · search). */\nexport function SmartTable({ fields = [], rows = [], title = \"Grid view\", toolbar = true, height, rowHeight = 32, onChange, style }) {\n const [data, setData] = React.useState(rows);\n const [hover, setHover] = React.useState(-1);\n const [editing, setEditing] = React.useState(null);\n const [draft, setDraft] = React.useState(\"\");\n const setCell = (r, key, v) => { const next = data.map((row, i) => i === r ? { ...row, [key]: v } : row); setData(next); onChange && onChange(next); };\n const widths = [\"40px\", ...fields.map(f => (f.width || 140) + \"px\"), \"40px\"];\n const grid = { display: \"grid\", gridTemplateColumns: widths.join(\" \"), width: \"fit-content\", minWidth: \"100%\" };\n const cellBase = { display: \"flex\", alignItems: \"center\", gap: 5, padding: \"0 8px\", borderRight: \"1px solid var(--border-subtle)\", borderBottom: \"1px solid var(--border-subtle)\", height: rowHeight, boxSizing: \"border-box\", overflow: \"hidden\", whiteSpace: \"nowrap\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-primary)\" };\n const agg = (f) => {\n if (!f.aggregate) return \"\";\n const vals = data.map(r => r[f.key]);\n if (f.aggregate === \"count\") return \"COUNT \" + vals.filter(v => v === true || (v !== \"\" && v != null && v !== false)).length;\n const nums = vals.map(Number).filter(n => !isNaN(n));\n if (f.aggregate === \"sum\") return \"SUM \" + nums.reduce((a, b) => a + b, 0);\n if (f.aggregate === \"avg\") return \"AVG \" + (nums.length ? Math.round(nums.reduce((a, b) => a + b, 0) / nums.length * 10) / 10 : 0);\n return \"\";\n };\n const renderCell = (f, row, r) => {\n const v = row[f.key];\n const isEdit = editing && editing.r === r && editing.key === f.key;\n if (isEdit) return <input autoFocus value={draft} onChange={(e) => setDraft(e.target.value)} onBlur={() => { setCell(r, f.key, f.type === \"number\" && draft !== \"\" && !isNaN(Number(draft)) ? Number(draft) : draft); setEditing(null); }} onKeyDown={(e) => { if (e.key === \"Enter\") e.target.blur(); if (e.key === \"Escape\") setEditing(null); }} style={{ position: \"absolute\", inset: 0, border: \"2px solid var(--action-primary)\", borderRadius: 2, outline: \"none\", padding: \"0 6px\", font: \"inherit\", background: \"var(--bg-surface)\", color: \"var(--text-primary)\", boxSizing: \"border-box\" }} />;\n switch (f.type) {\n case \"select\": { const hue = (f.options || {})[v] || \"gray\"; return v ? <Pill hue={hue}>{v}</Pill> : null; }\n case \"multiSelect\": return <span style={{ display: \"flex\", gap: 4, overflow: \"hidden\" }}>{(v || []).map(t => <Pill key={t} hue={(f.options || {})[t] || \"gray\"}>{t}</Pill>)}</span>;\n case \"person\": return v ? <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, minWidth: 0 }}><Avatar name={v} size={20} /><span style={{ overflow: \"hidden\", textOverflow: \"ellipsis\" }}>{v}</span></span> : null;\n case \"checkbox\": return <Checkbox size=\"sm\" checked={!!v} onChange={(nv) => setCell(r, f.key, nv)} />;\n case \"url\": return v ? <a href={\"https://\" + v} onClick={(e) => e.preventDefault()} style={{ color: \"var(--link)\", overflow: \"hidden\", textOverflow: \"ellipsis\" }}>{v}</a> : null;\n case \"progress\": return <span style={{ display: \"flex\", alignItems: \"center\", gap: 6, width: \"100%\" }}><span style={{ flex: 1, height: 4, borderRadius: \"var(--radius-full)\", background: \"var(--track)\", overflow: \"hidden\" }}><span style={{ display: \"block\", width: (v || 0) + \"%\", height: \"100%\", background: \"var(--chart-blue)\" }}></span></span><span style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-2xs)\", color: \"var(--text-secondary)\" }}>{v || 0}%</span></span>;\n case \"number\": return <span style={{ marginLeft: \"auto\", fontFamily: \"var(--font-mono)\", fontSize: 11.5 }}>{v}</span>;\n default: return <span style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", fontWeight: f.primary ? \"var(--weight-medium)\" : \"var(--weight-regular)\", color: f.primary ? \"var(--text-heading)\" : \"var(--text-primary)\" }}>{v}</span>;\n }\n };\n const tbBtn = (icon, label) => <button key={label} type=\"button\" style={{ display: \"inline-flex\", alignItems: \"center\", gap: 5, height: 26, padding: \"0 8px\", border: \"none\", borderRadius: \"var(--radius-sm)\", background: \"transparent\", cursor: \"pointer\", color: \"var(--text-body)\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\" }} onMouseEnter={(e) => e.currentTarget.style.background = \"var(--bg-hover)\"} onMouseLeave={(e) => e.currentTarget.style.background = \"transparent\"}><Icon name={icon} size={13} />{label}</button>;\n return (\n <div style={{ border: \"1px solid var(--border-control)\", borderRadius: \"var(--radius-lg)\", background: \"var(--bg-surface)\", overflow: \"hidden\", boxSizing: \"border-box\", ...style }}>\n {toolbar && <div style={{ display: \"flex\", alignItems: \"center\", gap: 4, padding: \"6px 10px\", borderBottom: \"1px solid var(--border-subtle)\" }}>\n <button type=\"button\" style={{ display: \"inline-flex\", alignItems: \"center\", gap: 6, height: 26, padding: \"0 8px\", border: \"none\", borderRadius: \"var(--radius-sm)\", background: \"var(--bg-active)\", cursor: \"pointer\", color: \"var(--text-heading)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\" }}><Icon name=\"layout-grid\" size={13} />{title}<Icon name=\"chevron-down\" size={12} /></button>\n <span style={{ width: 1, height: 16, background: \"var(--border-control)\", margin: \"0 4px\" }}></span>\n {tbBtn(\"eye-off\", \"Hide fields\")}{tbBtn(\"funnel\", \"Filter\")}{tbBtn(\"layers\", \"Group\")}{tbBtn(\"arrow-down\", \"Sort\")}\n <span style={{ flex: 1 }}></span>\n <SearchInput bare placeholder=\"Search...\" style={{ width: 140, gap: 6 }} />\n </div>}\n <div style={{ overflow: \"auto\", maxHeight: height }}>\n <div style={grid}>\n <span style={{ ...cellBase, background: \"var(--bg-inset)\", justifyContent: \"center\" }}></span>\n {fields.map(f => <span key={f.key} style={{ ...cellBase, background: \"var(--bg-inset)\", color: \"var(--text-secondary)\", fontSize: \"var(--text-xs)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\" }}><Icon name={typeIcon[f.type] || \"type\"} size={12} style={{ color: \"var(--text-muted)\" }} />{f.label}</span>)}\n <span style={{ ...cellBase, background: \"var(--bg-inset)\", justifyContent: \"center\" }}><IconButton icon=\"plus\" size=\"xs\" muted label=\"Add field\" /></span>\n {data.map((row, r) => [\n <span key={\"n\" + r} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} style={{ ...cellBase, justifyContent: \"center\", color: \"var(--text-muted)\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-2xs)\", background: hover === r ? \"var(--bg-inset)\" : \"transparent\", cursor: \"pointer\" }}>{hover === r ? <Icon name=\"maximize-2\" size={12} style={{ color: \"var(--action-primary)\" }} /> : r + 1}</span>,\n ...fields.map(f => <span key={r + f.key} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} onDoubleClick={() => { if (f.type === \"text\" || f.type === \"number\" || f.type === \"date\" || f.type === \"url\") { setEditing({ r, key: f.key }); setDraft(String(row[f.key] == null ? \"\" : row[f.key])); } }} style={{ ...cellBase, position: \"relative\", background: hover === r ? \"var(--bg-inset)\" : \"transparent\" }}>{renderCell(f, row, r)}</span>),\n <span key={\"a\" + r} style={{ ...cellBase, background: hover === r ? \"var(--bg-inset)\" : \"transparent\" }}></span>,\n ])}\n <button type=\"button\" onClick={() => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === \"multiSelect\" ? [] : f.type === \"checkbox\" ? false : \"\"); const next = [...data, empty]; setData(next); onChange && onChange(next); }} style={{ gridColumn: \"1 / -1\", display: \"flex\", alignItems: \"center\", gap: 6, height: rowHeight, padding: \"0 10px\", border: \"none\", borderBottom: \"1px solid var(--border-subtle)\", background: \"transparent\", cursor: \"pointer\", color: \"var(--text-secondary)\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", textAlign: \"left\" }} onMouseEnter={(e) => e.currentTarget.style.background = \"var(--bg-inset)\"} onMouseLeave={(e) => e.currentTarget.style.background = \"transparent\"}><Icon name=\"plus\" size={13} />New</button>\n <span style={{ ...cellBase, borderBottom: \"none\", background: \"var(--bg-inset)\" }}></span>\n {fields.map(f => <span key={\"g\" + f.key} style={{ ...cellBase, borderBottom: \"none\", background: \"var(--bg-inset)\", justifyContent: \"flex-end\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-2xs)\", color: \"var(--text-secondary)\" }}>{agg(f)}</span>)}\n <span style={{ ...cellBase, borderBottom: \"none\", background: \"var(--bg-inset)\" }}></span>\n </div>\n </div>\n </div>\n );\n}\n","data/SmartTable.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nimport Checkbox from '../forms/Checkbox.vue'\nimport Avatar from '../display/Avatar.vue'\nimport SearchInput from '../forms/SearchInput.vue'\n// Notion/Airtable-style database grid: field-type header icons, hue pills, person/date/checkbox/url/progress cells, row hover + expand, inline editing, + New row, footer aggregates, view toolbar.\nconst typeIcon = { text: 'type', longText: 'align-left', number: 'hash', select: 'chevron-down', multiSelect: 'tag', date: 'calendar', person: 'user', checkbox: 'check-square', url: 'link', progress: 'bar-chart-2' }\nconst props = defineProps({ fields: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, title: { type: String, default: 'Grid view' }, toolbar: { type: Boolean, default: true }, height: [Number, String], rowHeight: { type: Number, default: 32 }, style: Object })\nconst emit = defineEmits(['change'])\nconst data = ref(props.rows.map(r => ({ ...r })))\nconst hover = ref(-1), editing = ref(null), draft = ref('')\nconst gridCols = computed(() => ['40px', ...props.fields.map(f => (f.width || 140) + 'px'), '40px'].join(' '))\nconst cellBase = { display: 'flex', alignItems: 'center', gap: '5px', padding: '0 8px', borderRight: '1px solid var(--border-subtle)', borderBottom: '1px solid var(--border-subtle)', boxSizing: 'border-box', overflow: 'hidden', whiteSpace: 'nowrap', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', color: 'var(--text-primary)' }\nfunction setCell(r, key, v) { data.value[r][key] = v; emit('change', data.value) }\nfunction commitEdit(r, f) { setCell(r, f.key, f.type === 'number' && draft.value !== '' && !isNaN(Number(draft.value)) ? Number(draft.value) : draft.value); editing.value = null }\nfunction startEdit(r, f, row) { if (['text', 'number', 'date', 'url'].includes(f.type || 'text')) { editing.value = { r, key: f.key }; draft.value = String(row[f.key] == null ? '' : row[f.key]) } }\nfunction addRow() { const empty = {}; props.fields.forEach(f => empty[f.key] = f.type === 'multiSelect' ? [] : f.type === 'checkbox' ? false : ''); data.value.push(empty); emit('change', data.value) }\nfunction agg(f) {\n if (!f.aggregate) return ''\n const vals = data.value.map(r => r[f.key])\n if (f.aggregate === 'count') return 'COUNT ' + vals.filter(v => v === true || (v !== '' && v != null && v !== false)).length\n const nums = vals.map(Number).filter(n => !isNaN(n))\n if (f.aggregate === 'sum') return 'SUM ' + nums.reduce((a, b) => a + b, 0)\n return 'AVG ' + (nums.length ? Math.round(nums.reduce((a, b) => a + b, 0) / nums.length * 10) / 10 : 0)\n}\n</script>\n<template>\n <div :style=\"{ border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', background: 'var(--bg-surface)', overflow: 'hidden', boxSizing: 'border-box', ...style }\">\n <div v-if=\"toolbar\" style=\"display:flex;align-items:center;gap:4px;padding:6px 10px;border-bottom:1px solid var(--border-subtle)\">\n <button type=\"button\" style=\"display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 8px;border:none;border-radius:var(--radius-sm);background:var(--bg-active);cursor:pointer;color:var(--text-heading);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-sm)\"><Icon name=\"layout-grid\" :size=\"13\" />{{ title }}<Icon name=\"chevron-down\" :size=\"12\" /></button>\n <span style=\"width:1px;height:16px;background:var(--border-control);margin:0 4px\" />\n <button v-for=\"b in [['eye-off','Hide fields'],['funnel','Filter'],['layers','Group'],['arrow-down','Sort']]\" :key=\"b[1]\" type=\"button\" class=\"kb-st-btn\"><Icon :name=\"b[0]\" :size=\"13\" />{{ b[1] }}</button>\n <span style=\"flex:1\" />\n <SearchInput bare placeholder=\"Search...\" style=\"width:140px;gap:6px\" />\n </div>\n <div :style=\"{ overflow: 'auto', maxHeight: typeof height === 'number' ? height + 'px' : height }\">\n <div :style=\"{ display: 'grid', gridTemplateColumns: gridCols, width: 'fit-content', minWidth: '100%' }\">\n <span :style=\"{ ...cellBase, height: rowHeight + 'px', background: 'var(--bg-inset)' }\" />\n <span v-for=\"f in fields\" :key=\"f.key\" :style=\"{ ...cellBase, height: rowHeight + 'px', background: 'var(--bg-inset)', color: 'var(--text-secondary)', fontSize: 'var(--text-xs)', fontWeight: 'var(--weight-medium)' }\"><Icon :name=\"typeIcon[f.type] || 'type'\" :size=\"12\" style=\"color:var(--text-muted)\" />{{ f.label }}</span>\n <span :style=\"{ ...cellBase, height: rowHeight + 'px', background: 'var(--bg-inset)', justifyContent: 'center' }\"><IconButton icon=\"plus\" size=\"xs\" muted label=\"Add field\" /></span>\n <template v-for=\"(row, r) in data\" :key=\"r\">\n <span @mouseenter=\"hover = r\" @mouseleave=\"hover = -1\" :style=\"{ ...cellBase, height: rowHeight + 'px', justifyContent: 'center', color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-2xs)', background: hover === r ? 'var(--bg-inset)' : 'transparent', cursor: 'pointer' }\">\n <Icon v-if=\"hover === r\" name=\"maximize-2\" :size=\"12\" style=\"color:var(--action-primary)\" /><template v-else>{{ r + 1 }}</template>\n </span>\n <span v-for=\"f in fields\" :key=\"r + f.key\" @mouseenter=\"hover = r\" @mouseleave=\"hover = -1\" @dblclick=\"startEdit(r, f, row)\" :style=\"{ ...cellBase, height: rowHeight + 'px', position: 'relative', background: hover === r ? 'var(--bg-inset)' : 'transparent' }\">\n <input v-if=\"editing && editing.r === r && editing.key === f.key\" autofocus :value=\"draft\" @input=\"draft = $event.target.value\" @blur=\"commitEdit(r, f)\" @keydown.enter=\"$event.target.blur()\" @keydown.esc=\"editing = null\" style=\"position:absolute;inset:0;border:2px solid var(--action-primary);border-radius:2px;outline:none;padding:0 6px;font:inherit;background:var(--bg-surface);color:var(--text-primary);box-sizing:border-box\" />\n <template v-else-if=\"f.type === 'select'\"><span v-if=\"row[f.key]\" class=\"kb-pill\" :style=\"{ background: `var(--hue-${(f.options || {})[row[f.key]] || 'gray'}-bg)`, color: `var(--hue-${(f.options || {})[row[f.key]] || 'gray'}-fg)` }\">{{ row[f.key] }}</span></template>\n <template v-else-if=\"f.type === 'multiSelect'\"><span style=\"display:flex;gap:4px;overflow:hidden\"><span v-for=\"t in row[f.key] || []\" :key=\"t\" class=\"kb-pill\" :style=\"{ background: `var(--hue-${(f.options || {})[t] || 'gray'}-bg)`, color: `var(--hue-${(f.options || {})[t] || 'gray'}-fg)` }\">{{ t }}</span></span></template>\n <template v-else-if=\"f.type === 'person'\"><span v-if=\"row[f.key]\" style=\"display:inline-flex;align-items:center;gap:6px;min-width:0\"><Avatar :name=\"row[f.key]\" :size=\"20\" /><span style=\"overflow:hidden;text-overflow:ellipsis\">{{ row[f.key] }}</span></span></template>\n <template v-else-if=\"f.type === 'checkbox'\"><Checkbox size=\"sm\" :checked=\"!!row[f.key]\" @change=\"v => setCell(r, f.key, v)\" /></template>\n <template v-else-if=\"f.type === 'url'\"><a v-if=\"row[f.key]\" href=\"#\" @click.prevent style=\"overflow:hidden;text-overflow:ellipsis\">{{ row[f.key] }}</a></template>\n <template v-else-if=\"f.type === 'progress'\"><span style=\"display:flex;align-items:center;gap:6px;width:100%\"><span style=\"flex:1;height:4px;border-radius:var(--radius-full);background:var(--track);overflow:hidden\"><span :style=\"{ display: 'block', width: (row[f.key] || 0) + '%', height: '100%', background: 'var(--chart-blue)' }\" /></span><span style=\"font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--text-secondary)\">{{ row[f.key] || 0 }}%</span></span></template>\n <template v-else-if=\"f.type === 'number'\"><span style=\"margin-left:auto;font-family:var(--font-mono);font-size:11.5px\">{{ row[f.key] }}</span></template>\n <template v-else><span :style=\"{ overflow: 'hidden', textOverflow: 'ellipsis', fontWeight: f.primary ? 'var(--weight-medium)' : 'var(--weight-regular)', color: f.primary ? 'var(--text-heading)' : 'var(--text-primary)' }\">{{ row[f.key] }}</span></template>\n </span>\n <span :style=\"{ ...cellBase, height: rowHeight + 'px', background: hover === r ? 'var(--bg-inset)' : 'transparent' }\" />\n </template>\n <button type=\"button\" @click=\"addRow\" class=\"kb-st-new\" :style=\"{ gridColumn: '1 / -1', height: rowHeight + 'px' }\"><Icon name=\"plus\" :size=\"13\" />New</button>\n <span :style=\"{ ...cellBase, height: rowHeight + 'px', borderBottom: 'none', background: 'var(--bg-inset)' }\" />\n <span v-for=\"f in fields\" :key=\"'g' + f.key\" :style=\"{ ...cellBase, height: rowHeight + 'px', borderBottom: 'none', background: 'var(--bg-inset)', justifyContent: 'flex-end', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-2xs)', color: 'var(--text-secondary)' }\">{{ agg(f) }}</span>\n <span :style=\"{ ...cellBase, height: rowHeight + 'px', borderBottom: 'none', background: 'var(--bg-inset)' }\" />\n </div>\n </div>\n </div>\n</template>\n<style scoped>\n.kb-pill{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--radius-full);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-xs);line-height:1;white-space:nowrap}\n.kb-st-btn{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 8px;border:none;border-radius:var(--radius-sm);background:transparent;cursor:pointer;color:var(--text-body);font-family:var(--font-sans);font-size:var(--text-sm)}\n.kb-st-btn:hover{background:var(--bg-hover)}\n.kb-st-new{display:flex;align-items:center;gap:6px;padding:0 10px;border:none;border-bottom:1px solid var(--border-subtle);background:transparent;cursor:pointer;color:var(--text-secondary);font-family:var(--font-sans);font-size:var(--text-sm);text-align:left}\n.kb-st-new:hover{background:var(--bg-inset)}\n</style>\n","data/Spreadsheet.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\nimport { Select } from \"../forms/Select.jsx\";\n\nexport const colName = (i) => { let s = \"\"; i++; while (i > 0) { s = String.fromCharCode(64 + ((i - 1) % 26 + 1)) + s; i = Math.floor((i - 1) / 26); } return s; };\nconst norm = (cell) => cell == null ? { v: \"\" } : (typeof cell === \"object\" ? { ...cell } : { v: cell });\nconst refRC = (ref) => { const m = /^([A-Z]+)(\\d+)$/.exec(String(ref).trim().toUpperCase()); if (!m) return null; let c = 0; for (const ch of m[1]) c = c * 26 + (ch.charCodeAt(0) - 64); return { r: +m[2] - 1, c: c - 1 }; };\n\nfunction evalCell(cells, cell, depth = 0) {\n if (!cell || depth > 12) return \"\";\n const raw = cell.f != null ? cell.f : cell.v;\n if (typeof raw === \"string\" && raw.startsWith(\"=\")) return evalFormula(cells, raw, depth);\n return raw;\n}\nfunction numAt(cells, r, c, depth) { const v = evalCell(cells, cells[r] && cells[r][c], depth + 1); const n = typeof v === \"number\" ? v : parseFloat(String(v).replace(/[^0-9.eE+-]/g, \"\")); return isNaN(n) ? null : n; }\nfunction evalFormula(cells, f, depth = 0) {\n try {\n let expr = f.slice(1).toUpperCase().replace(/\\s+/g, \"\");\n expr = expr.replace(/(SUM|AVERAGE|AVG|COUNT|MIN|MAX)\\(([A-Z]+\\d+):([A-Z]+\\d+)\\)/g, (m, fn, a, b) => {\n const A = refRC(a), B = refRC(b), vals = [];\n for (let r = Math.min(A.r, B.r); r <= Math.max(A.r, B.r); r++) for (let c = Math.min(A.c, B.c); c <= Math.max(A.c, B.c); c++) { const n = numAt(cells, r, c, depth); if (n != null) vals.push(n); }\n if (fn === \"COUNT\") return String(vals.length);\n if (!vals.length) return \"0\";\n if (fn === \"SUM\") return String(vals.reduce((x, y) => x + y, 0));\n if (fn === \"MIN\") return String(Math.min(...vals));\n if (fn === \"MAX\") return String(Math.max(...vals));\n return String(vals.reduce((x, y) => x + y, 0) / vals.length);\n });\n expr = expr.replace(/[A-Z]+\\d+/g, (ref) => { const p = refRC(ref); const n = p ? numAt(cells, p.r, p.c, depth) : null; return String(n == null ? 0 : n); });\n if (!/^[-+*/().\\d\\seE]*$/.test(expr)) return \"#ERROR\";\n const out = Function('\"use strict\";return (' + expr + \")\")();\n return typeof out === \"number\" && isFinite(out) ? out : \"#ERROR\";\n } catch (e) { return \"#ERROR\"; }\n}\nfunction fmt(val, t, currency) {\n if (val === \"\" || val == null) return \"\";\n if (typeof val !== \"number\") return String(val);\n const r2 = Math.round(val * 100) / 100;\n if (t === \"percent\") return (Math.round(val * 1000) / 10) + \"%\";\n if (t === \"currency\") return currency + r2.toLocaleString();\n return r2.toLocaleString();\n}\n\n/** Excel-style spreadsheet: toolbar, formula bar (name box + fx), lettered/numbered frozen headers, click + shift-click range selection, double-click editing, =SUM()/AVERAGE()-style formulas, sheet tabs and a Sum/Avg/Count status bar. */\nexport function Spreadsheet({ data = [], rows, cols, colWidths = [], defaultColWidth = 92, currency = \"$\", defaultActive = \"A1\", sheets = [\"Sheet1\"], activeSheet, toolbar = true, formulaBar = true, statusBar = true, height, onChange, style }) {\n const nR = Math.max(rows || 0, data.length, 8), nC = Math.max(cols || 0, ...data.map(r => r.length), 6);\n const [cells, setCells] = React.useState(() => Array.from({ length: nR }, (_, r) => Array.from({ length: nC }, (_, c) => norm(data[r] && data[r][c]))));\n const a0 = refRC(defaultActive) || { r: 0, c: 0 };\n const [active, setActive] = React.useState(a0);\n const [anchor, setAnchor] = React.useState(a0);\n const [editing, setEditing] = React.useState(null);\n const [draft, setDraft] = React.useState(\"\");\n const [sheet, setSheet] = React.useState(activeSheet || sheets[0]);\n const cur = cells[active.r][active.c];\n const rawOf = (cell) => cell.f != null ? cell.f : (cell.v == null ? \"\" : String(cell.v));\n const commit = (r, c, text) => { const next = cells.map(row => row.map(cell => ({ ...cell }))); const cell = next[r][c]; delete cell.f; if (String(text).startsWith(\"=\")) { cell.f = text; cell.v = \"\"; } else { const n = text !== \"\" && !isNaN(Number(text)) ? Number(text) : text; cell.v = n; } setCells(next); onChange && onChange(next); };\n const lo = { r: Math.min(active.r, anchor.r), c: Math.min(active.c, anchor.c) }, hi = { r: Math.max(active.r, anchor.r), c: Math.max(active.c, anchor.c) };\n const multi = lo.r !== hi.r || lo.c !== hi.c;\n const inRange = (r, c) => r >= lo.r && r <= hi.r && c >= lo.c && c <= hi.c;\n const stats = React.useMemo(() => { if (!multi) return null; const vals = []; for (let r = lo.r; r <= hi.r; r++) for (let c = lo.c; c <= hi.c; c++) { const v = evalCell(cells, cells[r][c]); const n = typeof v === \"number\" ? v : NaN; if (!isNaN(n)) vals.push(n); } if (!vals.length) return null; const sum = vals.reduce((x, y) => x + y, 0); return { sum: Math.round(sum * 100) / 100, avg: Math.round(sum / vals.length * 100) / 100, count: vals.length }; }, [cells, active, anchor]);\n const pick = (r, c, e) => { setActive({ r, c }); if (!(e && e.shiftKey)) setAnchor({ r, c }); setEditing(null); };\n const setFlag = (k) => { const next = cells.map(row => row.map(cell => ({ ...cell }))); for (let r = lo.r; r <= hi.r; r++) for (let c = lo.c; c <= hi.c; c++) next[r][c][k] = !cur[k]; setCells(next); };\n const setType = (label) => { const t = { Automatic: undefined, Number: \"number\", Currency: \"currency\", Percent: \"percent\" }[label]; const next = cells.map(row => row.map(cell => ({ ...cell }))); for (let r = lo.r; r <= hi.r; r++) for (let c = lo.c; c <= hi.c; c++) { if (t) next[r][c].t = t; else delete next[r][c].t; } setCells(next); };\n const widths = [46]; for (let c = 0; c < nC; c++) widths.push(colWidths[c] || defaultColWidth);\n const hdr = { position: \"sticky\", top: 0, zIndex: 3, background: \"var(--bg-inset)\", borderRight: \"1px solid var(--border-control)\", borderBottom: \"1px solid var(--border-control)\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-secondary)\", height: 26, boxSizing: \"border-box\", userSelect: \"none\" };\n const tbBtn = (label, k, st) => <button key={label} type=\"button\" onClick={() => setFlag(k)} style={{ width: 24, height: 24, border: \"none\", borderRadius: \"var(--radius-sm)\", cursor: \"pointer\", background: cur[k] ? \"var(--bg-active)\" : \"transparent\", color: \"var(--text-body)\", fontFamily: \"var(--font-sans)\", fontSize: 12.5, ...st }}>{label}</button>;\n return (\n <div style={{ border: \"1px solid var(--border-control)\", borderRadius: \"var(--radius-lg)\", overflow: \"hidden\", background: \"var(--bg-surface)\", boxSizing: \"border-box\", ...style }}>\n {toolbar && <div style={{ height: 36, display: \"flex\", alignItems: \"center\", gap: 2, padding: \"0 8px\", borderBottom: \"1px solid var(--border-subtle)\" }}>\n <IconButton icon=\"undo-2\" size=\"sm\" muted label=\"Undo\" /><IconButton icon=\"redo-2\" size=\"sm\" muted label=\"Redo\" />\n <span style={{ width: 1, height: 16, background: \"var(--border-control)\", margin: \"0 6px\" }}></span>\n {tbBtn(\"B\", \"b\", { fontWeight: 700 })}{tbBtn(\"I\", \"i\", { fontStyle: \"italic\" })}{tbBtn(\"U\", \"u\", { textDecoration: \"underline\" })}\n <span style={{ width: 1, height: 16, background: \"var(--border-control)\", margin: \"0 6px\" }}></span>\n <Select size=\"sm\" options={[\"Automatic\", \"Number\", \"Currency\", \"Percent\"]} value={{ number: \"Number\", currency: \"Currency\", percent: \"Percent\" }[cur.t] || \"Automatic\"} onChange={setType} style={{ width: 110 }} />\n </div>}\n {formulaBar && <div style={{ height: 32, display: \"flex\", alignItems: \"center\", borderBottom: \"1px solid var(--border-control)\" }}>\n <span style={{ width: 72, alignSelf: \"stretch\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", borderRight: \"1px solid var(--border-control)\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: \"var(--text-heading)\" }}>{multi ? colName(lo.c) + (lo.r + 1) + \":\" + colName(hi.c) + (hi.r + 1) : colName(active.c) + (active.r + 1)}</span>\n <span style={{ padding: \"0 10px\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", fontStyle: \"italic\", color: \"var(--text-muted)\", borderRight: \"1px solid var(--border-subtle)\", alignSelf: \"stretch\", display: \"flex\", alignItems: \"center\" }}>fx</span>\n <input value={editing === \"fx\" ? draft : rawOf(cur)} onFocus={() => { setEditing(\"fx\"); setDraft(rawOf(cur)); }} onChange={(e) => setDraft(e.target.value)} onBlur={() => { if (editing === \"fx\") { commit(active.r, active.c, draft); setEditing(null); } }} onKeyDown={(e) => { if (e.key === \"Enter\") e.target.blur(); if (e.key === \"Escape\") { setEditing(null); e.target.blur(); } }} style={{ flex: 1, border: \"none\", outline: \"none\", background: \"transparent\", padding: \"0 10px\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: \"var(--text-primary)\" }} />\n </div>}\n <div style={{ overflow: \"auto\", maxHeight: height }}>\n <div style={{ display: \"grid\", gridTemplateColumns: widths.map(w => w + \"px\").join(\" \"), width: \"fit-content\" }}>\n <span style={{ ...hdr, zIndex: 5, left: 0 }}><span style={{ width: 0, height: 0, borderStyle: \"solid\", borderWidth: \"0 0 10px 10px\", borderColor: \"transparent transparent var(--border-control) transparent\", alignSelf: \"flex-end\", marginLeft: \"auto\", marginRight: 3, marginBottom: 3 }}></span></span>\n {Array.from({ length: nC }, (_, c) => <span key={c} style={{ ...hdr, ...(c >= lo.c && c <= hi.c ? { background: \"var(--soft-bg)\", color: \"var(--action-primary)\" } : null) }}>{colName(c)}</span>)}\n {cells.map((row, r) => [\n <span key={\"h\" + r} style={{ ...hdr, position: \"sticky\", left: 0, top: \"auto\", zIndex: 2, height: 28, ...(r >= lo.r && r <= hi.r ? { background: \"var(--soft-bg)\", color: \"var(--action-primary)\" } : null) }}>{r + 1}</span>,\n ...row.map((cell, c) => {\n const v = evalCell(cells, cell);\n const isNum = typeof v === \"number\";\n const isActive = active.r === r && active.c === c;\n const isEdit = editing && editing !== \"fx\" && editing.r === r && editing.c === c;\n return <span key={r + \",\" + c} onMouseDown={(e) => pick(r, c, e)} onDoubleClick={() => { setEditing({ r, c }); setDraft(rawOf(cell)); }}\n style={{ position: \"relative\", height: 28, boxSizing: \"border-box\", borderRight: \"1px solid var(--border-subtle)\", borderBottom: \"1px solid var(--border-subtle)\", display: \"flex\", alignItems: \"center\", justifyContent: cell.align || (isNum ? \"flex-end\" : \"flex-start\"), padding: \"0 8px\", cursor: \"cell\", background: isActive ? \"var(--bg-surface)\" : inRange(r, c) ? \"var(--soft-bg)\" : \"var(--bg-surface)\", boxShadow: isActive ? \"inset 0 0 0 2px var(--action-primary)\" : \"none\", zIndex: isActive ? 1 : 0, fontFamily: isNum ? \"var(--font-mono)\" : \"var(--font-sans)\", fontSize: isNum ? 11.5 : \"var(--text-sm)\", color: v === \"#ERROR\" ? \"var(--danger)\" : \"var(--text-primary)\", fontWeight: cell.b ? 600 : \"var(--weight-regular)\", fontStyle: cell.i ? \"italic\" : \"normal\", textDecoration: cell.u ? \"underline\" : \"none\", whiteSpace: \"nowrap\", overflow: \"hidden\", userSelect: \"none\" }}>\n {isEdit ? <input autoFocus value={draft} onChange={(e) => setDraft(e.target.value)} onBlur={() => { commit(r, c, draft); setEditing(null); }} onKeyDown={(e) => { if (e.key === \"Enter\") e.target.blur(); if (e.key === \"Escape\") setEditing(null); }} style={{ position: \"absolute\", inset: 0, border: \"none\", outline: \"none\", padding: \"0 8px\", fontFamily: \"inherit\", fontSize: \"inherit\", background: \"var(--bg-surface)\", color: \"var(--text-primary)\" }} /> : fmt(v, cell.t, currency)}\n {isActive && !isEdit && <span style={{ position: \"absolute\", right: -3, bottom: -3, width: 6, height: 6, background: \"var(--action-primary)\", border: \"1px solid var(--bg-surface)\", cursor: \"crosshair\", zIndex: 2 }}></span>}\n </span>;\n })])}\n </div>\n </div>\n {statusBar && <div style={{ height: 32, display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", borderTop: \"1px solid var(--border-control)\", background: \"var(--bg-inset)\", paddingRight: 12 }}>\n <div style={{ display: \"flex\", alignSelf: \"stretch\", alignItems: \"stretch\" }}>\n {sheets.map(s => <button key={s} type=\"button\" onClick={() => setSheet(s)} style={{ border: \"none\", borderRight: \"1px solid var(--border-subtle)\", padding: \"0 16px\", cursor: \"pointer\", background: sheet === s ? \"var(--bg-surface)\" : \"transparent\", color: sheet === s ? \"var(--text-heading)\" : \"var(--text-secondary)\", fontFamily: \"var(--font-sans)\", fontWeight: sheet === s ? \"var(--weight-medium)\" : \"var(--weight-regular)\", fontSize: \"var(--text-sm)\", boxShadow: sheet === s ? \"inset 0 2px 0 var(--action-primary)\" : \"none\" }}>{s}</button>)}\n <span style={{ display: \"flex\", alignItems: \"center\", padding: \"0 6px\" }}><IconButton icon=\"plus\" size=\"xs\" muted label=\"Add sheet\" /></span>\n </div>\n <span style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: \"var(--text-secondary)\" }}>{stats ? \"Sum \" + fmt(stats.sum, cur.t, currency) + \" Avg \" + fmt(stats.avg, cur.t, currency) + \" Count \" + stats.count : \"100%\"}</span>\n </div>}\n </div>\n );\n}\n","data/Spreadsheet.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nimport KSelect from '../forms/Select.vue'\n// Excel-style spreadsheet: toolbar, formula bar (name box + fx), frozen lettered/numbered headers, click + shift-click range selection, double-click editing, =SUM()/AVERAGE() formulas, sheet tabs, Sum/Avg/Count status bar.\nconst props = defineProps({ data: { type: Array, default: () => [] }, rows: Number, cols: Number, colWidths: { type: Array, default: () => [] }, defaultColWidth: { type: Number, default: 92 }, currency: { type: String, default: '$' }, defaultActive: { type: String, default: 'A1' }, sheets: { type: Array, default: () => ['Sheet1'] }, toolbar: { type: Boolean, default: true }, formulaBar: { type: Boolean, default: true }, statusBar: { type: Boolean, default: true }, height: [Number, String], style: Object })\nconst emit = defineEmits(['change'])\n\nconst colName = (i) => { let s = ''; i++; while (i > 0) { s = String.fromCharCode(64 + ((i - 1) % 26 + 1)) + s; i = Math.floor((i - 1) / 26) } return s }\nconst refRC = (ref) => { const m = /^([A-Z]+)(\\d+)$/.exec(String(ref).trim().toUpperCase()); if (!m) return null; let c = 0; for (const ch of m[1]) c = c * 26 + (ch.charCodeAt(0) - 64); return { r: +m[2] - 1, c: c - 1 } }\nconst norm = (cell) => cell == null ? { v: '' } : (typeof cell === 'object' ? { ...cell } : { v: cell })\nfunction evalCell(cells, cell, depth = 0) { if (!cell || depth > 12) return ''; const raw = cell.f != null ? cell.f : cell.v; if (typeof raw === 'string' && raw.startsWith('=')) return evalFormula(cells, raw, depth); return raw }\nfunction numAt(cells, r, c, depth) { const v = evalCell(cells, cells[r] && cells[r][c], depth + 1); const n = typeof v === 'number' ? v : parseFloat(String(v).replace(/[^0-9.eE+-]/g, '')); return isNaN(n) ? null : n }\nfunction evalFormula(cells, f, depth = 0) {\n try {\n let expr = f.slice(1).toUpperCase().replace(/\\s+/g, '')\n expr = expr.replace(/(SUM|AVERAGE|AVG|COUNT|MIN|MAX)\\(([A-Z]+\\d+):([A-Z]+\\d+)\\)/g, (m, fn, a, b) => {\n const A = refRC(a), B = refRC(b), vals = []\n for (let r = Math.min(A.r, B.r); r <= Math.max(A.r, B.r); r++) for (let c = Math.min(A.c, B.c); c <= Math.max(A.c, B.c); c++) { const n = numAt(cells, r, c, depth); if (n != null) vals.push(n) }\n if (fn === 'COUNT') return String(vals.length)\n if (!vals.length) return '0'\n if (fn === 'SUM') return String(vals.reduce((x, y) => x + y, 0))\n if (fn === 'MIN') return String(Math.min(...vals))\n if (fn === 'MAX') return String(Math.max(...vals))\n return String(vals.reduce((x, y) => x + y, 0) / vals.length)\n })\n expr = expr.replace(/[A-Z]+\\d+/g, (ref) => { const p = refRC(ref); const n = p ? numAt(cells, p.r, p.c, depth) : null; return String(n == null ? 0 : n) })\n if (!/^[-+*/().\\d\\seE]*$/.test(expr)) return '#ERROR'\n const out = Function('\"use strict\";return (' + expr + ')')()\n return typeof out === 'number' && isFinite(out) ? out : '#ERROR'\n } catch (e) { return '#ERROR' }\n}\nfunction fmt(val, t, currency) { if (val === '' || val == null) return ''; if (typeof val !== 'number') return String(val); const r2 = Math.round(val * 100) / 100; if (t === 'percent') return (Math.round(val * 1000) / 10) + '%'; if (t === 'currency') return currency + r2.toLocaleString(); return r2.toLocaleString() }\n\nconst nR = Math.max(props.rows || 0, props.data.length, 8)\nconst nC = Math.max(props.cols || 0, ...props.data.map(r => r.length), 6)\nconst cells = ref(Array.from({ length: nR }, (_, r) => Array.from({ length: nC }, (_, c) => norm(props.data[r] && props.data[r][c]))))\nconst a0 = refRC(props.defaultActive) || { r: 0, c: 0 }\nconst active = ref(a0), anchor = ref(a0), editing = ref(null), draft = ref(''), sheet = ref(props.sheets[0])\nconst cur = computed(() => cells.value[active.value.r][active.value.c])\nconst rawOf = (cell) => cell.f != null ? cell.f : (cell.v == null ? '' : String(cell.v))\nconst lo = computed(() => ({ r: Math.min(active.value.r, anchor.value.r), c: Math.min(active.value.c, anchor.value.c) }))\nconst hi = computed(() => ({ r: Math.max(active.value.r, anchor.value.r), c: Math.max(active.value.c, anchor.value.c) }))\nconst multi = computed(() => lo.value.r !== hi.value.r || lo.value.c !== hi.value.c)\nconst inRange = (r, c) => r >= lo.value.r && r <= hi.value.r && c >= lo.value.c && c <= hi.value.c\nconst stats = computed(() => { if (!multi.value) return null; const vals = []; for (let r = lo.value.r; r <= hi.value.r; r++) for (let c = lo.value.c; c <= hi.value.c; c++) { const v = evalCell(cells.value, cells.value[r][c]); if (typeof v === 'number') vals.push(v) } if (!vals.length) return null; const sum = vals.reduce((x, y) => x + y, 0); return { sum: Math.round(sum * 100) / 100, avg: Math.round(sum / vals.length * 100) / 100, count: vals.length } })\nfunction pick(r, c, e) { active.value = { r, c }; if (!(e && e.shiftKey)) anchor.value = { r, c }; editing.value = null }\nfunction commit(r, c, text) { const cell = cells.value[r][c]; delete cell.f; if (String(text).startsWith('=')) { cell.f = text; cell.v = '' } else cell.v = text !== '' && !isNaN(Number(text)) ? Number(text) : text; emit('change', cells.value) }\nfunction setFlag(k) { const on = !cur.value[k]; for (let r = lo.value.r; r <= hi.value.r; r++) for (let c = lo.value.c; c <= hi.value.c; c++) cells.value[r][c][k] = on }\nfunction setType(label) { const t = { Automatic: undefined, Number: 'number', Currency: 'currency', Percent: 'percent' }[label]; for (let r = lo.value.r; r <= hi.value.r; r++) for (let c = lo.value.c; c <= hi.value.c; c++) { if (t) cells.value[r][c].t = t; else delete cells.value[r][c].t } }\nconst gridCols = computed(() => ['46px', ...Array.from({ length: nC }, (_, c) => (props.colWidths[c] || props.defaultColWidth) + 'px')].join(' '))\nconst hdr = { position: 'sticky', top: 0, zIndex: 3, background: 'var(--bg-inset)', borderRight: '1px solid var(--border-control)', borderBottom: '1px solid var(--border-control)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', color: 'var(--text-secondary)', height: '26px', boxSizing: 'border-box', userSelect: 'none' }\nconst hl = { background: 'var(--soft-bg)', color: 'var(--action-primary)' }\n</script>\n<template>\n <div :style=\"{ border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', overflow: 'hidden', background: 'var(--bg-surface)', boxSizing: 'border-box', ...style }\">\n <div v-if=\"toolbar\" style=\"height:36px;display:flex;align-items:center;gap:2px;padding:0 8px;border-bottom:1px solid var(--border-subtle)\">\n <IconButton icon=\"undo-2\" size=\"sm\" muted label=\"Undo\" /><IconButton icon=\"redo-2\" size=\"sm\" muted label=\"Redo\" />\n <span style=\"width:1px;height:16px;background:var(--border-control);margin:0 6px\" />\n <button type=\"button\" @click=\"setFlag('b')\" :style=\"{ width: '24px', height: '24px', border: 'none', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: cur.b ? 'var(--bg-active)' : 'transparent', color: 'var(--text-body)', fontWeight: 700 }\">B</button>\n <button type=\"button\" @click=\"setFlag('i')\" :style=\"{ width: '24px', height: '24px', border: 'none', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: cur.i ? 'var(--bg-active)' : 'transparent', color: 'var(--text-body)', fontStyle: 'italic' }\">I</button>\n <button type=\"button\" @click=\"setFlag('u')\" :style=\"{ width: '24px', height: '24px', border: 'none', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: cur.u ? 'var(--bg-active)' : 'transparent', color: 'var(--text-body)', textDecoration: 'underline' }\">U</button>\n <span style=\"width:1px;height:16px;background:var(--border-control);margin:0 6px\" />\n <KSelect size=\"sm\" :options=\"['Automatic','Number','Currency','Percent']\" :model-value=\"{ number: 'Number', currency: 'Currency', percent: 'Percent' }[cur.t] || 'Automatic'\" @update:modelValue=\"setType\" style=\"width:110px\" />\n </div>\n <div v-if=\"formulaBar\" style=\"height:32px;display:flex;align-items:center;border-bottom:1px solid var(--border-control)\">\n <span style=\"width:72px;align-self:stretch;display:flex;align-items:center;justify-content:center;border-right:1px solid var(--border-control);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)\">{{ multi ? colName(lo.c) + (lo.r + 1) + ':' + colName(hi.c) + (hi.r + 1) : colName(active.c) + (active.r + 1) }}</span>\n <span style=\"padding:0 10px;font-family:var(--font-mono);font-size:var(--text-xs);font-style:italic;color:var(--text-muted);border-right:1px solid var(--border-subtle);align-self:stretch;display:flex;align-items:center\">fx</span>\n <input :value=\"editing === 'fx' ? draft : rawOf(cur)\" @focus=\"editing = 'fx'; draft = rawOf(cur)\" @input=\"draft = $event.target.value\" @blur=\"editing === 'fx' && (commit(active.r, active.c, draft), editing = null)\" @keydown.enter=\"$event.target.blur()\" @keydown.esc=\"editing = null; $event.target.blur()\" style=\"flex:1;border:none;outline:none;background:transparent;padding:0 10px;font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-primary)\" />\n </div>\n <div :style=\"{ overflow: 'auto', maxHeight: typeof height === 'number' ? height + 'px' : height }\">\n <div :style=\"{ display: 'grid', gridTemplateColumns: gridCols, width: 'fit-content' }\">\n <span :style=\"{ ...hdr, zIndex: 5, left: 0 }\" />\n <span v-for=\"c in nC\" :key=\"'c' + c\" :style=\"{ ...hdr, ...((c - 1) >= lo.c && (c - 1) <= hi.c ? hl : null) }\">{{ colName(c - 1) }}</span>\n <template v-for=\"(row, r) in cells\" :key=\"r\">\n <span :style=\"{ ...hdr, position: 'sticky', left: 0, top: 'auto', zIndex: 2, height: '28px', ...(r >= lo.r && r <= hi.r ? hl : null) }\">{{ r + 1 }}</span>\n <span v-for=\"(cell, c) in row\" :key=\"r + ',' + c\" @mousedown=\"pick(r, c, $event)\" @dblclick=\"editing = { r, c }; draft = rawOf(cell)\"\n :style=\"{ position: 'relative', height: '28px', boxSizing: 'border-box', borderRight: '1px solid var(--border-subtle)', borderBottom: '1px solid var(--border-subtle)', display: 'flex', alignItems: 'center', justifyContent: cell.align || (typeof evalCell(cells, cell) === 'number' ? 'flex-end' : 'flex-start'), padding: '0 8px', cursor: 'cell', background: inRange(r, c) && !(active.r === r && active.c === c) ? 'var(--soft-bg)' : 'var(--bg-surface)', boxShadow: active.r === r && active.c === c ? 'inset 0 0 0 2px var(--action-primary)' : 'none', fontFamily: typeof evalCell(cells, cell) === 'number' ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: typeof evalCell(cells, cell) === 'number' ? '11.5px' : 'var(--text-sm)', color: evalCell(cells, cell) === '#ERROR' ? 'var(--danger)' : 'var(--text-primary)', fontWeight: cell.b ? 600 : 'var(--weight-regular)', fontStyle: cell.i ? 'italic' : 'normal', textDecoration: cell.u ? 'underline' : 'none', whiteSpace: 'nowrap', overflow: 'hidden', userSelect: 'none' }\">\n <input v-if=\"editing && editing !== 'fx' && editing.r === r && editing.c === c\" autofocus :value=\"draft\" @input=\"draft = $event.target.value\" @blur=\"commit(r, c, draft); editing = null\" @keydown.enter=\"$event.target.blur()\" @keydown.esc=\"editing = null\" style=\"position:absolute;inset:0;border:none;outline:none;padding:0 8px;font:inherit;background:var(--bg-surface);color:var(--text-primary)\" />\n <template v-else>{{ fmt(evalCell(cells, cell), cell.t, currency) }}</template>\n <span v-if=\"active.r === r && active.c === c && !editing\" style=\"position:absolute;right:-3px;bottom:-3px;width:6px;height:6px;background:var(--action-primary);border:1px solid var(--bg-surface);cursor:crosshair;z-index:2\" />\n </span>\n </template>\n </div>\n </div>\n <div v-if=\"statusBar\" style=\"height:32px;display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--border-control);background:var(--bg-inset);padding-right:12px\">\n <div style=\"display:flex;align-self:stretch;align-items:stretch\">\n <button v-for=\"s in sheets\" :key=\"s\" type=\"button\" @click=\"sheet = s\" :style=\"{ border: 'none', borderRight: '1px solid var(--border-subtle)', padding: '0 16px', cursor: 'pointer', background: sheet === s ? 'var(--bg-surface)' : 'transparent', color: sheet === s ? 'var(--text-heading)' : 'var(--text-secondary)', fontFamily: 'var(--font-sans)', fontWeight: sheet === s ? 'var(--weight-medium)' : 'var(--weight-regular)', fontSize: 'var(--text-sm)', boxShadow: sheet === s ? 'inset 0 2px 0 var(--action-primary)' : 'none' }\">{{ s }}</button>\n <span style=\"display:flex;align-items:center;padding:0 6px\"><IconButton icon=\"plus\" size=\"xs\" muted label=\"Add sheet\" /></span>\n </div>\n <span style=\"font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-secondary)\">{{ stats ? 'Sum ' + fmt(stats.sum, cur.t, currency) + ' Avg ' + fmt(stats.avg, cur.t, currency) + ' Count ' + stats.count : '100%' }}</span>\n </div>\n </div>\n</template>\n"});
|
|
@@ -0,0 +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"});
|
|
@@ -0,0 +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"});
|