@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
/**
|
|
3
|
-
* Switch — 36×20
|
|
3
|
+
* Switch — pill track at the region's tier (sm 28×16 · md 36×20 · lg 44×24), knob 4px smaller, 160ms ease-out slide. Controlled.
|
|
4
4
|
* Variants: knob glyph (icon or one character), text inside the pill, captions beside it,
|
|
5
5
|
* a semantic red→green tone, and an async gate that shows a spinner in the knob.
|
|
6
6
|
*/
|
|
@@ -8,7 +8,7 @@ export interface SwitchProps {
|
|
|
8
8
|
checked?: boolean;
|
|
9
9
|
onChange?(next: boolean): void;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
-
/** sm 28×16 · md 36×20
|
|
11
|
+
/** Size tier: sm 28×16 · md 36×20 · lg 44×24. Omit to inherit the enclosing region's tier (the default); lg is roomy enough for a knob glyph */
|
|
12
12
|
size?: "sm" | "md" | "lg";
|
|
13
13
|
/** primary = action blue on / border-strong off · semantic = success on / danger off */
|
|
14
14
|
tone?: "primary" | "semantic";
|
|
@@ -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)";
|
|
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: GLYPH, 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: GLYPH, 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,33 @@
|
|
|
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
15
|
insideOn: String, insideOff: String, 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)'
|
|
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
23
|
const hasInside = computed(() => props.insideOn != null || props.insideOff != null)
|
|
24
|
-
|
|
24
|
+
// inside text needs a wider track so the knob never covers the word: text runs at half the track height, ~.68em per character
|
|
25
|
+
const width = computed(() => { if (!hasInside.value) return W; const n = Math.max((props.insideOn || '').length, (props.insideOff || '').length); return `max(${W}, calc(${H} * ${(1 + n * .34).toFixed(2)} + 6px))` })
|
|
25
26
|
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
27
|
const pick = (v) => v && typeof v === 'object' ? (props.checked ? v.on : v.off) : v
|
|
27
28
|
const glyphIcon = computed(() => pick(props.knobIcon))
|
|
28
29
|
const glyphText = computed(() => { const t = pick(props.knobText); return t ? String(t).slice(0, 1) : '' })
|
|
29
|
-
const knobX = computed(() => props.checked ?
|
|
30
|
+
const knobX = computed(() => props.checked ? `calc(100% - ${H} + 2px)` : '2px')
|
|
30
31
|
async function flip() {
|
|
31
32
|
if (inert.value) return
|
|
32
33
|
const next = !props.checked
|
|
@@ -39,23 +40,23 @@ async function flip() {
|
|
|
39
40
|
}
|
|
40
41
|
emit('update:checked', next); emit('change', next)
|
|
41
42
|
}
|
|
42
|
-
const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]:
|
|
43
|
-
const cap = (active) => ({ fontSize:
|
|
43
|
+
const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize: GLYPH, 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 })
|
|
44
|
+
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
45
|
</script>
|
|
45
46
|
<template>
|
|
46
|
-
<span :style="{ display: 'inline-flex', alignItems: 'center', gap: '
|
|
47
|
+
<span :data-size="size" data-sw="" :style="{ display: 'inline-flex', alignItems: 'center', gap: 'var(--control-gap)', opacity: disabled ? .5 : 1, ...style }">
|
|
47
48
|
<span v-if="labelOff != null" :style="cap(!checked)" @click="flip">{{ labelOff }}</span>
|
|
48
49
|
<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
|
+
: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' }">
|
|
50
51
|
<span v-if="hasInside" :style="insideStyle('left', true)">{{ insideOn }}</span>
|
|
51
52
|
<span v-if="hasInside" :style="insideStyle('right', false)">{{ insideOff }}</span>
|
|
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
|
+
<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: GLYPH, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
|
|
54
|
+
<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>
|
|
55
|
+
<Icon v-else-if="glyphIcon" :name="glyphIcon" :size="10" :stroke-width="2.5" :style="{ width: GLYPH, height: GLYPH }" />
|
|
55
56
|
<span v-else-if="glyphText">{{ glyphText }}</span>
|
|
56
57
|
</span>
|
|
57
58
|
</span>
|
|
58
59
|
<span v-if="labelOn != null" :style="cap(checked)" @click="flip">{{ labelOn }}</span>
|
|
59
|
-
<span v-if="label != null && labelOn == null && labelOff == null" :style="{ fontSize:
|
|
60
|
+
<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
61
|
</span>
|
|
61
62
|
</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>
|
|
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
18
|
<span v-if="showCount" :style="{ position: 'absolute', right: '10px', bottom: '7px', fontFamily: 'var(--font-mono)', 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 24 · md 32 · lg 36.
|
|
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%' }
|
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";
|
|
@@ -9,7 +9,7 @@ function Crumb({ label, last, hovered, onSelect }) {
|
|
|
9
9
|
onClick={() => !last && onSelect && onSelect(label)}
|
|
10
10
|
onKeyDown={(e) => { if (!last && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onSelect && onSelect(label); } }}
|
|
11
11
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
12
|
-
style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
12
|
+
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
13
|
);
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -34,7 +34,7 @@ function Overflow({ items, onSelect }) {
|
|
|
34
34
|
onClick={() => setOpen(!open)}
|
|
35
35
|
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(!open); } if (e.key === "Escape") setOpen(false); }}
|
|
36
36
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
37
|
-
style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width:
|
|
37
|
+
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
38
|
<Icon name="ellipsis" size={14} />
|
|
39
39
|
</span>
|
|
40
40
|
{open ? (
|
|
@@ -52,7 +52,7 @@ function OverflowItem({ label, depth, onSelect }) {
|
|
|
52
52
|
<span role="menuitem" tabIndex={0} onClick={() => onSelect(label)}
|
|
53
53
|
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onSelect(label); } }}
|
|
54
54
|
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-
|
|
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-base)", lineHeight: "var(--leading-base)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", transition: "var(--t-hover)" }}>
|
|
56
56
|
{depth > 0 ? <span style={{ color: "var(--text-muted)", display: "inline-flex", flexShrink: 0 }}><Icon name="chevron-right" size={11} /></span> : null}
|
|
57
57
|
{label}
|
|
58
58
|
</span>
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -30,7 +30,7 @@ export function Sidebar({ items = [], footerItems = [{ icon: "circle-help", labe
|
|
|
30
30
|
{onToggleCollapse ? <NavItem icon="panel-left" label="Collapse" collapsed={collapsed} onClick={onToggleCollapse} /> : null}
|
|
31
31
|
{items.map((it) => it.children ? (
|
|
32
32
|
<div key={it.label}>
|
|
33
|
-
<div onClick={() => toggleSection(it.label)} style={{ height: "var(--h-subnav-item)", display: "flex", alignItems: "center", gap: 10, padding: collapsed ? "6px 10px" : "6px 10px", justifyContent: collapsed ? "center" : "flex-start", cursor: "pointer", color: "var(--text-body)", borderRadius: "var(--radius-md)", boxSizing: "border-box" }}>
|
|
33
|
+
<div role="button" tabIndex={0} data-wash="" onClick={() => toggleSection(it.label)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggleSection(it.label); } }} style={{ background: "var(--wash, transparent)", transition: "var(--t-hover)", height: "var(--h-subnav-item)", display: "flex", alignItems: "center", gap: 10, padding: collapsed ? "6px 10px" : "6px 10px", justifyContent: collapsed ? "center" : "flex-start", cursor: "pointer", color: "var(--text-body)", borderRadius: "var(--radius-md)", boxSizing: "border-box" }}>
|
|
34
34
|
<Icon name={it.icon} size={16} />
|
|
35
35
|
{!collapsed ? <><span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", letterSpacing: "var(--tracking-base)", flex: 1 }}>{it.label}</span><Icon name={openSections.includes(it.label) ? "chevron-up" : "chevron-down"} size={14} /></> : null}
|
|
36
36
|
</div>
|
|
@@ -16,11 +16,11 @@ const props = defineProps({
|
|
|
16
16
|
const emit = defineEmits(['select', 'toggleCollapse'])
|
|
17
17
|
const openSections = ref(props.items.filter(i => i.children && (i.open || i.children.some(c => c.label === props.active))).map(i => i.label))
|
|
18
18
|
function toggleSection(l) { openSections.value = openSections.value.includes(l) ? openSections.value.filter(x => x !== l) : [...openSections.value, l] }
|
|
19
|
-
function itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-item)' : 'var(--h-nav-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap:
|
|
19
|
+
function itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-item)' : 'var(--h-nav-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '10px', padding: indent ? '6px 10px' : '8px 10px', boxSizing: 'border-box', cursor: 'pointer', background: active ? 'var(--bg-active)' : 'transparent', color: active ? 'var(--text-heading)' : 'var(--text-body)', transition: 'var(--t-hover)' } }
|
|
20
20
|
</script>
|
|
21
21
|
<template>
|
|
22
22
|
<aside :style="{ width: collapsed ? 'var(--rail-w)' : 'var(--sidebar-w)', height, background: 'var(--bg-surface)', borderRight: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, transition: 'width .15s ease', ...style }">
|
|
23
|
-
<div :style="{ height: 'var(--topbar-h)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', gap:
|
|
23
|
+
<div :style="{ height: 'var(--topbar-h)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', gap: '10px', padding: '0 16px', boxSizing: 'border-box', justifyContent: collapsed ? 'center' : 'flex-start', flexShrink: 0 }">
|
|
24
24
|
<img :src="logo" alt="" style="width:24px;height:24px;object-fit:contain" />
|
|
25
25
|
<span v-if="!collapsed" style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--leading-lg);letter-spacing:-.609px;color:var(--text-primary)">{{ brand }}</span>
|
|
26
26
|
</div>
|
|
@@ -28,7 +28,7 @@ function itemStyle(active, indent) { return { height: indent ? 'var(--h-subnav-i
|
|
|
28
28
|
<div v-if="canCollapse" role="button" tabindex="0" @click="emit('toggleCollapse')" :style="itemStyle(false, false)"><Icon name="panel-left" :size="16" /><span v-if="!collapsed" 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);white-space:nowrap;flex:1">Collapse</span></div>
|
|
29
29
|
<template v-for="it in items" :key="it.label">
|
|
30
30
|
<div v-if="it.children">
|
|
31
|
-
<div role="button" tabindex="0" @click="toggleSection(it.label)" :style="{ height: 'var(--h-subnav-item)', display: 'flex', alignItems: 'center', gap:
|
|
31
|
+
<div role="button" tabindex="0" data-wash="" @click="toggleSection(it.label)" @keydown.enter.prevent="toggleSection(it.label)" @keydown.space.prevent="toggleSection(it.label)" :style="{ background: 'var(--wash, transparent)', transition: 'var(--t-hover)', height: 'var(--h-subnav-item)', display: 'flex', alignItems: 'center', gap: '10px', padding: '6px 10px', justifyContent: collapsed ? 'center' : 'flex-start', cursor: 'pointer', color: 'var(--text-body)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box' }">
|
|
32
32
|
<Icon :name="it.icon" :size="16" />
|
|
33
33
|
<template v-if="!collapsed"><span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);flex:1">{{ it.label }}</span><Icon :name="openSections.includes(it.label) ? 'chevron-up' : 'chevron-down'" :size="14" /></template>
|
|
34
34
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { px } from '../px.js'
|
|
2
3
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
3
4
|
import Icon from '../icons/Icon.vue'
|
|
4
5
|
const props = defineProps({ size: { type: Number, default: 34 }, dark: { type: Boolean, default: null }, style: Object })
|
|
@@ -12,7 +13,7 @@ function toggle() { if (props.dark != null) emit('update:dark', !dark.value); el
|
|
|
12
13
|
</script>
|
|
13
14
|
<template>
|
|
14
15
|
<button type="button" :aria-label="dark ? 'Switch to light mode' : 'Switch to dark mode'" @click="toggle" @mouseenter="hover = true" @mouseleave="hover = false"
|
|
15
|
-
:style="{ width: size, height: size, borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: hover ? 'var(--bg-hover)' : 'transparent', color: 'var(--text-body)', padding: 0, transition: 'var(--t-hover)', ...style }">
|
|
16
|
+
:style="{ width: px(size), height: px(size), borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: hover ? 'var(--bg-hover)' : 'transparent', color: 'var(--text-body)', padding: 0, transition: 'var(--t-hover)', ...style }">
|
|
16
17
|
<Icon :name="dark ? 'sparkles' : 'moon'" :size="18" />
|
|
17
18
|
</button>
|
|
18
19
|
</template>
|
|
@@ -15,10 +15,12 @@ export interface PageToolbarProps {
|
|
|
15
15
|
status?: string;
|
|
16
16
|
actions?: React.ReactNode;
|
|
17
17
|
onBack?: () => void;
|
|
18
|
+
/** Size tier for the toolbar's controls. Omit to inherit (the default). */
|
|
19
|
+
size?: "sm" | "md" | "lg";
|
|
18
20
|
style?: React.CSSProperties;
|
|
19
21
|
}
|
|
20
22
|
export declare function PageToolbar(props: PageToolbarProps): JSX.Element;
|
|
21
|
-
/**
|
|
23
|
+
/** Page title (--text-lg 20px/500) + muted subtitle. */
|
|
22
24
|
export interface PageHeadingProps {
|
|
23
25
|
title?: string;
|
|
24
26
|
subtitle?: string;
|
|
@@ -20,15 +20,15 @@ export function TopBar({ placeholder = "Search documents, workflows, data...", a
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/** Editor toolbar: back chevron, 14px/600 title, "• Editing" status, right action slot. */
|
|
23
|
-
export function PageToolbar({ title, status = "• Editing", actions, onBack, style }) {
|
|
23
|
+
export function PageToolbar({ title, status = "• Editing", actions, onBack, size, style }) {
|
|
24
24
|
return (
|
|
25
|
-
<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
|
|
26
|
-
<div style={{ display: "flex", alignItems: "center", gap:
|
|
27
|
-
{onBack ? <IconButton icon="chevron-left"
|
|
25
|
+
<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 }}>
|
|
26
|
+
<div style={{ display: "flex", alignItems: "center", gap: "var(--control-gap)" }}>
|
|
27
|
+
{onBack ? <IconButton icon="chevron-left" onClick={onBack} label="Back" /> : null}
|
|
28
28
|
<span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-md)", lineHeight: "var(--leading-md)", letterSpacing: "var(--tracking-md)", color: "var(--text-heading)" }}>{title}</span>
|
|
29
|
-
{status ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text
|
|
29
|
+
{status ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-muted)", marginLeft: 6 }}>{status}</span> : null}
|
|
30
30
|
</div>
|
|
31
|
-
<div style={{ display: "flex", alignItems: "center", gap:
|
|
31
|
+
<div style={{ display: "flex", alignItems: "center", gap: "var(--control-gap)" }}>{actions}</div>
|
|
32
32
|
</div>
|
|
33
33
|
);
|
|
34
34
|
}
|
|
@@ -153,7 +153,7 @@ function Row({ item, state, cfg, api }) {
|
|
|
153
153
|
{chevron}
|
|
154
154
|
{cfg.checkable && node.checkable !== false ? (
|
|
155
155
|
<span onClick={(e) => e.stopPropagation()} style={{ display: "inline-flex", flexShrink: 0 }}>
|
|
156
|
-
<Checkbox
|
|
156
|
+
<Checkbox checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} onChange={() => api.check(key)} />
|
|
157
157
|
</span>
|
|
158
158
|
) : null}
|
|
159
159
|
{cfg.showIcon ? (
|
|
@@ -161,11 +161,11 @@ function Row({ item, state, cfg, api }) {
|
|
|
161
161
|
<Icon name={node.icon || (branch ? (open ? "folder-open" : "folder") : "file-text")} size={15} />
|
|
162
162
|
</span>
|
|
163
163
|
) : null}
|
|
164
|
-
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
164
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", fontWeight: selected ? "var(--weight-medium)" : "var(--weight-regular)", color: disabled ? "var(--text-muted)" : "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
|
|
165
165
|
<Highlight text={titleOf(node)} query={cfg.searchValue} />
|
|
166
166
|
</span>
|
|
167
|
-
{node.tag ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
168
|
-
{node.count != null ? <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-
|
|
167
|
+
{node.tag ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: 1, padding: "2px 5px", borderRadius: "var(--radius-sm)", background: "var(--bg-inset)", color: "var(--text-secondary)", flexShrink: 0 }}>{node.tag}</span> : null}
|
|
168
|
+
{node.count != null ? <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--text-muted)", paddingLeft: 8 }}>{node.count}</span> : null}
|
|
169
169
|
{cfg.rowActions && !disabled ? <span style={{ marginLeft: node.count != null ? 6 : "auto", display: "flex", gap: 2, opacity: hover ? 1 : 0, transition: "var(--t-hover)" }} onClick={(e) => e.stopPropagation()}>{cfg.rowActions(node)}</span> : null}
|
|
170
170
|
</div>
|
|
171
171
|
{open && loading && !kids.length ? (
|
|
@@ -340,7 +340,7 @@ export function Tree({
|
|
|
340
340
|
<div role="tree" aria-multiselectable={multiple || undefined} style={{ display: "flex", flexDirection: "column", gap: showLine ? 0 : 1, ...style }}>
|
|
341
341
|
<style>{"@keyframes kb-tree-spin{to{transform:rotate(360deg)}}"}</style>
|
|
342
342
|
{rows.length === 0 ? (
|
|
343
|
-
<div style={{ padding: "10px 8px", fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
343
|
+
<div style={{ padding: "10px 8px", fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", color: "var(--text-muted)" }}>No matching items</div>
|
|
344
344
|
) : rows.map((r) => <Row key={r.key} item={r} state={state} cfg={cfg} api={api} />)}
|
|
345
345
|
</div>
|
|
346
346
|
);
|