@keyfunc/keybricks-ds 1.0.4 → 2.1.1
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 +4 -4
- package/DESIGN.md +154 -53
- package/INTEGRATION.md +174 -35
- package/SKILL.md +6 -4
- package/_adherence.oxlintrc.json +164 -68
- package/_scripts/build-index.mjs +9 -0
- package/_scripts/build-tokens.mjs +5 -2
- package/assets/icons/icon-index.js +1 -1
- package/assets/icons/icon-index.json +1 -1
- package/components/_cards/accordion.card.html +2 -2
- package/components/_cards/action-chip.card.html +8 -3
- package/components/_cards/activity.card.html +2 -2
- package/components/_cards/alert.card.html +2 -2
- package/components/_cards/app-chrome.card.html +4 -4
- package/components/_cards/banner.card.html +1 -1
- package/components/_cards/breadcrumb.card.html +2 -2
- package/components/_cards/button-1-base.card.html +2 -2
- package/components/_cards/button-2-states.card.html +14 -14
- package/components/_cards/button-3-sizes-icons.card.html +1 -1
- package/components/_cards/button-4-group.card.html +5 -5
- package/components/_cards/canvas-zoom-controls.card.html +1 -1
- package/components/_cards/cards-indicators.card.html +2 -2
- package/components/_cards/charts-1-sparkline.card.html +2 -2
- package/components/_cards/charts-10-gauge.card.html +4 -4
- package/components/_cards/charts-11-funnel.card.html +2 -2
- package/components/_cards/charts-12-treemap.card.html +2 -2
- package/components/_cards/charts-14-radar.card.html +1 -1
- package/components/_cards/charts-15-distribution.card.html +1 -1
- package/components/_cards/charts-16-pareto.card.html +2 -2
- package/components/_cards/charts-17-candlestick.card.html +2 -2
- package/components/_cards/charts-18-sankey.card.html +2 -2
- package/components/_cards/charts-19-chord.card.html +2 -2
- package/components/_cards/charts-2-line.card.html +2 -2
- package/components/_cards/charts-20-sunburst.card.html +2 -2
- package/components/_cards/charts-21-worldmap.card.html +2 -2
- package/components/_cards/charts-22-gantt.card.html +2 -2
- package/components/_cards/charts-23-bar3d.card.html +2 -2
- package/components/_cards/charts-24-pie3d.card.html +2 -2
- package/components/_cards/charts-25-surface3d.card.html +2 -2
- package/components/_cards/charts-26-scatter3d.card.html +2 -2
- package/components/_cards/charts-27-ribbon3d.card.html +2 -2
- package/components/_cards/charts-3-bar.card.html +2 -2
- package/components/_cards/charts-4-barlist.card.html +2 -2
- package/components/_cards/charts-5-donut.card.html +3 -3
- package/components/_cards/charts-6-scatter.card.html +2 -2
- package/components/_cards/charts-7-heatmap.card.html +2 -2
- package/components/_cards/charts-8-waterfall.card.html +2 -2
- package/components/_cards/charts-9-bullet.card.html +2 -2
- package/components/_cards/charts-99-fullpage.card.html +5 -5
- package/components/_cards/checkbox.card.html +4 -4
- package/components/_cards/codeblock.card.html +1 -1
- package/components/_cards/combobox.card.html +3 -2
- package/components/_cards/context-menu.card.html +3 -2
- package/components/_cards/date-picker.card.html +5 -2
- package/components/_cards/diffvalue.card.html +2 -2
- package/components/_cards/divider.card.html +3 -3
- package/components/_cards/drawer.card.html +2 -2
- package/components/_cards/dropdown-menu.card.html +6 -6
- package/components/_cards/dropzone.card.html +1 -1
- package/components/_cards/empty-state.card.html +5 -5
- package/components/_cards/filter-bar.card.html +5 -5
- package/components/_cards/icon-button.card.html +2 -2
- package/components/_cards/icons-1-core.card.html +2 -2
- package/components/_cards/icons-2-feather.card.html +3 -3
- package/components/_cards/icons-3-unicons.card.html +3 -3
- package/components/_cards/input.card.html +5 -2
- package/components/_cards/kbd.card.html +2 -2
- package/components/_cards/keyvalue.card.html +2 -2
- package/components/_cards/listrow.card.html +2 -2
- package/components/_cards/loading.card.html +2 -2
- package/components/_cards/log-viewer.card.html +2 -2
- package/components/_cards/modal.card.html +9 -9
- package/components/_cards/multi-select.card.html +3 -2
- package/components/_cards/number-stepper.card.html +3 -2
- package/components/_cards/pagination.card.html +3 -2
- package/components/_cards/people.card.html +3 -3
- package/components/_cards/pipeline-node.card.html +2 -2
- package/components/_cards/pipeline-palette.card.html +4 -4
- package/components/_cards/radio.card.html +2 -2
- package/components/_cards/search.card.html +5 -2
- package/components/_cards/segmented-control.card.html +2 -2
- package/components/_cards/select.card.html +4 -4
- package/components/_cards/slider.card.html +1 -1
- package/components/_cards/smart-table.card.html +1 -1
- package/components/_cards/spreadsheet.card.html +2 -2
- package/components/_cards/stepper.card.html +2 -2
- package/components/_cards/table.card.html +103 -0
- package/components/_cards/tabs.card.html +3 -2
- package/components/_cards/tag.card.html +5 -2
- package/components/_cards/textarea.card.html +2 -2
- package/components/_cards/timeline.card.html +1 -1
- package/components/_cards/toast.card.html +4 -4
- package/components/_cards/toggles.card.html +3 -3
- package/components/_cards/tooltip.card.html +5 -5
- package/components/_cards/tree.card.html +2 -2
- package/components/_shared/src-buttons.js +1 -1
- package/components/_shared/src-charts.js +1 -1
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-display.js +1 -1
- package/components/_shared/src-feedback.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_shared/src-pipeline.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/_vue/index.mjs +9 -5
- package/components/buttons/ActionChip.d.ts +3 -1
- package/components/buttons/ActionChip.jsx +4 -4
- package/components/buttons/ActionChip.vue +4 -4
- package/components/buttons/Button.d.ts +5 -2
- package/components/buttons/Button.jsx +15 -18
- package/components/buttons/Button.vue +17 -18
- package/components/buttons/ButtonGroup.vue +1 -1
- package/components/buttons/IconButton.d.ts +2 -1
- package/components/buttons/IconButton.jsx +9 -7
- package/components/buttons/IconButton.vue +10 -8
- package/components/buttons/buttons.prompt.md +6 -5
- package/components/charts/Bar3D.jsx +2 -2
- package/components/charts/Bar3D.vue +2 -2
- package/components/charts/BarChart.jsx +4 -4
- package/components/charts/BarChart.vue +4 -4
- package/components/charts/BarList.jsx +1 -1
- package/components/charts/BarList.vue +1 -1
- package/components/charts/BoxPlot.jsx +1 -1
- package/components/charts/BoxPlot.vue +2 -2
- package/components/charts/BulletChart.jsx +1 -1
- package/components/charts/BulletChart.vue +1 -1
- package/components/charts/CandlestickChart.jsx +2 -2
- package/components/charts/CandlestickChart.vue +3 -3
- package/components/charts/ChartKit.jsx +5 -5
- package/components/charts/ChartLegend.vue +16 -0
- package/components/charts/ChartTooltip.vue +16 -0
- package/components/charts/ChordChart.jsx +2 -2
- package/components/charts/ChordChart.vue +2 -2
- package/components/charts/GanttChart.jsx +2 -2
- package/components/charts/GanttChart.vue +2 -2
- package/components/charts/GaugeChart.jsx +2 -2
- package/components/charts/GaugeChart.vue +2 -2
- package/components/charts/HeatmapChart.jsx +2 -2
- package/components/charts/HeatmapChart.vue +2 -2
- package/components/charts/Histogram.jsx +2 -2
- package/components/charts/Histogram.vue +3 -3
- package/components/charts/LineChart.jsx +1 -1
- package/components/charts/LineChart.vue +3 -3
- package/components/charts/ParetoChart.jsx +3 -3
- package/components/charts/ParetoChart.vue +4 -4
- package/components/charts/PlotGrid.vue +18 -0
- package/components/charts/RadarChart.jsx +1 -1
- package/components/charts/RadarChart.vue +1 -1
- package/components/charts/Ribbon3D.jsx +2 -2
- package/components/charts/Ribbon3D.vue +2 -2
- package/components/charts/SankeyChart.jsx +2 -2
- package/components/charts/SankeyChart.vue +2 -2
- package/components/charts/Scatter3D.jsx +1 -1
- package/components/charts/Scatter3D.vue +1 -1
- package/components/charts/ScatterChart.jsx +3 -3
- package/components/charts/ScatterChart.vue +4 -4
- package/components/charts/Sparkline.jsx +1 -1
- package/components/charts/Sparkline.vue +1 -1
- package/components/charts/SunburstChart.jsx +1 -1
- package/components/charts/SunburstChart.vue +1 -1
- package/components/charts/Surface3D.jsx +2 -2
- package/components/charts/Surface3D.vue +2 -2
- package/components/charts/TreemapChart.jsx +2 -2
- package/components/charts/TreemapChart.vue +2 -2
- package/components/charts/WaterfallChart.jsx +2 -2
- package/components/charts/WaterfallChart.vue +3 -3
- package/components/charts/WorldMap.jsx +2 -2
- package/components/charts/WorldMap.vue +2 -2
- package/components/charts/XLabels.vue +11 -0
- package/components/data/CodeBlock.jsx +1 -1
- package/components/data/CodeBlock.vue +1 -1
- package/components/data/DiffValue.jsx +1 -1
- package/components/data/DiffValue.vue +1 -1
- package/components/data/FilterBar.d.ts +2 -0
- package/components/data/FilterBar.jsx +10 -9
- package/components/data/FilterBar.vue +9 -8
- package/components/data/KeyValue.jsx +3 -3
- package/components/data/KeyValue.vue +3 -3
- package/components/data/ListRow.jsx +2 -2
- package/components/data/ListRow.vue +2 -2
- package/components/data/Pagination.d.ts +5 -1
- package/components/data/Pagination.jsx +16 -12
- package/components/data/Pagination.vue +14 -9
- package/components/data/SmartTable.d.ts +4 -1
- package/components/data/SmartTable.jsx +16 -14
- package/components/data/SmartTable.vue +25 -22
- package/components/data/Spreadsheet.d.ts +2 -0
- package/components/data/Spreadsheet.jsx +12 -11
- package/components/data/Spreadsheet.vue +14 -13
- package/components/data/Table.d.ts +47 -0
- package/components/data/Table.jsx +85 -0
- package/components/data/Table.vue +63 -0
- package/components/data/data.prompt.md +3 -3
- package/components/display/Avatar.vue +3 -2
- package/components/display/AvatarGroup.vue +3 -2
- package/components/display/Badge.vue +1 -1
- package/components/display/Divider.vue +3 -3
- package/components/display/Donut.vue +3 -2
- package/components/display/IconTile.vue +2 -1
- package/components/display/Kbd.vue +1 -1
- package/components/display/ProgressBar.jsx +1 -1
- package/components/display/ProgressBar.vue +1 -1
- package/components/display/StatCard.vue +1 -1
- package/components/display/Tag.d.ts +3 -1
- package/components/display/Tag.jsx +4 -4
- package/components/display/Tag.vue +4 -4
- package/components/feedback/Accordion.jsx +2 -2
- package/components/feedback/Accordion.vue +2 -2
- package/components/feedback/ActivityItem.d.ts +3 -1
- package/components/feedback/ActivityItem.jsx +5 -5
- package/components/feedback/ActivityItem.vue +5 -4
- package/components/feedback/Alert.jsx +4 -4
- package/components/feedback/Alert.vue +3 -3
- package/components/feedback/Banner.jsx +5 -5
- package/components/feedback/Banner.vue +5 -5
- package/components/feedback/ContextMenu.d.ts +3 -1
- package/components/feedback/ContextMenu.jsx +5 -5
- package/components/feedback/ContextMenu.vue +5 -5
- package/components/feedback/DotActivity.vue +3 -3
- package/components/feedback/Drawer.d.ts +2 -0
- package/components/feedback/Drawer.jsx +6 -6
- package/components/feedback/Drawer.vue +22 -15
- package/components/feedback/EmptyState.vue +2 -2
- package/components/feedback/LogViewer.vue +1 -1
- package/components/feedback/Modal.d.ts +2 -0
- package/components/feedback/Modal.jsx +12 -11
- package/components/feedback/Modal.vue +10 -8
- package/components/feedback/Skeleton.vue +2 -1
- package/components/feedback/Spinner.vue +2 -1
- package/components/feedback/Stepper.vue +1 -1
- package/components/feedback/Timeline.jsx +1 -1
- package/components/feedback/Timeline.vue +1 -1
- package/components/feedback/Toast.vue +4 -4
- package/components/feedback/ToastStack.vue +11 -0
- package/components/feedback/Tooltip.vue +19 -0
- package/components/feedback/feedback.prompt.md +4 -4
- package/components/forms/Checkbox.d.ts +3 -2
- package/components/forms/Checkbox.jsx +7 -7
- package/components/forms/Checkbox.vue +8 -8
- package/components/forms/Combobox.d.ts +2 -0
- package/components/forms/Combobox.jsx +12 -10
- package/components/forms/Combobox.vue +11 -10
- package/components/forms/DatePicker.d.ts +4 -0
- package/components/forms/DatePicker.jsx +16 -12
- package/components/forms/DatePicker.vue +15 -11
- package/components/forms/Dropzone.jsx +2 -2
- package/components/forms/Dropzone.vue +5 -5
- package/components/forms/Field.d.ts +5 -1
- package/components/forms/Field.jsx +4 -4
- package/components/forms/Field.vue +4 -4
- package/components/forms/Input.d.ts +5 -1
- package/components/forms/Input.jsx +7 -7
- package/components/forms/Input.vue +3 -3
- package/components/forms/MultiSelect.d.ts +2 -0
- package/components/forms/MultiSelect.jsx +13 -11
- package/components/forms/MultiSelect.vue +12 -11
- package/components/forms/NumberStepper.d.ts +3 -1
- package/components/forms/NumberStepper.jsx +7 -7
- package/components/forms/NumberStepper.vue +8 -7
- package/components/forms/Radio.d.ts +5 -3
- package/components/forms/Radio.jsx +13 -15
- package/components/forms/Radio.vue +9 -10
- package/components/forms/RadioGroup.vue +5 -5
- package/components/forms/SearchInput.d.ts +3 -1
- package/components/forms/SearchInput.jsx +6 -6
- package/components/forms/SearchInput.vue +7 -6
- package/components/forms/Select.d.ts +3 -2
- package/components/forms/Select.jsx +6 -7
- package/components/forms/Select.vue +6 -6
- package/components/forms/Slider.jsx +3 -3
- package/components/forms/Slider.vue +3 -3
- package/components/forms/Switch.d.ts +2 -2
- package/components/forms/Switch.jsx +20 -22
- package/components/forms/Switch.vue +15 -14
- package/components/forms/Textarea.vue +4 -4
- package/components/forms/forms.prompt.md +1 -1
- package/components/forms/input-base.js +2 -1
- package/components/index.d.ts +1 -2
- package/components/index.js +3 -4
- package/components/navigation/Breadcrumb.jsx +3 -3
- package/components/navigation/Breadcrumb.vue +4 -4
- package/components/navigation/DocTree.jsx +3 -3
- package/components/navigation/DocTree.vue +3 -3
- package/components/navigation/PageHeading.vue +1 -1
- package/components/navigation/PageToolbar.vue +7 -6
- package/components/navigation/Sidebar.jsx +1 -1
- package/components/navigation/Sidebar.vue +3 -3
- package/components/navigation/ThemeToggle.vue +2 -1
- package/components/navigation/TopBar.d.ts +3 -1
- package/components/navigation/TopBar.jsx +6 -6
- package/components/navigation/Tree.jsx +5 -5
- package/components/navigation/Tree.vue +5 -5
- package/components/pipeline/ConfigPanel.d.ts +2 -0
- package/components/pipeline/ConfigPanel.jsx +9 -9
- package/components/pipeline/ConfigPanel.vue +9 -9
- package/components/pipeline/DataPreview.d.ts +3 -1
- package/components/pipeline/DataPreview.jsx +7 -7
- package/components/pipeline/DataPreview.vue +7 -7
- package/components/pipeline/NodeCard.jsx +3 -3
- package/components/pipeline/NodeCard.vue +3 -3
- package/components/pipeline/PaletteItem.vue +2 -2
- package/components/pipeline/PaletteListItem.vue +1 -1
- package/components/pipeline/PalettePanel.jsx +2 -2
- package/components/pipeline/PalettePanel.vue +1 -1
- package/components/pipeline/Port.vue +15 -0
- package/components/pipeline/PortLabel.vue +15 -0
- package/components/pipeline/ZoomControls.vue +1 -1
- package/components/pipeline/port-skin.js +13 -0
- package/components/px.js +4 -0
- package/components/tabs/PanelTabs.vue +5 -4
- package/components/tabs/PillTabs.vue +5 -4
- package/components/tabs/SegmentedControl.d.ts +2 -2
- package/components/tabs/SegmentedControl.jsx +7 -7
- package/components/tabs/SegmentedControl.vue +7 -7
- package/components/tabs/Tabs.d.ts +5 -3
- package/components/tabs/Tabs.jsx +15 -15
- package/components/tabs/Tabs.vue +5 -5
- package/components/tabs/ViewToggle.d.ts +3 -1
- package/components/tabs/ViewToggle.jsx +5 -5
- package/components/tabs/ViewToggle.vue +5 -5
- package/components/tabs/tabs.prompt.md +1 -1
- package/keybricks.css +4 -0
- package/package.json +6 -3
- package/readme.md +90 -122
- package/readme.zh-CN.md +120 -0
- package/tokens/charts.css +20 -17
- package/tokens/colors.css +2 -2
- package/tokens/dark.css +2 -2
- package/tokens/hover.css +32 -0
- package/tokens/interaction.css +3 -1
- package/tokens/layout.css +29 -7
- package/tokens/tokens.d.ts +36 -0
- package/tokens/tokens.js +143 -89
- package/tokens/tokens.json +240 -106
- package/tokens/typography.css +27 -14
- package/components/_cards/brand-visual.card.html +0 -43
- package/components/_cards/datatable.card.html +0 -48
- package/components/_shared/src-brand.js +0 -1
- package/components/brand/BrandVisual.d.ts +0 -17
- package/components/brand/BrandVisual.jsx +0 -29
- package/components/brand/BrandVisual.vue +0 -34
- package/components/brand/_dir.card.html +0 -13
- package/components/brand/brandMotifs.js +0 -210
- package/components/buttons/_dir.card.html +0 -13
- package/components/charts/ChartKit.vue +0 -47
- package/components/charts/_dir.card.html +0 -7
- package/components/data/DataTable.d.ts +0 -28
- package/components/data/DataTable.jsx +0 -32
- package/components/data/DataTable.vue +0 -25
- package/components/data/_dir.card.html +0 -13
- package/components/display/_dir.card.html +0 -13
- package/components/feedback/_dir.card.html +0 -13
- package/components/forms/_dir.card.html +0 -13
- package/components/icons/_dir.card.html +0 -13
- package/components/navigation/_dir.card.html +0 -13
- package/components/pipeline/_dir.card.html +0 -13
- package/components/tabs/_dir.card.html +0 -13
|
@@ -43,7 +43,8 @@ function fmt(val, t, currency) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** 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. */
|
|
46
|
-
|
|
46
|
+
/* A size region that defaults to sm: a spreadsheet is the densest surface in the system — 24px toolbar controls, 12px text, 28px cells. */
|
|
47
|
+
export function Spreadsheet({ data = [], rows, cols, colWidths = [], defaultColWidth = 92, currency = "$", defaultActive = "A1", sheets = ["Sheet1"], activeSheet, toolbar = true, formulaBar = true, statusBar = true, height, size = "sm", onChange, style }) {
|
|
47
48
|
const nR = Math.max(rows || 0, data.length, 8), nC = Math.max(cols || 0, ...data.map(r => r.length), 6);
|
|
48
49
|
const [cells, setCells] = React.useState(() => Array.from({ length: nR }, (_, r) => Array.from({ length: nC }, (_, c) => norm(data[r] && data[r][c]))));
|
|
49
50
|
const a0 = refRC(defaultActive) || { r: 0, c: 0 };
|
|
@@ -64,17 +65,17 @@ export function Spreadsheet({ data = [], rows, cols, colWidths = [], defaultColW
|
|
|
64
65
|
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); };
|
|
65
66
|
const widths = [46]; for (let c = 0; c < nC; c++) widths.push(colWidths[c] || defaultColWidth);
|
|
66
67
|
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" };
|
|
67
|
-
const tbBtn = (label, k, st) => <button key={label} type="button" onClick={() => setFlag(k)} style={{ width:
|
|
68
|
+
const tbBtn = (label, k, st) => <button key={label} type="button" data-wash={cur[k] ? undefined : ""} onClick={() => setFlag(k)} style={{ width: "var(--control-h)", height: "var(--control-h)", border: "none", borderRadius: "var(--radius-md)", cursor: "pointer", background: cur[k] ? "var(--bg-active)" : "var(--wash, transparent)", color: "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", ...st }}>{label}</button>;
|
|
68
69
|
return (
|
|
69
|
-
<div style={{ border: "1px solid var(--border-control)", borderRadius: "var(--radius-lg)", overflow: "hidden", background: "var(--bg-surface)", boxSizing: "border-box", ...style }}>
|
|
70
|
-
{toolbar && <div style={{ height:
|
|
71
|
-
<IconButton icon="undo-2"
|
|
70
|
+
<div data-size={size} style={{ border: "1px solid var(--border-control)", borderRadius: "var(--radius-lg)", overflow: "hidden", background: "var(--bg-surface)", boxSizing: "border-box", ...style }}>
|
|
71
|
+
{toolbar && <div style={{ height: "var(--bar-card-h)", display: "flex", alignItems: "center", gap: 2, padding: "0 var(--edge-pad)", boxSizing: "border-box", borderBottom: "1px solid var(--border-subtle)" }}>
|
|
72
|
+
<IconButton icon="undo-2" muted label="Undo" /><IconButton icon="redo-2" muted label="Redo" />
|
|
72
73
|
<span style={{ width: 1, height: 16, background: "var(--border-control)", margin: "0 6px" }}></span>
|
|
73
74
|
{tbBtn("B", "b", { fontWeight: 700 })}{tbBtn("I", "i", { fontStyle: "italic" })}{tbBtn("U", "u", { textDecoration: "underline" })}
|
|
74
75
|
<span style={{ width: 1, height: 16, background: "var(--border-control)", margin: "0 6px" }}></span>
|
|
75
|
-
<Select
|
|
76
|
+
<Select options={["Automatic", "Number", "Currency", "Percent"]} value={{ number: "Number", currency: "Currency", percent: "Percent" }[cur.t] || "Automatic"} onChange={setType} style={{ width: 110 }} />
|
|
76
77
|
</div>}
|
|
77
|
-
{formulaBar && <div style={{ height:
|
|
78
|
+
{formulaBar && <div style={{ height: "var(--bar-h)", display: "flex", alignItems: "center", borderBottom: "1px solid var(--border-control)" }}>
|
|
78
79
|
<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>
|
|
79
80
|
<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>
|
|
80
81
|
<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)" }} />
|
|
@@ -91,17 +92,17 @@ export function Spreadsheet({ data = [], rows, cols, colWidths = [], defaultColW
|
|
|
91
92
|
const isActive = active.r === r && active.c === c;
|
|
92
93
|
const isEdit = editing && editing !== "fx" && editing.r === r && editing.c === c;
|
|
93
94
|
return <span key={r + "," + c} onMouseDown={(e) => pick(r, c, e)} onDoubleClick={() => { setEditing({ r, c }); setDraft(rawOf(cell)); }}
|
|
94
|
-
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:
|
|
95
|
+
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: "var(--control-text)", fontVariantNumeric: "tabular-nums", 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" }}>
|
|
95
96
|
{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)}
|
|
96
97
|
{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>}
|
|
97
98
|
</span>;
|
|
98
99
|
})])}
|
|
99
100
|
</div>
|
|
100
101
|
</div>
|
|
101
|
-
{statusBar && <div style={{ height:
|
|
102
|
+
{statusBar && <div style={{ height: "var(--bar-h)", display: "flex", alignItems: "center", justifyContent: "space-between", borderTop: "1px solid var(--border-control)", background: "var(--bg-inset)", paddingRight: 12 }}>
|
|
102
103
|
<div style={{ display: "flex", alignSelf: "stretch", alignItems: "stretch" }}>
|
|
103
|
-
{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
|
|
104
|
-
<span style={{ display: "flex", alignItems: "center", padding: "0 6px" }}><IconButton icon="plus"
|
|
104
|
+
{sheets.map(s => <button key={s} type="button" data-ink={sheet === s ? undefined : ""} 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(--ink, var(--text-secondary))", fontFamily: "var(--font-sans)", fontWeight: sheet === s ? "var(--weight-medium)" : "var(--weight-regular)", fontSize: "var(--control-text)", boxShadow: sheet === s ? "inset 0 2px 0 var(--action-primary)" : "none" }}>{s}</button>)}
|
|
105
|
+
<span style={{ display: "flex", alignItems: "center", padding: "0 6px" }}><IconButton icon="plus" muted label="Add sheet" /></span>
|
|
105
106
|
</div>
|
|
106
107
|
<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>
|
|
107
108
|
</div>}
|
|
@@ -4,7 +4,8 @@ import Icon from '../icons/Icon.vue'
|
|
|
4
4
|
import IconButton from '../buttons/IconButton.vue'
|
|
5
5
|
import KSelect from '../forms/Select.vue'
|
|
6
6
|
// 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.
|
|
7
|
-
const 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 })
|
|
7
|
+
const 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], size: { type: String, default: 'sm' }, style: Object })
|
|
8
|
+
// A size region that defaults to sm: a spreadsheet is the densest surface in the system — 24px toolbar controls, 12px text, 28px cells.
|
|
8
9
|
const emit = defineEmits(['change'])
|
|
9
10
|
|
|
10
11
|
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 }
|
|
@@ -54,17 +55,17 @@ const hdr = { position: 'sticky', top: 0, zIndex: 3, background: 'var(--bg-inset
|
|
|
54
55
|
const hl = { background: 'var(--soft-bg)', color: 'var(--action-primary)' }
|
|
55
56
|
</script>
|
|
56
57
|
<template>
|
|
57
|
-
<div :style="{ border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', overflow: 'hidden', background: 'var(--bg-surface)', boxSizing: 'border-box', ...style }">
|
|
58
|
-
<div v-if="toolbar" style="height:
|
|
59
|
-
<IconButton icon="undo-2"
|
|
58
|
+
<div :data-size="size" :style="{ border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', overflow: 'hidden', background: 'var(--bg-surface)', boxSizing: 'border-box', ...style }">
|
|
59
|
+
<div v-if="toolbar" style="height:var(--bar-card-h);display:flex;align-items:center;gap:2px;padding:0 var(--edge-pad);box-sizing:border-box;border-bottom:1px solid var(--border-subtle)">
|
|
60
|
+
<IconButton icon="undo-2" muted label="Undo" /><IconButton icon="redo-2" muted label="Redo" />
|
|
60
61
|
<span style="width:1px;height:16px;background:var(--border-control);margin:0 6px" />
|
|
61
|
-
<button type="button" @click="setFlag('b')" :style="{ width: '
|
|
62
|
-
<button type="button" @click="setFlag('i')" :style="{ width: '
|
|
63
|
-
<button type="button" @click="setFlag('u')" :style="{ width: '
|
|
62
|
+
<button type="button" :data-wash="cur.b ? undefined : ''" @click="setFlag('b')" :style="{ width: 'var(--control-h)', height: 'var(--control-h)', border: 'none', borderRadius: 'var(--radius-md)', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', background: cur.b ? 'var(--bg-active)' : 'var(--wash, transparent)', color: 'var(--text-body)', fontWeight: 700 }">B</button>
|
|
63
|
+
<button type="button" :data-wash="cur.i ? undefined : ''" @click="setFlag('i')" :style="{ width: 'var(--control-h)', height: 'var(--control-h)', border: 'none', borderRadius: 'var(--radius-md)', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', background: cur.i ? 'var(--bg-active)' : 'var(--wash, transparent)', color: 'var(--text-body)', fontStyle: 'italic' }">I</button>
|
|
64
|
+
<button type="button" :data-wash="cur.u ? undefined : ''" @click="setFlag('u')" :style="{ width: 'var(--control-h)', height: 'var(--control-h)', border: 'none', borderRadius: 'var(--radius-md)', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', background: cur.u ? 'var(--bg-active)' : 'var(--wash, transparent)', color: 'var(--text-body)', textDecoration: 'underline' }">U</button>
|
|
64
65
|
<span style="width:1px;height:16px;background:var(--border-control);margin:0 6px" />
|
|
65
|
-
<KSelect
|
|
66
|
+
<KSelect :options="['Automatic','Number','Currency','Percent']" :model-value="{ number: 'Number', currency: 'Currency', percent: 'Percent' }[cur.t] || 'Automatic'" @update:modelValue="setType" style="width:110px" />
|
|
66
67
|
</div>
|
|
67
|
-
<div v-if="formulaBar" style="height:
|
|
68
|
+
<div v-if="formulaBar" style="height:var(--bar-h);display:flex;align-items:center;border-bottom:1px solid var(--border-control)">
|
|
68
69
|
<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>
|
|
69
70
|
<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>
|
|
70
71
|
<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)" />
|
|
@@ -76,7 +77,7 @@ const hl = { background: 'var(--soft-bg)', color: 'var(--action-primary)' }
|
|
|
76
77
|
<template v-for="(row, r) in cells" :key="r">
|
|
77
78
|
<span :style="{ ...hdr, position: 'sticky', left: 0, top: 'auto', zIndex: 2, height: '28px', ...(r >= lo.r && r <= hi.r ? hl : null) }">{{ r + 1 }}</span>
|
|
78
79
|
<span v-for="(cell, c) in row" :key="r + ',' + c" @mousedown="pick(r, c, $event)" @dblclick="editing = { r, c }; draft = rawOf(cell)"
|
|
79
|
-
: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:
|
|
80
|
+
: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: 'var(--control-text)', fontVariantNumeric: 'tabular-nums', 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' }">
|
|
80
81
|
<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)" />
|
|
81
82
|
<template v-else>{{ fmt(evalCell(cells, cell), cell.t, currency) }}</template>
|
|
82
83
|
<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" />
|
|
@@ -84,10 +85,10 @@ const hl = { background: 'var(--soft-bg)', color: 'var(--action-primary)' }
|
|
|
84
85
|
</template>
|
|
85
86
|
</div>
|
|
86
87
|
</div>
|
|
87
|
-
<div v-if="statusBar" style="height:
|
|
88
|
+
<div v-if="statusBar" style="height:var(--bar-h);display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--border-control);background:var(--bg-inset);padding-right:12px">
|
|
88
89
|
<div style="display:flex;align-self:stretch;align-items:stretch">
|
|
89
|
-
<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
|
|
90
|
-
<span style="display:flex;align-items:center;padding:0 6px"><IconButton icon="plus"
|
|
90
|
+
<button v-for="s in sheets" :key="s" type="button" :data-ink="sheet === s ? undefined : ''" @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(--ink, var(--text-secondary))', fontFamily: 'var(--font-sans)', fontWeight: sheet === s ? 'var(--weight-medium)' : 'var(--weight-regular)', fontSize: 'var(--control-text)', boxShadow: sheet === s ? 'inset 0 2px 0 var(--action-primary)' : 'none' }">{{ s }}</button>
|
|
91
|
+
<span style="display:flex;align-items:center;padding:0 6px"><IconButton icon="plus" muted label="Add sheet" /></span>
|
|
91
92
|
</div>
|
|
92
93
|
<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>
|
|
93
94
|
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Hairline table — --bg-table-header header tint, one step below --bg-app so it reads on grey pages; one rule weight (--border-subtle) for header rule and row separators alike.
|
|
4
|
+
* The table is a size region: rows are the tier's bar height (sm 32 · md 40 · lg 44), cells and header share the tier's control text (header in weight 600, text-secondary, sentence case), and every control rendered inside a cell takes the same tier.
|
|
5
|
+
* @startingPoint section="Data" subtitle="Comparison / preview tables with mono cells" viewport="700x300"
|
|
6
|
+
*/
|
|
7
|
+
export interface DataTableColumn {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
width?: number | string;
|
|
11
|
+
/** Lower bound while resizing (default 48) */
|
|
12
|
+
minWidth?: number;
|
|
13
|
+
/** Menlo cell at the row's text size with tabular figures (config codes, amounts) */
|
|
14
|
+
mono?: boolean;
|
|
15
|
+
align?: "left" | "right" | "center";
|
|
16
|
+
render?: (value: any, row: any, index: number) => React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export interface DataTableProps {
|
|
19
|
+
columns?: DataTableColumn[];
|
|
20
|
+
rows?: Record<string, any>[];
|
|
21
|
+
/** Size tier of the table region: sm 32px rows / 12px text (dense grids, preview drawers) · md 40px / 14px (default) · lg 44px / 14px. Omit to inherit the enclosing region. */
|
|
22
|
+
size?: "sm" | "md" | "lg";
|
|
23
|
+
/** Alias of size="sm", kept for 1.x callers */
|
|
24
|
+
dense?: boolean;
|
|
25
|
+
/** Leading checkbox column */
|
|
26
|
+
selectable?: boolean;
|
|
27
|
+
selected?: number[];
|
|
28
|
+
/** onToggle("all"|rowIndex, checked) */
|
|
29
|
+
onToggle?: (which: number | "all", checked: boolean) => void;
|
|
30
|
+
headerBorder?: boolean;
|
|
31
|
+
/** Draws its own rounded card frame (border-card, radius-lg) and drops the header-top and last-row rules that would double it. Use instead of wrapping the table in a bordered div. */
|
|
32
|
+
bordered?: boolean;
|
|
33
|
+
/** For a table placed flush inside a container that already has a border (Card pad={false}): drops only the header's top rule. */
|
|
34
|
+
framed?: boolean;
|
|
35
|
+
/** Short divider between header cells; hover darkens it, drag sets that column's width. Uses table-layout: fixed. */
|
|
36
|
+
resizable?: boolean;
|
|
37
|
+
/** Controlled widths by column key (px). Omit to let the table keep them locally. */
|
|
38
|
+
columnWidths?: Record<string, number>;
|
|
39
|
+
/** Fires on every drag frame with the new px width. Vue: `@column-resize`. */
|
|
40
|
+
onColumnResize?: (key: string, width: number) => void;
|
|
41
|
+
/** Rendered inside the table (full-width cell) when rows is empty — an EmptyState or a muted line. Vue: `#empty` slot. */
|
|
42
|
+
empty?: React.ReactNode;
|
|
43
|
+
style?: React.CSSProperties;
|
|
44
|
+
}
|
|
45
|
+
export declare function Table(props: DataTableProps): JSX.Element;
|
|
46
|
+
/** @deprecated renamed to Table in 1.0.6 — still exported in 2.x, removed in 3.0 */
|
|
47
|
+
export declare const DataTable: typeof Table;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Checkbox } from "../forms/Checkbox.jsx";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Keybricks table. columns: [{key,label,width,minWidth,mono,align,render}].
|
|
6
|
+
* The table is a size region: `size` ("sm" | "md" | "lg", default inherit → md) sets its rows, header, cell padding and every
|
|
7
|
+
* control inside the cells from the tier tokens — rows are the tier's bar height (sm 32 · md 40 · lg 44), cells read the tier's
|
|
8
|
+
* control text, header cells are the same size in weight 600 / text-secondary, sentence case, no letter-spacing.
|
|
9
|
+
* `dense` is kept as an alias of size="sm" (the data-preview drawer).
|
|
10
|
+
* Every rule in the table is one weight (--border-subtle); the header is set apart by its --bg-table-header tint, never by a heavier line.
|
|
11
|
+
* bordered — the table draws its own rounded card frame (border-card, radius-lg) and drops the edge rules that would double it.
|
|
12
|
+
* framed — the table sits flush inside a container that already has a border (Card pad={false}); drops only the header's top rule.
|
|
13
|
+
* resizable — a short divider between header cells (hover darkens it, cursor col-resize); drag to set that column's width.
|
|
14
|
+
* Widths live in local state unless you pass columnWidths/onColumnResize to control them. Min width = column.minWidth ?? 48.
|
|
15
|
+
*/
|
|
16
|
+
export function Table({ columns = [], rows = [], size, dense, selectable, selected = [], onToggle, headerBorder = true, bordered, framed, resizable, columnWidths, onColumnResize, empty, style }) {
|
|
17
|
+
const tier = size || (dense ? "sm" : undefined);
|
|
18
|
+
const flushTop = bordered || framed;
|
|
19
|
+
const colCount = columns.length + (selectable ? 1 : 0);
|
|
20
|
+
const [localW, setLocalW] = React.useState({});
|
|
21
|
+
const [drag, setDrag] = React.useState(null); // key being dragged
|
|
22
|
+
const [hot, setHot] = React.useState(null); // key whose handle is hovered
|
|
23
|
+
const thRefs = React.useRef({});
|
|
24
|
+
const widths = columnWidths || localW;
|
|
25
|
+
const widthOf = (c) => widths[c.key] ?? c.width;
|
|
26
|
+
|
|
27
|
+
const startDrag = (e, c) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
const th = thRefs.current[c.key];
|
|
30
|
+
const startX = e.clientX, startW = th ? th.getBoundingClientRect().width : (parseFloat(widthOf(c)) || 120);
|
|
31
|
+
const min = c.minWidth ?? 48;
|
|
32
|
+
setDrag(c.key);
|
|
33
|
+
const move = (ev) => {
|
|
34
|
+
const w = Math.max(min, Math.round(startW + ev.clientX - startX));
|
|
35
|
+
if (onColumnResize) onColumnResize(c.key, w); else setLocalW((s) => ({ ...s, [c.key]: w }));
|
|
36
|
+
};
|
|
37
|
+
const up = () => { setDrag(null); window.removeEventListener("mousemove", move); window.removeEventListener("mouseup", up); document.body.style.cursor = ""; document.body.style.userSelect = ""; };
|
|
38
|
+
document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none";
|
|
39
|
+
window.addEventListener("mousemove", move); window.addEventListener("mouseup", up);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const cellFont = (c) => c.mono
|
|
43
|
+
? { fontFamily: "var(--font-mono)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: 0, fontVariantNumeric: "tabular-nums", color: "var(--text-heading)" }
|
|
44
|
+
: { fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-primary)" };
|
|
45
|
+
// first and last cells keep the card's edge padding; every other cell the tier's cell padding
|
|
46
|
+
const cellPad = (i) => ({ paddingTop: 0, paddingBottom: 0, paddingLeft: i === 0 && !selectable ? "var(--edge-pad)" : "var(--cell-px)", paddingRight: i === columns.length - 1 ? "var(--edge-pad)" : "var(--cell-px)" });
|
|
47
|
+
|
|
48
|
+
const table = (
|
|
49
|
+
<table data-size={tier} style={{ width: "100%", borderCollapse: "collapse", tableLayout: resizable ? "fixed" : "auto", ...(bordered ? null : style) }}>
|
|
50
|
+
<thead>
|
|
51
|
+
<tr style={{ background: "var(--bg-table-header)", borderTop: headerBorder && !flushTop ? "1px solid var(--border-subtle)" : "none", borderBottom: headerBorder ? "1px solid var(--border-subtle)" : "none", height: "var(--bar-h)" }}>
|
|
52
|
+
{selectable ? <th style={{ width: "calc(var(--edge-pad) + var(--check-size) + var(--cell-px))", padding: "0 0 0 var(--edge-pad)", textAlign: "left" }}><Checkbox checked={rows.length > 0 && selected.length === rows.length} indeterminate={selected.length > 0 && selected.length < rows.length} onChange={(v) => onToggle && onToggle("all", v)} /></th> : null}
|
|
53
|
+
{columns.map((c, i) => {
|
|
54
|
+
const last = i === columns.length - 1;
|
|
55
|
+
const active = drag === c.key, lit = active || hot === c.key;
|
|
56
|
+
return (
|
|
57
|
+
<th key={c.key} ref={(el) => { thRefs.current[c.key] = el; }} style={{ position: "relative", textAlign: c.align || "left", ...cellPad(i), width: widthOf(c), fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-secondary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
|
|
58
|
+
{c.label}
|
|
59
|
+
{resizable && !last ? (
|
|
60
|
+
<span aria-hidden="true" onMouseDown={(e) => startDrag(e, c)} onMouseEnter={() => setHot(c.key)} onMouseLeave={() => setHot(null)}
|
|
61
|
+
style={{ position: "absolute", top: 0, right: -5, width: 11, height: "100%", cursor: "col-resize", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1 }}>
|
|
62
|
+
<span style={{ width: active ? 2 : 1, height: "50%", background: lit ? "var(--action-primary)" : "var(--border-control)", borderRadius: 1, transition: "var(--t-hover)" }}></span>
|
|
63
|
+
</span>
|
|
64
|
+
) : null}
|
|
65
|
+
</th>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</tr>
|
|
69
|
+
</thead>
|
|
70
|
+
<tbody>
|
|
71
|
+
{rows.map((r, ri) => (
|
|
72
|
+
<tr key={ri} data-row="" style={{ height: "var(--bar-h)", background: "var(--row-wash, transparent)", transition: "var(--t-hover)", borderBottom: bordered && ri === rows.length - 1 ? "none" : "1px solid var(--border-subtle)" }}>
|
|
73
|
+
{selectable ? <td style={{ padding: "0 0 0 var(--edge-pad)" }}><Checkbox checked={selected.includes(ri)} onChange={(v) => onToggle && onToggle(ri, v)} /></td> : null}
|
|
74
|
+
{columns.map((c, i) => (
|
|
75
|
+
<td key={c.key} style={{ ...cellPad(i), textAlign: c.align || "left", whiteSpace: "nowrap", ...(resizable ? { overflow: "hidden", textOverflow: "ellipsis" } : null), ...cellFont(c) }}>{c.render ? c.render(r[c.key], r, ri) : r[c.key]}</td>
|
|
76
|
+
))}
|
|
77
|
+
</tr>
|
|
78
|
+
))}
|
|
79
|
+
{rows.length === 0 && empty ? <tr><td colSpan={colCount} style={{ padding: 0 }}>{empty}</td></tr> : null}
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
);
|
|
83
|
+
if (!bordered) return table;
|
|
84
|
+
return <div data-size={tier} style={{ background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)", overflow: "hidden", ...style }}>{table}</div>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { px } from '../px.js'
|
|
3
|
+
import { ref, computed, reactive } from 'vue'
|
|
4
|
+
import Checkbox from '../forms/Checkbox.vue'
|
|
5
|
+
// Keybricks table. columns: [{key,label,width,minWidth,mono,align,render}].
|
|
6
|
+
// The table is a size region: `size` ("sm" | "md" | "lg", default inherit → md) sets its rows, header, cell padding and every control
|
|
7
|
+
// inside the cells from the tier tokens — rows are the tier's bar height (sm 32 · md 40 · lg 44), cells read the tier's control text,
|
|
8
|
+
// header cells are the same size in weight 600 / text-secondary, sentence case, no letter-spacing. `dense` is an alias of size="sm".
|
|
9
|
+
// Every rule in the table is one weight (--border-subtle); the header is set apart by its --bg-table-header tint, never by a heavier line.
|
|
10
|
+
// bordered — the table draws its own rounded card frame (border-card, radius-lg) and drops the edge rules that would double it.
|
|
11
|
+
// framed — the table sits flush inside a container that already has a border (Card :pad="false"); drops only the header's top rule.
|
|
12
|
+
// resizable — a short divider between header cells (hover darkens it, cursor col-resize); drag to set that column's width.
|
|
13
|
+
// Widths live in local state unless you pass columnWidths + listen to @column-resize(key, px). Min width = column.minWidth ?? 48.
|
|
14
|
+
// #empty slot renders inside the table when rows is empty.
|
|
15
|
+
const props = defineProps({ columns: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, size: String, dense: Boolean, selectable: Boolean, selected: { type: Array, default: () => [] }, headerBorder: { type: Boolean, default: true }, bordered: Boolean, framed: Boolean, resizable: Boolean, columnWidths: Object, style: Object })
|
|
16
|
+
const emit = defineEmits(['toggle', 'columnResize'])
|
|
17
|
+
const tier = computed(() => props.size || (props.dense ? 'sm' : undefined))
|
|
18
|
+
const flushTop = computed(() => props.bordered || props.framed)
|
|
19
|
+
const colCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))
|
|
20
|
+
const frame = computed(() => props.bordered ? { background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', overflow: 'hidden', ...props.style } : { display: 'contents' })
|
|
21
|
+
const localW = reactive({})
|
|
22
|
+
const drag = ref(null), hot = ref(null)
|
|
23
|
+
const thEls = {}
|
|
24
|
+
const widthOf = (c) => px((props.columnWidths || localW)[c.key] ?? c.width)
|
|
25
|
+
function startDrag(e, c) {
|
|
26
|
+
e.preventDefault()
|
|
27
|
+
const th = thEls[c.key]
|
|
28
|
+
const startX = e.clientX, startW = th ? th.getBoundingClientRect().width : (parseFloat(widthOf(c)) || 120), min = c.minWidth ?? 48
|
|
29
|
+
drag.value = c.key
|
|
30
|
+
const move = (ev) => { const w = Math.max(min, Math.round(startW + ev.clientX - startX)); if (props.columnWidths) emit('columnResize', c.key, w); else localW[c.key] = w }
|
|
31
|
+
const up = () => { drag.value = null; window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); document.body.style.cursor = ''; document.body.style.userSelect = '' }
|
|
32
|
+
document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'
|
|
33
|
+
window.addEventListener('mousemove', move); window.addEventListener('mouseup', up)
|
|
34
|
+
}
|
|
35
|
+
function cellFont(c) { return c.mono ? { fontFamily: 'var(--font-mono)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 0, fontVariantNumeric: 'tabular-nums', color: 'var(--text-heading)' } : { fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-primary)' } }
|
|
36
|
+
// first and last cells keep the card's edge padding; every other cell the tier's cell padding
|
|
37
|
+
function cellPad(i) { return { paddingTop: 0, paddingBottom: 0, paddingLeft: i === 0 && !props.selectable ? 'var(--edge-pad)' : 'var(--cell-px)', paddingRight: i === props.columns.length - 1 ? 'var(--edge-pad)' : 'var(--cell-px)' } }
|
|
38
|
+
</script>
|
|
39
|
+
<template>
|
|
40
|
+
<div :style="frame" :data-size="bordered ? tier : undefined">
|
|
41
|
+
<table :data-size="tier" :style="{ width: '100%', borderCollapse: 'collapse', tableLayout: resizable ? 'fixed' : 'auto', ...(bordered ? null : style) }">
|
|
42
|
+
<thead>
|
|
43
|
+
<tr :style="{ background: 'var(--bg-table-header)', borderTop: (headerBorder && !flushTop) ? '1px solid var(--border-subtle)' : 'none', borderBottom: headerBorder ? '1px solid var(--border-subtle)' : 'none', height: 'var(--bar-h)' }">
|
|
44
|
+
<th v-if="selectable" style="width:calc(var(--edge-pad) + var(--check-size) + var(--cell-px));padding:0 0 0 var(--edge-pad);text-align:left"><Checkbox :checked="rows.length > 0 && selected.length === rows.length" :indeterminate="selected.length > 0 && selected.length < rows.length" @change="v => emit('toggle', 'all', v)" /></th>
|
|
45
|
+
<th v-for="(c, i) in columns" :key="c.key" :ref="el => { thEls[c.key] = el }" :style="{ position: 'relative', textAlign: c.align || 'left', ...cellPad(i), width: widthOf(c), fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-semibold)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-secondary)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }">
|
|
46
|
+
{{ c.label }}
|
|
47
|
+
<span v-if="resizable && i < columns.length - 1" aria-hidden="true" @mousedown="e => startDrag(e, c)" @mouseenter="hot = c.key" @mouseleave="hot = null"
|
|
48
|
+
style="position:absolute;top:0;right:-5px;width:11px;height:100%;cursor:col-resize;display:flex;align-items:center;justify-content:center;z-index:1">
|
|
49
|
+
<span :style="{ width: drag === c.key ? '2px' : '1px', height: '50%', background: (drag === c.key || hot === c.key) ? 'var(--action-primary)' : 'var(--border-control)', borderRadius: '1px', transition: 'var(--t-hover)' }"></span>
|
|
50
|
+
</span>
|
|
51
|
+
</th>
|
|
52
|
+
</tr>
|
|
53
|
+
</thead>
|
|
54
|
+
<tbody>
|
|
55
|
+
<tr v-for="(r, ri) in rows" :key="ri" data-row="" :style="{ height: 'var(--bar-h)', background: 'var(--row-wash, transparent)', transition: 'var(--t-hover)', borderBottom: bordered && ri === rows.length - 1 ? 'none' : '1px solid var(--border-subtle)' }">
|
|
56
|
+
<td v-if="selectable" style="padding:0 0 0 var(--edge-pad)"><Checkbox :checked="selected.includes(ri)" @change="v => emit('toggle', ri, v)" /></td>
|
|
57
|
+
<td v-for="(c, i) in columns" :key="c.key" :style="{ ...cellPad(i), textAlign: c.align || 'left', whiteSpace: 'nowrap', ...(resizable ? { overflow: 'hidden', textOverflow: 'ellipsis' } : null), ...cellFont(c) }">{{ c.render ? c.render(r[c.key], r, ri) : r[c.key] }}</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<tr v-if="rows.length === 0 && $slots.empty"><td :colspan="colCount" style="padding:0"><slot name="empty" /></td></tr>
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Data lists: `
|
|
1
|
+
Data lists: `Table` (hairline table, mono cells, selectable; a size region — `size="sm"` for preview grids) and `ListRow` (expandable workflow rows in a `Card pad={false}`).
|
|
2
2
|
|
|
3
3
|
```jsx
|
|
4
|
-
<
|
|
4
|
+
<Table selectable columns={[
|
|
5
5
|
{key:"module",label:"Module"},
|
|
6
6
|
{key:"code",label:"Code",mono:true},
|
|
7
7
|
{key:"cur",label:"Current Value"},
|
|
@@ -10,7 +10,7 @@ Data lists: `DataTable` (hairline table, mono cells, selectable, dense preview m
|
|
|
10
10
|
<ListRow icon="chart-line" title="Business Analysis" meta="1 document" trailing="1 item"/>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
dense
|
|
13
|
+
`size="sm"` (the 1.x `dense`) = 32px rows / 12px text for the pipeline data-preview drawer; md = 40px rows / 14px text; lg = 44 / 14. Header cells are the same size as body cells in weight 600, sentence case, no letter-spacing; mono cells keep the row's size with tabular figures.
|
|
14
14
|
|
|
15
15
|
`Pagination` (addition) sits under a table:
|
|
16
16
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { px } from '../px.js'
|
|
2
3
|
import { computed } from 'vue'
|
|
3
4
|
const palette = ['var(--cat-join)', 'var(--cat-source)', 'var(--cat-transform)', 'var(--cat-merge)', 'var(--cat-output)']
|
|
4
5
|
function initials(name = '') {
|
|
@@ -12,6 +13,6 @@ const props = defineProps({ name: { type: String, default: '' }, src: String, si
|
|
|
12
13
|
const hue = computed(() => palette[[...props.name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length])
|
|
13
14
|
</script>
|
|
14
15
|
<template>
|
|
15
|
-
<img v-if="src" :src="src" :alt="name" :style="{ width: size, height: size, borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }" />
|
|
16
|
-
<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>
|
|
16
|
+
<img v-if="src" :src="src" :alt="name" :style="{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }" />
|
|
17
|
+
<span v-else :title="name" :style="{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-hover)', color: hue, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: px(Math.max(9, Math.round(size * .4))), letterSpacing: 0, flexShrink: 0, boxSizing: 'border-box', border: '1px solid var(--border-card)', ...style }">{{ initials(name) }}</span>
|
|
17
18
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { px } from '../px.js'
|
|
2
3
|
import { computed } from 'vue'
|
|
3
4
|
import Avatar from './Avatar.vue'
|
|
4
5
|
const props = defineProps({ names: { type: Array, default: () => [] }, size: { type: Number, default: 24 }, max: { type: Number, default: 4 }, style: Object })
|
|
@@ -7,7 +8,7 @@ const rest = computed(() => props.names.length - shown.value.length)
|
|
|
7
8
|
</script>
|
|
8
9
|
<template>
|
|
9
10
|
<div :style="{ display: 'flex', alignItems: 'center', ...style }">
|
|
10
|
-
<Avatar v-for="(n, i) in shown" :key="n + i" :name="n" :size="size" :style="{ marginLeft: i ? -
|
|
11
|
-
<span v-if="rest > 0" :style="{ marginLeft: -
|
|
11
|
+
<Avatar v-for="(n, i) in shown" :key="n + i" :name="n" :size="size" :style="{ marginLeft: i ? '-8px' : 0, boxShadow: 'var(--ring-surface)' }" />
|
|
12
|
+
<span v-if="rest > 0" :style="{ marginLeft: '-8px', width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-active)', color: 'var(--text-secondary)', boxShadow: 'var(--ring-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontSize: px(Math.max(9, Math.round(size * .38))) }">+{{ rest }}</span>
|
|
12
13
|
</div>
|
|
13
14
|
</template>
|
|
@@ -9,5 +9,5 @@ const tones = {
|
|
|
9
9
|
defineProps({ tone: { type: String, default: 'neutral' }, style: Object })
|
|
10
10
|
</script>
|
|
11
11
|
<template>
|
|
12
|
-
<span :style="{ display: 'inline-flex', alignItems: 'center', height:
|
|
12
|
+
<span :style="{ display: 'inline-flex', alignItems: 'center', height: '20px', padding: '0 8px', borderRadius: 'var(--radius-full)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', lineHeight: 1, letterSpacing: 'var(--tracking-xs)', ...tones[tone], ...style }"><slot /></span>
|
|
13
13
|
</template>
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
defineProps({ label: String, vertical: Boolean, style: Object })
|
|
4
4
|
</script>
|
|
5
5
|
<template>
|
|
6
|
-
<span v-if="vertical" :style="{ width:
|
|
7
|
-
<div v-else-if="!label" :style="{ height:
|
|
8
|
-
<div v-else :style="{ display: 'flex', alignItems: 'center', gap:
|
|
6
|
+
<span v-if="vertical" :style="{ width: '1px', alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }" />
|
|
7
|
+
<div v-else-if="!label" :style="{ height: '1px', background: 'var(--border-subtle)', ...style }" />
|
|
8
|
+
<div v-else :style="{ display: 'flex', alignItems: 'center', gap: '12px', ...style }">
|
|
9
9
|
<div style="flex:1;height:1px;background:var(--border-subtle)" />
|
|
10
10
|
<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>
|
|
11
11
|
<div style="flex:1;height:1px;background:var(--border-subtle)" />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { px } from '../px.js'
|
|
2
3
|
import { computed } from 'vue'
|
|
3
4
|
const 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 })
|
|
4
5
|
const R = computed(() => props.size / 2 - props.thickness / 2 - props.size * .02)
|
|
@@ -7,11 +8,11 @@ const segs = computed(() => props.segments || [{ value: props.value, color: 'var
|
|
|
7
8
|
const 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 }) })
|
|
8
9
|
</script>
|
|
9
10
|
<template>
|
|
10
|
-
<div :style="{ position: 'relative', width: size, height: size, ...style }">
|
|
11
|
+
<div :style="{ position: 'relative', width: px(size), height: px(size), ...style }">
|
|
11
12
|
<svg :width="size" :height="size" style="transform:rotate(-90deg)">
|
|
12
13
|
<circle :cx="size / 2" :cy="size / 2" :r="R" fill="none" stroke="var(--track)" :stroke-width="thickness" />
|
|
13
14
|
<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" />
|
|
14
15
|
</svg>
|
|
15
|
-
<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:
|
|
16
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)">{{ label != null ? label : `${value}%` }}</div>
|
|
16
17
|
</div>
|
|
17
18
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { px } from '../px.js'
|
|
2
3
|
import { computed } from 'vue'
|
|
3
4
|
import Icon from '../icons/Icon.vue'
|
|
4
5
|
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 } }
|
|
@@ -7,7 +8,7 @@ const t = computed(() => dims[props.kind])
|
|
|
7
8
|
const tinted = computed(() => props.kind !== 'row')
|
|
8
9
|
</script>
|
|
9
10
|
<template>
|
|
10
|
-
<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 }">
|
|
11
|
+
<span :style="{ width: px(t.box), height: px(t.box), borderRadius: px(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 }">
|
|
11
12
|
<Icon :name="icon" :size="t.icon" :stroke-width="2" />
|
|
12
13
|
</span>
|
|
13
14
|
</template>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
defineProps({ keys: { type: Array, default: () => [] }, style: Object })
|
|
4
4
|
</script>
|
|
5
5
|
<template>
|
|
6
|
-
<span :style="{ display: 'inline-flex', gap:
|
|
6
|
+
<span :style="{ display: 'inline-flex', gap: '3px', ...style }">
|
|
7
7
|
<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>
|
|
8
8
|
</span>
|
|
9
9
|
</template>
|
|
@@ -35,7 +35,7 @@ export function Donut({ size = 128, thickness = 7.68, segments, value = 75, labe
|
|
|
35
35
|
return el;
|
|
36
36
|
})}
|
|
37
37
|
</svg>
|
|
38
|
-
<div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-light)", fontSize:
|
|
38
|
+
<div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-light)", fontSize: "var(--text-lg)", letterSpacing: "var(--tracking-lg)", color: "var(--text-primary)" }}>{label != null ? label : `${value}%`}</div>
|
|
39
39
|
</div>
|
|
40
40
|
);
|
|
41
41
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
defineProps({ label: String, value: { type: Number, default: 50 }, color: { type: String, default: 'var(--chart-blue)' }, showValue: { type: Boolean, default: true }, style: Object })
|
|
4
4
|
</script>
|
|
5
5
|
<template>
|
|
6
|
-
<div :style="{ display: 'flex', flexDirection: 'column', gap:
|
|
6
|
+
<div :style="{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }">
|
|
7
7
|
<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)">
|
|
8
8
|
<span style="color:var(--text-body)">{{ label }}</span>
|
|
9
9
|
<span v-if="showValue" style="color:var(--text-heading)">{{ value }}%</span>
|
|
@@ -3,7 +3,7 @@ import Card from './Card.vue'
|
|
|
3
3
|
defineProps({ label: String, value: [String, Number], dense: Boolean, style: Object })
|
|
4
4
|
</script>
|
|
5
5
|
<template>
|
|
6
|
-
<Card :dense="dense" :style="{ display: 'flex', flexDirection: 'column', gap:
|
|
6
|
+
<Card :dense="dense" :style="{ display: 'flex', flexDirection: 'column', gap: '12px', ...style }">
|
|
7
7
|
<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>
|
|
8
8
|
<slot />
|
|
9
9
|
<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>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Small removable filter tag (
|
|
2
|
+
/** Small removable filter tag (full radius; sm 20 · md 24 · lg 28 tall, 12px text). Addition — distinct from Badge/ActionChip. */
|
|
3
3
|
export interface TagProps {
|
|
4
4
|
children?: React.ReactNode;
|
|
5
|
+
/** Size tier. Omit to inherit the enclosing region's tier (the default). */
|
|
6
|
+
size?: "sm" | "md" | "lg";
|
|
5
7
|
onRemove?: () => void;
|
|
6
8
|
tone?: "neutral" | "info";
|
|
7
9
|
style?: React.CSSProperties;
|
|
@@ -3,13 +3,13 @@ import { Icon } from "../icons/Icon.jsx";
|
|
|
3
3
|
|
|
4
4
|
const tones = { neutral: { bg: "var(--bg-hover)", fg: "var(--text-body)" }, info: { bg: "var(--info-bg)", fg: "var(--info-fg)" } };
|
|
5
5
|
|
|
6
|
-
/** Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). */
|
|
7
|
-
export function Tag({ children, onRemove, tone = "neutral", style }) {
|
|
6
|
+
/** Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md 24 · lg 28, --tag-h); text stays 12px. */
|
|
7
|
+
export function Tag({ children, onRemove, tone = "neutral", size, style }) {
|
|
8
8
|
const t = tones[tone] || tones.neutral;
|
|
9
9
|
return (
|
|
10
|
-
<span style={{ display: "inline-flex", alignItems: "center", gap: 4, height:
|
|
10
|
+
<span data-size={size} style={{ display: "inline-flex", alignItems: "center", gap: 4, height: "var(--tag-h)", padding: onRemove ? "0 6px 0 10px" : "0 10px", borderRadius: "var(--radius-full)", background: t.bg, color: t.fg, fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", fontWeight: "var(--weight-medium)", letterSpacing: "var(--tracking-xs)", whiteSpace: "nowrap", ...style }}>
|
|
11
11
|
{children}
|
|
12
|
-
{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={
|
|
12
|
+
{onRemove ? <button type="button" onClick={onRemove} aria-label="Remove" data-ink="" style={{ border: "none", background: "transparent", cursor: "pointer", color: "inherit", opacity: "var(--ink-op, .7)", display: "flex", padding: 0 }}><Icon name="x" size={12} /></button> : null}
|
|
13
13
|
</span>
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
4
|
const tones = { neutral: { bg: 'var(--bg-hover)', fg: 'var(--text-body)' }, info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)' } }
|
|
5
|
-
// Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action).
|
|
6
|
-
const props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, style: Object })
|
|
5
|
+
// Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md 24 · lg 28, --tag-h); text stays 12px.
|
|
6
|
+
const props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, size: String, style: Object })
|
|
7
7
|
defineEmits(['remove'])
|
|
8
8
|
const t = computed(() => tones[props.tone] || tones.neutral)
|
|
9
9
|
</script>
|
|
10
10
|
<template>
|
|
11
|
-
<span :style="{ display: 'inline-flex', alignItems: 'center', gap:
|
|
11
|
+
<span :data-size="size" :style="{ display: 'inline-flex', alignItems: 'center', gap: '4px', height: 'var(--tag-h)', padding: removable ? '0 6px 0 10px' : '0 10px', borderRadius: 'var(--radius-full)', background: t.bg, color: t.fg, fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', whiteSpace: 'nowrap', ...style }">
|
|
12
12
|
<slot />
|
|
13
|
-
<button v-if="removable" type="button" @click="$emit('remove')" style="border:none;background:transparent;cursor:pointer;color:inherit;opacity
|
|
13
|
+
<button v-if="removable" type="button" aria-label="Remove" data-ink="" @click="$emit('remove')" style="border:none;background:transparent;cursor:pointer;color:inherit;opacity:var(--ink-op, .7);display:flex;padding:0"><Icon name="x" :size="12" /></button>
|
|
14
14
|
</span>
|
|
15
15
|
</template>
|