@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
|
@@ -6,7 +6,7 @@ const toneColor = { neutral: "var(--text-heading)", warning: "var(--warning-text
|
|
|
6
6
|
/** Before → after value change ("USD → EUR"). tone colors the new value; from renders muted + struck through when both sides are given. */
|
|
7
7
|
export function DiffValue({ from, to, tone = "warning", mono = true, style }) {
|
|
8
8
|
return (
|
|
9
|
-
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--text
|
|
9
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--control-text)", ...style }}>
|
|
10
10
|
{from != null ? <span style={{ color: "var(--text-muted)", textDecoration: "line-through" }}>{from}</span> : null}
|
|
11
11
|
<Icon name="arrow-right" size={12} style={{ color: "var(--text-muted)" }} />
|
|
12
12
|
<span style={{ color: toneColor[tone], fontWeight: "var(--weight-medium)" }}>{to}</span>
|
|
@@ -5,7 +5,7 @@ const toneColor = { neutral: 'var(--text-heading)', warning: 'var(--warning-text
|
|
|
5
5
|
defineProps({ from: [String, Number], to: [String, Number], tone: { type: String, default: 'warning' }, mono: { type: Boolean, default: true }, style: Object })
|
|
6
6
|
</script>
|
|
7
7
|
<template>
|
|
8
|
-
<span :style="{ display: 'inline-flex', alignItems: 'center', gap:
|
|
8
|
+
<span :style="{ display: 'inline-flex', alignItems: 'center', gap: '6px', fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--control-text)', ...style }">
|
|
9
9
|
<span v-if="from != null" style="color:var(--text-muted);text-decoration:line-through">{{ from }}</span>
|
|
10
10
|
<Icon name="arrow-right" :size="12" style="color:var(--text-muted)" />
|
|
11
11
|
<span :style="{ color: toneColor[tone], fontWeight: 'var(--weight-medium)' }">{{ to }}</span>
|
|
@@ -9,6 +9,8 @@ export interface FilterBarProps {
|
|
|
9
9
|
/** Count shown in "Adapt Filters (n)" */
|
|
10
10
|
adaptCount?: number;
|
|
11
11
|
defaultExpanded?: boolean;
|
|
12
|
+
/** Size tier for the whole bar and every field in it. Omit to inherit the enclosing region's tier (the default). */
|
|
13
|
+
size?: "sm" | "md" | "lg";
|
|
12
14
|
onGo?: () => void;
|
|
13
15
|
onAdapt?: () => void;
|
|
14
16
|
children?: React.ReactNode;
|
|
@@ -2,21 +2,22 @@ import React from "react";
|
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
import { Button } from "../buttons/Button.jsx";
|
|
4
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
|
-
|
|
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
|
+
* A size region: `size` (default inherit → md) sets the title bar height, the fields' tier and the gap between fields (--group-gap) so every control in it matches. */
|
|
7
|
+
export function FilterBar({ variant = "Standard", modified, adaptCount, defaultExpanded = true, size, onGo, onAdapt, children, style }) {
|
|
7
8
|
const [open, setOpen] = React.useState(defaultExpanded);
|
|
8
9
|
return (
|
|
9
|
-
<div style={{ background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)",
|
|
10
|
-
<div style={{ display: "flex", alignItems: "center", gap:
|
|
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-
|
|
10
|
+
<div data-size={size} style={{ background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-lg)", boxSizing: "border-box", ...style }}>
|
|
11
|
+
<div style={{ display: "flex", alignItems: "center", gap: "var(--control-gap)", minHeight: "var(--bar-card-h)", padding: "0 var(--edge-pad)", boxSizing: "border-box" }}>
|
|
12
|
+
<button type="button" aria-expanded={open} 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(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)" }}>
|
|
12
13
|
{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 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={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></span>
|
|
14
15
|
</button>
|
|
15
16
|
<span style={{ flex: 1 }}></span>
|
|
16
|
-
<Button variant="text"
|
|
17
|
-
<Button
|
|
17
|
+
<Button variant="text" onClick={onAdapt}>Adapt Filters{adaptCount ? ` (${adaptCount})` : ""}</Button>
|
|
18
|
+
<Button onClick={onGo}>Go</Button>
|
|
18
19
|
</div>
|
|
19
|
-
{open && <div style={{ display: "flex", flexWrap: "wrap", gap: "
|
|
20
|
+
{open && <div style={{ display: "flex", flexWrap: "wrap", alignItems: "flex-end", gap: "var(--control-gap) var(--group-gap)", padding: "12px var(--edge-pad) var(--edge-pad)", borderTop: "1px solid var(--border-subtle)" }}>{children}</div>}
|
|
20
21
|
</div>
|
|
21
22
|
);
|
|
22
23
|
}
|
|
@@ -3,21 +3,22 @@ import { ref } from 'vue'
|
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
4
|
import Button from '../buttons/Button.vue'
|
|
5
5
|
// SAP Fiori-style list-report filter bar: variant title + expand/collapse, wrapping labeled filter fields (slot), Adapt Filters (n) + Go.
|
|
6
|
-
|
|
6
|
+
// A size region: `size` (default inherit → md) sets the title bar height, the fields' tier and the gap between fields (--group-gap) so every control in it matches.
|
|
7
|
+
const props = defineProps({ variant: { type: String, default: 'Standard' }, modified: Boolean, adaptCount: Number, defaultExpanded: { type: Boolean, default: true }, size: String, style: Object })
|
|
7
8
|
const emit = defineEmits(['go', 'adapt'])
|
|
8
9
|
const open = ref(props.defaultExpanded)
|
|
9
10
|
</script>
|
|
10
11
|
<template>
|
|
11
|
-
<div :style="{ background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)',
|
|
12
|
-
<div style="display:flex;align-items:center;gap:
|
|
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-
|
|
12
|
+
<div :data-size="size" :style="{ background: 'var(--bg-surface)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', boxSizing: 'border-box', ...style }">
|
|
13
|
+
<div style="display:flex;align-items:center;gap:var(--control-gap);min-height:var(--bar-card-h);padding:0 var(--edge-pad);box-sizing:border-box">
|
|
14
|
+
<button type="button" :aria-expanded="open" @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(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking)">
|
|
14
15
|
{{ 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="
|
|
16
|
+
<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="16" :style="{ width: 'var(--control-icon)', height: 'var(--control-icon)' }" /></span>
|
|
16
17
|
</button>
|
|
17
18
|
<span style="flex:1" />
|
|
18
|
-
<Button variant="text"
|
|
19
|
-
<Button
|
|
19
|
+
<Button variant="text" @click="emit('adapt')">Adapt Filters{{ adaptCount ? ` (${adaptCount})` : '' }}</Button>
|
|
20
|
+
<Button @click="emit('go')">Go</Button>
|
|
20
21
|
</div>
|
|
21
|
-
<div v-if="open" style="display:flex;flex-wrap:wrap;gap:
|
|
22
|
+
<div v-if="open" style="display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--control-gap) var(--group-gap);padding:12px var(--edge-pad) var(--edge-pad);border-top:1px solid var(--border-subtle)"><slot /></div>
|
|
22
23
|
</div>
|
|
23
24
|
</template>
|
|
@@ -3,9 +3,9 @@ import React from "react";
|
|
|
3
3
|
/** Label/value row (config summaries, confirmation steps). mono renders the value in Menlo. */
|
|
4
4
|
export function KeyValue({ label, value, mono, style }) {
|
|
5
5
|
return (
|
|
6
|
-
<div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "
|
|
7
|
-
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
8
|
-
<span style={{ fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize:
|
|
6
|
+
<div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "6px 0", ...style }}>
|
|
7
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-secondary)" }}>{label}</span>
|
|
8
|
+
<span style={{ fontFamily: mono ? "var(--font-mono)" : "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: mono ? 0 : "var(--tracking-base)", fontVariantNumeric: "tabular-nums", color: "var(--text-heading)", textAlign: "right" }}>{value}</span>
|
|
9
9
|
</div>
|
|
10
10
|
);
|
|
11
11
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
defineProps({ label: String, value: [String, Number], mono: Boolean, style: Object })
|
|
4
4
|
</script>
|
|
5
5
|
<template>
|
|
6
|
-
<div :style="{ display: 'flex', justifyContent: 'space-between', gap:
|
|
7
|
-
<span style="font-family:var(--font-sans);font-size:var(--text-
|
|
8
|
-
<span :style="{ fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize:
|
|
6
|
+
<div :style="{ display: 'flex', justifyContent: 'space-between', gap: '12px', padding: '6px 0', ...style }">
|
|
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
|
+
<span :style="{ fontFamily: mono ? 'var(--font-mono)' : 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: mono ? 0 : 'var(--tracking-base)', fontVariantNumeric: 'tabular-nums', color: 'var(--text-heading)', textAlign: 'right' }">{{ value }}</span>
|
|
9
9
|
</div>
|
|
10
10
|
</template>
|
|
@@ -14,10 +14,10 @@ export function ListRow({ icon = "file-text", title, meta, trailing, expanded, o
|
|
|
14
14
|
<IconTile icon={icon} kind="row" />
|
|
15
15
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
16
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-
|
|
17
|
+
{meta ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-regular)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", color: "var(--text-secondary)" }}>{meta}</span> : null}
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
|
-
{trailing ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-
|
|
20
|
+
{trailing ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", fontVariantNumeric: "tabular-nums", color: "var(--text-secondary)" }}>{trailing}</span> : null}
|
|
21
21
|
</div>
|
|
22
22
|
{expanded && children ? <div style={{ padding: "0 16px 12px 72px" }}>{children}</div> : null}
|
|
23
23
|
</div>
|
|
@@ -15,10 +15,10 @@ const hover = ref(false)
|
|
|
15
15
|
<IconTile :icon="icon" kind="row" />
|
|
16
16
|
<div style="display:flex;flex-direction:column">
|
|
17
17
|
<span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)">{{ title }}</span>
|
|
18
|
-
<span v-if="meta" style="font-family:var(--font-sans);font-weight:var(--weight-
|
|
18
|
+
<span v-if="meta" style="font-family:var(--font-sans);font-weight:var(--weight-regular);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)">{{ meta }}</span>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
|
-
<span v-if="trailing" style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-
|
|
21
|
+
<span v-if="trailing" style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);font-variant-numeric:tabular-nums;color:var(--text-secondary)">{{ trailing }}</span>
|
|
22
22
|
</div>
|
|
23
23
|
<div v-if="expanded" style="padding:0 16px 12px 72px"><slot /></div>
|
|
24
24
|
</div>
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Table pagination
|
|
2
|
+
/** Table pagination — page items are control-height squares at the region's tier, r8. Inside a Table card it follows the table's tier. */
|
|
3
3
|
export interface PaginationProps {
|
|
4
4
|
page?: number;
|
|
5
5
|
pageCount?: number;
|
|
6
6
|
/** Total rows; shows an "1–20 of 128" range label when given */
|
|
7
7
|
total?: number;
|
|
8
8
|
pageSize?: number;
|
|
9
|
+
/** Size tier. Omit to inherit the enclosing region's tier (the default). */
|
|
10
|
+
size?: "sm" | "md" | "lg";
|
|
11
|
+
/** Builds the range label, for other languages: (from, to, total, page, pageCount) => string. from/to/total are null when `total` is not given. */
|
|
12
|
+
format?: (from: number | null, to: number | null, total: number | null, page: number, pageCount: number) => string;
|
|
9
13
|
onChange?: (page: number) => void;
|
|
10
14
|
style?: React.CSSProperties;
|
|
11
15
|
}
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
|
|
5
|
+
|
|
6
|
+
/** Table pagination — range label + prev/next + numbered pages, all at the region's tier (page items are control-height squares). `format(from, to, total, page, pageCount)` localises the label. */
|
|
7
|
+
export function Pagination({ page = 1, pageCount = 1, total, pageSize = 20, size, format, onChange, style }) {
|
|
8
|
+
const btn = (content, disabled, onClick, active, label) => (
|
|
9
|
+
<button type="button" disabled={disabled} onClick={onClick} aria-label={label} aria-current={active ? "page" : undefined} data-wash={active || disabled ? undefined : ""}
|
|
10
|
+
style={{ minWidth: "var(--control-h)", height: "var(--control-h)", padding: "0 6px", boxSizing: "border-box", borderRadius: "var(--radius-md)", border: `1px solid ${active ? "var(--action-primary)" : "var(--border-control)"}`, background: active ? "var(--action-primary)" : "var(--wash, var(--bg-surface))", color: active ? "var(--on-accent)" : "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", fontVariantNumeric: "tabular-nums", cursor: disabled ? "default" : "pointer", opacity: disabled ? .45 : 1, display: "inline-flex", alignItems: "center", justifyContent: "center", transition: "var(--t-hover)" }}>
|
|
9
11
|
{content}
|
|
10
12
|
</button>
|
|
11
13
|
);
|
|
12
14
|
const pages = [];
|
|
13
|
-
const
|
|
14
|
-
for (let i =
|
|
15
|
+
const first = Math.max(1, Math.min(page - 1, pageCount - 2));
|
|
16
|
+
for (let i = first; i < first + 3 && i <= pageCount; i++) pages.push(i);
|
|
17
|
+
const from = total != null ? (page - 1) * pageSize + 1 : null, to = total != null ? Math.min(page * pageSize, total) : null;
|
|
18
|
+
const text = format ? format(from, to, total, page, pageCount) : (total != null ? `${from}–${to} of ${total}` : `Page ${page} of ${pageCount}`);
|
|
15
19
|
return (
|
|
16
|
-
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap:
|
|
17
|
-
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text
|
|
18
|
-
{
|
|
20
|
+
<div data-size={size} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "var(--group-gap)", ...style }}>
|
|
21
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", fontVariantNumeric: "tabular-nums", color: "var(--text-secondary)" }}>
|
|
22
|
+
{text}
|
|
19
23
|
</span>
|
|
20
24
|
<div style={{ display: "flex", gap: 4 }}>
|
|
21
|
-
{btn(<Icon name="chevron-left" size={
|
|
25
|
+
{btn(<Icon name="chevron-left" size={16} style={tierIcon} />, page <= 1, () => onChange && onChange(page - 1), false, "Previous page")}
|
|
22
26
|
{pages.map((p) => <React.Fragment key={p}>{btn(p, false, () => onChange && onChange(p), p === page)}</React.Fragment>)}
|
|
23
|
-
{btn(<Icon name="chevron-right" size={
|
|
27
|
+
{btn(<Icon name="chevron-right" size={16} style={tierIcon} />, page >= pageCount, () => onChange && onChange(page + 1), false, "Next page")}
|
|
24
28
|
</div>
|
|
25
29
|
</div>
|
|
26
30
|
);
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
|
-
// Table pagination —
|
|
5
|
-
const
|
|
4
|
+
// Table pagination — range label + prev/next + numbered pages, all at the region's tier (page items are control-height squares). `format(from, to, total, page, pageCount)` localises the label.
|
|
5
|
+
const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
|
|
6
|
+
const props = defineProps({ page: { type: Number, default: 1 }, pageCount: { type: Number, default: 1 }, total: Number, pageSize: { type: Number, default: 20 }, size: String, format: Function, style: Object })
|
|
6
7
|
const emit = defineEmits(['change'])
|
|
7
8
|
const pages = computed(() => {
|
|
8
9
|
const from = Math.max(1, Math.min(props.page - 1, props.pageCount - 2)), out = []
|
|
9
10
|
for (let i = from; i < from + 3 && i <= props.pageCount; i++) out.push(i)
|
|
10
11
|
return out
|
|
11
12
|
})
|
|
12
|
-
const label = computed(() =>
|
|
13
|
-
|
|
13
|
+
const label = computed(() => {
|
|
14
|
+
const from = props.total != null ? (props.page - 1) * props.pageSize + 1 : null, to = props.total != null ? Math.min(props.page * props.pageSize, props.total) : null
|
|
15
|
+
if (props.format) return props.format(from, to, props.total, props.page, props.pageCount)
|
|
16
|
+
return props.total != null ? `${from}–${to} of ${props.total}` : `Page ${props.page} of ${props.pageCount}`
|
|
17
|
+
})
|
|
18
|
+
function btnStyle(active, disabled) { return { minWidth: 'var(--control-h)', height: 'var(--control-h)', padding: '0 6px', boxSizing: 'border-box', borderRadius: 'var(--radius-md)', border: `1px solid ${active ? 'var(--action-primary)' : 'var(--border-control)'}`, background: active ? 'var(--action-primary)' : 'var(--wash, var(--bg-surface))', color: active ? 'var(--on-accent)' : 'var(--text-body)', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', letterSpacing: 'var(--control-tracking)', fontVariantNumeric: 'tabular-nums', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? .45 : 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', transition: 'var(--t-hover)' } }
|
|
14
19
|
</script>
|
|
15
20
|
<template>
|
|
16
|
-
<div :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap:
|
|
17
|
-
<span style="font-family:var(--font-sans);font-size:var(--text
|
|
21
|
+
<div :data-size="size" :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--group-gap)', ...style }">
|
|
22
|
+
<span style="font-family:var(--font-sans);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking);font-variant-numeric:tabular-nums;color:var(--text-secondary)">{{ label }}</span>
|
|
18
23
|
<div style="display:flex;gap:4px">
|
|
19
|
-
<button type="button" :disabled="page <= 1" @click="emit('change', page - 1)" :style="btnStyle(false, page <= 1)"><Icon name="chevron-left" :size="
|
|
20
|
-
<button v-for="p in pages" :key="p" type="button" @click="emit('change', p)" :style="btnStyle(p === page, false)">{{ p }}</button>
|
|
21
|
-
<button type="button" :disabled="page >= pageCount" @click="emit('change', page + 1)" :style="btnStyle(false, page >= pageCount)"><Icon name="chevron-right" :size="
|
|
24
|
+
<button type="button" aria-label="Previous page" :disabled="page <= 1" :data-wash="page <= 1 ? undefined : ''" @click="emit('change', page - 1)" :style="btnStyle(false, page <= 1)"><Icon name="chevron-left" :size="16" :style="tierIcon" /></button>
|
|
25
|
+
<button v-for="p in pages" :key="p" type="button" :aria-current="p === page ? 'page' : undefined" :data-wash="p === page ? undefined : ''" @click="emit('change', p)" :style="btnStyle(p === page, false)">{{ p }}</button>
|
|
26
|
+
<button type="button" aria-label="Next page" :disabled="page >= pageCount" :data-wash="page >= pageCount ? undefined : ''" @click="emit('change', page + 1)" :style="btnStyle(false, page >= pageCount)"><Icon name="chevron-right" :size="16" :style="tierIcon" /></button>
|
|
22
27
|
</div>
|
|
23
28
|
</div>
|
|
24
29
|
</template>
|
|
@@ -24,7 +24,10 @@ export interface SmartTableProps {
|
|
|
24
24
|
toolbar?: boolean;
|
|
25
25
|
/** Scroll-area max height */
|
|
26
26
|
height?: number | string;
|
|
27
|
-
|
|
27
|
+
/** Row height; defaults to the tier's bar height (32 at sm) */
|
|
28
|
+
rowHeight?: number | string;
|
|
29
|
+
/** Size tier of the grid region. Defaults to "sm" — a database grid is a dense surface (32px rows, 12px text, 24px toolbar buttons). */
|
|
30
|
+
size?: "sm" | "md" | "lg";
|
|
28
31
|
onChange?: (rows: object[]) => void;
|
|
29
32
|
style?: React.CSSProperties;
|
|
30
33
|
}
|
|
@@ -9,7 +9,8 @@ const typeIcon = { text: "type", longText: "align-left", number: "hash", select:
|
|
|
9
9
|
const Pill = ({ hue = "gray", children }) => <span style={{ display: "inline-flex", alignItems: "center", height: 20, padding: "0 8px", borderRadius: "var(--radius-full)", background: `var(--hue-${hue}-bg)`, color: `var(--hue-${hue}-fg)`, fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-xs)", lineHeight: 1, whiteSpace: "nowrap" }}>{children}</span>;
|
|
10
10
|
|
|
11
11
|
/** Notion/Airtable-style database grid ("smart table"): field-type icons in headers, colored select pills, person/date/checkbox/url/progress cells, row hover + expand, inline text/number editing, + New row, add-field column, per-column footer aggregates, and a view toolbar (Hide fields · Filter · Group · Sort · search). */
|
|
12
|
-
|
|
12
|
+
/* A size region that defaults to sm: a database grid is a dense surface, so rows are the sm bar height (32), cells and toolbar read the sm control text (12px) and the toolbar's buttons are 24px. Pass size="md" for a roomier grid. */
|
|
13
|
+
export function SmartTable({ fields = [], rows = [], title = "Grid view", toolbar = true, height, rowHeight = "var(--bar-h)", size = "sm", onChange, style }) {
|
|
13
14
|
const [data, setData] = React.useState(rows);
|
|
14
15
|
const [hover, setHover] = React.useState(-1);
|
|
15
16
|
const [editing, setEditing] = React.useState(null);
|
|
@@ -17,7 +18,8 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
|
|
|
17
18
|
const setCell = (r, key, v) => { const next = data.map((row, i) => i === r ? { ...row, [key]: v } : row); setData(next); onChange && onChange(next); };
|
|
18
19
|
const widths = ["40px", ...fields.map(f => (f.width || 140) + "px"), "40px"];
|
|
19
20
|
const grid = { display: "grid", gridTemplateColumns: widths.join(" "), width: "fit-content", minWidth: "100%" };
|
|
20
|
-
const cellBase = { display: "flex", alignItems: "center", gap: 5, padding: "0
|
|
21
|
+
const cellBase = { display: "flex", alignItems: "center", gap: 5, padding: "0 var(--cell-px)", borderRight: "1px solid var(--border-subtle)", borderBottom: "1px solid var(--border-subtle)", height: rowHeight, boxSizing: "border-box", overflow: "hidden", whiteSpace: "nowrap", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-primary)" };
|
|
22
|
+
const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
|
|
21
23
|
const agg = (f) => {
|
|
22
24
|
if (!f.aggregate) return "";
|
|
23
25
|
const vals = data.map(r => r[f.key]);
|
|
@@ -35,18 +37,18 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
|
|
|
35
37
|
case "select": { const hue = (f.options || {})[v] || "gray"; return v ? <Pill hue={hue}>{v}</Pill> : null; }
|
|
36
38
|
case "multiSelect": return <span style={{ display: "flex", gap: 4, overflow: "hidden" }}>{(v || []).map(t => <Pill key={t} hue={(f.options || {})[t] || "gray"}>{t}</Pill>)}</span>;
|
|
37
39
|
case "person": return v ? <span style={{ display: "inline-flex", alignItems: "center", gap: 6, minWidth: 0 }}><Avatar name={v} size={20} /><span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{v}</span></span> : null;
|
|
38
|
-
case "checkbox": return <Checkbox
|
|
40
|
+
case "checkbox": return <Checkbox checked={!!v} onChange={(nv) => setCell(r, f.key, nv)} />;
|
|
39
41
|
case "url": return v ? <a href={"https://" + v} onClick={(e) => e.preventDefault()} style={{ color: "var(--link)", overflow: "hidden", textOverflow: "ellipsis" }}>{v}</a> : null;
|
|
40
|
-
case "progress": return <span style={{ display: "flex", alignItems: "center", gap: 6, width: "100%" }}><span style={{ flex: 1, height: 4, borderRadius: "var(--radius-full)", background: "var(--track)", overflow: "hidden" }}><span style={{ display: "block", width: (v || 0) + "%", height: "100%", background: "var(--chart-blue)" }}></span></span><span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-
|
|
41
|
-
case "number": return <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize:
|
|
42
|
+
case "progress": return <span style={{ display: "flex", alignItems: "center", gap: 6, width: "100%" }}><span style={{ flex: 1, height: 4, borderRadius: "var(--radius-full)", background: "var(--track)", overflow: "hidden" }}><span style={{ display: "block", width: (v || 0) + "%", height: "100%", background: "var(--chart-blue)" }}></span></span><span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-secondary)" }}>{v || 0}%</span></span>;
|
|
43
|
+
case "number": return <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--control-text)", letterSpacing: 0, fontVariantNumeric: "tabular-nums" }}>{v}</span>;
|
|
42
44
|
default: return <span style={{ overflow: "hidden", textOverflow: "ellipsis", fontWeight: f.primary ? "var(--weight-medium)" : "var(--weight-regular)", color: f.primary ? "var(--text-heading)" : "var(--text-primary)" }}>{v}</span>;
|
|
43
45
|
}
|
|
44
46
|
};
|
|
45
|
-
const tbBtn = (icon, label) => <button key={label} type="button" style={{ display: "inline-flex", alignItems: "center", gap: 5, height:
|
|
47
|
+
const tbBtn = (icon, label) => <button key={label} type="button" style={{ display: "inline-flex", alignItems: "center", gap: 5, height: "var(--control-h)", padding: "0 var(--control-px)", border: "none", borderRadius: "var(--radius-md)", background: "transparent", cursor: "pointer", color: "var(--text-body)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-hover)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name={icon} size={14} style={tierIcon} />{label}</button>;
|
|
46
48
|
return (
|
|
47
|
-
<div style={{ border: "1px solid var(--border-control)", borderRadius: "var(--radius-lg)", background: "var(--bg-surface)", overflow: "hidden", boxSizing: "border-box", ...style }}>
|
|
48
|
-
{toolbar && <div style={{ display: "flex", alignItems: "center", gap: 4, padding: "
|
|
49
|
-
<button type="button" style={{ display: "inline-flex", alignItems: "center", gap: 6, height:
|
|
49
|
+
<div data-size={size} style={{ border: "1px solid var(--border-control)", borderRadius: "var(--radius-lg)", background: "var(--bg-surface)", overflow: "hidden", boxSizing: "border-box", ...style }}>
|
|
50
|
+
{toolbar && <div style={{ display: "flex", alignItems: "center", gap: 4, minHeight: "var(--bar-card-h)", padding: "0 var(--edge-pad)", boxSizing: "border-box", borderBottom: "1px solid var(--border-subtle)" }}>
|
|
51
|
+
<button type="button" data-wash="strong" style={{ display: "inline-flex", alignItems: "center", gap: 6, height: "var(--control-h)", padding: "0 var(--control-px)", border: "none", borderRadius: "var(--radius-md)", background: "var(--wash, var(--bg-active))", transition: "var(--t-hover)", cursor: "pointer", color: "var(--text-heading)", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)" }}><Icon name="layout-grid" size={14} style={tierIcon} />{title}<Icon name="chevron-down" size={14} style={tierIcon} /></button>
|
|
50
52
|
<span style={{ width: 1, height: 16, background: "var(--border-control)", margin: "0 4px" }}></span>
|
|
51
53
|
{tbBtn("eye-off", "Hide fields")}{tbBtn("funnel", "Filter")}{tbBtn("layers", "Group")}{tbBtn("arrow-down", "Sort")}
|
|
52
54
|
<span style={{ flex: 1 }}></span>
|
|
@@ -55,16 +57,16 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
|
|
|
55
57
|
<div style={{ overflow: "auto", maxHeight: height }}>
|
|
56
58
|
<div style={grid}>
|
|
57
59
|
<span style={{ ...cellBase, background: "var(--bg-inset)", justifyContent: "center" }}></span>
|
|
58
|
-
{fields.map(f => <span key={f.key} style={{ ...cellBase, background: "var(--bg-inset)", color: "var(--text-secondary)",
|
|
59
|
-
<span style={{ ...cellBase, background: "var(--bg-inset)", justifyContent: "center" }}><IconButton icon="plus"
|
|
60
|
+
{fields.map(f => <span key={f.key} style={{ ...cellBase, background: "var(--bg-inset)", color: "var(--text-secondary)", fontWeight: "var(--weight-semibold)" }}><Icon name={typeIcon[f.type] || "type"} size={14} style={{ ...tierIcon, color: "var(--text-muted)" }} />{f.label}</span>)}
|
|
61
|
+
<span style={{ ...cellBase, background: "var(--bg-inset)", justifyContent: "center", padding: 0 }}><IconButton icon="plus" muted label="Add field" /></span>
|
|
60
62
|
{data.map((row, r) => [
|
|
61
|
-
<span key={"n" + r} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} style={{ ...cellBase, justifyContent: "center", color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: "var(--text-
|
|
63
|
+
<span key={"n" + r} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} style={{ ...cellBase, justifyContent: "center", color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: 0, background: hover === r ? "var(--bg-inset)" : "transparent", cursor: "pointer" }}>{hover === r ? <Icon name="maximize-2" size={14} style={{ ...tierIcon, color: "var(--action-primary)" }} /> : r + 1}</span>,
|
|
62
64
|
...fields.map(f => <span key={r + f.key} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} onDoubleClick={() => { if (f.type === "text" || f.type === "number" || f.type === "date" || f.type === "url") { setEditing({ r, key: f.key }); setDraft(String(row[f.key] == null ? "" : row[f.key])); } }} style={{ ...cellBase, position: "relative", background: hover === r ? "var(--bg-inset)" : "transparent" }}>{renderCell(f, row, r)}</span>),
|
|
63
65
|
<span key={"a" + r} style={{ ...cellBase, background: hover === r ? "var(--bg-inset)" : "transparent" }}></span>,
|
|
64
66
|
])}
|
|
65
|
-
<button type="button" onClick={() => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === "multiSelect" ? [] : f.type === "checkbox" ? false : ""); const next = [...data, empty]; setData(next); onChange && onChange(next); }} style={{ gridColumn: "1 / -1", display: "flex", alignItems: "center", gap: 6, height: rowHeight, padding: "0
|
|
67
|
+
<button type="button" onClick={() => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === "multiSelect" ? [] : f.type === "checkbox" ? false : ""); const next = [...data, empty]; setData(next); onChange && onChange(next); }} style={{ gridColumn: "1 / -1", display: "flex", alignItems: "center", gap: 6, height: rowHeight, padding: "0 var(--edge-pad)", border: "none", borderBottom: "1px solid var(--border-subtle)", background: "transparent", cursor: "pointer", color: "var(--text-secondary)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", textAlign: "left" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-inset)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name="plus" size={13} />New</button>
|
|
66
68
|
<span style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)" }}></span>
|
|
67
|
-
{fields.map(f => <span key={"g" + f.key} style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)", justifyContent: "flex-end", fontFamily: "var(--font-mono)", fontSize: "var(--text-
|
|
69
|
+
{fields.map(f => <span key={"g" + f.key} style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)", justifyContent: "flex-end", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: 0, color: "var(--text-secondary)" }}>{agg(f)}</span>)}
|
|
68
70
|
<span style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)" }}></span>
|
|
69
71
|
</div>
|
|
70
72
|
</div>
|
|
@@ -7,12 +7,15 @@ import Avatar from '../display/Avatar.vue'
|
|
|
7
7
|
import SearchInput from '../forms/SearchInput.vue'
|
|
8
8
|
// Notion/Airtable-style database grid: field-type header icons, hue pills, person/date/checkbox/url/progress cells, row hover + expand, inline editing, + New row, footer aggregates, view toolbar.
|
|
9
9
|
const typeIcon = { text: 'type', longText: 'align-left', number: 'hash', select: 'chevron-down', multiSelect: 'tag', date: 'calendar', person: 'user', checkbox: 'check-square', url: 'link', progress: 'bar-chart-2' }
|
|
10
|
-
const props = defineProps({ fields: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, title: { type: String, default: 'Grid view' }, toolbar: { type: Boolean, default: true }, height: [Number, String], rowHeight: { type:
|
|
10
|
+
const props = defineProps({ fields: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, title: { type: String, default: 'Grid view' }, toolbar: { type: Boolean, default: true }, height: [Number, String], rowHeight: [Number, String], size: { type: String, default: 'sm' }, style: Object })
|
|
11
|
+
// A size region that defaults to sm: a database grid is a dense surface, so rows are the sm bar height (32), cells and toolbar read the sm control text (12px) and the toolbar's buttons are 24px. Pass size="md" for a roomier grid.
|
|
12
|
+
const rowH = computed(() => props.rowHeight == null ? 'var(--bar-h)' : (typeof props.rowHeight === 'number' ? props.rowHeight + 'px' : props.rowHeight))
|
|
13
|
+
const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
|
|
11
14
|
const emit = defineEmits(['change'])
|
|
12
15
|
const data = ref(props.rows.map(r => ({ ...r })))
|
|
13
16
|
const hover = ref(-1), editing = ref(null), draft = ref('')
|
|
14
17
|
const gridCols = computed(() => ['40px', ...props.fields.map(f => (f.width || 140) + 'px'), '40px'].join(' '))
|
|
15
|
-
const cellBase = { display: 'flex', alignItems: 'center', gap: '5px', padding: '0
|
|
18
|
+
const cellBase = { display: 'flex', alignItems: 'center', gap: '5px', padding: '0 var(--cell-px)', borderRight: '1px solid var(--border-subtle)', borderBottom: '1px solid var(--border-subtle)', boxSizing: 'border-box', overflow: 'hidden', whiteSpace: 'nowrap', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-primary)' }
|
|
16
19
|
function setCell(r, key, v) { data.value[r][key] = v; emit('change', data.value) }
|
|
17
20
|
function commitEdit(r, f) { setCell(r, f.key, f.type === 'number' && draft.value !== '' && !isNaN(Number(draft.value)) ? Number(draft.value) : draft.value); editing.value = null }
|
|
18
21
|
function startEdit(r, f, row) { if (['text', 'number', 'date', 'url'].includes(f.type || 'text')) { editing.value = { r, key: f.key }; draft.value = String(row[f.key] == null ? '' : row[f.key]) } }
|
|
@@ -27,48 +30,48 @@ function agg(f) {
|
|
|
27
30
|
}
|
|
28
31
|
</script>
|
|
29
32
|
<template>
|
|
30
|
-
<div :style="{ border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', background: 'var(--bg-surface)', overflow: 'hidden', boxSizing: 'border-box', ...style }">
|
|
31
|
-
<div v-if="toolbar" style="display:flex;align-items:center;gap:4px;padding:
|
|
32
|
-
<button type="button" style="display:inline-flex;align-items:center;gap:6px;height:
|
|
33
|
+
<div :data-size="size" :style="{ border: '1px solid var(--border-control)', borderRadius: 'var(--radius-lg)', background: 'var(--bg-surface)', overflow: 'hidden', boxSizing: 'border-box', ...style }">
|
|
34
|
+
<div v-if="toolbar" style="display:flex;align-items:center;gap:4px;min-height:var(--bar-card-h);padding:0 var(--edge-pad);box-sizing:border-box;border-bottom:1px solid var(--border-subtle)">
|
|
35
|
+
<button type="button" data-wash="strong" style="display:inline-flex;align-items:center;gap:6px;height:var(--control-h);padding:0 var(--control-px);border:none;border-radius:var(--radius-md);background:var(--wash, var(--bg-active));transition:var(--t-hover);cursor:pointer;color:var(--text-heading);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--control-text);letter-spacing:var(--control-tracking)"><Icon name="layout-grid" :size="14" :style="tierIcon" />{{ title }}<Icon name="chevron-down" :size="14" :style="tierIcon" /></button>
|
|
33
36
|
<span style="width:1px;height:16px;background:var(--border-control);margin:0 4px" />
|
|
34
|
-
<button v-for="b in [['eye-off','Hide fields'],['funnel','Filter'],['layers','Group'],['arrow-down','Sort']]" :key="b[1]" type="button" class="kb-st-btn"><Icon :name="b[0]" :size="
|
|
37
|
+
<button v-for="b in [['eye-off','Hide fields'],['funnel','Filter'],['layers','Group'],['arrow-down','Sort']]" :key="b[1]" type="button" class="kb-st-btn"><Icon :name="b[0]" :size="14" :style="tierIcon" />{{ b[1] }}</button>
|
|
35
38
|
<span style="flex:1" />
|
|
36
39
|
<SearchInput bare placeholder="Search..." style="width:140px;gap:6px" />
|
|
37
40
|
</div>
|
|
38
41
|
<div :style="{ overflow: 'auto', maxHeight: typeof height === 'number' ? height + 'px' : height }">
|
|
39
42
|
<div :style="{ display: 'grid', gridTemplateColumns: gridCols, width: 'fit-content', minWidth: '100%' }">
|
|
40
|
-
<span :style="{ ...cellBase, height:
|
|
41
|
-
<span v-for="f in fields" :key="f.key" :style="{ ...cellBase, height:
|
|
42
|
-
<span :style="{ ...cellBase, height:
|
|
43
|
+
<span :style="{ ...cellBase, height: rowH, background: 'var(--bg-inset)' }" />
|
|
44
|
+
<span v-for="f in fields" :key="f.key" :style="{ ...cellBase, height: rowH, background: 'var(--bg-inset)', color: 'var(--text-secondary)', fontWeight: 'var(--weight-semibold)' }"><Icon :name="typeIcon[f.type] || 'type'" :size="14" :style="{ ...tierIcon, color: 'var(--text-muted)' }" />{{ f.label }}</span>
|
|
45
|
+
<span :style="{ ...cellBase, height: rowH, background: 'var(--bg-inset)', justifyContent: 'center', padding: 0 }"><IconButton icon="plus" muted label="Add field" /></span>
|
|
43
46
|
<template v-for="(row, r) in data" :key="r">
|
|
44
|
-
<span @mouseenter="hover = r" @mouseleave="hover = -1" :style="{ ...cellBase, height:
|
|
45
|
-
<Icon v-if="hover === r" name="maximize-2" :size="
|
|
47
|
+
<span @mouseenter="hover = r" @mouseleave="hover = -1" :style="{ ...cellBase, height: rowH, justifyContent: 'center', color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', letterSpacing: 0, background: hover === r ? 'var(--bg-inset)' : 'transparent', cursor: 'pointer' }">
|
|
48
|
+
<Icon v-if="hover === r" name="maximize-2" :size="14" :style="{ ...tierIcon, color: 'var(--action-primary)' }" /><template v-else>{{ r + 1 }}</template>
|
|
46
49
|
</span>
|
|
47
|
-
<span v-for="f in fields" :key="r + f.key" @mouseenter="hover = r" @mouseleave="hover = -1" @dblclick="startEdit(r, f, row)" :style="{ ...cellBase, height:
|
|
50
|
+
<span v-for="f in fields" :key="r + f.key" @mouseenter="hover = r" @mouseleave="hover = -1" @dblclick="startEdit(r, f, row)" :style="{ ...cellBase, height: rowH, position: 'relative', background: hover === r ? 'var(--bg-inset)' : 'transparent' }">
|
|
48
51
|
<input v-if="editing && editing.r === r && editing.key === f.key" autofocus :value="draft" @input="draft = $event.target.value" @blur="commitEdit(r, f)" @keydown.enter="$event.target.blur()" @keydown.esc="editing = null" style="position:absolute;inset:0;border:2px solid var(--action-primary);border-radius:2px;outline:none;padding:0 6px;font:inherit;background:var(--bg-surface);color:var(--text-primary);box-sizing:border-box" />
|
|
49
52
|
<template v-else-if="f.type === 'select'"><span v-if="row[f.key]" class="kb-pill" :style="{ background: `var(--hue-${(f.options || {})[row[f.key]] || 'gray'}-bg)`, color: `var(--hue-${(f.options || {})[row[f.key]] || 'gray'}-fg)` }">{{ row[f.key] }}</span></template>
|
|
50
53
|
<template v-else-if="f.type === 'multiSelect'"><span style="display:flex;gap:4px;overflow:hidden"><span v-for="t in row[f.key] || []" :key="t" class="kb-pill" :style="{ background: `var(--hue-${(f.options || {})[t] || 'gray'}-bg)`, color: `var(--hue-${(f.options || {})[t] || 'gray'}-fg)` }">{{ t }}</span></span></template>
|
|
51
54
|
<template v-else-if="f.type === 'person'"><span v-if="row[f.key]" style="display:inline-flex;align-items:center;gap:6px;min-width:0"><Avatar :name="row[f.key]" :size="20" /><span style="overflow:hidden;text-overflow:ellipsis">{{ row[f.key] }}</span></span></template>
|
|
52
|
-
<template v-else-if="f.type === 'checkbox'"><Checkbox
|
|
55
|
+
<template v-else-if="f.type === 'checkbox'"><Checkbox :checked="!!row[f.key]" @change="v => setCell(r, f.key, v)" /></template>
|
|
53
56
|
<template v-else-if="f.type === 'url'"><a v-if="row[f.key]" href="#" @click.prevent style="overflow:hidden;text-overflow:ellipsis">{{ row[f.key] }}</a></template>
|
|
54
|
-
<template v-else-if="f.type === 'progress'"><span style="display:flex;align-items:center;gap:6px;width:100%"><span style="flex:1;height:4px;border-radius:var(--radius-full);background:var(--track);overflow:hidden"><span :style="{ display: 'block', width: (row[f.key] || 0) + '%', height: '100%', background: 'var(--chart-blue)' }" /></span><span style="font-family:var(--font-mono);font-size:var(--text-
|
|
55
|
-
<template v-else-if="f.type === 'number'"><span style="margin-left:auto;font-family:var(--font-mono);font-size:
|
|
57
|
+
<template v-else-if="f.type === 'progress'"><span style="display:flex;align-items:center;gap:6px;width:100%"><span style="flex:1;height:4px;border-radius:var(--radius-full);background:var(--track);overflow:hidden"><span :style="{ display: 'block', width: (row[f.key] || 0) + '%', height: '100%', background: 'var(--chart-blue)' }" /></span><span style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-secondary)">{{ row[f.key] || 0 }}%</span></span></template>
|
|
58
|
+
<template v-else-if="f.type === 'number'"><span style="margin-left:auto;font-family:var(--font-mono);font-size:var(--control-text);letter-spacing:0;font-variant-numeric:tabular-nums">{{ row[f.key] }}</span></template>
|
|
56
59
|
<template v-else><span :style="{ overflow: 'hidden', textOverflow: 'ellipsis', fontWeight: f.primary ? 'var(--weight-medium)' : 'var(--weight-regular)', color: f.primary ? 'var(--text-heading)' : 'var(--text-primary)' }">{{ row[f.key] }}</span></template>
|
|
57
60
|
</span>
|
|
58
|
-
<span :style="{ ...cellBase, height:
|
|
61
|
+
<span :style="{ ...cellBase, height: rowH, background: hover === r ? 'var(--bg-inset)' : 'transparent' }" />
|
|
59
62
|
</template>
|
|
60
|
-
<button type="button" @click="addRow" class="kb-st-new" :style="{ gridColumn: '1 / -1', height:
|
|
61
|
-
<span :style="{ ...cellBase, height:
|
|
62
|
-
<span v-for="f in fields" :key="'g' + f.key" :style="{ ...cellBase, height:
|
|
63
|
-
<span :style="{ ...cellBase, height:
|
|
63
|
+
<button type="button" @click="addRow" class="kb-st-new" :style="{ gridColumn: '1 / -1', height: rowH }"><Icon name="plus" :size="14" :style="tierIcon" />New</button>
|
|
64
|
+
<span :style="{ ...cellBase, height: rowH, borderBottom: 'none', background: 'var(--bg-inset)' }" />
|
|
65
|
+
<span v-for="f in fields" :key="'g' + f.key" :style="{ ...cellBase, height: rowH, borderBottom: 'none', background: 'var(--bg-inset)', justifyContent: 'flex-end', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', letterSpacing: 0, color: 'var(--text-secondary)' }">{{ agg(f) }}</span>
|
|
66
|
+
<span :style="{ ...cellBase, height: rowH, borderBottom: 'none', background: 'var(--bg-inset)' }" />
|
|
64
67
|
</div>
|
|
65
68
|
</div>
|
|
66
69
|
</div>
|
|
67
70
|
</template>
|
|
68
71
|
<style scoped>
|
|
69
72
|
.kb-pill{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--radius-full);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-xs);line-height:1;white-space:nowrap}
|
|
70
|
-
.kb-st-btn{display:inline-flex;align-items:center;gap:5px;height:
|
|
73
|
+
.kb-st-btn{display:inline-flex;align-items:center;gap:5px;height:var(--control-h);padding:0 var(--control-px);border:none;border-radius:var(--radius-md);background:transparent;cursor:pointer;color:var(--text-body);font-family:var(--font-sans);font-size:var(--control-text);letter-spacing:var(--control-tracking)}
|
|
71
74
|
.kb-st-btn:hover{background:var(--bg-hover)}
|
|
72
|
-
.kb-st-new{display:flex;align-items:center;gap:6px;padding:0
|
|
75
|
+
.kb-st-new{display:flex;align-items:center;gap:6px;padding:0 var(--edge-pad);border:none;border-bottom:1px solid var(--border-subtle);background:transparent;cursor:pointer;color:var(--text-secondary);font-family:var(--font-sans);font-size:var(--control-text);letter-spacing:var(--control-tracking);text-align:left}
|
|
73
76
|
.kb-st-new:hover{background:var(--bg-inset)}
|
|
74
77
|
</style>
|
|
@@ -28,6 +28,8 @@ export interface SpreadsheetProps {
|
|
|
28
28
|
toolbar?: boolean; formulaBar?: boolean; statusBar?: boolean;
|
|
29
29
|
/** Scroll-area max height */
|
|
30
30
|
height?: number | string;
|
|
31
|
+
/** Size tier of the sheet region. Defaults to "sm": a spreadsheet is the densest surface (24px toolbar controls, 12px text). */
|
|
32
|
+
size?: "sm" | "md" | "lg";
|
|
31
33
|
onChange?: (cells: object[][]) => void;
|
|
32
34
|
style?: React.CSSProperties;
|
|
33
35
|
}
|