@keyfunc/keybricks-ds 1.0.4 → 2.3.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 +14 -5
- package/DESIGN.md +245 -59
- package/INTEGRATION.md +243 -49
- package/SKILL.md +13 -11
- package/_adherence.oxlintrc.json +183 -70
- package/_scripts/build-index.mjs +24 -4
- 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 +11 -9
- package/components/_cards/activity.card.html +3 -3
- package/components/_cards/alert.card.html +3 -3
- package/components/_cards/app-chrome.card.html +4 -4
- package/components/_cards/banner.card.html +1 -1
- package/components/_cards/breadcrumb.card.html +5 -5
- package/components/_cards/button-1-base.card.html +23 -26
- package/components/_cards/button-2-states.card.html +27 -25
- package/components/_cards/button-3-sizes-icons.card.html +20 -19
- package/components/_cards/button-4-group.card.html +14 -31
- 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 +5 -5
- package/components/_cards/charts-10-gauge.card.html +5 -5
- package/components/_cards/charts-11-funnel.card.html +3 -3
- package/components/_cards/charts-12-treemap.card.html +3 -3
- package/components/_cards/charts-14-radar.card.html +3 -3
- package/components/_cards/charts-15-distribution.card.html +2 -2
- package/components/_cards/charts-16-pareto.card.html +3 -3
- package/components/_cards/charts-17-candlestick.card.html +3 -3
- package/components/_cards/charts-18-sankey.card.html +3 -3
- package/components/_cards/charts-19-chord.card.html +3 -3
- package/components/_cards/charts-2-line.card.html +4 -4
- package/components/_cards/charts-20-sunburst.card.html +3 -3
- package/components/_cards/charts-21-worldmap.card.html +4 -4
- package/components/_cards/charts-22-gantt.card.html +3 -3
- package/components/_cards/charts-23-bar3d.card.html +3 -3
- package/components/_cards/charts-24-pie3d.card.html +3 -3
- package/components/_cards/charts-25-surface3d.card.html +3 -3
- package/components/_cards/charts-26-scatter3d.card.html +3 -3
- package/components/_cards/charts-27-ribbon3d.card.html +3 -3
- package/components/_cards/charts-3-bar.card.html +7 -7
- package/components/_cards/charts-4-barlist.card.html +3 -3
- package/components/_cards/charts-5-donut.card.html +4 -4
- package/components/_cards/charts-6-scatter.card.html +3 -3
- package/components/_cards/charts-7-heatmap.card.html +3 -3
- package/components/_cards/charts-8-waterfall.card.html +3 -3
- package/components/_cards/charts-9-bullet.card.html +3 -3
- package/components/_cards/charts-99-fullpage.card.html +5 -5
- package/components/_cards/checkbox.card.html +9 -6
- package/components/_cards/codeblock.card.html +2 -2
- package/components/_cards/combobox.card.html +6 -3
- package/components/_cards/context-menu.card.html +6 -3
- package/components/_cards/date-picker.card.html +10 -4
- package/components/_cards/diffvalue.card.html +3 -3
- package/components/_cards/divider.card.html +4 -4
- package/components/_cards/drawer.card.html +4 -4
- package/components/_cards/dropdown-menu.card.html +7 -7
- package/components/_cards/dropzone.card.html +5 -3
- package/components/_cards/empty-state.card.html +6 -6
- package/components/_cards/filter-bar.card.html +5 -5
- package/components/_cards/icon-button.card.html +14 -14
- package/components/_cards/icons-1-core.card.html +4 -4
- package/components/_cards/icons-2-feather.card.html +4 -4
- package/components/_cards/icons-3-unicons.card.html +4 -4
- 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 +3 -3
- package/components/_cards/log-viewer.card.html +3 -3
- package/components/_cards/modal.card.html +9 -9
- package/components/_cards/multi-select.card.html +6 -3
- package/components/_cards/number-stepper.card.html +9 -5
- package/components/_cards/pagination.card.html +6 -3
- package/components/_cards/people.card.html +4 -4
- package/components/_cards/pipeline-node.card.html +8 -8
- package/components/_cards/pipeline-palette.card.html +5 -5
- package/components/_cards/radio.card.html +12 -7
- package/components/_cards/search.card.html +6 -3
- package/components/_cards/segmented-control.card.html +11 -10
- package/components/_cards/select.card.html +8 -4
- package/components/_cards/slider.card.html +2 -2
- package/components/_cards/smart-table.card.html +3 -3
- package/components/_cards/spreadsheet.card.html +3 -3
- package/components/_cards/stepper.card.html +3 -3
- package/components/_cards/table.card.html +165 -0
- package/components/_cards/tabs.card.html +6 -3
- package/components/_cards/tag.card.html +6 -3
- package/components/_cards/textarea.card.html +2 -2
- package/components/_cards/timeline.card.html +3 -3
- package/components/_cards/toast.card.html +10 -8
- package/components/_cards/toggles.card.html +15 -10
- package/components/_cards/tooltip.card.html +7 -7
- package/components/_cards/tree.card.html +3 -3
- package/components/_shared/card-kit.css +25 -0
- package/components/_shared/card-kit.js +50 -0
- 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-icons.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/_shared/view-source.js +3 -3
- package/components/_vue/index.mjs +16 -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 +13 -9
- package/components/buttons/IconButton.vue +12 -9
- package/components/buttons/buttons.prompt.md +6 -5
- package/components/charts/Bar3D.jsx +2 -2
- package/components/charts/Bar3D.vue +3 -3
- package/components/charts/BarChart.jsx +4 -4
- package/components/charts/BarChart.vue +6 -6
- package/components/charts/BarList.jsx +4 -4
- package/components/charts/BarList.vue +4 -4
- package/components/charts/BoxPlot.jsx +1 -1
- package/components/charts/BoxPlot.vue +3 -3
- package/components/charts/BulletChart.jsx +6 -6
- package/components/charts/BulletChart.vue +6 -6
- package/components/charts/CandlestickChart.jsx +2 -2
- package/components/charts/CandlestickChart.vue +5 -5
- package/components/charts/ChartKit.jsx +8 -7
- 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 +3 -3
- package/components/charts/DonutChart.jsx +1 -1
- package/components/charts/DonutChart.vue +2 -2
- package/components/charts/FunnelChart.jsx +3 -3
- package/components/charts/FunnelChart.vue +4 -4
- package/components/charts/GanttChart.jsx +2 -2
- package/components/charts/GanttChart.vue +3 -3
- 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 +4 -4
- package/components/charts/LineChart.jsx +1 -1
- package/components/charts/LineChart.vue +4 -4
- package/components/charts/ParetoChart.jsx +3 -3
- package/components/charts/ParetoChart.vue +5 -5
- package/components/charts/Pie3D.jsx +1 -1
- package/components/charts/Pie3D.vue +2 -2
- package/components/charts/PlotGrid.vue +18 -0
- package/components/charts/RadarChart.jsx +1 -1
- package/components/charts/RadarChart.vue +2 -2
- package/components/charts/Ribbon3D.jsx +2 -2
- package/components/charts/Ribbon3D.vue +3 -3
- package/components/charts/SankeyChart.jsx +2 -2
- package/components/charts/SankeyChart.vue +3 -3
- package/components/charts/Scatter3D.jsx +1 -1
- package/components/charts/Scatter3D.vue +2 -2
- package/components/charts/ScatterChart.jsx +3 -3
- package/components/charts/ScatterChart.vue +5 -5
- 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 +2 -2
- package/components/charts/Surface3D.jsx +2 -2
- package/components/charts/Surface3D.vue +3 -3
- package/components/charts/TreemapChart.jsx +2 -2
- package/components/charts/TreemapChart.vue +3 -3
- package/components/charts/WaterfallChart.jsx +2 -2
- package/components/charts/WaterfallChart.vue +6 -5
- package/components/charts/WorldMap.jsx +4 -4
- package/components/charts/WorldMap.vue +5 -5
- 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.d.ts +1 -0
- 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.d.ts +1 -1
- package/components/data/KeyValue.jsx +4 -4
- package/components/data/KeyValue.vue +4 -4
- 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 +26 -23
- package/components/data/Spreadsheet.d.ts +2 -0
- package/components/data/Spreadsheet.jsx +16 -15
- package/components/data/Spreadsheet.vue +19 -18
- package/components/data/Table.d.ts +101 -0
- package/components/data/Table.jsx +223 -0
- package/components/data/Table.vue +153 -0
- package/components/data/data.prompt.md +3 -3
- package/components/display/Avatar.jsx +2 -2
- package/components/display/Avatar.vue +3 -2
- package/components/display/AvatarGroup.vue +3 -2
- package/components/display/Badge.d.ts +4 -1
- package/components/display/Badge.jsx +10 -5
- 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 +10 -4
- package/components/display/Kbd.d.ts +1 -1
- package/components/display/Kbd.jsx +1 -1
- package/components/display/Kbd.vue +2 -2
- 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 +6 -6
- 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 +3 -3
- 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 +8 -8
- 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 +22 -18
- package/components/forms/Textarea.vue +5 -5
- package/components/forms/forms.prompt.md +1 -1
- package/components/forms/input-base.js +2 -1
- package/components/icons/Icon.jsx +17 -1
- package/components/icons/Icon.prompt.md +2 -0
- package/components/icons/Icon.vue +1 -1
- package/components/icons/icon-paths.js +15 -0
- package/components/index.d.ts +1 -2
- package/components/index.js +4 -5
- package/components/navigation/Breadcrumb.jsx +6 -10
- package/components/navigation/Breadcrumb.vue +6 -12
- 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 +4 -23
- package/components/navigation/Sidebar.vue +5 -10
- 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 +13 -8
- package/components/navigation/nav-model.js +23 -0
- package/components/navigation/theme-store.js +9 -0
- 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 +91 -122
- package/readme.zh-CN.md +121 -0
- package/tokens/alpha.css +3 -1
- package/tokens/charts.css +20 -17
- package/tokens/colors.css +3 -3
- package/tokens/dark.css +3 -3
- package/tokens/hover.css +32 -0
- package/tokens/interaction.css +3 -1
- package/tokens/layout.css +31 -7
- package/tokens/tokens.d.ts +58 -0
- package/tokens/tokens.js +179 -92
- package/tokens/tokens.json +327 -114
- package/tokens/typography.css +45 -15
- 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
|
@@ -2,11 +2,12 @@ import React from "react";
|
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Switch — 36×20
|
|
5
|
+
* Switch — pill track sized by the region's tier (sm 28×16 · md 36×20 · lg 44×24), knob 4px smaller, 160ms ease-out slide.
|
|
6
|
+
* Track, knob, glyph and label all read the tier tokens (tokens/layout.css), so a Switch in a form row matches its neighbours.
|
|
6
7
|
*
|
|
7
8
|
* checked / onChange(next) controlled boolean
|
|
8
9
|
* disabled 50% opacity, no pointer
|
|
9
|
-
* size "
|
|
10
|
+
* size "sm" | "md" | "lg" — omit to inherit the region's tier; lg is roomy enough for knobIcon / knobText
|
|
10
11
|
* tone "primary" (default, action blue) | "semantic" (red off → green on)
|
|
11
12
|
* onColor / offColor explicit track colors; override tone
|
|
12
13
|
* knobIcon / knobText glyph inside the knob: string | { on, off }. knobText is ONE letter or ONE CJK character.
|
|
@@ -18,27 +19,23 @@ import { Icon } from "../icons/Icon.jsx";
|
|
|
18
19
|
* inlinePrompt (Vue-friendly alias of insideOn/insideOff pair {on,off})
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
md: { w: 36, h: 20, knob: 16, pad: 2, inside: 10, glyph: 10, label: "var(--text-sm)" },
|
|
24
|
-
lg: { w: 44, h: 24, knob: 20, pad: 2, inside: 11, glyph: 12, label: "var(--text-sm)" }
|
|
25
|
-
};
|
|
22
|
+
/* Geometry in CSS so it follows the tier without JavaScript: knob = track height − 4, glyph = half the track height. */
|
|
23
|
+
const H = "var(--switch-h)", W = "var(--switch-w)", KNOB = "calc(var(--switch-h) - 4px)", GLYPH = "calc(var(--switch-h) * .5)", TEXT = "max(calc(var(--switch-h) * .5), var(--text-glyph-min))" /* text never drops below the glyph floor (12px in CJK) */;
|
|
26
24
|
|
|
27
25
|
const pick = (v, on) => (v && typeof v === "object" ? (on ? v.on : v.off) : v);
|
|
28
26
|
|
|
29
|
-
export function Switch({ checked = false, onChange, disabled, size
|
|
27
|
+
export function Switch({ checked = false, onChange, disabled, size, tone = "primary", onColor, offColor, knobIcon, knobText, insideOn, insideOff, inlinePrompt, label, labelOn, labelOff, loading: loadingProp, beforeChange, style }) {
|
|
30
28
|
// `label` = single caption to the right, shown in text-body regardless of state (settings-row usage)
|
|
31
29
|
const single = label != null && labelOn == null && labelOff == null;
|
|
32
30
|
const [busy, setBusy] = React.useState(false);
|
|
33
31
|
const alive = React.useRef(true);
|
|
34
32
|
React.useEffect(() => () => { alive.current = false; }, []);
|
|
35
33
|
const loading = loadingProp || busy;
|
|
36
|
-
const s = SIZES[size] || SIZES.md;
|
|
37
34
|
if (inlinePrompt) { insideOn = insideOn ?? inlinePrompt.on; insideOff = insideOff ?? inlinePrompt.off; }
|
|
38
35
|
const hasInside = insideOn != null || insideOff != null;
|
|
39
|
-
// inside text needs a wider track so the knob never covers the word
|
|
40
|
-
const
|
|
41
|
-
const w =
|
|
36
|
+
// inside text needs a wider track so the knob never covers the word: text runs at half the track height, ~.68em per character
|
|
37
|
+
const insideChars = hasInside ? Math.max(String(insideOn ?? "").length, String(insideOff ?? "").length) : 0;
|
|
38
|
+
const w = hasInside ? `max(${W}, calc(${H} * ${(1 + insideChars * .34).toFixed(2)} + 6px))` : W;
|
|
42
39
|
|
|
43
40
|
const onTrack = onColor || (tone === "semantic" ? "var(--success)" : "var(--action-primary)");
|
|
44
41
|
const offTrack = offColor || (tone === "semantic" ? "var(--danger)" : "var(--border-strong)");
|
|
@@ -61,30 +58,31 @@ export function Switch({ checked = false, onChange, disabled, size = "md", tone
|
|
|
61
58
|
|
|
62
59
|
const glyphIcon = pick(knobIcon, checked);
|
|
63
60
|
const glyphText = pick(knobText, checked);
|
|
64
|
-
const knobX = checked ?
|
|
61
|
+
const knobX = checked ? `calc(100% - ${H} + 2px)` : 2;
|
|
65
62
|
|
|
66
|
-
const knob = <span aria-hidden="true" style={{ position: "absolute", top:
|
|
63
|
+
const knob = <span aria-hidden="true" style={{ position: "absolute", top: 2, left: knobX, width: KNOB, height: KNOB, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", boxShadow: "0 1px 2px rgba(0,0,0,.18)", transition: "left 160ms ease-out", display: "flex", alignItems: "center", justifyContent: "center", color: track, fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", overflow: "hidden" }}>
|
|
67
64
|
{loading
|
|
68
|
-
? <span style={{ width:
|
|
69
|
-
: glyphIcon ? <Icon name={glyphIcon} size={
|
|
65
|
+
? <span style={{ width: GLYPH, height: GLYPH, borderRadius: "50%", border: `1.5px solid ${track}`, borderRightColor: "transparent", animation: "kb-spin 720ms linear infinite" }}></span>
|
|
66
|
+
: glyphIcon ? <Icon name={glyphIcon} size={10} strokeWidth={2.5} style={{ width: GLYPH, height: GLYPH }} />
|
|
70
67
|
: glyphText ? <span>{String(glyphText).slice(0, 1)}</span> : null}
|
|
71
68
|
</span>;
|
|
72
69
|
|
|
73
|
-
const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]:
|
|
70
|
+
const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: 6, display: "flex", alignItems: "center", fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", letterSpacing: ".2px", color: "var(--on-accent)", transition: "opacity 160ms ease-out", pointerEvents: "none", whiteSpace: "nowrap" });
|
|
74
71
|
|
|
75
72
|
const pill = <span role="switch" aria-checked={checked} aria-busy={loading || undefined} aria-disabled={disabled || undefined} tabIndex={inert ? -1 : 0} onClick={flip}
|
|
76
73
|
onKeyDown={e => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); flip(); } }}
|
|
77
|
-
style={{ position: "relative", display: "inline-block", width: w, height:
|
|
74
|
+
style={{ position: "relative", display: "inline-block", width: w, height: H, borderRadius: "var(--radius-full)", "--sw-track": track, background: "var(--sw-bg, var(--sw-track))", transition: "background 160ms ease-out", cursor: inert ? (loading ? "progress" : "not-allowed") : "pointer", flexShrink: 0, verticalAlign: "middle", outline: "none" }}>
|
|
78
75
|
{hasInside && <span style={{ ...insideStyle("left"), opacity: checked ? 1 : 0 }}>{insideOn}</span>}
|
|
79
76
|
{hasInside && <span style={{ ...insideStyle("right"), opacity: checked ? 0 : 1 }}>{insideOff}</span>}
|
|
80
77
|
{knob}
|
|
81
78
|
</span>;
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
if (
|
|
80
|
+
const capBase = { fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", fontFamily: "var(--font-sans)", cursor: inert ? "default" : "pointer", userSelect: "none" };
|
|
81
|
+
if (single) return <span data-size={size} data-sw="" style={{ display: "inline-flex", alignItems: "center", gap: "var(--control-gap)", opacity: disabled ? .5 : 1, ...style }}>{pill}<span onClick={flip} style={{ ...capBase, fontWeight: "var(--weight-regular)", color: "var(--text-body)" }}>{label}</span></span>;
|
|
82
|
+
if (labelOn == null && labelOff == null) return <span data-size={size} style={{ display: "inline-flex", alignItems: "center", opacity: disabled ? .5 : 1, ...style }}>{pill}</span>;
|
|
85
83
|
|
|
86
|
-
const cap = (text, active) => text == null ? null : <span style={{
|
|
87
|
-
return <span style={{ display: "inline-flex", alignItems: "center", gap:
|
|
84
|
+
const cap = (text, active) => text == null ? null : <span style={{ ...capBase, fontWeight: active ? "var(--weight-medium)" : "var(--weight-regular)", color: active ? "var(--text-heading)" : "var(--text-muted)", transition: "color 160ms ease-out" }} onClick={flip}>{text}</span>;
|
|
85
|
+
return <span data-size={size} data-sw="" style={{ display: "inline-flex", alignItems: "center", gap: "var(--control-gap)", opacity: disabled ? .5 : 1, ...style }}>
|
|
88
86
|
{cap(labelOff, !checked)}{pill}{cap(labelOn, checked)}
|
|
89
87
|
</span>;
|
|
90
88
|
}
|
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, onBeforeUnmount } from 'vue'
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
|
-
// Switch — 36×20
|
|
4
|
+
// Switch — pill track sized by the region's tier (sm 28×16 · md 36×20 · lg 44×24), knob 4px smaller, 160ms slide. v-model:checked.
|
|
5
|
+
// Geometry is CSS (tokens/layout.css --switch-w/-h), so it follows the tier without JavaScript; `size` stamps data-size to override.
|
|
5
6
|
// tone "primary" | "semantic" (red off → green on); onColor/offColor override.
|
|
6
7
|
// knobIcon / knobText: string | { on, off } — knobText is ONE letter or ONE CJK char.
|
|
7
8
|
// insideOn / insideOff: text inside the pill opposite the knob. labelOn / labelOff: captions beside the pill.
|
|
8
9
|
// loading: controlled spinner. beforeChange(next) → Promise|boolean gates the flip and shows loading meanwhile.
|
|
9
10
|
const props = defineProps({
|
|
10
11
|
checked: Boolean, disabled: Boolean, loading: Boolean,
|
|
11
|
-
size:
|
|
12
|
+
size: String, tone: { type: String, default: 'primary' },
|
|
12
13
|
onColor: String, offColor: String,
|
|
13
14
|
knobIcon: [String, Object], knobText: [String, Object],
|
|
14
|
-
insideOn: String, insideOff: String, label: String, labelOn: String, labelOff: String,
|
|
15
|
+
insideOn: String, insideOff: String, inlinePrompt: Object, label: String, labelOn: String, labelOff: String,
|
|
15
16
|
beforeChange: Function, style: Object
|
|
16
17
|
})
|
|
17
18
|
const emit = defineEmits(['update:checked', 'change'])
|
|
18
|
-
const
|
|
19
|
-
const s = computed(() => SIZES[props.size] || SIZES.md)
|
|
19
|
+
const H = 'var(--switch-h)', W = 'var(--switch-w)', KNOB = 'calc(var(--switch-h) - 4px)', GLYPH = 'calc(var(--switch-h) * .5)', TEXT = 'max(calc(var(--switch-h) * .5), var(--text-glyph-min))' /* text never drops below the glyph floor (12px in CJK) */
|
|
20
20
|
const busy = ref(false); let alive = true; onBeforeUnmount(() => { alive = false })
|
|
21
21
|
const isLoading = computed(() => props.loading || busy.value)
|
|
22
22
|
const inert = computed(() => props.disabled || isLoading.value)
|
|
23
|
-
|
|
24
|
-
const
|
|
23
|
+
// inlinePrompt { on, off } is an alias of the insideOn / insideOff pair (same as Switch.jsx)
|
|
24
|
+
const inOn = computed(() => props.insideOn ?? (props.inlinePrompt ? props.inlinePrompt.on : undefined))
|
|
25
|
+
const inOff = computed(() => props.insideOff ?? (props.inlinePrompt ? props.inlinePrompt.off : undefined))
|
|
26
|
+
const hasInside = computed(() => inOn.value != null || inOff.value != null)
|
|
27
|
+
// inside text needs a wider track so the knob never covers the word: text runs at half the track height, ~.68em per character
|
|
28
|
+
const width = computed(() => { if (!hasInside.value) return W; const n = Math.max((inOn.value || '').length, (inOff.value || '').length); return `max(${W}, calc(${H} * ${(1 + n * .34).toFixed(2)} + 6px))` })
|
|
25
29
|
const track = computed(() => props.checked ? (props.onColor || (props.tone === 'semantic' ? 'var(--success)' : 'var(--action-primary)')) : (props.offColor || (props.tone === 'semantic' ? 'var(--danger)' : 'var(--border-strong)')))
|
|
26
30
|
const pick = (v) => v && typeof v === 'object' ? (props.checked ? v.on : v.off) : v
|
|
27
31
|
const glyphIcon = computed(() => pick(props.knobIcon))
|
|
28
32
|
const glyphText = computed(() => { const t = pick(props.knobText); return t ? String(t).slice(0, 1) : '' })
|
|
29
|
-
const knobX = computed(() => props.checked ?
|
|
33
|
+
const knobX = computed(() => props.checked ? `calc(100% - ${H} + 2px)` : '2px')
|
|
30
34
|
async function flip() {
|
|
31
35
|
if (inert.value) return
|
|
32
36
|
const next = !props.checked
|
|
@@ -39,23 +43,23 @@ async function flip() {
|
|
|
39
43
|
}
|
|
40
44
|
emit('update:checked', next); emit('change', next)
|
|
41
45
|
}
|
|
42
|
-
const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]:
|
|
43
|
-
const cap = (active) => ({ fontSize:
|
|
46
|
+
const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', letterSpacing: '.2px', color: 'var(--on-accent)', transition: 'opacity 160ms ease-out', pointerEvents: 'none', whiteSpace: 'nowrap', opacity: on === props.checked ? 1 : 0 })
|
|
47
|
+
const cap = (active) => ({ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: active ? 'var(--weight-medium)' : 'var(--weight-regular)', color: active ? 'var(--text-heading)' : 'var(--text-muted)', transition: 'color 160ms ease-out', cursor: inert.value ? 'default' : 'pointer', userSelect: 'none' })
|
|
44
48
|
</script>
|
|
45
49
|
<template>
|
|
46
|
-
<span :style="{ display: 'inline-flex', alignItems: 'center', gap: '
|
|
50
|
+
<span :data-size="size" data-sw="" :style="{ display: 'inline-flex', alignItems: 'center', gap: 'var(--control-gap)', opacity: disabled ? .5 : 1, ...style }">
|
|
47
51
|
<span v-if="labelOff != null" :style="cap(!checked)" @click="flip">{{ labelOff }}</span>
|
|
48
52
|
<span role="switch" :aria-checked="checked" :aria-busy="isLoading || undefined" :aria-disabled="disabled || undefined" :tabindex="inert ? -1 : 0" @click="flip" @keydown.space.prevent="flip" @keydown.enter.prevent="flip"
|
|
49
|
-
:style="{ position: 'relative', display: 'inline-block', width: width
|
|
50
|
-
<span v-if="hasInside" :style="insideStyle('left', true)">{{
|
|
51
|
-
<span v-if="hasInside" :style="insideStyle('right', false)">{{
|
|
52
|
-
<span aria-hidden="true" :style="{ position: 'absolute', top:
|
|
53
|
-
<span v-if="isLoading" :style="{ width:
|
|
54
|
-
<Icon v-else-if="glyphIcon" :name="glyphIcon" :size="
|
|
53
|
+
:style="{ position: 'relative', display: 'inline-block', width: width, height: H, borderRadius: 'var(--radius-full)', '--sw-track': track, background: 'var(--sw-bg, var(--sw-track))', transition: 'background 160ms ease-out', cursor: inert ? (isLoading ? 'progress' : 'not-allowed') : 'pointer', flexShrink: 0, verticalAlign: 'middle', outline: 'none' }">
|
|
54
|
+
<span v-if="hasInside" :style="insideStyle('left', true)">{{ inOn }}</span>
|
|
55
|
+
<span v-if="hasInside" :style="insideStyle('right', false)">{{ inOff }}</span>
|
|
56
|
+
<span aria-hidden="true" :style="{ position: 'absolute', top: '2px', left: knobX, width: KNOB, height: KNOB, borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', boxShadow: '0 1px 2px rgba(0,0,0,.18)', transition: 'left 160ms ease-out', display: 'flex', alignItems: 'center', justifyContent: 'center', color: track, fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
|
|
57
|
+
<span v-if="isLoading" :style="{ width: GLYPH, height: GLYPH, borderRadius: '50%', border: '1.5px solid ' + track, borderRightColor: 'transparent', animation: 'kb-spin 720ms linear infinite' }"></span>
|
|
58
|
+
<Icon v-else-if="glyphIcon" :name="glyphIcon" :size="10" :stroke-width="2.5" :style="{ width: GLYPH, height: GLYPH }" />
|
|
55
59
|
<span v-else-if="glyphText">{{ glyphText }}</span>
|
|
56
60
|
</span>
|
|
57
61
|
</span>
|
|
58
62
|
<span v-if="labelOn != null" :style="cap(checked)" @click="flip">{{ labelOn }}</span>
|
|
59
|
-
<span v-if="label != null && labelOn == null && labelOff == null" :style="{ fontSize:
|
|
63
|
+
<span v-if="label != null && labelOn == null && labelOff == null" :style="{ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-regular)', color: 'var(--text-body)', cursor: inert ? 'default' : 'pointer', userSelect: 'none' }" @click="flip">{{ label }}</span>
|
|
60
64
|
</span>
|
|
61
65
|
</template>
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from 'vue'
|
|
3
3
|
import { inputBase } from './input-base.js'
|
|
4
|
-
// Multiline textarea, r8, default 90px tall. v-model. showCount + maxLength renders a bottom-right "n/max" counter (red past the limit).
|
|
5
|
-
const props = defineProps({ modelValue: String, rows: { type: Number, default: 4 }, error: Boolean, disabled: Boolean, showCount: Boolean, maxLength: Number, style: Object })
|
|
4
|
+
// Multiline textarea, r8, default 90px tall; text follows the region's tier. v-model. showCount + maxLength renders a bottom-right "n/max" counter (red past the limit).
|
|
5
|
+
const props = defineProps({ modelValue: String, rows: { type: Number, default: 4 }, error: Boolean, disabled: Boolean, showCount: Boolean, maxLength: Number, size: String, style: Object })
|
|
6
6
|
defineEmits(['update:modelValue'])
|
|
7
7
|
const focus = ref(false)
|
|
8
8
|
const len = computed(() => (props.modelValue || '').length)
|
|
9
9
|
const over = computed(() => props.maxLength != null && len.value > props.maxLength)
|
|
10
|
-
const s = computed(() => ({ ...inputBase, height: '90px', resize: 'vertical', paddingBottom: props.showCount ? '22px' : '8px',
|
|
10
|
+
const s = computed(() => ({ ...inputBase, height: '90px', resize: 'vertical', paddingTop: '8px', paddingBottom: props.showCount ? '22px' : '8px',
|
|
11
11
|
...((props.error || over.value) ? { borderColor: 'var(--danger)' } : null),
|
|
12
12
|
...(focus.value && !props.disabled ? { borderColor: (props.error || over.value) ? 'var(--danger)' : 'var(--focus-border)', boxShadow: `0 0 0 2px ${(props.error || over.value) ? 'var(--danger-bg)' : 'var(--focus-ring)'}` } : null),
|
|
13
13
|
...(props.disabled ? { opacity: .5, cursor: 'not-allowed', background: 'var(--bg-inset)' } : null), ...props.style }))
|
|
14
14
|
</script>
|
|
15
15
|
<template>
|
|
16
16
|
<div style="position:relative">
|
|
17
|
-
<textarea :rows="rows" :disabled="disabled" :maxlength="showCount ? undefined : maxLength" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @focus="focus = true" @blur="focus = false" :style="s"></textarea>
|
|
18
|
-
<span v-if="showCount" :style="{ position: 'absolute', right: '10px', bottom: '7px', fontFamily: 'var(--font-
|
|
17
|
+
<textarea :rows="rows" :disabled="disabled" :data-size="size" :data-field="disabled ? undefined : ''" :maxlength="showCount ? undefined : maxLength" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @focus="focus = true" @blur="focus = false" :style="s"></textarea>
|
|
18
|
+
<span v-if="showCount" :style="{ position: 'absolute', right: '10px', bottom: '7px', fontFamily: 'var(--font-sans)', fontVariantNumeric: 'tabular-nums', fontSize: 'var(--text-xs)', color: over ? 'var(--danger)' : 'var(--text-muted)' }">{{ len }}{{ maxLength != null ? `/${maxLength}` : '' }}</span>
|
|
19
19
|
</div>
|
|
20
20
|
</template>
|
|
@@ -15,7 +15,7 @@ Form controls, split by component (each with its own specimen card covering defa
|
|
|
15
15
|
Everything is 12px text in 36px controls; readOnly inputs go #fafafa; disabled = 50% opacity; error = red border + red helper text via `Field`'s `error` prop.
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
`Switch` / `Radio` / `RadioGroup` (additions, not in the source file). Switch:
|
|
18
|
+
`Switch` / `Radio` / `RadioGroup` (additions, not in the source file). Switch: the track follows the size tier (sm 28×16 · md 36×20 · lg 44×24), blue-on / border-strong-off; a knob glyph needs the lg tier (`size="lg"` only when the switch stands alone) — `knobIcon` (string or `{on,off}`) or `knobText` (ONE letter / ONE CJK char); `insideOn`/`insideOff` put 1–3 characters inside the pill; `labelOn`/`labelOff` put captions on both sides with the active one in text-heading; `tone="semantic"` = danger off → success on, only for states with consequence (a live integration, a kill switch); `beforeChange` async-gates the flip with a spinner in the knob, `loading` does the same under your control.
|
|
19
19
|
|
|
20
20
|
```jsx
|
|
21
21
|
<Switch label="Debug Mode" checked={on} onChange={setOn}/>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
// Shared base style for text-like controls (Input, Textarea, Combobox, DatePicker).
|
|
2
2
|
// Lives in a plain module because <script setup> cannot export values.
|
|
3
|
-
|
|
3
|
+
// Height, text and padding come from the size tier of the enclosing region (tokens/layout.css): sm 28 · md 32 · lg 40.
|
|
4
|
+
export const inputBase = { height: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: '1px solid var(--field-border, var(--border-control))', background: 'var(--bg-surface)', padding: '0 var(--control-px)', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-primary)', outline: 'none', boxSizing: 'border-box', width: '100%' }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
// Keybricks icon set — the source file uses Lucide stroke glyphs (24-grid, stroke 2,
|
|
3
3
|
// round caps/joins, currentColor) rendered at 14/16px. Path data below is from the
|
|
4
|
-
// public Lucide set, matching the glyphs used across Keyfunc UI v8
|
|
4
|
+
// public Lucide set, matching the glyphs used across Keyfunc UI v8, plus thirteen Keybricks
|
|
5
|
+
// glyphs (event … time-restatement, ai, ai-sparkles) drawn to the same rules.
|
|
5
6
|
export const ICON_PATHS = {
|
|
6
7
|
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
7
8
|
bell: '<path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>',
|
|
@@ -74,6 +75,21 @@ export const ICON_PATHS = {
|
|
|
74
75
|
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
75
76
|
"triangle-alert": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 20h16a2 2 0 0 0 1.73-2Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
76
77
|
calendar: '<path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/>',
|
|
78
|
+
// Keybricks glyphs — drawn to the core-set rules (see readme.md › Iconography)
|
|
79
|
+
event: '<path d="M17 2 22 12 17 22 12 12Z"/><path d="M7 2 2 12l5 10"/><path d="M12 2 7 12l5 10"/>',
|
|
80
|
+
consolidation: '<rect width="4" height="4" x="2" y="17" rx="1"/><rect width="4" height="4" x="10" y="17" rx="1"/><rect width="4" height="4" x="18" y="17" rx="1"/><path d="M4 17v-5a2 2 0 0 1 2-2h2"/><path d="M20 17v-5a2 2 0 0 0-2-2h-2"/><path d="M12 17v-4"/><path d="M12 3v6"/><path d="M9 6h6"/>',
|
|
81
|
+
journal: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="M2 8h20"/><path d="M12 8v12"/><path d="M6 12h2"/><path d="M16 16h2"/>',
|
|
82
|
+
reconciliation: '<path d="M3 6h8"/><path d="m15 4 2 2 4-4"/><path d="M3 10h18"/><path d="M3 18h8"/><path d="m15 16 2 2 4-4"/><path d="M3 22h18"/>',
|
|
83
|
+
"view-tree": '<rect width="4" height="4" x="4" y="2" rx="1"/><rect width="4" height="4" x="16" y="10" rx="1"/><rect width="4" height="4" x="16" y="18" rx="1"/><path d="M6 6v4a2 2 0 0 0 2 2h8"/><path d="M6 10v8a2 2 0 0 0 2 2h8"/>',
|
|
84
|
+
"drilldown": '<path d="M12 2v11"/><path d="m8 9 4 4 4-4"/><path d="M19 17H5"/><path d="M16 21H8"/>',
|
|
85
|
+
"add-below": '<path d="M11 2v10"/><path d="M6 7h10"/><path d="M22 9v5a4 4 0 0 1-4 4H2"/><path d="m6 14-4 4 4 4"/>',
|
|
86
|
+
"copy-data": '<path d="M14 22h5a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-5"/><path d="m11 16 4-4-4-4"/><path d="M3 22v-6a4 4 0 0 1 4-4h8"/>',
|
|
87
|
+
"task-execution": '<rect width="8" height="4" x="8" y="2" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M8 14h8"/><path d="m13 11 3 3-3 3"/>',
|
|
88
|
+
"time-future": '<path d="M20 6A10 10 0 1 0 22 12"/><path d="M12 7v5h5"/><path d="M21 2v5h-5"/>',
|
|
89
|
+
"time-restatement": '<path d="M2 12A10 10 0 1 0 4 6"/><path d="M12 7v5h5"/><path d="M3 2v5h5"/>',
|
|
90
|
+
// AI glyphs (2.3.0) — traced from the two reference glyphs, redrawn to core rules (stroke 2, no fill, integer endpoints, >=4 between separate elements)
|
|
91
|
+
ai: '<path d="M9 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-3"/><path d="M16 2c1.2 3 3 4.8 6 6c-3 1.2-4.8 3-6 6c-1.2-3-3-4.8-6-6c3-1.2 4.8-3 6-6Z"/><path d="M9 12c.6 1.5 1.5 2.4 3 3c-1.5.6-2.4 1.5-3 3c-.6-1.5-1.5-2.4-3-3c1.5-.6 2.4-1.5 3-3Z"/>',
|
|
92
|
+
"ai-sparkles": '<path d="M9 2c1.4 3.5 3.5 5.6 7 7c-3.5 1.4-5.6 3.5-7 7c-1.4-3.5-3.5-5.6-7-7c3.5-1.4 5.6-3.5 7-7Z"/><path d="M17 13c.8 2 2 3.2 4 4c-2 .8-3.2 2-4 4c-.8-2-2-3.2-4-4c2-.8 3.2-2 4-4Z"/>',
|
|
77
93
|
};
|
|
78
94
|
|
|
79
95
|
import { FEATHER_PATHS } from "./feather-paths.js";
|
|
@@ -6,3 +6,5 @@ Stroke icon component embedding the Lucide glyphs used across Keybricks; use for
|
|
|
6
6
|
```
|
|
7
7
|
|
|
8
8
|
Sizes in the product: 14 (dense chrome), 16 (nav/buttons), 18 (palette tiles), 22 (node tiles). Color inherits `currentColor`. Categories pair hue + tint bg: source `database/table/layers/grid-3x3`, join `text-search/shuffle`, transform `sliders-horizontal/split/arrow-right-left/menu/funnel/chart-no-axes-column/trending-up/code`, merge `database/merge`, output `table/layers/grid-3x3`.
|
|
9
|
+
|
|
10
|
+
Keybricks glyphs (drawn to the same grid rules): modules `reconciliation`, `consolidation`, `journal`, `event`, `view-tree`; actions `drilldown`, `add-below`, `copy-data`, `task-execution`, `time-future`, `time-restatement`. For hierarchy use `layers`; for change use `repeat`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
// Keybricks icon set — Lucide stroke glyphs (24-grid, stroke 2, round caps/joins, currentColor), rendered at 14/16px.
|
|
3
|
-
// Full path dictionary mirrors icons/Icon.jsx's ICON_PATHS (
|
|
3
|
+
// Full path dictionary mirrors icons/Icon.jsx's ICON_PATHS (81 entries) — see that file for the complete list.
|
|
4
4
|
import { VUE_ICON_PATHS } from './icon-paths.js'
|
|
5
5
|
import { FEATHER_PATHS } from './feather-paths.js'
|
|
6
6
|
const props = defineProps({ name: String, size: { type: Number, default: 16 }, strokeWidth: { type: Number, default: 2 }, color: String, style: Object })
|
|
@@ -70,4 +70,19 @@ export const VUE_ICON_PATHS = {
|
|
|
70
70
|
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
71
71
|
"triangle-alert": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 20h16a2 2 0 0 0 1.73-2Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
72
72
|
calendar: '<path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/>',
|
|
73
|
+
// Keybricks glyphs — drawn to the core-set rules (see readme.md › Iconography)
|
|
74
|
+
event: '<path d="M17 2 22 12 17 22 12 12Z"/><path d="M7 2 2 12l5 10"/><path d="M12 2 7 12l5 10"/>',
|
|
75
|
+
consolidation: '<rect width="4" height="4" x="2" y="17" rx="1"/><rect width="4" height="4" x="10" y="17" rx="1"/><rect width="4" height="4" x="18" y="17" rx="1"/><path d="M4 17v-5a2 2 0 0 1 2-2h2"/><path d="M20 17v-5a2 2 0 0 0-2-2h-2"/><path d="M12 17v-4"/><path d="M12 3v6"/><path d="M9 6h6"/>',
|
|
76
|
+
journal: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="M2 8h20"/><path d="M12 8v12"/><path d="M6 12h2"/><path d="M16 16h2"/>',
|
|
77
|
+
reconciliation: '<path d="M3 6h8"/><path d="m15 4 2 2 4-4"/><path d="M3 10h18"/><path d="M3 18h8"/><path d="m15 16 2 2 4-4"/><path d="M3 22h18"/>',
|
|
78
|
+
"view-tree": '<rect width="4" height="4" x="4" y="2" rx="1"/><rect width="4" height="4" x="16" y="10" rx="1"/><rect width="4" height="4" x="16" y="18" rx="1"/><path d="M6 6v4a2 2 0 0 0 2 2h8"/><path d="M6 10v8a2 2 0 0 0 2 2h8"/>',
|
|
79
|
+
"drilldown": '<path d="M12 2v11"/><path d="m8 9 4 4 4-4"/><path d="M19 17H5"/><path d="M16 21H8"/>',
|
|
80
|
+
"add-below": '<path d="M11 2v10"/><path d="M6 7h10"/><path d="M22 9v5a4 4 0 0 1-4 4H2"/><path d="m6 14-4 4 4 4"/>',
|
|
81
|
+
"copy-data": '<path d="M14 22h5a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-5"/><path d="m11 16 4-4-4-4"/><path d="M3 22v-6a4 4 0 0 1 4-4h8"/>',
|
|
82
|
+
"task-execution": '<rect width="8" height="4" x="8" y="2" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M8 14h8"/><path d="m13 11 3 3-3 3"/>',
|
|
83
|
+
"time-future": '<path d="M20 6A10 10 0 1 0 22 12"/><path d="M12 7v5h5"/><path d="M21 2v5h-5"/>',
|
|
84
|
+
"time-restatement": '<path d="M2 12A10 10 0 1 0 4 6"/><path d="M12 7v5h5"/><path d="M3 2v5h5"/>',
|
|
85
|
+
// AI glyphs (2.3.0) — traced from the two reference glyphs, redrawn to core rules (stroke 2, no fill, integer endpoints, >=4 between separate elements)
|
|
86
|
+
ai: '<path d="M9 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-3"/><path d="M16 2c1.2 3 3 4.8 6 6c-3 1.2-4.8 3-6 6c-1.2-3-3-4.8-6-6c3-1.2 4.8-3 6-6Z"/><path d="M9 12c.6 1.5 1.5 2.4 3 3c-1.5.6-2.4 1.5-3 3c-.6-1.5-1.5-2.4-3-3c1.5-.6 2.4-1.5 3-3Z"/>',
|
|
87
|
+
"ai-sparkles": '<path d="M9 2c1.4 3.5 3.5 5.6 7 7c-3.5 1.4-5.6 3.5-7 7c-1.4-3.5-3.5-5.6-7-7c3.5-1.4 5.6-3.5 7-7Z"/><path d="M17 13c.8 2 2 3.2 4 4c-2 .8-3.2 2-4 4c-.8-2-2-3.2-4-4c2-.8 3.2-2 4-4Z"/>',
|
|
73
88
|
};
|
package/components/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* Generated barrel — do not edit by hand. */
|
|
2
|
-
export * from "./brand/BrandVisual";
|
|
3
2
|
export * from "./buttons/ActionChip";
|
|
4
3
|
export * from "./buttons/Button";
|
|
5
4
|
export * from "./buttons/IconButton";
|
|
@@ -32,7 +31,6 @@ export * from "./charts/TreemapChart";
|
|
|
32
31
|
export * from "./charts/WaterfallChart";
|
|
33
32
|
export * from "./charts/WorldMap";
|
|
34
33
|
export * from "./data/CodeBlock";
|
|
35
|
-
export * from "./data/DataTable";
|
|
36
34
|
export * from "./data/DiffValue";
|
|
37
35
|
export * from "./data/FilterBar";
|
|
38
36
|
export * from "./data/KeyValue";
|
|
@@ -40,6 +38,7 @@ export * from "./data/ListRow";
|
|
|
40
38
|
export * from "./data/Pagination";
|
|
41
39
|
export * from "./data/SmartTable";
|
|
42
40
|
export * from "./data/Spreadsheet";
|
|
41
|
+
export * from "./data/Table";
|
|
43
42
|
export * from "./display/Avatar";
|
|
44
43
|
export * from "./display/Badge";
|
|
45
44
|
export * from "./display/Card";
|
package/components/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/* Generated barrel — do not edit by hand. Run: node scripts/build-index.mjs
|
|
2
2
|
Source-only package: consumers compile these .jsx files with their own toolchain. */
|
|
3
3
|
|
|
4
|
-
/* brand/ */
|
|
5
|
-
export { BrandVisual } from "./brand/BrandVisual.jsx";
|
|
6
|
-
|
|
7
4
|
/* buttons/ */
|
|
8
5
|
export { ActionChip } from "./buttons/ActionChip.jsx";
|
|
9
6
|
export { Button, ButtonGroup } from "./buttons/Button.jsx";
|
|
@@ -41,7 +38,6 @@ export { WorldMap } from "./charts/WorldMap.jsx";
|
|
|
41
38
|
|
|
42
39
|
/* data/ */
|
|
43
40
|
export { CodeBlock } from "./data/CodeBlock.jsx";
|
|
44
|
-
export { DataTable } from "./data/DataTable.jsx";
|
|
45
41
|
export { DiffValue } from "./data/DiffValue.jsx";
|
|
46
42
|
export { FilterBar } from "./data/FilterBar.jsx";
|
|
47
43
|
export { KeyValue, KeyValueList } from "./data/KeyValue.jsx";
|
|
@@ -49,6 +45,9 @@ export { ListRow } from "./data/ListRow.jsx";
|
|
|
49
45
|
export { Pagination } from "./data/Pagination.jsx";
|
|
50
46
|
export { SmartTable } from "./data/SmartTable.jsx";
|
|
51
47
|
export { Spreadsheet } from "./data/Spreadsheet.jsx";
|
|
48
|
+
export { Table } from "./data/Table.jsx";
|
|
49
|
+
/** @deprecated renamed to Table in 1.0.6 — still exported in 2.x, removed in 3.0 */
|
|
50
|
+
export { Table as DataTable } from "./data/Table.jsx";
|
|
52
51
|
|
|
53
52
|
/* display/ */
|
|
54
53
|
export { Avatar, AvatarGroup } from "./display/Avatar.jsx";
|
|
@@ -91,7 +90,7 @@ export { ICON_PATHS, Icon } from "./icons/Icon.jsx";
|
|
|
91
90
|
export { Unicon } from "./icons/Unicon.jsx";
|
|
92
91
|
|
|
93
92
|
/* navigation/ */
|
|
94
|
-
export { Breadcrumb, ThemeToggle } from "./navigation/Breadcrumb.jsx";
|
|
93
|
+
export { Breadcrumb, ThemeToggle, applyStoredTheme } from "./navigation/Breadcrumb.jsx";
|
|
95
94
|
export { DocTree } from "./navigation/DocTree.jsx";
|
|
96
95
|
export { KEYBRICKS_NAV, Sidebar } from "./navigation/Sidebar.jsx";
|
|
97
96
|
export { PageHeading, PageToolbar, TopBar } from "./navigation/TopBar.jsx";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { applyStoredTheme } from "./theme-store.js";
|
|
3
4
|
|
|
4
5
|
function Crumb({ label, last, hovered, onSelect }) {
|
|
5
6
|
const [hover, setHover] = React.useState(false);
|
|
@@ -9,7 +10,7 @@ function Crumb({ label, last, hovered, onSelect }) {
|
|
|
9
10
|
onClick={() => !last && onSelect && onSelect(label)}
|
|
10
11
|
onKeyDown={(e) => { if (!last && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onSelect && onSelect(label); } }}
|
|
11
12
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
12
|
-
style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
13
|
+
style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", color: last || on ? "var(--text-heading)" : "var(--text-secondary)", fontWeight: last ? "var(--weight-medium)" : "var(--weight-regular)", cursor: last ? "default" : "pointer", borderRadius: "var(--radius-sm)", transition: "var(--t-hover)" }}>{label}</span>
|
|
13
14
|
);
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -34,7 +35,7 @@ function Overflow({ items, onSelect }) {
|
|
|
34
35
|
onClick={() => setOpen(!open)}
|
|
35
36
|
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(!open); } if (e.key === "Escape") setOpen(false); }}
|
|
36
37
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
37
|
-
style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width:
|
|
38
|
+
style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 24, height: 24, borderRadius: "var(--radius-sm)", cursor: "pointer", color: open || hover ? "var(--text-heading)" : "var(--text-muted)", background: open ? "var(--bg-active)" : hover ? "var(--bg-hover)" : "transparent", transition: "var(--t-hover)" }}>
|
|
38
39
|
<Icon name="ellipsis" size={14} />
|
|
39
40
|
</span>
|
|
40
41
|
{open ? (
|
|
@@ -52,7 +53,7 @@ function OverflowItem({ label, depth, onSelect }) {
|
|
|
52
53
|
<span role="menuitem" tabIndex={0} onClick={() => onSelect(label)}
|
|
53
54
|
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onSelect(label); } }}
|
|
54
55
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
55
|
-
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 9px", paddingLeft: 9 + depth * 12, borderRadius: "var(--radius-sm)", cursor: "pointer", background: hover ? "var(--bg-hover)" : "transparent", fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
56
|
+
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 9px", paddingLeft: 9 + depth * 12, borderRadius: "var(--radius-sm)", cursor: "pointer", background: hover ? "var(--bg-hover)" : "transparent", fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", transition: "var(--t-hover)" }}>
|
|
56
57
|
{depth > 0 ? <span style={{ color: "var(--text-muted)", display: "inline-flex", flexShrink: 0 }}><Icon name="chevron-right" size={11} /></span> : null}
|
|
57
58
|
{label}
|
|
58
59
|
</span>
|
|
@@ -111,10 +112,5 @@ export function ThemeToggle({ size = 34, dark: darkProp, onToggle, style }) {
|
|
|
111
112
|
);
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
/** Reads the persisted theme and applies it before first paint. Call once at app start. */
|
|
115
|
-
export
|
|
116
|
-
if (typeof document === "undefined") return;
|
|
117
|
-
let t = "light";
|
|
118
|
-
try { t = localStorage.getItem("kb-theme") || "light"; } catch (e) {}
|
|
119
|
-
document.documentElement.dataset.theme = t;
|
|
120
|
-
}
|
|
115
|
+
/** Reads the persisted theme and applies it before first paint. Call once at app start. Defined in theme-store.js, shared with the Vue entry. */
|
|
116
|
+
export { applyStoredTheme };
|
|
@@ -24,10 +24,10 @@ function pickHidden(it) { open.value = false; emit('select', it) }
|
|
|
24
24
|
function onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }
|
|
25
25
|
watch(open, (v) => { if (v) document.addEventListener('mousedown', onDoc); else document.removeEventListener('mousedown', onDoc) })
|
|
26
26
|
onUnmounted(() => document.removeEventListener('mousedown', onDoc))
|
|
27
|
-
const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-
|
|
27
|
+
const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: isLast(it) || on(i, it) ? 'var(--text-heading)' : 'var(--text-secondary)', fontWeight: isLast(it) ? 'var(--weight-medium)' : 'var(--weight-regular)', cursor: isLast(it) ? 'default' : 'pointer', borderRadius: 'var(--radius-sm)', transition: 'var(--t-hover)' })
|
|
28
28
|
</script>
|
|
29
29
|
<template>
|
|
30
|
-
<nav :style="{ display: 'flex', alignItems: 'center', gap:
|
|
30
|
+
<nav :style="{ display: 'flex', alignItems: 'center', gap: '4px', ...style }">
|
|
31
31
|
<template v-for="(it, i) in head" :key="it">
|
|
32
32
|
<span v-if="i > 0" style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
33
33
|
<span :role="isLast(it) ? undefined : 'button'" :tabindex="isLast(it) ? undefined : 0"
|
|
@@ -40,14 +40,14 @@ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(
|
|
|
40
40
|
<span role="button" tabindex="0" :aria-label="`Show ${hidden.length} hidden levels`" :aria-expanded="open"
|
|
41
41
|
@click="open = !open" @keydown.enter.prevent="open = !open" @keydown.space.prevent="open = !open" @keydown.esc="open = false"
|
|
42
42
|
@mouseenter="ovHover = true" @mouseleave="ovHover = false"
|
|
43
|
-
:style="{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '
|
|
43
|
+
:style="{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '24px', height: '24px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', color: open || ovHover ? 'var(--text-heading)' : 'var(--text-muted)', background: open ? 'var(--bg-active)' : ovHover ? 'var(--bg-hover)' : 'transparent', transition: 'var(--t-hover)' }">
|
|
44
44
|
<Icon name="ellipsis" :size="14" />
|
|
45
45
|
</span>
|
|
46
46
|
<span v-if="open" style="position:absolute;top:calc(100% + 5px);left:0;z-index:var(--z-menu);min-width:168px;max-width:260px;background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;display:flex;flex-direction:column">
|
|
47
47
|
<span v-for="(h, hi) in hidden" :key="h" role="menuitem" tabindex="0"
|
|
48
48
|
@click="pickHidden(h)" @keydown.enter.prevent="pickHidden(h)" @keydown.space.prevent="pickHidden(h)"
|
|
49
49
|
@mouseenter="menuHover = hi" @mouseleave="menuHover = -1"
|
|
50
|
-
:style="{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 9px', paddingLeft: (9 + hi * 12) + 'px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: menuHover === hi ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-
|
|
50
|
+
:style="{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 9px', paddingLeft: (9 + hi * 12) + 'px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', background: menuHover === hi ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }">
|
|
51
51
|
<span v-if="hi > 0" style="color:var(--text-muted);display:inline-flex;flex-shrink:0"><Icon name="chevron-right" :size="11" /></span>{{ h }}
|
|
52
52
|
</span>
|
|
53
53
|
</span>
|
|
@@ -62,11 +62,5 @@ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(
|
|
|
62
62
|
</nav>
|
|
63
63
|
</template>
|
|
64
64
|
|
|
65
|
-
<!-- applyStoredTheme()
|
|
66
|
-
|
|
67
|
-
export function applyStoredTheme() {
|
|
68
|
-
let t = 'light'
|
|
69
|
-
try { t = localStorage.getItem('kb-theme') || 'light' } catch (e) {}
|
|
70
|
-
document.documentElement.dataset.theme = t
|
|
71
|
-
}
|
|
72
|
-
-->
|
|
65
|
+
<!-- applyStoredTheme() lives in ./theme-store.js (shared with React) and is exported from the Vue entry.
|
|
66
|
+
Call it once in main.js before mounting. -->
|
|
@@ -20,14 +20,14 @@ export function DocTree({ title = "Documents", items = [], active, onSelect, sty
|
|
|
20
20
|
{items.map((it) => {
|
|
21
21
|
const is = active === it.label;
|
|
22
22
|
return (
|
|
23
|
-
<div key={it.label} role="treeitem" tabIndex={0} onClick={() => onSelect && onSelect(it.label)}
|
|
24
|
-
style={{ height: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", boxSizing: "border-box", cursor: "pointer", background: is ? "var(--bg-active)" : "transparent" }}
|
|
23
|
+
<div key={it.label} role="treeitem" tabIndex={0} data-wash={is ? undefined : ""} onClick={() => onSelect && onSelect(it.label)}
|
|
24
|
+
style={{ height: "var(--h-tree-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", boxSizing: "border-box", cursor: "pointer", background: is ? "var(--bg-active)" : "var(--wash, transparent)", transition: "var(--t-hover)" }}
|
|
25
25
|
onMouseEnter={(e) => { if (!is) e.currentTarget.style.background = "var(--bg-hover)"; }}
|
|
26
26
|
onMouseLeave={(e) => { if (!is) e.currentTarget.style.background = "transparent"; }}>
|
|
27
27
|
<span style={{ color: it.kind === "folder" ? "var(--text-muted)" : docHues[it.hue || "gray"], display: "inline-flex" }}>
|
|
28
28
|
<Icon name={it.icon || (it.kind === "folder" ? "folder" : "file-text")} size={16} />
|
|
29
29
|
</span>
|
|
30
|
-
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
30
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{it.label}</span>
|
|
31
31
|
</div>
|
|
32
32
|
);
|
|
33
33
|
})}
|
|
@@ -15,10 +15,10 @@ const emit = defineEmits(['select'])
|
|
|
15
15
|
</div>
|
|
16
16
|
<div style="padding:12px 16px 8px;border-bottom:1px solid var(--border-subtle)"><SearchInput bare placeholder="Search..." style="gap:8px" /></div>
|
|
17
17
|
<div style="padding:8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto">
|
|
18
|
-
<div v-for="it in items" :key="it.label" @click="emit('select', it.label)"
|
|
19
|
-
:style="{ height: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap:
|
|
18
|
+
<div v-for="it in items" :key="it.label" role="treeitem" tabindex="0" @click="emit('select', it.label)" @keydown.enter="emit('select', it.label)"
|
|
19
|
+
:style="{ height: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', boxSizing: 'border-box', cursor: 'pointer', background: active === it.label ? 'var(--bg-active)' : 'var(--wash, transparent)', transition: 'var(--t-hover)' }" :data-wash="active === it.label ? undefined : ''">
|
|
20
20
|
<span :style="{ color: it.kind === 'folder' ? 'var(--text-muted)' : docHues[it.hue || 'gray'], display: 'inline-flex' }"><Icon :name="it.icon || (it.kind === 'folder' ? 'folder' : 'file-text')" :size="16" /></span>
|
|
21
|
-
<span style="font-family:var(--font-sans);font-size:var(--text-
|
|
21
|
+
<span style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ it.label }}</span>
|
|
22
22
|
</div>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
defineProps({ title: String, subtitle: String, style: Object })
|
|
3
3
|
</script>
|
|
4
4
|
<template>
|
|
5
|
-
<div :style="{ display: 'flex', flexDirection: 'column', gap:
|
|
5
|
+
<div :style="{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }">
|
|
6
6
|
<span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-lg);line-height:var(--leading-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)">{{ title }}</span>
|
|
7
7
|
<span v-if="subtitle" 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)">{{ subtitle }}</span>
|
|
8
8
|
</div>
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import IconButton from '../buttons/IconButton.vue'
|
|
3
|
-
|
|
3
|
+
// Page toolbar under the top bar: back · title · status, actions on the right. A size region (default inherit → md).
|
|
4
|
+
defineProps({ title: String, status: { type: String, default: '• Editing' }, showBack: Boolean, size: String, style: Object })
|
|
4
5
|
const emit = defineEmits(['back'])
|
|
5
6
|
</script>
|
|
6
7
|
<template>
|
|
7
|
-
<div :style="{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0
|
|
8
|
-
<div style="display:flex;align-items:center;gap:
|
|
9
|
-
<IconButton v-if="showBack" icon="chevron-left"
|
|
8
|
+
<div :data-size="size" :style="{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 var(--edge-pad)', boxSizing: 'border-box', flexShrink: 0, ...style }">
|
|
9
|
+
<div style="display:flex;align-items:center;gap:var(--control-gap)">
|
|
10
|
+
<IconButton v-if="showBack" icon="chevron-left" label="Back" @click="emit('back')" />
|
|
10
11
|
<span style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)">{{ title }}</span>
|
|
11
|
-
<span v-if="status" style="font-family:var(--font-sans);font-size:var(--text
|
|
12
|
+
<span v-if="status" style="font-family:var(--font-sans);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking);color:var(--text-muted);margin-left:6px">{{ status }}</span>
|
|
12
13
|
</div>
|
|
13
|
-
<div style="display:flex;align-items:center;gap:
|
|
14
|
+
<div style="display:flex;align-items:center;gap:var(--control-gap)"><slot name="actions" /></div>
|
|
14
15
|
</div>
|
|
15
16
|
</template>
|