@keyfunc/keybricks-ds 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPLOY.md +204 -0
- package/DESIGN.md +269 -0
- package/INTEGRATION.md +417 -0
- package/SKILL.md +32 -0
- package/_adherence.oxlintrc.json +1469 -0
- package/_scripts/build-index.mjs +80 -0
- package/_scripts/build-tokens.mjs +135 -0
- package/assets/icons/icon-index.js +1 -0
- package/assets/icons/icon-index.json +1 -0
- package/assets/icons/unicons-index.js +1 -0
- package/assets/icons/unicons-index.json +1 -0
- package/assets/keybricks-logo.png +0 -0
- package/components/_cards/accordion.card.html +32 -0
- package/components/_cards/action-chip.card.html +22 -0
- package/components/_cards/activity.card.html +34 -0
- package/components/_cards/alert.card.html +32 -0
- package/components/_cards/app-chrome.card.html +36 -0
- package/components/_cards/banner.card.html +21 -0
- package/components/_cards/brand-visual.card.html +43 -0
- package/components/_cards/breadcrumb.card.html +23 -0
- package/components/_cards/button-1-base.card.html +39 -0
- package/components/_cards/button-2-states.card.html +56 -0
- package/components/_cards/button-3-sizes-icons.card.html +32 -0
- package/components/_cards/button-4-group.card.html +47 -0
- package/components/_cards/canvas-zoom-controls.card.html +19 -0
- package/components/_cards/cards-indicators.card.html +59 -0
- package/components/_cards/charts-1-sparkline.card.html +55 -0
- package/components/_cards/charts-10-gauge.card.html +31 -0
- package/components/_cards/charts-11-funnel.card.html +28 -0
- package/components/_cards/charts-12-treemap.card.html +23 -0
- package/components/_cards/charts-14-radar.card.html +31 -0
- package/components/_cards/charts-15-distribution.card.html +32 -0
- package/components/_cards/charts-16-pareto.card.html +25 -0
- package/components/_cards/charts-17-candlestick.card.html +34 -0
- package/components/_cards/charts-18-sankey.card.html +29 -0
- package/components/_cards/charts-19-chord.card.html +31 -0
- package/components/_cards/charts-2-line.card.html +46 -0
- package/components/_cards/charts-20-sunburst.card.html +38 -0
- package/components/_cards/charts-21-worldmap.card.html +33 -0
- package/components/_cards/charts-22-gantt.card.html +37 -0
- package/components/_cards/charts-23-bar3d.card.html +26 -0
- package/components/_cards/charts-24-pie3d.card.html +32 -0
- package/components/_cards/charts-25-surface3d.card.html +24 -0
- package/components/_cards/charts-26-scatter3d.card.html +24 -0
- package/components/_cards/charts-27-ribbon3d.card.html +23 -0
- package/components/_cards/charts-3-bar.card.html +71 -0
- package/components/_cards/charts-4-barlist.card.html +30 -0
- package/components/_cards/charts-5-donut.card.html +35 -0
- package/components/_cards/charts-6-scatter.card.html +26 -0
- package/components/_cards/charts-7-heatmap.card.html +28 -0
- package/components/_cards/charts-8-waterfall.card.html +25 -0
- package/components/_cards/charts-9-bullet.card.html +31 -0
- package/components/_cards/charts-99-fullpage.card.html +194 -0
- package/components/_cards/checkbox.card.html +34 -0
- package/components/_cards/codeblock.card.html +19 -0
- package/components/_cards/combobox.card.html +25 -0
- package/components/_cards/context-menu.card.html +21 -0
- package/components/_cards/datatable.card.html +48 -0
- package/components/_cards/date-picker.card.html +31 -0
- package/components/_cards/diffvalue.card.html +21 -0
- package/components/_cards/divider.card.html +24 -0
- package/components/_cards/drawer.card.html +30 -0
- package/components/_cards/dropdown-menu.card.html +38 -0
- package/components/_cards/dropzone.card.html +22 -0
- package/components/_cards/empty-state.card.html +29 -0
- package/components/_cards/filter-bar.card.html +34 -0
- package/components/_cards/icon-button.card.html +27 -0
- package/components/_cards/icons-1-core.card.html +49 -0
- package/components/_cards/icons-2-feather.card.html +39 -0
- package/components/_cards/icons-3-unicons.card.html +44 -0
- package/components/_cards/input.card.html +25 -0
- package/components/_cards/kbd.card.html +20 -0
- package/components/_cards/keyvalue.card.html +23 -0
- package/components/_cards/listrow.card.html +30 -0
- package/components/_cards/loading.card.html +33 -0
- package/components/_cards/log-viewer.card.html +23 -0
- package/components/_cards/modal.card.html +35 -0
- package/components/_cards/multi-select.card.html +26 -0
- package/components/_cards/number-stepper.card.html +24 -0
- package/components/_cards/pagination.card.html +25 -0
- package/components/_cards/people.card.html +46 -0
- package/components/_cards/pipeline-node.card.html +76 -0
- package/components/_cards/pipeline-palette.card.html +54 -0
- package/components/_cards/search.card.html +23 -0
- package/components/_cards/select.card.html +23 -0
- package/components/_cards/slider.card.html +25 -0
- package/components/_cards/smart-table.card.html +37 -0
- package/components/_cards/spreadsheet.card.html +31 -0
- package/components/_cards/stepper.card.html +25 -0
- package/components/_cards/tabs.card.html +25 -0
- package/components/_cards/tag.card.html +26 -0
- package/components/_cards/textarea.card.html +27 -0
- package/components/_cards/timeline.card.html +32 -0
- package/components/_cards/toast.card.html +33 -0
- package/components/_cards/toggles.card.html +68 -0
- package/components/_cards/tooltip.card.html +22 -0
- package/components/_cards/tree.card.html +150 -0
- package/components/_shared/frame-theme.js +16 -0
- package/components/_shared/src-brand.js +1 -0
- package/components/_shared/src-buttons.js +1 -0
- package/components/_shared/src-charts.js +1 -0
- package/components/_shared/src-data.js +1 -0
- package/components/_shared/src-display.js +1 -0
- package/components/_shared/src-feedback.js +1 -0
- package/components/_shared/src-forms.js +1 -0
- package/components/_shared/src-icons.js +1 -0
- package/components/_shared/src-navigation.js +1 -0
- package/components/_shared/src-pipeline.js +1 -0
- package/components/_shared/src-tabs.js +1 -0
- package/components/_shared/svg-download.js +19 -0
- package/components/_shared/view-source.js +185 -0
- package/components/_vue/index.mjs +114 -0
- package/components/brand/BrandVisual.d.ts +17 -0
- package/components/brand/BrandVisual.jsx +29 -0
- package/components/brand/BrandVisual.vue +34 -0
- package/components/brand/_dir.card.html +13 -0
- package/components/brand/brandMotifs.js +210 -0
- package/components/buttons/ActionChip.d.ts +10 -0
- package/components/buttons/ActionChip.jsx +20 -0
- package/components/buttons/ActionChip.vue +18 -0
- package/components/buttons/Button.d.ts +40 -0
- package/components/buttons/Button.jsx +101 -0
- package/components/buttons/Button.vue +87 -0
- package/components/buttons/IconButton.d.ts +26 -0
- package/components/buttons/IconButton.jsx +28 -0
- package/components/buttons/IconButton.vue +21 -0
- package/components/buttons/_dir.card.html +13 -0
- package/components/buttons/buttons.prompt.md +19 -0
- package/components/charts/Bar3D.d.ts +15 -0
- package/components/charts/Bar3D.jsx +93 -0
- package/components/charts/Bar3D.vue +79 -0
- package/components/charts/BarChart.d.ts +23 -0
- package/components/charts/BarChart.jsx +135 -0
- package/components/charts/BarChart.vue +139 -0
- package/components/charts/BarList.d.ts +13 -0
- package/components/charts/BarList.jsx +31 -0
- package/components/charts/BarList.vue +25 -0
- package/components/charts/BoxPlot.d.ts +18 -0
- package/components/charts/BoxPlot.jsx +62 -0
- package/components/charts/BoxPlot.vue +58 -0
- package/components/charts/BulletChart.d.ts +12 -0
- package/components/charts/BulletChart.jsx +41 -0
- package/components/charts/BulletChart.vue +33 -0
- package/components/charts/CandlestickChart.d.ts +15 -0
- package/components/charts/CandlestickChart.jsx +63 -0
- package/components/charts/CandlestickChart.vue +62 -0
- package/components/charts/ChartKit.d.ts +8 -0
- package/components/charts/ChartKit.jsx +76 -0
- package/components/charts/ChartKit.vue +47 -0
- package/components/charts/ChordChart.d.ts +13 -0
- package/components/charts/ChordChart.jsx +55 -0
- package/components/charts/ChordChart.vue +54 -0
- package/components/charts/DonutChart.d.ts +15 -0
- package/components/charts/DonutChart.jsx +73 -0
- package/components/charts/DonutChart.vue +61 -0
- package/components/charts/FunnelChart.d.ts +11 -0
- package/components/charts/FunnelChart.jsx +37 -0
- package/components/charts/FunnelChart.vue +29 -0
- package/components/charts/GanttChart.d.ts +20 -0
- package/components/charts/GanttChart.jsx +80 -0
- package/components/charts/GanttChart.vue +60 -0
- package/components/charts/GaugeChart.d.ts +13 -0
- package/components/charts/GaugeChart.jsx +33 -0
- package/components/charts/GaugeChart.vue +29 -0
- package/components/charts/HeatmapChart.d.ts +13 -0
- package/components/charts/HeatmapChart.jsx +62 -0
- package/components/charts/HeatmapChart.vue +38 -0
- package/components/charts/Histogram.d.ts +16 -0
- package/components/charts/Histogram.jsx +56 -0
- package/components/charts/Histogram.vue +53 -0
- package/components/charts/LineChart.d.ts +17 -0
- package/components/charts/LineChart.jsx +84 -0
- package/components/charts/LineChart.vue +86 -0
- package/components/charts/ParetoChart.d.ts +13 -0
- package/components/charts/ParetoChart.jsx +54 -0
- package/components/charts/ParetoChart.vue +55 -0
- package/components/charts/Pie3D.d.ts +17 -0
- package/components/charts/Pie3D.jsx +145 -0
- package/components/charts/Pie3D.vue +115 -0
- package/components/charts/RadarChart.d.ts +14 -0
- package/components/charts/RadarChart.jsx +79 -0
- package/components/charts/RadarChart.vue +73 -0
- package/components/charts/Ribbon3D.d.ts +13 -0
- package/components/charts/Ribbon3D.jsx +89 -0
- package/components/charts/Ribbon3D.vue +79 -0
- package/components/charts/SankeyChart.d.ts +15 -0
- package/components/charts/SankeyChart.jsx +59 -0
- package/components/charts/SankeyChart.vue +53 -0
- package/components/charts/Scatter3D.d.ts +12 -0
- package/components/charts/Scatter3D.jsx +69 -0
- package/components/charts/Scatter3D.vue +59 -0
- package/components/charts/ScatterChart.d.ts +12 -0
- package/components/charts/ScatterChart.jsx +53 -0
- package/components/charts/ScatterChart.vue +57 -0
- package/components/charts/Sparkline.d.ts +19 -0
- package/components/charts/Sparkline.jsx +63 -0
- package/components/charts/Sparkline.vue +54 -0
- package/components/charts/SunburstChart.d.ts +12 -0
- package/components/charts/SunburstChart.jsx +61 -0
- package/components/charts/SunburstChart.vue +60 -0
- package/components/charts/Surface3D.d.ts +13 -0
- package/components/charts/Surface3D.jsx +70 -0
- package/components/charts/Surface3D.vue +63 -0
- package/components/charts/TreemapChart.d.ts +11 -0
- package/components/charts/TreemapChart.jsx +35 -0
- package/components/charts/TreemapChart.vue +27 -0
- package/components/charts/WaterfallChart.d.ts +11 -0
- package/components/charts/WaterfallChart.jsx +59 -0
- package/components/charts/WaterfallChart.vue +46 -0
- package/components/charts/WorldMap.d.ts +21 -0
- package/components/charts/WorldMap.jsx +107 -0
- package/components/charts/WorldMap.vue +73 -0
- package/components/charts/_dir.card.html +7 -0
- package/components/charts/chart-3d.js +84 -0
- package/components/charts/chart-geometry.js +223 -0
- package/components/charts/charts.prompt.md +24 -0
- package/components/data/CodeBlock.d.ts +9 -0
- package/components/data/CodeBlock.jsx +19 -0
- package/components/data/CodeBlock.vue +22 -0
- package/components/data/DataTable.d.ts +28 -0
- package/components/data/DataTable.jsx +32 -0
- package/components/data/DataTable.vue +25 -0
- package/components/data/DiffValue.d.ts +11 -0
- package/components/data/DiffValue.jsx +15 -0
- package/components/data/DiffValue.vue +13 -0
- package/components/data/FilterBar.d.ts +16 -0
- package/components/data/FilterBar.jsx +22 -0
- package/components/data/FilterBar.vue +23 -0
- package/components/data/KeyValue.d.ts +16 -0
- package/components/data/KeyValue.jsx +25 -0
- package/components/data/KeyValue.vue +26 -0
- package/components/data/ListRow.d.ts +14 -0
- package/components/data/ListRow.jsx +25 -0
- package/components/data/ListRow.vue +25 -0
- package/components/data/Pagination.d.ts +12 -0
- package/components/data/Pagination.jsx +27 -0
- package/components/data/Pagination.vue +24 -0
- package/components/data/SmartTable.d.ts +30 -0
- package/components/data/SmartTable.jsx +73 -0
- package/components/data/SmartTable.vue +74 -0
- package/components/data/Spreadsheet.d.ts +33 -0
- package/components/data/Spreadsheet.jsx +110 -0
- package/components/data/Spreadsheet.vue +95 -0
- package/components/data/_dir.card.html +13 -0
- package/components/data/data.prompt.md +33 -0
- package/components/display/Avatar.d.ts +21 -0
- package/components/display/Avatar.jsx +31 -0
- package/components/display/Avatar.vue +34 -0
- package/components/display/Badge.d.ts +27 -0
- package/components/display/Badge.jsx +40 -0
- package/components/display/Badge.vue +47 -0
- package/components/display/Card.d.ts +27 -0
- package/components/display/Card.jsx +27 -0
- package/components/display/Card.vue +28 -0
- package/components/display/Divider.d.ts +8 -0
- package/components/display/Divider.jsx +14 -0
- package/components/display/Divider.vue +13 -0
- package/components/display/Kbd.d.ts +7 -0
- package/components/display/Kbd.jsx +12 -0
- package/components/display/Kbd.vue +9 -0
- package/components/display/ProgressBar.d.ts +42 -0
- package/components/display/ProgressBar.jsx +70 -0
- package/components/display/ProgressBar.vue +69 -0
- package/components/display/Tag.d.ts +9 -0
- package/components/display/Tag.jsx +15 -0
- package/components/display/Tag.vue +15 -0
- package/components/display/_dir.card.html +13 -0
- package/components/display/display.prompt.md +30 -0
- package/components/feedback/Accordion.d.ts +13 -0
- package/components/feedback/Accordion.jsx +21 -0
- package/components/feedback/Accordion.vue +22 -0
- package/components/feedback/ActivityItem.d.ts +25 -0
- package/components/feedback/ActivityItem.jsx +39 -0
- package/components/feedback/ActivityItem.vue +46 -0
- package/components/feedback/Alert.d.ts +36 -0
- package/components/feedback/Alert.jsx +48 -0
- package/components/feedback/Alert.vue +59 -0
- package/components/feedback/Banner.d.ts +15 -0
- package/components/feedback/Banner.jsx +25 -0
- package/components/feedback/Banner.vue +25 -0
- package/components/feedback/ContextMenu.d.ts +8 -0
- package/components/feedback/ContextMenu.jsx +18 -0
- package/components/feedback/ContextMenu.vue +19 -0
- package/components/feedback/Drawer.d.ts +18 -0
- package/components/feedback/Drawer.jsx +26 -0
- package/components/feedback/Drawer.vue +24 -0
- package/components/feedback/Menu.d.ts +10 -0
- package/components/feedback/Menu.jsx +24 -0
- package/components/feedback/Menu.vue +21 -0
- package/components/feedback/Modal.d.ts +38 -0
- package/components/feedback/Modal.jsx +69 -0
- package/components/feedback/Modal.vue +65 -0
- package/components/feedback/Stepper.d.ts +9 -0
- package/components/feedback/Stepper.jsx +24 -0
- package/components/feedback/Stepper.vue +18 -0
- package/components/feedback/Timeline.d.ts +15 -0
- package/components/feedback/Timeline.jsx +27 -0
- package/components/feedback/Timeline.vue +21 -0
- package/components/feedback/_dir.card.html +13 -0
- package/components/feedback/feedback.prompt.md +35 -0
- package/components/forms/Checkbox.d.ts +13 -0
- package/components/forms/Checkbox.jsx +17 -0
- package/components/forms/Checkbox.vue +18 -0
- package/components/forms/Combobox.d.ts +13 -0
- package/components/forms/Combobox.jsx +34 -0
- package/components/forms/Combobox.vue +35 -0
- package/components/forms/DatePicker.d.ts +12 -0
- package/components/forms/DatePicker.jsx +48 -0
- package/components/forms/DatePicker.vue +46 -0
- package/components/forms/Dropzone.d.ts +20 -0
- package/components/forms/Dropzone.jsx +36 -0
- package/components/forms/Dropzone.vue +28 -0
- package/components/forms/Field.d.ts +13 -0
- package/components/forms/Field.jsx +13 -0
- package/components/forms/Field.vue +12 -0
- package/components/forms/Input.d.ts +18 -0
- package/components/forms/Input.jsx +35 -0
- package/components/forms/Input.vue +38 -0
- package/components/forms/MultiSelect.d.ts +14 -0
- package/components/forms/MultiSelect.jsx +33 -0
- package/components/forms/MultiSelect.vue +36 -0
- package/components/forms/NumberStepper.d.ts +13 -0
- package/components/forms/NumberStepper.jsx +15 -0
- package/components/forms/NumberStepper.vue +17 -0
- package/components/forms/Radio.vue +16 -0
- package/components/forms/RadioGroup.vue +11 -0
- package/components/forms/SearchInput.d.ts +12 -0
- package/components/forms/SearchInput.jsx +17 -0
- package/components/forms/SearchInput.vue +14 -0
- package/components/forms/Select.d.ts +14 -0
- package/components/forms/Select.jsx +17 -0
- package/components/forms/Select.vue +16 -0
- package/components/forms/Slider.d.ts +15 -0
- package/components/forms/Slider.jsx +21 -0
- package/components/forms/Slider.vue +23 -0
- package/components/forms/Switch.d.ts +31 -0
- package/components/forms/Switch.jsx +40 -0
- package/components/forms/Switch.vue +16 -0
- package/components/forms/_dir.card.html +13 -0
- package/components/forms/forms.prompt.md +23 -0
- package/components/icons/Icon.d.ts +19 -0
- package/components/icons/Icon.jsx +93 -0
- package/components/icons/Icon.prompt.md +8 -0
- package/components/icons/Icon.vue +12 -0
- package/components/icons/Unicon.d.ts +12 -0
- package/components/icons/Unicon.jsx +16 -0
- package/components/icons/Unicon.vue +15 -0
- package/components/icons/_dir.card.html +13 -0
- package/components/icons/feather-paths.js +2 -0
- package/components/icons/icon-paths.js +73 -0
- package/components/index.d.ts +87 -0
- package/components/index.js +110 -0
- package/components/navigation/Breadcrumb.d.ts +24 -0
- package/components/navigation/Breadcrumb.jsx +120 -0
- package/components/navigation/Breadcrumb.vue +94 -0
- package/components/navigation/DocTree.d.ts +18 -0
- package/components/navigation/DocTree.jsx +37 -0
- package/components/navigation/DocTree.vue +25 -0
- package/components/navigation/Sidebar.d.ts +29 -0
- package/components/navigation/Sidebar.jsx +75 -0
- package/components/navigation/Sidebar.vue +54 -0
- package/components/navigation/TopBar.d.ts +27 -0
- package/components/navigation/TopBar.jsx +44 -0
- package/components/navigation/TopBar.vue +51 -0
- package/components/navigation/Tree.d.ts +83 -0
- package/components/navigation/Tree.jsx +347 -0
- package/components/navigation/Tree.vue +258 -0
- package/components/navigation/_dir.card.html +13 -0
- package/components/navigation/navigation.prompt.md +22 -0
- package/components/pipeline/ConfigPanel.d.ts +18 -0
- package/components/pipeline/ConfigPanel.jsx +30 -0
- package/components/pipeline/ConfigPanel.vue +32 -0
- package/components/pipeline/DataPreview.d.ts +14 -0
- package/components/pipeline/DataPreview.jsx +24 -0
- package/components/pipeline/DataPreview.vue +21 -0
- package/components/pipeline/NodeCard.d.ts +47 -0
- package/components/pipeline/NodeCard.jsx +97 -0
- package/components/pipeline/NodeCard.vue +75 -0
- package/components/pipeline/PaletteItem.vue +15 -0
- package/components/pipeline/PaletteListItem.vue +15 -0
- package/components/pipeline/PalettePanel.d.ts +20 -0
- package/components/pipeline/PalettePanel.jsx +74 -0
- package/components/pipeline/PalettePanel.vue +45 -0
- package/components/pipeline/PipelineCanvas.d.ts +25 -0
- package/components/pipeline/PipelineCanvas.jsx +94 -0
- package/components/pipeline/PipelineCanvas.vue +73 -0
- package/components/pipeline/ZoomControls.d.ts +9 -0
- package/components/pipeline/ZoomControls.jsx +15 -0
- package/components/pipeline/ZoomControls.vue +15 -0
- package/components/pipeline/_dir.card.html +13 -0
- package/components/pipeline/pipeline.prompt.md +22 -0
- package/components/tabs/SegmentedControl.d.ts +11 -0
- package/components/tabs/SegmentedControl.jsx +21 -0
- package/components/tabs/SegmentedControl.vue +15 -0
- package/components/tabs/Tabs.d.ts +13 -0
- package/components/tabs/Tabs.jsx +52 -0
- package/components/tabs/Tabs.vue +39 -0
- package/components/tabs/ViewToggle.d.ts +8 -0
- package/components/tabs/ViewToggle.jsx +12 -0
- package/components/tabs/ViewToggle.vue +12 -0
- package/components/tabs/_dir.card.html +13 -0
- package/components/tabs/tabs.prompt.md +14 -0
- package/package.json +76 -0
- package/readme.md +150 -0
- package/styles.css +10 -0
- package/tokens/alpha.css +27 -0
- package/tokens/charts.css +55 -0
- package/tokens/colors-extended.css +40 -0
- package/tokens/colors.css +39 -0
- package/tokens/dark.css +96 -0
- package/tokens/fonts.css +4 -0
- package/tokens/interaction.css +48 -0
- package/tokens/layout.css +30 -0
- package/tokens/motion.css +22 -0
- package/tokens/tokens.d.ts +680 -0
- package/tokens/tokens.js +1018 -0
- package/tokens/tokens.json +2928 -0
- package/tokens/typography.css +22 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
|
|
4
|
+
function Crumb({ label, last, hovered, onSelect }) {
|
|
5
|
+
const [hover, setHover] = React.useState(false);
|
|
6
|
+
const on = (hover || hovered) && !last;
|
|
7
|
+
return (
|
|
8
|
+
<span role={last ? undefined : "button"} tabIndex={last ? undefined : 0}
|
|
9
|
+
onClick={() => !last && onSelect && onSelect(label)}
|
|
10
|
+
onKeyDown={(e) => { if (!last && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onSelect && onSelect(label); } }}
|
|
11
|
+
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
12
|
+
style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", 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
|
+
}
|
|
15
|
+
|
|
16
|
+
function Sep() {
|
|
17
|
+
return <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Icon name="chevron-right" size={13} /></span>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Overflow crumb: an ellipsis button that drops down the levels hidden by collapsing. */
|
|
21
|
+
function Overflow({ items, onSelect }) {
|
|
22
|
+
const [open, setOpen] = React.useState(false);
|
|
23
|
+
const [hover, setHover] = React.useState(false);
|
|
24
|
+
const root = React.useRef(null);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (!open) return;
|
|
27
|
+
const h = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); };
|
|
28
|
+
document.addEventListener("mousedown", h);
|
|
29
|
+
return () => document.removeEventListener("mousedown", h);
|
|
30
|
+
}, [open]);
|
|
31
|
+
return (
|
|
32
|
+
<span ref={root} style={{ position: "relative", display: "inline-flex" }}>
|
|
33
|
+
<span role="button" tabIndex={0} aria-label={`Show ${items.length} hidden levels`} aria-expanded={open}
|
|
34
|
+
onClick={() => setOpen(!open)}
|
|
35
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(!open); } if (e.key === "Escape") setOpen(false); }}
|
|
36
|
+
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
37
|
+
style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 20, height: 18, 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
|
+
<Icon name="ellipsis" size={14} />
|
|
39
|
+
</span>
|
|
40
|
+
{open ? (
|
|
41
|
+
<span style={{ position: "absolute", top: "calc(100% + 5px)", left: 0, zIndex: "var(--z-menu)", minWidth: 168, maxWidth: 260, background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-md)", boxShadow: "var(--shadow-menu)", padding: 4, display: "flex", flexDirection: "column" }}>
|
|
42
|
+
{items.map((it, i) => <OverflowItem key={it} label={it} depth={i} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />)}
|
|
43
|
+
</span>
|
|
44
|
+
) : null}
|
|
45
|
+
</span>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function OverflowItem({ label, depth, onSelect }) {
|
|
50
|
+
const [hover, setHover] = React.useState(false);
|
|
51
|
+
return (
|
|
52
|
+
<span role="menuitem" tabIndex={0} onClick={() => onSelect(label)}
|
|
53
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onSelect(label); } }}
|
|
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-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", transition: "var(--t-hover)" }}>
|
|
56
|
+
{depth > 0 ? <span style={{ color: "var(--text-muted)", display: "inline-flex", flexShrink: 0 }}><Icon name="chevron-right" size={11} /></span> : null}
|
|
57
|
+
{label}
|
|
58
|
+
</span>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Breadcrumb trail — 12px, chevron separators, last crumb in heading color. Clickable crumbs darken on hover and carry press + keyboard focus from the global interaction layer. Trails longer than `maxItems` collapse to first · … · last two, with the hidden levels in a dropdown on the ellipsis. */
|
|
63
|
+
export function Breadcrumb({ items = [], onSelect, maxItems = 4, hoveredIndex, style }) {
|
|
64
|
+
const collapse = items.length > maxItems && maxItems >= 3;
|
|
65
|
+
const tailCount = maxItems - 2;
|
|
66
|
+
const head = collapse ? items.slice(0, 1) : items;
|
|
67
|
+
const hidden = collapse ? items.slice(1, items.length - tailCount) : [];
|
|
68
|
+
const tail = collapse ? items.slice(items.length - tailCount) : [];
|
|
69
|
+
const lastLabel = items[items.length - 1];
|
|
70
|
+
return (
|
|
71
|
+
<nav style={{ display: "flex", alignItems: "center", gap: 4, ...style }}>
|
|
72
|
+
{head.map((it, i) => (
|
|
73
|
+
<React.Fragment key={it}>
|
|
74
|
+
{i > 0 ? <Sep /> : null}
|
|
75
|
+
<Crumb label={it} last={it === lastLabel} hovered={hoveredIndex === i} onSelect={onSelect} />
|
|
76
|
+
</React.Fragment>
|
|
77
|
+
))}
|
|
78
|
+
{collapse ? (
|
|
79
|
+
<React.Fragment>
|
|
80
|
+
<Sep />
|
|
81
|
+
<Overflow items={hidden} onSelect={onSelect} />
|
|
82
|
+
{tail.map((it) => (
|
|
83
|
+
<React.Fragment key={it}>
|
|
84
|
+
<Sep />
|
|
85
|
+
<Crumb label={it} last={it === lastLabel} onSelect={onSelect} />
|
|
86
|
+
</React.Fragment>
|
|
87
|
+
))}
|
|
88
|
+
</React.Fragment>
|
|
89
|
+
) : null}
|
|
90
|
+
</nav>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Theme toggle — flips <html data-theme> and persists to localStorage("kb-theme"). Pass `dark`+`onToggle` to control it externally. */
|
|
95
|
+
export function ThemeToggle({ size = 34, dark: darkProp, onToggle, style }) {
|
|
96
|
+
const controlled = darkProp != null;
|
|
97
|
+
const [darkState, setDark] = React.useState(() => (typeof document !== "undefined" && document.documentElement.dataset.theme === "dark"));
|
|
98
|
+
const dark = controlled ? darkProp : darkState;
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
if (controlled || typeof document === "undefined") return;
|
|
101
|
+
document.documentElement.dataset.theme = dark ? "dark" : "light";
|
|
102
|
+
try { localStorage.setItem("kb-theme", dark ? "dark" : "light"); } catch (e) {}
|
|
103
|
+
}, [dark, controlled]);
|
|
104
|
+
const [hover, setHover] = React.useState(false);
|
|
105
|
+
return (
|
|
106
|
+
<button type="button" aria-label={dark ? "Switch to light mode" : "Switch to dark mode"} onClick={() => (controlled ? onToggle && onToggle(!dark) : setDark(!dark))}
|
|
107
|
+
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
108
|
+
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 }}>
|
|
109
|
+
<Icon name={dark ? "sparkles" : "moon"} size={18} />
|
|
110
|
+
</button>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Reads the persisted theme and applies it before first paint. Call once at app start. */
|
|
115
|
+
export function applyStoredTheme() {
|
|
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
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, watch, onUnmounted } from 'vue'
|
|
3
|
+
import Icon from '../icons/Icon.vue'
|
|
4
|
+
// Breadcrumb trail — 12px, chevron separators, last crumb in heading color.
|
|
5
|
+
// Clickable crumbs darken on hover (no underline) and carry press + keyboard focus from the global interaction layer.
|
|
6
|
+
// Trails longer than maxItems collapse to first · … · last two, with the hidden levels in a dropdown on the ellipsis.
|
|
7
|
+
const props = defineProps({ items: { type: Array, default: () => [] }, maxItems: { type: Number, default: 4 }, hoveredIndex: { type: Number, default: null }, style: Object })
|
|
8
|
+
const emit = defineEmits(['select'])
|
|
9
|
+
const hover = ref(-1)
|
|
10
|
+
const ovHover = ref(false)
|
|
11
|
+
const menuHover = ref(-1)
|
|
12
|
+
const open = ref(false)
|
|
13
|
+
const root = ref(null)
|
|
14
|
+
const collapse = computed(() => props.items.length > props.maxItems && props.maxItems >= 3)
|
|
15
|
+
const tailCount = computed(() => props.maxItems - 2)
|
|
16
|
+
const head = computed(() => collapse.value ? props.items.slice(0, 1) : props.items)
|
|
17
|
+
const hidden = computed(() => collapse.value ? props.items.slice(1, props.items.length - tailCount.value) : [])
|
|
18
|
+
const tail = computed(() => collapse.value ? props.items.slice(props.items.length - tailCount.value) : [])
|
|
19
|
+
const lastLabel = computed(() => props.items[props.items.length - 1])
|
|
20
|
+
const isLast = (it) => it === lastLabel.value
|
|
21
|
+
const on = (i, it) => (hover.value === i || props.hoveredIndex === i) && !isLast(it)
|
|
22
|
+
function pick(it) { if (!isLast(it)) emit('select', it) }
|
|
23
|
+
function pickHidden(it) { open.value = false; emit('select', it) }
|
|
24
|
+
function onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }
|
|
25
|
+
watch(open, (v) => { if (v) document.addEventListener('mousedown', onDoc); else document.removeEventListener('mousedown', onDoc) })
|
|
26
|
+
onUnmounted(() => document.removeEventListener('mousedown', onDoc))
|
|
27
|
+
const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-sm)', lineHeight: 'var(--leading-sm)', 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
|
+
</script>
|
|
29
|
+
<template>
|
|
30
|
+
<nav :style="{ display: 'flex', alignItems: 'center', gap: 4, ...style }">
|
|
31
|
+
<template v-for="(it, i) in head" :key="it">
|
|
32
|
+
<span v-if="i > 0" style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
33
|
+
<span :role="isLast(it) ? undefined : 'button'" :tabindex="isLast(it) ? undefined : 0"
|
|
34
|
+
@click="pick(it)" @keydown.enter.prevent="pick(it)" @keydown.space.prevent="pick(it)"
|
|
35
|
+
@mouseenter="hover = i" @mouseleave="hover = -1" :style="crumbStyle(i, it)">{{ it }}</span>
|
|
36
|
+
</template>
|
|
37
|
+
<template v-if="collapse">
|
|
38
|
+
<span style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
39
|
+
<span ref="root" style="position:relative;display:inline-flex">
|
|
40
|
+
<span role="button" tabindex="0" :aria-label="`Show ${hidden.length} hidden levels`" :aria-expanded="open"
|
|
41
|
+
@click="open = !open" @keydown.enter.prevent="open = !open" @keydown.space.prevent="open = !open" @keydown.esc="open = false"
|
|
42
|
+
@mouseenter="ovHover = true" @mouseleave="ovHover = false"
|
|
43
|
+
:style="{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '20px', height: '18px', 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
|
+
<Icon name="ellipsis" :size="14" />
|
|
45
|
+
</span>
|
|
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
|
+
<span v-for="(h, hi) in hidden" :key="h" role="menuitem" tabindex="0"
|
|
48
|
+
@click="pickHidden(h)" @keydown.enter.prevent="pickHidden(h)" @keydown.space.prevent="pickHidden(h)"
|
|
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-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }">
|
|
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
|
+
</span>
|
|
53
|
+
</span>
|
|
54
|
+
</span>
|
|
55
|
+
<template v-for="it in tail" :key="it">
|
|
56
|
+
<span style="color:var(--text-muted);display:inline-flex"><Icon name="chevron-right" :size="13" /></span>
|
|
57
|
+
<span :role="isLast(it) ? undefined : 'button'" :tabindex="isLast(it) ? undefined : 0"
|
|
58
|
+
@click="pick(it)" @keydown.enter.prevent="pick(it)" @keydown.space.prevent="pick(it)"
|
|
59
|
+
:style="crumbStyle(-1, it)">{{ it }}</span>
|
|
60
|
+
</template>
|
|
61
|
+
</template>
|
|
62
|
+
</nav>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<!-- ThemeToggle — flips <html data-theme> and persists to localStorage("kb-theme"). Pass `dark` (v-model) to control it externally. -->
|
|
66
|
+
<!--
|
|
67
|
+
<script setup name="ThemeToggle">
|
|
68
|
+
import { ref, computed, watch, onMounted } from 'vue'
|
|
69
|
+
import Icon from '../icons/Icon.vue'
|
|
70
|
+
const props = defineProps({ size: { type: Number, default: 34 }, dark: { type: Boolean, default: null }, style: Object })
|
|
71
|
+
const emit = defineEmits(['update:dark'])
|
|
72
|
+
const darkState = ref(false)
|
|
73
|
+
const dark = computed(() => props.dark != null ? props.dark : darkState.value)
|
|
74
|
+
const hover = ref(false)
|
|
75
|
+
onMounted(() => { darkState.value = document.documentElement.dataset.theme === 'dark' })
|
|
76
|
+
watch(dark, (v) => { if (props.dark != null) return; document.documentElement.dataset.theme = v ? 'dark' : 'light'; try { localStorage.setItem('kb-theme', v ? 'dark' : 'light') } catch (e) {} })
|
|
77
|
+
function toggle() { if (props.dark != null) emit('update:dark', !dark.value); else darkState.value = !darkState.value }
|
|
78
|
+
</script>
|
|
79
|
+
<template>
|
|
80
|
+
<button type="button" :aria-label="dark ? 'Switch to light mode' : 'Switch to dark mode'" @click="toggle" @mouseenter="hover = true" @mouseleave="hover = false"
|
|
81
|
+
: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 }">
|
|
82
|
+
<Icon :name="dark ? 'sparkles' : 'moon'" :size="18" />
|
|
83
|
+
</button>
|
|
84
|
+
</template>
|
|
85
|
+
-->
|
|
86
|
+
|
|
87
|
+
<!-- applyStoredTheme() — reads the persisted theme and applies it before first paint. Call once at app start (main.js): -->
|
|
88
|
+
<!--
|
|
89
|
+
export function applyStoredTheme() {
|
|
90
|
+
let t = 'light'
|
|
91
|
+
try { t = localStorage.getItem('kb-theme') || 'light' } catch (e) {}
|
|
92
|
+
document.documentElement.dataset.theme = t
|
|
93
|
+
}
|
|
94
|
+
-->
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Secondary 240px documents panel: header, search, 30px tree rows with hued doc icons. */
|
|
3
|
+
export interface DocTreeItem {
|
|
4
|
+
label: string;
|
|
5
|
+
kind?: "folder" | "doc";
|
|
6
|
+
/** Doc icon hue: orange | green | purple | blue | teal | gray */
|
|
7
|
+
hue?: string;
|
|
8
|
+
/** Override glyph */
|
|
9
|
+
icon?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DocTreeProps {
|
|
12
|
+
title?: string;
|
|
13
|
+
items?: DocTreeItem[];
|
|
14
|
+
active?: string;
|
|
15
|
+
onSelect?: (label: string) => void;
|
|
16
|
+
style?: React.CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
export declare function DocTree(props: DocTreeProps): JSX.Element;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { IconButton } from "../buttons/IconButton.jsx";
|
|
4
|
+
import { SearchInput } from "../forms/SearchInput.jsx";
|
|
5
|
+
|
|
6
|
+
const docHues = { orange: "var(--cat-transform)", green: "var(--cat-source)", purple: "var(--cat-merge)", blue: "var(--info-fg)", teal: "var(--cat-output)", gray: "var(--text-muted)" };
|
|
7
|
+
|
|
8
|
+
/** Secondary documents panel — 240px: title header, search row, 30px tree items. items: [{label, kind:"folder"|"doc", hue, icon}]. */
|
|
9
|
+
export function DocTree({ title = "Documents", items = [], active, onSelect, style }) {
|
|
10
|
+
return (
|
|
11
|
+
<div style={{ width: "var(--sidebar-w)", background: "var(--bg-surface)", borderRight: "1px solid var(--border-card)", display: "flex", flexDirection: "column", boxSizing: "border-box", flexShrink: 0, ...style }}>
|
|
12
|
+
<div style={{ height: "var(--topbar-h)", borderBottom: "1px solid var(--border-card)", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 12px 0 16px", boxSizing: "border-box", flexShrink: 0 }}>
|
|
13
|
+
<span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-md)", lineHeight: "var(--leading-md)", letterSpacing: "var(--tracking-md)", color: "var(--text-heading)" }}>{title}</span>
|
|
14
|
+
<IconButton icon="chevron-right" size="md" muted />
|
|
15
|
+
</div>
|
|
16
|
+
<div style={{ padding: "12px 16px 8px", borderBottom: "1px solid var(--border-subtle)" }}>
|
|
17
|
+
<SearchInput bare placeholder="Search..." style={{ gap: 8 }} />
|
|
18
|
+
</div>
|
|
19
|
+
<div style={{ padding: 8, display: "flex", flexDirection: "column", gap: 2, overflowY: "auto" }}>
|
|
20
|
+
{items.map((it) => {
|
|
21
|
+
const is = active === it.label;
|
|
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" }}
|
|
25
|
+
onMouseEnter={(e) => { if (!is) e.currentTarget.style.background = "var(--bg-hover)"; }}
|
|
26
|
+
onMouseLeave={(e) => { if (!is) e.currentTarget.style.background = "transparent"; }}>
|
|
27
|
+
<span style={{ color: it.kind === "folder" ? "var(--text-muted)" : docHues[it.hue || "gray"], display: "inline-flex" }}>
|
|
28
|
+
<Icon name={it.icon || (it.kind === "folder" ? "folder" : "file-text")} size={16} />
|
|
29
|
+
</span>
|
|
30
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-heading)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{it.label}</span>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
})}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Icon from '../icons/Icon.vue'
|
|
3
|
+
import IconButton from '../buttons/IconButton.vue'
|
|
4
|
+
import SearchInput from '../forms/SearchInput.vue'
|
|
5
|
+
const docHues = { orange: 'var(--cat-transform)', green: 'var(--cat-source)', purple: 'var(--cat-merge)', blue: 'var(--info-fg)', teal: 'var(--cat-output)', gray: 'var(--text-muted)' }
|
|
6
|
+
// Secondary documents panel — 240px: title header, search row, 30px tree items. items: [{label, kind:"folder"|"doc", hue, icon}].
|
|
7
|
+
defineProps({ title: { type: String, default: 'Documents' }, items: { type: Array, default: () => [] }, active: String, style: Object })
|
|
8
|
+
const emit = defineEmits(['select'])
|
|
9
|
+
</script>
|
|
10
|
+
<template>
|
|
11
|
+
<div :style="{ width: 'var(--sidebar-w)', background: 'var(--bg-surface)', borderRight: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, ...style }">
|
|
12
|
+
<div style="height:var(--topbar-h);border-bottom:1px solid var(--border-card);display:flex;align-items:center;justify-content:space-between;padding:0 12px 0 16px;box-sizing:border-box;flex-shrink:0">
|
|
13
|
+
<span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)">{{ title }}</span>
|
|
14
|
+
<IconButton icon="chevron-right" size="md" muted />
|
|
15
|
+
</div>
|
|
16
|
+
<div style="padding:12px 16px 8px;border-bottom:1px solid var(--border-subtle)"><SearchInput bare placeholder="Search..." style="gap:8px" /></div>
|
|
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: 8, padding: '6px 8px', boxSizing: 'border-box', cursor: 'pointer', background: active === it.label ? 'var(--bg-active)' : 'transparent' }">
|
|
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-sm);line-height:var(--leading-sm);color:var(--text-heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ it.label }}</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* App sidebar — 240px expanded / 64px icon rail. Logo header, nav, expandable sections, pinned footer.
|
|
4
|
+
* @startingPoint section="Chrome" subtitle="240px app sidebar with icon-rail collapse" viewport="240x600"
|
|
5
|
+
*/
|
|
6
|
+
export interface SidebarItem {
|
|
7
|
+
icon: string;
|
|
8
|
+
label: string;
|
|
9
|
+
children?: SidebarItem[];
|
|
10
|
+
/** Expand this section initially */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SidebarProps {
|
|
14
|
+
items?: SidebarItem[];
|
|
15
|
+
footerItems?: SidebarItem[];
|
|
16
|
+
/** Active item label */
|
|
17
|
+
active?: string;
|
|
18
|
+
onSelect?: (label: string) => void;
|
|
19
|
+
collapsed?: boolean;
|
|
20
|
+
/** Renders the "Collapse" item */
|
|
21
|
+
onToggleCollapse?: () => void;
|
|
22
|
+
logo?: string;
|
|
23
|
+
brand?: string;
|
|
24
|
+
height?: number | string;
|
|
25
|
+
style?: React.CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
export declare function Sidebar(props: SidebarProps): JSX.Element;
|
|
28
|
+
/** The product's real nav model. */
|
|
29
|
+
export declare const KEYBRICKS_NAV: SidebarItem[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
|
|
4
|
+
function NavItem({ icon, label, active, indent, collapsed, badge, onClick }) {
|
|
5
|
+
const [hover, setHover] = React.useState(false);
|
|
6
|
+
return (
|
|
7
|
+
<div role="button" tabIndex={0} onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
|
|
8
|
+
style={{ height: indent ? "var(--h-subnav-item)" : "var(--h-nav-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", justifyContent: collapsed ? "center" : "flex-start", gap: 10, padding: collapsed ? "8px 10px" : indent ? "6px 10px" : "8px 10px", boxSizing: "border-box", cursor: "pointer", background: active ? "var(--bg-active)" : hover ? "var(--bg-hover)" : "transparent", color: active ? "var(--text-heading)" : "var(--text-body)", transition: "var(--t-hover)" }}>
|
|
9
|
+
<Icon name={icon} size={indent ? 14 : 16} />
|
|
10
|
+
{!collapsed ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", whiteSpace: "nowrap", flex: 1 }}>{label}</span> : null}
|
|
11
|
+
{!collapsed && badge ? badge : null}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Keybricks app sidebar — 240px (collapsed: 64px icon rail). White, hairline right border.
|
|
18
|
+
* items: [{icon,label,children:[{icon,label}]}]; active by label; footer items pinned.
|
|
19
|
+
*/
|
|
20
|
+
export function Sidebar({ items = [], footerItems = [{ icon: "circle-help", label: "Help" }, { icon: "settings", label: "Settings" }, { icon: "moon", label: "Dark Mode" }], active, onSelect, collapsed = false, onToggleCollapse, logo = "assets/keybricks-logo.png", brand = "Keybricks", height = "100%", style }) {
|
|
21
|
+
const [openSections, setOpen] = React.useState(() => items.filter((i) => i.children && (i.open || (i.children.some((c) => c.label === active)))).map((i) => i.label));
|
|
22
|
+
const toggleSection = (l) => setOpen(openSections.includes(l) ? openSections.filter((x) => x !== l) : [...openSections, l]);
|
|
23
|
+
return (
|
|
24
|
+
<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 }}>
|
|
25
|
+
<div style={{ height: "var(--topbar-h)", borderBottom: "1px solid var(--border-card)", display: "flex", alignItems: "center", gap: 10, padding: "0 16px", boxSizing: "border-box", justifyContent: collapsed ? "center" : "flex-start", flexShrink: 0 }}>
|
|
26
|
+
<img src={logo} alt="" style={{ width: 24, height: 24, objectFit: "contain" }} />
|
|
27
|
+
{!collapsed ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-lg)", lineHeight: "var(--leading-lg)", letterSpacing: "-.609px", color: "var(--text-primary)" }}>{brand}</span> : null}
|
|
28
|
+
</div>
|
|
29
|
+
<nav style={{ flex: 1, overflowY: "auto", padding: 8, display: "flex", flexDirection: "column", gap: 0 }}>
|
|
30
|
+
{onToggleCollapse ? <NavItem icon="panel-left" label="Collapse" collapsed={collapsed} onClick={onToggleCollapse} /> : null}
|
|
31
|
+
{items.map((it) => it.children ? (
|
|
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" }}>
|
|
34
|
+
<Icon name={it.icon} size={16} />
|
|
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
|
+
</div>
|
|
37
|
+
{!collapsed && openSections.includes(it.label) ? (
|
|
38
|
+
<div style={{ marginLeft: 24, display: "flex", flexDirection: "column", gap: 2, paddingTop: 2 }}>
|
|
39
|
+
{it.children.map((c) => <NavItem key={c.label} icon={c.icon} label={c.label} indent active={active === c.label} onClick={() => onSelect && onSelect(c.label)} />)}
|
|
40
|
+
</div>
|
|
41
|
+
) : null}
|
|
42
|
+
</div>
|
|
43
|
+
) : (
|
|
44
|
+
<NavItem key={it.label} icon={it.icon} label={it.label} collapsed={collapsed} active={active === it.label} onClick={() => onSelect && onSelect(it.label)} />
|
|
45
|
+
))}
|
|
46
|
+
</nav>
|
|
47
|
+
<div style={{ borderTop: "1px solid var(--border-card)", padding: 8, display: "flex", flexDirection: "column", flexShrink: 0 }}>
|
|
48
|
+
{footerItems.map((f) => <NavItem key={f.label} icon={f.icon} label={f.label} collapsed={collapsed} onClick={() => onSelect && onSelect(f.label)} />)}
|
|
49
|
+
</div>
|
|
50
|
+
</aside>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Default Keybricks nav model (Home dashboard). */
|
|
55
|
+
export const KEYBRICKS_NAV = [
|
|
56
|
+
{ icon: "house", label: "Home" },
|
|
57
|
+
{ icon: "file-text", label: "Documents" },
|
|
58
|
+
{ icon: "layout-grid", label: "Business Intelligence" },
|
|
59
|
+
{ icon: "database", label: "Data Integration" },
|
|
60
|
+
{ icon: "scale", label: "Reconciliation" },
|
|
61
|
+
{ icon: "network", label: "Consolidation" },
|
|
62
|
+
{
|
|
63
|
+
icon: "folder-cog", label: "Management", children: [
|
|
64
|
+
{ icon: "box", label: "Modeling" },
|
|
65
|
+
{ icon: "workflow", label: "Workflow" },
|
|
66
|
+
{ icon: "shield", label: "Security Management" },
|
|
67
|
+
{ icon: "refresh-cw", label: "System Migration" },
|
|
68
|
+
{ icon: "chart-column", label: "Business Logic" },
|
|
69
|
+
{ icon: "list-todo", label: "Tasks Management" },
|
|
70
|
+
{ icon: "circle-check", label: "Validation Rules" },
|
|
71
|
+
{ icon: "settings-2", label: "Control Center" },
|
|
72
|
+
{ icon: "network", label: "Reconciliation" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import Icon from '../icons/Icon.vue'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Keybricks app sidebar — 240px (collapsed: 64px icon rail). White, hairline right border.
|
|
7
|
+
* items: [{icon,label,children:[{icon,label}]}]; active by label; footer items pinned.
|
|
8
|
+
*/
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
items: { type: Array, default: () => [] },
|
|
11
|
+
footerItems: { type: Array, default: () => [{ icon: 'circle-help', label: 'Help' }, { icon: 'settings', label: 'Settings' }, { icon: 'moon', label: 'Dark Mode' }] },
|
|
12
|
+
active: String, collapsed: { type: Boolean, default: false },
|
|
13
|
+
logo: { type: String, default: 'assets/keybricks-logo.png' }, brand: { type: String, default: 'Keybricks' },
|
|
14
|
+
height: { type: [String, Number], default: '100%' }, style: Object, canCollapse: Boolean,
|
|
15
|
+
})
|
|
16
|
+
const emit = defineEmits(['select', 'toggleCollapse'])
|
|
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
|
+
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: 10, 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
|
+
</script>
|
|
21
|
+
<template>
|
|
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: 10, padding: '0 16px', boxSizing: 'border-box', justifyContent: collapsed ? 'center' : 'flex-start', flexShrink: 0 }">
|
|
24
|
+
<img :src="logo" alt="" style="width:24px;height:24px;object-fit:contain" />
|
|
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
|
+
</div>
|
|
27
|
+
<nav style="flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:0">
|
|
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
|
+
<template v-for="it in items" :key="it.label">
|
|
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: 10, padding: '6px 10px', justifyContent: collapsed ? 'center' : 'flex-start', cursor: 'pointer', color: 'var(--text-body)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box' }">
|
|
32
|
+
<Icon :name="it.icon" :size="16" />
|
|
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
|
+
</div>
|
|
35
|
+
<div v-if="!collapsed && openSections.includes(it.label)" style="margin-left:24px;display:flex;flex-direction:column;gap:2px;padding-top:2px">
|
|
36
|
+
<div v-for="c in it.children" :key="c.label" role="button" tabindex="0" @click="emit('select', c.label)" :style="itemStyle(active === c.label, true)"><Icon :name="c.icon" :size="14" /><span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);white-space:nowrap;flex:1">{{ c.label }}</span></div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div v-else role="button" tabindex="0" @click="emit('select', it.label)" :style="itemStyle(active === it.label, false)"><Icon :name="it.icon" :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">{{ it.label }}</span></div>
|
|
40
|
+
</template>
|
|
41
|
+
</nav>
|
|
42
|
+
<div style="border-top:1px solid var(--border-card);padding:8px;display:flex;flex-direction:column;flex-shrink:0">
|
|
43
|
+
<div v-for="f in footerItems" :key="f.label" role="button" tabindex="0" @click="emit('select', f.label)" :style="itemStyle(false, false)"><Icon :name="f.icon" :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">{{ f.label }}</span></div>
|
|
44
|
+
</div>
|
|
45
|
+
</aside>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<!-- KEYBRICKS_NAV — default Keybricks nav model (Home dashboard), same shape as Sidebar.jsx's export, for reference:
|
|
49
|
+
export const KEYBRICKS_NAV = [
|
|
50
|
+
{ icon: 'house', label: 'Home' }, { icon: 'file-text', label: 'Documents' },
|
|
51
|
+
{ icon: 'layout-grid', label: 'Business Intelligence' }, { icon: 'database', label: 'Data Integration' },
|
|
52
|
+
{ icon: 'scale', label: 'Reconciliation' }, { icon: 'network', label: 'Consolidation' },
|
|
53
|
+
{ icon: 'folder-cog', label: 'Management', children: [ ... ] },
|
|
54
|
+
] -->
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** 56px top bar with bare search + bell + dark AI Assistant button. */
|
|
3
|
+
export interface TopBarProps {
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
/** Replaces the default bell + AI Assistant pair */
|
|
6
|
+
actions?: React.ReactNode;
|
|
7
|
+
/** Receives the new search string */
|
|
8
|
+
onSearch?: (value: string) => void;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
}
|
|
11
|
+
export declare function TopBar(props: TopBarProps): JSX.Element;
|
|
12
|
+
/** Editor toolbar: back, 14px/600 title, "• Editing", right actions. */
|
|
13
|
+
export interface PageToolbarProps {
|
|
14
|
+
title?: string;
|
|
15
|
+
status?: string;
|
|
16
|
+
actions?: React.ReactNode;
|
|
17
|
+
onBack?: () => void;
|
|
18
|
+
style?: React.CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
export declare function PageToolbar(props: PageToolbarProps): JSX.Element;
|
|
21
|
+
/** 15px/500 page title + muted subtitle. */
|
|
22
|
+
export interface PageHeadingProps {
|
|
23
|
+
title?: string;
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
style?: React.CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
export declare function PageHeading(props: PageHeadingProps): JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { IconButton } from "../buttons/IconButton.jsx";
|
|
4
|
+
import { Button } from "../buttons/Button.jsx";
|
|
5
|
+
import { SearchInput } from "../forms/SearchInput.jsx";
|
|
6
|
+
|
|
7
|
+
/** 56px top bar: bare search left, bell + dark AI Assistant button right. */
|
|
8
|
+
export function TopBar({ placeholder = "Search documents, workflows, data...", actions, onSearch, style }) {
|
|
9
|
+
return (
|
|
10
|
+
<header style={{ height: "var(--topbar-h)", background: "var(--bg-surface)", borderBottom: "1px solid var(--border-card)", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 24px", boxSizing: "border-box", flexShrink: 0, ...style }}>
|
|
11
|
+
<SearchInput bare placeholder={placeholder} onChange={onSearch} style={{ width: 576, maxWidth: "48%" }} />
|
|
12
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
13
|
+
{actions || (<>
|
|
14
|
+
<IconButton icon="bell" size="lg" />
|
|
15
|
+
<Button variant="dark" icon="sparkles">AI Assistant</Button>
|
|
16
|
+
</>)}
|
|
17
|
+
</div>
|
|
18
|
+
</header>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Editor toolbar: back chevron, 14px/600 title, "• Editing" status, right action slot. */
|
|
23
|
+
export function PageToolbar({ title, status = "• Editing", actions, onBack, style }) {
|
|
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 16px", boxSizing: "border-box", flexShrink: 0, ...style }}>
|
|
26
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
27
|
+
{onBack ? <IconButton icon="chevron-left" size="md" onClick={onBack} /> : null}
|
|
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-sm)", lineHeight: "var(--leading-sm)", color: "var(--text-muted)", marginLeft: 6 }}>{status}</span> : null}
|
|
30
|
+
</div>
|
|
31
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>{actions}</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Page heading block: 15px/500 title + 13px muted subtitle (System Migration pages). */
|
|
37
|
+
export function PageHeading({ title, subtitle, style }) {
|
|
38
|
+
return (
|
|
39
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 4, ...style }}>
|
|
40
|
+
<span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-lg)", lineHeight: "var(--leading-lg)", letterSpacing: "var(--tracking-lg)", color: "var(--text-primary)" }}>{title}</span>
|
|
41
|
+
{subtitle ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-secondary)" }}>{subtitle}</span> : null}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import IconButton from '../buttons/IconButton.vue'
|
|
3
|
+
import Button from '../buttons/Button.vue'
|
|
4
|
+
import SearchInput from '../forms/SearchInput.vue'
|
|
5
|
+
// 56px top bar: bare search left, bell + dark AI Assistant button right.
|
|
6
|
+
defineProps({ placeholder: { type: String, default: 'Search documents, workflows, data...' }, style: Object })
|
|
7
|
+
const emit = defineEmits(['search'])
|
|
8
|
+
</script>
|
|
9
|
+
<template>
|
|
10
|
+
<header :style="{ height: 'var(--topbar-h)', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-card)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 24px', boxSizing: 'border-box', flexShrink: 0, ...style }">
|
|
11
|
+
<SearchInput bare :placeholder="placeholder" @update:modelValue="v => emit('search', v)" style="width:576px;max-width:48%" />
|
|
12
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
13
|
+
<slot name="actions">
|
|
14
|
+
<IconButton icon="bell" size="lg" />
|
|
15
|
+
<Button variant="dark" icon="sparkles">AI Assistant</Button>
|
|
16
|
+
</slot>
|
|
17
|
+
</div>
|
|
18
|
+
</header>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<!-- PageToolbar — editor toolbar: back chevron, 14px/600 title, "• Editing" status, right action slot. -->
|
|
22
|
+
<!--
|
|
23
|
+
<script setup name="PageToolbar">
|
|
24
|
+
import IconButton from '../buttons/IconButton.vue'
|
|
25
|
+
defineProps({ title: String, status: { type: String, default: '• Editing' }, showBack: Boolean, style: Object })
|
|
26
|
+
const emit = defineEmits(['back'])
|
|
27
|
+
</script>
|
|
28
|
+
<template>
|
|
29
|
+
<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 16px', boxSizing: 'border-box', flexShrink: 0, ...style }">
|
|
30
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
31
|
+
<IconButton v-if="showBack" icon="chevron-left" size="md" @click="emit('back')" />
|
|
32
|
+
<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>
|
|
33
|
+
<span v-if="status" style="font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-muted);margin-left:6px">{{ status }}</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div style="display:flex;align-items:center;gap:8px"><slot name="actions" /></div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
-->
|
|
39
|
+
|
|
40
|
+
<!-- PageHeading — page heading block: 15px/500 title + 13px muted subtitle. -->
|
|
41
|
+
<!--
|
|
42
|
+
<script setup name="PageHeading">
|
|
43
|
+
defineProps({ title: String, subtitle: String, style: Object })
|
|
44
|
+
</script>
|
|
45
|
+
<template>
|
|
46
|
+
<div :style="{ display: 'flex', flexDirection: 'column', gap: 4, ...style }">
|
|
47
|
+
<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>
|
|
48
|
+
<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>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
-->
|