@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,223 @@
|
|
|
1
|
+
// Framework-neutral chart math shared by the React and Vue chart components.
|
|
2
|
+
// Everything here is pure: values in, geometry out. No DOM, no tokens.
|
|
3
|
+
|
|
4
|
+
export const SERIES = ["var(--series-1)","var(--series-2)","var(--series-3)","var(--series-4)","var(--series-5)","var(--series-6)","var(--series-7)","var(--series-8)","var(--series-9)","var(--series-10)"];
|
|
5
|
+
export const seriesColor = (i) => SERIES[i % SERIES.length];
|
|
6
|
+
|
|
7
|
+
/** Compact number label: 1.24M · 843K · 12,400 → 12.4K · 0.82 */
|
|
8
|
+
export function fmtNum(v, opts = {}) {
|
|
9
|
+
if (v == null || isNaN(v)) return "–";
|
|
10
|
+
const abs = Math.abs(v);
|
|
11
|
+
const sign = v < 0 ? "-" : "";
|
|
12
|
+
const f = (n, d) => n.toFixed(d).replace(/\.0+$|(\.\d*[1-9])0+$/, "$1");
|
|
13
|
+
if (opts.pct) return f(v, abs < 10 ? 1 : 0) + "%";
|
|
14
|
+
if (abs >= 1e9) return sign + f(abs / 1e9, 2) + "B";
|
|
15
|
+
if (abs >= 1e6) return sign + f(abs / 1e6, 2) + "M";
|
|
16
|
+
if (abs >= 1e4) return sign + f(abs / 1e3, 1) + "K";
|
|
17
|
+
if (abs >= 1000) return sign + abs.toLocaleString("en-US");
|
|
18
|
+
return sign + f(abs, abs < 1 && abs > 0 ? 2 : abs < 100 && abs % 1 ? 1 : 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Nice axis ticks covering [min,max]. Returns {ticks, lo, hi}. */
|
|
22
|
+
export function niceTicks(min, max, count = 4) {
|
|
23
|
+
if (min === max) { max = min + (min === 0 ? 1 : Math.abs(min) * 0.1); }
|
|
24
|
+
const span = max - min;
|
|
25
|
+
const step0 = span / Math.max(1, count);
|
|
26
|
+
const mag = Math.pow(10, Math.floor(Math.log10(step0)));
|
|
27
|
+
const step = [1, 2, 2.5, 5, 10].map((m) => m * mag).find((s) => span / s <= count + 0.5) || 10 * mag;
|
|
28
|
+
const lo = Math.floor(min / step) * step;
|
|
29
|
+
const hi = Math.ceil(max / step) * step;
|
|
30
|
+
const ticks = [];
|
|
31
|
+
for (let t = lo; t <= hi + step / 1e6; t += step) ticks.push(Math.abs(t) < step / 1e6 ? 0 : +t.toPrecision(12));
|
|
32
|
+
return { ticks, lo, hi };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const scaleLin = (lo, hi, r0, r1) => (v) => r0 + ((v - lo) / (hi - lo || 1)) * (r1 - r0);
|
|
36
|
+
|
|
37
|
+
/** Extent over arrays of numbers (ignores null). */
|
|
38
|
+
export function extent(arrs, zero = true) {
|
|
39
|
+
let min = Infinity, max = -Infinity;
|
|
40
|
+
arrs.forEach((a) => a.forEach((v) => { if (v == null) return; if (v < min) min = v; if (v > max) max = v; }));
|
|
41
|
+
if (min === Infinity) { min = 0; max = 1; }
|
|
42
|
+
if (zero) { min = Math.min(0, min); max = Math.max(0, max); }
|
|
43
|
+
return [min, max];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const linePath = (pts) => pts.length ? "M" + pts.map((p) => p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" L") : "";
|
|
47
|
+
export const areaPath = (pts, y0) => pts.length ? linePath(pts) + " L" + pts[pts.length - 1][0].toFixed(1) + " " + y0.toFixed(1) + " L" + pts[0][0].toFixed(1) + " " + y0.toFixed(1) + " Z" : "";
|
|
48
|
+
|
|
49
|
+
/** Stack rows of series values → per-series [y0,y1] bands (positive stacking). */
|
|
50
|
+
export function stack(seriesValues) {
|
|
51
|
+
const n = seriesValues[0] ? seriesValues[0].length : 0;
|
|
52
|
+
const acc = new Array(n).fill(0);
|
|
53
|
+
return seriesValues.map((vals) => vals.map((v, i) => { const y0 = acc[i]; acc[i] += v || 0; return [y0, acc[i]]; }));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Polar arc path (annular sector) for donut/gauge. Angles in degrees, 0 = 12 o'clock, clockwise. */
|
|
57
|
+
export function arcPath(cx, cy, rOuter, rInner, a0, a1) {
|
|
58
|
+
const rad = (a) => ((a - 90) * Math.PI) / 180;
|
|
59
|
+
const p = (r, a) => [cx + r * Math.cos(rad(a)), cy + r * Math.sin(rad(a))];
|
|
60
|
+
const large = a1 - a0 > 180 ? 1 : 0;
|
|
61
|
+
const [x0, y0] = p(rOuter, a0), [x1, y1] = p(rOuter, a1), [x2, y2] = p(rInner, a1), [x3, y3] = p(rInner, a0);
|
|
62
|
+
return `M${x0.toFixed(2)} ${y0.toFixed(2)} A${rOuter} ${rOuter} 0 ${large} 1 ${x1.toFixed(2)} ${y1.toFixed(2)} L${x2.toFixed(2)} ${y2.toFixed(2)} A${rInner} ${rInner} 0 ${large} 0 ${x3.toFixed(2)} ${y3.toFixed(2)} Z`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Waterfall: steps [{label,value,total}] → bars [{label,y0,y1,kind}] with running sum. kind: pos|neg|total */
|
|
66
|
+
export function waterfall(steps) {
|
|
67
|
+
let run = 0;
|
|
68
|
+
return steps.map((s) => {
|
|
69
|
+
if (s.total) { const b = { label: s.label, y0: 0, y1: run, kind: "total", value: run }; return b; }
|
|
70
|
+
const y0 = run; run += s.value;
|
|
71
|
+
return { label: s.label, y0: Math.min(y0, run), y1: Math.max(y0, run), kind: s.value >= 0 ? "pos" : "neg", value: s.value, from: y0, to: run };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Squarified treemap. items [{value,...}] → [{x,y,w,h,item}] in a w×h box. */
|
|
76
|
+
export function treemap(items, w, h) {
|
|
77
|
+
const total = items.reduce((s, it) => s + it.value, 0) || 1;
|
|
78
|
+
const scaled = items.map((it) => ({ it, a: (it.value / total) * w * h })).sort((a, b) => b.a - a.a);
|
|
79
|
+
const out = [];
|
|
80
|
+
let x = 0, y = 0, rw = w, rh = h, row = [];
|
|
81
|
+
const worst = (row, side) => {
|
|
82
|
+
const s = row.reduce((t, r) => t + r.a, 0);
|
|
83
|
+
let mx = 0;
|
|
84
|
+
row.forEach((r) => { const ratio = Math.max((side * side * r.a) / (s * s), (s * s) / (side * side * r.a)); if (ratio > mx) mx = ratio; });
|
|
85
|
+
return mx;
|
|
86
|
+
};
|
|
87
|
+
const layoutRow = (row) => {
|
|
88
|
+
const s = row.reduce((t, r) => t + r.a, 0);
|
|
89
|
+
const horiz = rw >= rh;
|
|
90
|
+
const side = horiz ? rh : rw;
|
|
91
|
+
const thick = s / side;
|
|
92
|
+
let off = 0;
|
|
93
|
+
row.forEach((r) => {
|
|
94
|
+
const len = r.a / thick;
|
|
95
|
+
out.push(horiz ? { x, y: y + off, w: thick, h: len, item: r.it } : { x: x + off, y, w: len, h: thick, item: r.it });
|
|
96
|
+
off += len;
|
|
97
|
+
});
|
|
98
|
+
if (horiz) { x += thick; rw -= thick; } else { y += thick; rh -= thick; }
|
|
99
|
+
};
|
|
100
|
+
scaled.forEach((r) => {
|
|
101
|
+
const side = rw >= rh ? rh : rw;
|
|
102
|
+
if (row.length && worst([...row, r], side) > worst(row, side)) { layoutRow(row); row = [r]; } else row.push(r);
|
|
103
|
+
});
|
|
104
|
+
if (row.length) layoutRow(row);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 5-step sequential heat color index for v within [lo,hi]. */
|
|
109
|
+
export function heatStep(v, lo, hi) {
|
|
110
|
+
const t = (v - lo) / (hi - lo || 1);
|
|
111
|
+
return Math.max(0, Math.min(4, Math.floor(t * 5 - 1e-9)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Monotone cubic path through points — smooth without overshooting real values. */
|
|
115
|
+
export function curvePath(pts) {
|
|
116
|
+
const n = pts.length;
|
|
117
|
+
if (n < 3) return linePath(pts);
|
|
118
|
+
const xs = pts.map((p) => p[0]), ys = pts.map((p) => p[1]);
|
|
119
|
+
const dx = [], dy = [], m = [];
|
|
120
|
+
for (let i = 0; i < n - 1; i++) { dx.push(xs[i + 1] - xs[i]); dy.push(ys[i + 1] - ys[i]); m.push(dy[i] / (dx[i] || 1)); }
|
|
121
|
+
const t = [m[0]];
|
|
122
|
+
for (let i = 1; i < n - 1; i++) t.push(m[i - 1] * m[i] <= 0 ? 0 : (m[i - 1] + m[i]) / 2);
|
|
123
|
+
t.push(m[n - 2]);
|
|
124
|
+
let d = "M" + xs[0].toFixed(1) + " " + ys[0].toFixed(1);
|
|
125
|
+
for (let i = 0; i < n - 1; i++) {
|
|
126
|
+
const c = dx[i] / 3;
|
|
127
|
+
d += " C" + (xs[i] + c).toFixed(1) + " " + (ys[i] + t[i] * c).toFixed(1) + "," + (xs[i + 1] - c).toFixed(1) + " " + (ys[i + 1] - t[i + 1] * c).toFixed(1) + "," + xs[i + 1].toFixed(1) + " " + ys[i + 1].toFixed(1);
|
|
128
|
+
}
|
|
129
|
+
return d;
|
|
130
|
+
}
|
|
131
|
+
/** Step path (stairs) — for values that hold until the next reading. */
|
|
132
|
+
export function stepPath(pts) {
|
|
133
|
+
if (!pts.length) return "";
|
|
134
|
+
let d = "M" + pts[0][0].toFixed(1) + " " + pts[0][1].toFixed(1);
|
|
135
|
+
for (let i = 1; i < pts.length; i++) { const mx = (pts[i - 1][0] + pts[i][0]) / 2; d += " L" + mx.toFixed(1) + " " + pts[i - 1][1].toFixed(1) + " L" + mx.toFixed(1) + " " + pts[i][1].toFixed(1) + " L" + pts[i][0].toFixed(1) + " " + pts[i][1].toFixed(1); }
|
|
136
|
+
return d;
|
|
137
|
+
}
|
|
138
|
+
/** linear | smooth | step in one call. */
|
|
139
|
+
export const shapePath = (pts, curve) => (curve === "smooth" ? curvePath(pts) : curve === "step" ? stepPath(pts) : linePath(pts));
|
|
140
|
+
export const closeToBaseline = (d, pts, y0) => (d ? d + " L" + pts[pts.length - 1][0].toFixed(1) + " " + y0.toFixed(1) + " L" + pts[0][0].toFixed(1) + " " + y0.toFixed(1) + " Z" : "");
|
|
141
|
+
|
|
142
|
+
/** Sankey layout. nodes:[{id,label}], links:[{source,target,value}] — depth by longest path. */
|
|
143
|
+
export function sankey(nodes, links, w, h, nodeW, pad) {
|
|
144
|
+
nodeW = nodeW || 14; pad = pad == null ? 14 : pad;
|
|
145
|
+
const byId = {};
|
|
146
|
+
nodes.forEach((n) => { byId[n.id] = Object.assign({}, n, { in: [], out: [], depth: 0, value: 0 }); });
|
|
147
|
+
links.forEach((l) => { byId[l.source].out.push(l); byId[l.target].in.push(l); });
|
|
148
|
+
let changed = true, guard = 0;
|
|
149
|
+
while (changed && guard++ < 60) {
|
|
150
|
+
changed = false;
|
|
151
|
+
links.forEach((l) => { const d = byId[l.source].depth + 1; if (d > byId[l.target].depth) { byId[l.target].depth = d; changed = true; } });
|
|
152
|
+
}
|
|
153
|
+
const list = Object.keys(byId).map((k) => byId[k]);
|
|
154
|
+
list.forEach((n) => { n.value = Math.max(n.in.reduce((s, l) => s + l.value, 0), n.out.reduce((s, l) => s + l.value, 0)); });
|
|
155
|
+
const maxDepth = Math.max.apply(null, list.map((n) => n.depth));
|
|
156
|
+
/* Sinks hug the right edge so the diagram reads left-to-right even with uneven path lengths. */
|
|
157
|
+
list.forEach((n) => { if (!n.out.length) n.depth = maxDepth; });
|
|
158
|
+
const cols = [];
|
|
159
|
+
list.forEach((n) => { (cols[n.depth] = cols[n.depth] || []).push(n); });
|
|
160
|
+
const colX = maxDepth ? (w - nodeW) / maxDepth : 0;
|
|
161
|
+
const scale = Math.min.apply(null, cols.map((c) => (h - pad * (c.length - 1)) / (c.reduce((s, n) => s + n.value, 0) || 1)));
|
|
162
|
+
cols.forEach((col, ci) => {
|
|
163
|
+
let y = 0;
|
|
164
|
+
col.forEach((n) => { n.x = ci * colX; n.w = nodeW; n.y = y; n.h = Math.max(2, n.value * scale); y += n.h + pad; });
|
|
165
|
+
const extra = (h - (y - pad)) / 2;
|
|
166
|
+
col.forEach((n) => { n.y += extra; });
|
|
167
|
+
});
|
|
168
|
+
const outAt = {}, inAt = {};
|
|
169
|
+
const ribbons = links.map((l) => {
|
|
170
|
+
const s = byId[l.source], t = byId[l.target];
|
|
171
|
+
const so = (outAt[l.source] = outAt[l.source] || 0), ti = (inAt[l.target] = inAt[l.target] || 0);
|
|
172
|
+
const th = Math.max(1, l.value * scale);
|
|
173
|
+
outAt[l.source] += th; inAt[l.target] += th;
|
|
174
|
+
const x0 = s.x + s.w, x1 = t.x, cx = (x1 - x0) / 2;
|
|
175
|
+
const y0 = s.y + so, y1 = t.y + ti;
|
|
176
|
+
const d = "M" + x0 + " " + y0 + " C" + (x0 + cx) + " " + y0 + ", " + (x1 - cx) + " " + y1 + ", " + x1 + " " + y1
|
|
177
|
+
+ " L" + x1 + " " + (y1 + th) + " C" + (x1 - cx) + " " + (y1 + th) + ", " + (x0 + cx) + " " + (y0 + th) + ", " + x0 + " " + (y0 + th) + " Z";
|
|
178
|
+
return { link: l, thickness: th, d: d, source: s, target: t };
|
|
179
|
+
});
|
|
180
|
+
return { nodes: list, ribbons: ribbons, maxDepth: maxDepth };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Chord (circular sankey) layout from a square matrix of flows. */
|
|
184
|
+
export function chord(matrix, gapDeg) {
|
|
185
|
+
gapDeg = gapDeg == null ? 2 : gapDeg;
|
|
186
|
+
const n = matrix.length;
|
|
187
|
+
const totals = matrix.map((row, i) => row.reduce((s, v) => s + v, 0) + matrix.reduce((s, r2, j) => s + (j === i ? 0 : r2[i]), 0));
|
|
188
|
+
const grand = totals.reduce((s, v) => s + v, 0) || 1;
|
|
189
|
+
const span = 360 - gapDeg * n;
|
|
190
|
+
const arcs = []; let a = 0;
|
|
191
|
+
totals.forEach((v, i) => { const sweep = (v / grand) * span; arcs.push({ i: i, a0: a, a1: a + sweep, value: v }); a += sweep + gapDeg; });
|
|
192
|
+
const cursor = arcs.map((x) => x.a0);
|
|
193
|
+
const ribbons = [];
|
|
194
|
+
for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) {
|
|
195
|
+
const v = matrix[i][j];
|
|
196
|
+
if (!v || i === j) continue;
|
|
197
|
+
const sweep = (v / grand) * span;
|
|
198
|
+
const a0 = cursor[i], a1 = a0 + sweep; cursor[i] = a1;
|
|
199
|
+
const b0 = cursor[j], b1 = b0 + sweep; cursor[j] = b1;
|
|
200
|
+
ribbons.push({ i: i, j: j, value: v, a0: a0, a1: a1, b0: b0, b1: b1 });
|
|
201
|
+
}
|
|
202
|
+
return { arcs: arcs, ribbons: ribbons, totals: totals, grand: grand };
|
|
203
|
+
}
|
|
204
|
+
export const polar = (cx, cy, r, deg) => [cx + r * Math.cos(((deg - 90) * Math.PI) / 180), cy + r * Math.sin(((deg - 90) * Math.PI) / 180)];
|
|
205
|
+
/** Ribbon between two arc spans, bowed through the circle centre. */
|
|
206
|
+
export function chordRibbon(cx, cy, r, a0, a1, b0, b1) {
|
|
207
|
+
const p = (deg) => polar(cx, cy, r, deg);
|
|
208
|
+
const s0 = p(a0), s1 = p(a1), t0 = p(b0), t1 = p(b1);
|
|
209
|
+
const big = (x, y) => (Math.abs(y - x) > 180 ? 1 : 0);
|
|
210
|
+
return "M" + s0[0].toFixed(1) + " " + s0[1].toFixed(1)
|
|
211
|
+
+ " A" + r + " " + r + " 0 " + big(a0, a1) + " 1 " + s1[0].toFixed(1) + " " + s1[1].toFixed(1)
|
|
212
|
+
+ " Q" + cx + " " + cy + " " + t0[0].toFixed(1) + " " + t0[1].toFixed(1)
|
|
213
|
+
+ " A" + r + " " + r + " 0 " + big(b0, b1) + " 1 " + t1[0].toFixed(1) + " " + t1[1].toFixed(1)
|
|
214
|
+
+ " Q" + cx + " " + cy + " " + s0[0].toFixed(1) + " " + s0[1].toFixed(1) + " Z";
|
|
215
|
+
}
|
|
216
|
+
/** Quantile buckets for choropleth shading — returns a 0..steps-1 index fn. */
|
|
217
|
+
export function quantize(values, steps) {
|
|
218
|
+
const sorted = values.filter((v) => v != null).slice().sort((a, b) => a - b);
|
|
219
|
+
if (!sorted.length) return () => 0;
|
|
220
|
+
const cuts = [];
|
|
221
|
+
for (let i = 1; i < steps; i++) cuts.push(sorted[Math.floor((i / steps) * sorted.length)]);
|
|
222
|
+
return (v) => { if (v == null) return -1; let k = 0; while (k < cuts.length && v >= cuts[k]) k++; return k; };
|
|
223
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Charting module — 12 chart types on one geometry core (`chart-geometry.js`), React + Vue 3.
|
|
2
|
+
Types: `Sparkline`, `LineChart` (area/stacked/`curve`), `BarChart` (grouped/stacked/diverging + `overlay` combo line on a right axis), `BarList`, `DonutChart` (pie opt), `ScatterChart` (bubble), `HeatmapChart` (sequential/diverging), `WaterfallChart`, `BulletChart`, `GaugeChart`, `FunnelChart`, `TreemapChart`, `RadarChart`, `Histogram`, `BoxPlot`, `ParetoChart`, `CandlestickChart`, `SankeyChart`, `ChordChart` (circular sankey), `SunburstChart`, `WorldMap`, `GanttChart`. Shared furniture in `ChartKit.jsx` (`ChartLegend`, `ChartTooltip`).
|
|
3
|
+
|
|
4
|
+
Picking one: trend over time → Line (`curve="smooth"` for a soft trend, `"step"` for hold-until-next readings); composition → Donut ≤6 slices, Treemap 15+, Sunburst when depth matters; ranking → BarList or Pareto (when "which few cause most" is the question); distribution → Histogram for one population, BoxPlot for many; correlation → Scatter; flow → Sankey (directional) or Chord (cyclical, many-to-many); price → Candlestick; schedule → Gantt; geography → WorldMap (needs the pinned d3 + topojson tags on the page); profile comparison → Radar. 3D (`Surface3D`, `Scatter3D`, `Bar3D`, `Pie3D`, `Ribbon3D`) is presentation-tier only — default to the 2D equivalent for analysis; orbitable via drag (Pie3D spins in place), SVG-rendered, React + Vue 3.
|
|
5
|
+
|
|
6
|
+
Size tiers, one component each:
|
|
7
|
+
- **sm (tile)** — `Sparkline`, or any chart with `axes={false} legend={false} interactive={false}` at ~200×64. Lives inside a stat card next to the number.
|
|
8
|
+
- **md (interactive)** — defaults: hover tooltip + guideline, legend click mutes a series. ~560–650px wide inside a content card.
|
|
9
|
+
- **lg (full page)** — one chart owns the screen: KPI rail on top, the chart at ~880×430+, breakdown panels (`BarList`, `DonutChart`) at the side. See the Full-Page Analytics card.
|
|
10
|
+
|
|
11
|
+
Rules:
|
|
12
|
+
- Colors: series walk `--series-1…10` in order; signed values are ALWAYS `--chart-pos`/`--chart-neg` (diverging bars, waterfall, deltas, auto sparklines); totals/overlay lines are `--chart-total`; guides are `--chart-guide` dashed. Never hard-code a hex.
|
|
13
|
+
- Lines are straight — no spline smoothing. The only gradient in the system stays the area fill (`--chart-area-alpha`).
|
|
14
|
+
- Numbers are `--font-mono`, compact-formatted via `fmtNum` (1.24M · 843K · 12%).
|
|
15
|
+
- Every chart answers a hover, at every size — a tile-tier chart drops its axes and legend, never its tooltip.
|
|
16
|
+
- Stacked areas fill to the baseline so their gradients overlap; stacked bars round only the topmost segment so the joins stay flush.
|
|
17
|
+
- Geography is data, never a drawing: `WorldMap` projects Natural Earth geometry with d3-geo.
|
|
18
|
+
- Donuts cap at ~6 segments; beyond that use BarList or Treemap. Bullets beat a wall of gauges for 3+ KPIs.
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
<LineChart series={[{name:"Actual",values:[...]},{name:"Plan",values:[...]}]} xLabels={months} target={1250}/>
|
|
22
|
+
<BarChart mode="diverging" categories={entities} series={[{name:"Δ vs plan",values:[120,-80,45]}]}/>
|
|
23
|
+
<WaterfallChart steps={[{label:"Revenue",value:6100},{label:"COGS",value:-2400},{label:"Net",total:true}]}/>
|
|
24
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Monospace code/JSON block with copy button. Addition — upgrades the plain LogViewer for single-block SQL/JSON display. */
|
|
3
|
+
export interface CodeBlockProps {
|
|
4
|
+
code?: string;
|
|
5
|
+
language?: string;
|
|
6
|
+
height?: number | string;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
}
|
|
9
|
+
export declare function CodeBlock(props: CodeBlockProps): JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
|
|
4
|
+
/** Monospace code/JSON block with a language label and copy-to-clipboard button (SQL Preview, export JSON). */
|
|
5
|
+
export function CodeBlock({ code = "", language, height, style }) {
|
|
6
|
+
const [copied, setCopied] = React.useState(false);
|
|
7
|
+
const copy = () => { try { navigator.clipboard && navigator.clipboard.writeText(code); } catch (e) {} setCopied(true); setTimeout(() => setCopied(false), 1400); };
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ background: "var(--bg-inset)", border: "1px solid var(--border-subtle)", borderRadius: "var(--radius-md)", ...style }}>
|
|
10
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "7px 10px", borderBottom: "1px solid var(--border-subtle)" }}>
|
|
11
|
+
<span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-muted)" }}>{language || "code"}</span>
|
|
12
|
+
<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 }}>
|
|
13
|
+
<Icon name={copied ? "check" : "copy"} size={12} />{copied ? "Copied" : "Copy"}
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
<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>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import Icon from '../icons/Icon.vue'
|
|
4
|
+
// Monospace code/JSON block with a language label and copy-to-clipboard button (SQL Preview, export JSON).
|
|
5
|
+
const props = defineProps({ code: { type: String, default: '' }, language: String, height: [Number, String], style: Object })
|
|
6
|
+
const copied = ref(false)
|
|
7
|
+
function copy() {
|
|
8
|
+
try { navigator.clipboard && navigator.clipboard.writeText(props.code) } catch (e) {}
|
|
9
|
+
copied.value = true; setTimeout(() => copied.value = false, 1400)
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
12
|
+
<template>
|
|
13
|
+
<div :style="{ background: 'var(--bg-inset)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', ...style }">
|
|
14
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:7px 10px;border-bottom:1px solid var(--border-subtle)">
|
|
15
|
+
<span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-muted)">{{ language || 'code' }}</span>
|
|
16
|
+
<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 }">
|
|
17
|
+
<Icon :name="copied ? 'check' : 'copy'" :size="12" />{{ copied ? 'Copied' : 'Copy' }}
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
<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>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Hairline data table — #fafafa header, #f5f5f5 row separators.
|
|
4
|
+
* @startingPoint section="Data" subtitle="Comparison / preview tables with mono cells" viewport="700x300"
|
|
5
|
+
*/
|
|
6
|
+
export interface DataTableColumn {
|
|
7
|
+
key: string;
|
|
8
|
+
label: string;
|
|
9
|
+
width?: number | string;
|
|
10
|
+
/** Menlo 11px cell (config codes, values) */
|
|
11
|
+
mono?: boolean;
|
|
12
|
+
align?: "left" | "right" | "center";
|
|
13
|
+
render?: (value: any, row: any, index: number) => React.ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export interface DataTableProps {
|
|
16
|
+
columns?: DataTableColumn[];
|
|
17
|
+
rows?: Record<string, any>[];
|
|
18
|
+
/** 29px rows + lowercase 11px headers (data-preview drawer) */
|
|
19
|
+
dense?: boolean;
|
|
20
|
+
/** Leading checkbox column */
|
|
21
|
+
selectable?: boolean;
|
|
22
|
+
selected?: number[];
|
|
23
|
+
/** onToggle("all"|rowIndex, checked) */
|
|
24
|
+
onToggle?: (which: number | "all", checked: boolean) => void;
|
|
25
|
+
headerBorder?: boolean;
|
|
26
|
+
style?: React.CSSProperties;
|
|
27
|
+
}
|
|
28
|
+
export declare function DataTable(props: DataTableProps): JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Checkbox } from "../forms/Checkbox.jsx";
|
|
3
|
+
|
|
4
|
+
/** Keybricks data table. columns: [{key,label,width,mono,render}]. dense = 29px preview-drawer rows; default = 51.5px config rows. */
|
|
5
|
+
export function DataTable({ columns = [], rows = [], dense, selectable, selected = [], onToggle, headerBorder = true, style }) {
|
|
6
|
+
const rowH = dense ? 29.5 : 51.5;
|
|
7
|
+
const cellFont = (c) => c.mono
|
|
8
|
+
? { fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: "var(--tracking-xs)", color: "var(--text-heading)" }
|
|
9
|
+
: { fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", color: "var(--text-primary)" };
|
|
10
|
+
return (
|
|
11
|
+
<table style={{ width: "100%", borderCollapse: "collapse", ...style }}>
|
|
12
|
+
<thead>
|
|
13
|
+
<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 }}>
|
|
14
|
+
{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}
|
|
15
|
+
{columns.map((c) => (
|
|
16
|
+
<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>
|
|
17
|
+
))}
|
|
18
|
+
</tr>
|
|
19
|
+
</thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
{rows.map((r, ri) => (
|
|
22
|
+
<tr key={ri} style={{ height: rowH, borderBottom: "1px solid var(--border-subtle)" }}>
|
|
23
|
+
{selectable ? <td style={{ padding: "0 0 0 16px" }}><Checkbox size="sm" checked={selected.includes(ri)} onChange={(v) => onToggle && onToggle(ri, v)} /></td> : null}
|
|
24
|
+
{columns.map((c) => (
|
|
25
|
+
<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>
|
|
26
|
+
))}
|
|
27
|
+
</tr>
|
|
28
|
+
))}
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import Checkbox from '../forms/Checkbox.vue'
|
|
4
|
+
// Keybricks data table. columns: [{key,label,width,mono,render}]. dense = 29px preview-drawer rows; default = 51.5px config rows.
|
|
5
|
+
const 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 })
|
|
6
|
+
const emit = defineEmits(['toggle'])
|
|
7
|
+
const rowH = computed(() => props.dense ? 29.5 : 51.5)
|
|
8
|
+
function 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)' } }
|
|
9
|
+
</script>
|
|
10
|
+
<template>
|
|
11
|
+
<table :style="{ width: '100%', borderCollapse: 'collapse', ...style }">
|
|
12
|
+
<thead>
|
|
13
|
+
<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 }">
|
|
14
|
+
<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>
|
|
15
|
+
<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>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
<tr v-for="(r, ri) in rows" :key="ri" :style="{ height: rowH, borderBottom: '1px solid var(--border-subtle)' }">
|
|
20
|
+
<td v-if="selectable" style="padding:0 0 0 16px"><Checkbox size="sm" :checked="selected.includes(ri)" @change="v => emit('toggle', ri, v)" /></td>
|
|
21
|
+
<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>
|
|
22
|
+
</tr>
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Before→after change value, e.g. "USD → EUR". Addition — formalizes the comparison/confirmation-step pattern. */
|
|
3
|
+
export interface DiffValueProps {
|
|
4
|
+
/** Omit to show only the new value with an arrow (new field) */
|
|
5
|
+
from?: React.ReactNode;
|
|
6
|
+
to: React.ReactNode;
|
|
7
|
+
tone?: "neutral" | "warning" | "success" | "danger";
|
|
8
|
+
mono?: boolean;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
}
|
|
11
|
+
export declare function DiffValue(props: DiffValueProps): JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
|
|
4
|
+
const toneColor = { neutral: "var(--text-heading)", warning: "var(--warning-text)", success: "var(--success-text)", danger: "var(--danger-text)" };
|
|
5
|
+
|
|
6
|
+
/** Before → after value change ("USD → EUR"). tone colors the new value; from renders muted + struck through when both sides are given. */
|
|
7
|
+
export function DiffValue({ from, to, tone = "warning", mono = true, style }) {
|
|
8
|
+
return (
|
|
9
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--text-sm)", ...style }}>
|
|
10
|
+
{from != null ? <span style={{ color: "var(--text-muted)", textDecoration: "line-through" }}>{from}</span> : null}
|
|
11
|
+
<Icon name="arrow-right" size={12} style={{ color: "var(--text-muted)" }} />
|
|
12
|
+
<span style={{ color: toneColor[tone], fontWeight: "var(--weight-medium)" }}>{to}</span>
|
|
13
|
+
</span>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Icon from '../icons/Icon.vue'
|
|
3
|
+
const toneColor = { neutral: 'var(--text-heading)', warning: 'var(--warning-text)', success: 'var(--success-text)', danger: 'var(--danger-text)' }
|
|
4
|
+
// Before → after value change ("USD → EUR"). tone colors the new value; from renders muted + struck through when both sides are given.
|
|
5
|
+
defineProps({ from: [String, Number], to: [String, Number], tone: { type: String, default: 'warning' }, mono: { type: Boolean, default: true }, style: Object })
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<span :style="{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--text-sm)', ...style }">
|
|
9
|
+
<span v-if="from != null" style="color:var(--text-muted);text-decoration:line-through">{{ from }}</span>
|
|
10
|
+
<Icon name="arrow-right" :size="12" style="color:var(--text-muted)" />
|
|
11
|
+
<span :style="{ color: toneColor[tone], fontWeight: 'var(--weight-medium)' }">{{ to }}</span>
|
|
12
|
+
</span>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
/** SAP Fiori-style list-report filter bar: variant title + expand/collapse, wrapping labeled filter fields (children), Adapt Filters (n) + Go actions. Compose with Field + Select/Input/DatePicker/MultiSelect children. */
|
|
4
|
+
export interface FilterBarProps {
|
|
5
|
+
/** Variant name shown top-left (e.g. "Standard") */
|
|
6
|
+
variant?: string;
|
|
7
|
+
/** Show the modified asterisk after the variant name */
|
|
8
|
+
modified?: boolean;
|
|
9
|
+
/** Count shown in "Adapt Filters (n)" */
|
|
10
|
+
adaptCount?: number;
|
|
11
|
+
defaultExpanded?: boolean;
|
|
12
|
+
onGo?: () => void;
|
|
13
|
+
onAdapt?: () => void;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
style?: React.CSSProperties;
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { Button } from "../buttons/Button.jsx";
|
|
4
|
+
|
|
5
|
+
/** 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. */
|
|
6
|
+
export function FilterBar({ variant = "Standard", modified, adaptCount, defaultExpanded = true, onGo, onAdapt, children, style }) {
|
|
7
|
+
const [open, setOpen] = React.useState(defaultExpanded);
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)", padding: "10px 16px 0", boxSizing: "border-box", ...style }}>
|
|
10
|
+
<div style={{ display: "flex", alignItems: "center", gap: 6, paddingBottom: 10 }}>
|
|
11
|
+
<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)" }}>
|
|
12
|
+
{variant}{modified && <span style={{ color: "var(--text-muted)", fontWeight: "var(--weight-regular)" }}>*</span>}
|
|
13
|
+
<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>
|
|
14
|
+
</button>
|
|
15
|
+
<span style={{ flex: 1 }}></span>
|
|
16
|
+
<Button variant="text" size="sm" onClick={onAdapt}>Adapt Filters{adaptCount ? ` (${adaptCount})` : ""}</Button>
|
|
17
|
+
<Button size="sm" onClick={onGo}>Go</Button>
|
|
18
|
+
</div>
|
|
19
|
+
{open && <div style={{ display: "flex", flexWrap: "wrap", gap: "12px 16px", paddingBottom: 14, borderTop: "1px solid var(--border-subtle)", paddingTop: 12 }}>{children}</div>}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import Icon from '../icons/Icon.vue'
|
|
4
|
+
import Button from '../buttons/Button.vue'
|
|
5
|
+
// SAP Fiori-style list-report filter bar: variant title + expand/collapse, wrapping labeled filter fields (slot), Adapt Filters (n) + Go.
|
|
6
|
+
const props = defineProps({ variant: { type: String, default: 'Standard' }, modified: Boolean, adaptCount: Number, defaultExpanded: { type: Boolean, default: true }, style: Object })
|
|
7
|
+
const emit = defineEmits(['go', 'adapt'])
|
|
8
|
+
const open = ref(props.defaultExpanded)
|
|
9
|
+
</script>
|
|
10
|
+
<template>
|
|
11
|
+
<div :style="{ background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', padding: '10px 16px 0', boxSizing: 'border-box', ...style }">
|
|
12
|
+
<div style="display:flex;align-items:center;gap:6px;padding-bottom:10px">
|
|
13
|
+
<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)">
|
|
14
|
+
{{ variant }}<span v-if="modified" style="color:var(--text-muted);font-weight:var(--weight-regular)">*</span>
|
|
15
|
+
<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>
|
|
16
|
+
</button>
|
|
17
|
+
<span style="flex:1" />
|
|
18
|
+
<Button variant="text" size="sm" @click="emit('adapt')">Adapt Filters{{ adaptCount ? ` (${adaptCount})` : '' }}</Button>
|
|
19
|
+
<Button size="sm" @click="emit('go')">Go</Button>
|
|
20
|
+
</div>
|
|
21
|
+
<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>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Label/value summary row. Addition — formalizes the ad-hoc rows used in config and confirmation steps. */
|
|
3
|
+
export interface KeyValueProps {
|
|
4
|
+
label: string;
|
|
5
|
+
value?: React.ReactNode;
|
|
6
|
+
/** Render value in Menlo (config codes) */
|
|
7
|
+
mono?: boolean;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
}
|
|
10
|
+
export declare function KeyValue(props: KeyValueProps): JSX.Element;
|
|
11
|
+
/** Stack of KeyValue rows separated by hairlines. */
|
|
12
|
+
export interface KeyValueListProps {
|
|
13
|
+
rows?: KeyValueProps[];
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
}
|
|
16
|
+
export declare function KeyValueList(props: KeyValueListProps): JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
/** Label/value row (config summaries, confirmation steps). mono renders the value in Menlo. */
|
|
4
|
+
export function KeyValue({ label, value, mono, style }) {
|
|
5
|
+
return (
|
|
6
|
+
<div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "7px 0", ...style }}>
|
|
7
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-secondary)" }}>{label}</span>
|
|
8
|
+
<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>
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Stack of KeyValue rows with hairline separators. rows: [{label,value,mono}]. */
|
|
14
|
+
export function KeyValueList({ rows = [], style }) {
|
|
15
|
+
return (
|
|
16
|
+
<div style={{ display: "flex", flexDirection: "column", ...style }}>
|
|
17
|
+
{rows.map((r, i) => (
|
|
18
|
+
<React.Fragment key={r.label}>
|
|
19
|
+
<KeyValue {...r} />
|
|
20
|
+
{i < rows.length - 1 ? <div style={{ height: 1, background: "var(--border-subtle)" }} /> : null}
|
|
21
|
+
</React.Fragment>
|
|
22
|
+
))}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// Label/value row (config summaries, confirmation steps). mono renders the value in Menlo.
|
|
3
|
+
defineProps({ label: String, value: [String, Number], mono: Boolean, style: Object })
|
|
4
|
+
</script>
|
|
5
|
+
<template>
|
|
6
|
+
<div :style="{ display: 'flex', justifyContent: 'space-between', gap: 12, padding: '7px 0', ...style }">
|
|
7
|
+
<span style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)">{{ label }}</span>
|
|
8
|
+
<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>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<!-- KeyValueList — stack of KeyValue rows with hairline separators. rows: [{label,value,mono}]. -->
|
|
13
|
+
<!--
|
|
14
|
+
<script setup name="KeyValueList">
|
|
15
|
+
import KeyValue from './KeyValue.vue'
|
|
16
|
+
defineProps({ rows: { type: Array, default: () => [] }, style: Object })
|
|
17
|
+
</script>
|
|
18
|
+
<template>
|
|
19
|
+
<div :style="{ display: 'flex', flexDirection: 'column', ...style }">
|
|
20
|
+
<template v-for="(r, i) in rows" :key="r.label">
|
|
21
|
+
<KeyValue v-bind="r" />
|
|
22
|
+
<div v-if="i < rows.length - 1" style="height:1px;background:var(--border-subtle)" />
|
|
23
|
+
</template>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
-->
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Expandable list row (Workflow groups): chevron, 32px hairline icon tile, 13px/500 title, 12px meta, right count. */
|
|
3
|
+
export interface ListRowProps {
|
|
4
|
+
icon?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
meta?: string;
|
|
7
|
+
/** Right-aligned text ("1 item") */
|
|
8
|
+
trailing?: React.ReactNode;
|
|
9
|
+
expanded?: boolean;
|
|
10
|
+
onClick?: () => void;
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
export declare function ListRow(props: ListRowProps): JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { IconTile } from "../display/Badge.jsx";
|
|
4
|
+
|
|
5
|
+
/** Expandable workflow list row: chevron + bordered 32px icon tile + title/meta + right count. */
|
|
6
|
+
export function ListRow({ icon = "file-text", title, meta, trailing, expanded, onClick, children, style }) {
|
|
7
|
+
const [hover, setHover] = React.useState(false);
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ borderBottom: "1px solid var(--border-card)", ...style }}>
|
|
10
|
+
<div onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
11
|
+
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 16px", cursor: "pointer", background: hover ? "var(--bg-inset)" : "transparent", transition: "var(--t-hover)" }}>
|
|
12
|
+
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
|
13
|
+
<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>
|
|
14
|
+
<IconTile icon={icon} kind="row" />
|
|
15
|
+
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
16
|
+
<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>
|
|
17
|
+
{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}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
{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}
|
|
21
|
+
</div>
|
|
22
|
+
{expanded && children ? <div style={{ padding: "0 16px 12px 72px" }}>{children}</div> : null}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|