@keyfunc/keybricks-ds 2.1.1 → 2.4.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 +20 -5
- package/DESIGN.md +148 -20
- package/INTEGRATION.md +97 -24
- package/SKILL.md +11 -11
- package/_adherence.oxlintrc.json +56 -24
- package/_scripts/build-index.mjs +15 -4
- package/assets/icons/icon-index.js +1 -1
- package/assets/icons/icon-index.json +1 -1
- package/assets/logos/datasource/dat.png +0 -0
- package/assets/logos/datasource/excel.png +0 -0
- package/assets/logos/datasource/ftp.png +0 -0
- package/assets/logos/datasource/hierarchy.png +0 -0
- package/assets/logos/datasource/hive.png +0 -0
- package/assets/logos/datasource/json.png +0 -0
- package/assets/logos/datasource/mariadb.png +0 -0
- package/assets/logos/datasource/mongodb.png +0 -0
- package/assets/logos/datasource/mysql.png +0 -0
- package/assets/logos/datasource/odbc.png +0 -0
- package/assets/logos/datasource/oracle.png +0 -0
- package/assets/logos/datasource/postgresql.png +0 -0
- package/assets/logos/datasource/rest-api.png +0 -0
- package/assets/logos/datasource/sap-alt.png +0 -0
- package/assets/logos/datasource/sap.png +0 -0
- package/assets/logos/datasource/soap.png +0 -0
- package/assets/logos/datasource/sql-server.png +0 -0
- package/components/_cards/accordion.card.html +1 -1
- package/components/_cards/action-chip.card.html +11 -14
- package/components/_cards/activity.card.html +2 -2
- package/components/_cards/alert.card.html +2 -2
- package/components/_cards/app-chrome.card.html +34 -22
- package/components/_cards/banner.card.html +1 -1
- package/components/_cards/breadcrumb.card.html +4 -4
- package/components/_cards/button-1-base.card.html +23 -26
- package/components/_cards/button-2-states.card.html +14 -12
- package/components/_cards/button-3-sizes-icons.card.html +20 -19
- package/components/_cards/button-4-group.card.html +14 -31
- package/components/_cards/canvas-zoom-controls.card.html +1 -1
- package/components/_cards/cards-indicators.card.html +1 -1
- package/components/_cards/charts-1-sparkline.card.html +4 -4
- package/components/_cards/charts-10-gauge.card.html +2 -2
- 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 +3 -3
- package/components/_cards/charts-15-distribution.card.html +2 -2
- 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 +3 -3
- package/components/_cards/charts-20-sunburst.card.html +2 -2
- package/components/_cards/charts-21-worldmap.card.html +3 -3
- 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 +6 -6
- package/components/_cards/charts-4-barlist.card.html +2 -2
- package/components/_cards/charts-5-donut.card.html +2 -2
- 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 +3 -3
- package/components/_cards/charts-99-fullpage.card.html +1 -1
- package/components/_cards/checkbox.card.html +6 -3
- package/components/_cards/codeblock.card.html +2 -2
- package/components/_cards/combobox.card.html +6 -4
- package/components/_cards/context-menu.card.html +12 -5
- package/components/_cards/date-picker.card.html +8 -5
- package/components/_cards/diffvalue.card.html +2 -2
- package/components/_cards/divider.card.html +2 -2
- package/components/_cards/drawer.card.html +3 -3
- package/components/_cards/dropdown-menu.card.html +2 -2
- package/components/_cards/dropzone.card.html +5 -3
- package/components/_cards/empty-state.card.html +2 -2
- package/components/_cards/filter-bar.card.html +1 -1
- package/components/_cards/icon-button.card.html +14 -14
- package/components/_cards/icons-0-usage.card.html +88 -0
- package/components/_cards/icons-1-core.card.html +4 -4
- package/components/_cards/icons-2-feather.card.html +4 -4
- package/components/_cards/icons-3-unicons.card.html +3 -3
- package/components/_cards/icons-4-datasource.card.html +5 -0
- package/components/_cards/input.card.html +5 -5
- package/components/_cards/kbd.card.html +1 -1
- package/components/_cards/keyvalue.card.html +1 -1
- package/components/_cards/listrow.card.html +1 -1
- package/components/_cards/loading.card.html +2 -2
- package/components/_cards/log-viewer.card.html +2 -2
- package/components/_cards/modal.card.html +1 -1
- package/components/_cards/multi-select.card.html +6 -4
- package/components/_cards/number-stepper.card.html +9 -6
- package/components/_cards/pagination.card.html +6 -4
- package/components/_cards/people.card.html +2 -2
- package/components/_cards/pipeline-node.card.html +8 -8
- package/components/_cards/pipeline-palette.card.html +2 -2
- package/components/_cards/radio.card.html +12 -7
- package/components/_cards/search.card.html +6 -6
- package/components/_cards/segmented-control.card.html +19 -12
- package/components/_cards/select.card.html +7 -3
- package/components/_cards/slider.card.html +2 -2
- package/components/_cards/smart-table.card.html +3 -3
- package/components/_cards/spreadsheet.card.html +2 -2
- package/components/_cards/stepper.card.html +2 -2
- package/components/_cards/table.card.html +79 -17
- package/components/_cards/tabs.card.html +10 -5
- package/components/_cards/tag.card.html +6 -6
- package/components/_cards/textarea.card.html +1 -1
- package/components/_cards/timeline.card.html +3 -3
- package/components/_cards/toast.card.html +7 -5
- package/components/_cards/toggles.card.html +14 -9
- package/components/_cards/tooltip.card.html +3 -3
- package/components/_cards/tree.card.html +2 -2
- package/components/_shared/card-kit.css +25 -0
- package/components/_shared/card-kit.js +50 -0
- package/components/_shared/src-buttons.js +1 -1
- package/components/_shared/src-charts.js +1 -1
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-display.js +1 -1
- package/components/_shared/src-feedback.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-icons.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/_shared/view-source.js +12 -12
- package/components/_vue/index.mjs +10 -0
- package/components/buttons/ActionChip.d.ts +1 -1
- package/components/buttons/Button.d.ts +1 -1
- package/components/buttons/Button.jsx +1 -1
- package/components/buttons/Button.vue +1 -1
- package/components/buttons/IconButton.d.ts +1 -1
- package/components/buttons/IconButton.jsx +5 -3
- package/components/buttons/IconButton.vue +3 -2
- package/components/charts/Bar3D.jsx +1 -1
- package/components/charts/Bar3D.vue +2 -2
- package/components/charts/BarChart.jsx +3 -3
- package/components/charts/BarChart.vue +6 -6
- package/components/charts/BarList.jsx +3 -3
- package/components/charts/BarList.vue +3 -3
- package/components/charts/BoxPlot.vue +2 -2
- package/components/charts/BulletChart.jsx +5 -5
- package/components/charts/BulletChart.vue +5 -5
- package/components/charts/CandlestickChart.vue +3 -3
- package/components/charts/ChartKit.jsx +4 -3
- package/components/charts/ChartTooltip.vue +1 -1
- package/components/charts/ChordChart.jsx +1 -1
- package/components/charts/ChordChart.vue +2 -2
- package/components/charts/DonutChart.jsx +1 -1
- package/components/charts/DonutChart.vue +2 -2
- package/components/charts/FunnelChart.jsx +3 -3
- package/components/charts/FunnelChart.vue +4 -4
- package/components/charts/GanttChart.vue +1 -1
- package/components/charts/GaugeChart.jsx +2 -2
- package/components/charts/GaugeChart.vue +2 -2
- package/components/charts/HeatmapChart.jsx +1 -1
- package/components/charts/HeatmapChart.vue +1 -1
- package/components/charts/Histogram.jsx +2 -2
- package/components/charts/Histogram.vue +4 -4
- package/components/charts/LineChart.vue +2 -2
- package/components/charts/ParetoChart.jsx +1 -1
- package/components/charts/ParetoChart.vue +3 -3
- package/components/charts/Pie3D.jsx +1 -1
- package/components/charts/Pie3D.vue +2 -2
- package/components/charts/PlotGrid.vue +1 -1
- package/components/charts/RadarChart.vue +1 -1
- package/components/charts/Ribbon3D.jsx +1 -1
- package/components/charts/Ribbon3D.vue +2 -2
- package/components/charts/SankeyChart.jsx +1 -1
- package/components/charts/SankeyChart.vue +2 -2
- package/components/charts/Scatter3D.vue +1 -1
- package/components/charts/ScatterChart.jsx +1 -1
- package/components/charts/ScatterChart.vue +3 -3
- package/components/charts/Sparkline.jsx +1 -1
- package/components/charts/Sparkline.vue +1 -1
- package/components/charts/SunburstChart.vue +1 -1
- package/components/charts/Surface3D.jsx +1 -1
- package/components/charts/Surface3D.vue +2 -2
- package/components/charts/TreemapChart.jsx +1 -1
- package/components/charts/TreemapChart.vue +2 -2
- package/components/charts/WaterfallChart.jsx +1 -1
- package/components/charts/WaterfallChart.vue +5 -4
- package/components/charts/WorldMap.jsx +4 -4
- package/components/charts/WorldMap.vue +5 -5
- package/components/data/DiffValue.d.ts +1 -0
- package/components/data/DiffValue.jsx +1 -1
- package/components/data/DiffValue.vue +1 -1
- package/components/data/KeyValue.d.ts +1 -1
- package/components/data/KeyValue.jsx +2 -2
- package/components/data/KeyValue.vue +2 -2
- package/components/data/Pagination.jsx +2 -2
- package/components/data/Pagination.vue +1 -1
- package/components/data/SmartTable.jsx +5 -5
- package/components/data/SmartTable.vue +6 -6
- package/components/data/Spreadsheet.jsx +5 -5
- package/components/data/Spreadsheet.vue +6 -6
- package/components/data/Table.d.ts +58 -4
- package/components/data/Table.jsx +153 -15
- package/components/data/Table.vue +105 -15
- package/components/data/data.prompt.md +1 -1
- package/components/display/Avatar.jsx +2 -2
- package/components/display/Avatar.vue +1 -1
- package/components/display/AvatarGroup.vue +1 -1
- package/components/display/Badge.d.ts +4 -1
- package/components/display/Badge.jsx +10 -5
- package/components/display/IconTile.vue +9 -4
- package/components/display/Kbd.d.ts +1 -1
- package/components/display/Kbd.jsx +1 -1
- package/components/display/Kbd.vue +1 -1
- package/components/feedback/ActivityItem.jsx +1 -1
- package/components/feedback/ContextMenu.d.ts +3 -2
- package/components/feedback/ContextMenu.jsx +8 -4
- package/components/feedback/ContextMenu.vue +9 -6
- package/components/feedback/Menu.d.ts +1 -1
- package/components/feedback/Modal.jsx +1 -1
- package/components/feedback/Modal.vue +1 -1
- package/components/feedback/Timeline.vue +2 -2
- package/components/feedback/feedback.prompt.md +1 -1
- package/components/forms/Combobox.d.ts +1 -1
- package/components/forms/DatePicker.d.ts +1 -1
- package/components/forms/Input.d.ts +1 -1
- package/components/forms/Input.jsx +2 -2
- package/components/forms/Input.vue +1 -1
- package/components/forms/MultiSelect.d.ts +1 -1
- package/components/forms/NumberStepper.d.ts +1 -1
- package/components/forms/SearchInput.d.ts +1 -1
- package/components/forms/Select.d.ts +1 -1
- package/components/forms/Select.jsx +1 -1
- package/components/forms/Select.vue +1 -1
- package/components/forms/Slider.jsx +1 -1
- package/components/forms/Slider.vue +1 -1
- package/components/forms/Switch.jsx +3 -3
- package/components/forms/Switch.vue +11 -8
- package/components/forms/Textarea.vue +1 -1
- package/components/forms/input-base.js +1 -1
- package/components/icons/Icon.jsx +17 -1
- package/components/icons/Icon.prompt.md +2 -0
- package/components/icons/Icon.vue +1 -1
- package/components/icons/icon-paths.js +15 -0
- package/components/index.d.ts +1 -0
- package/components/index.js +5 -4
- package/components/navigation/Breadcrumb.jsx +3 -7
- package/components/navigation/Breadcrumb.vue +2 -8
- package/components/navigation/SidePanel.d.ts +22 -0
- package/components/navigation/SidePanel.jsx +23 -0
- package/components/navigation/SidePanel.vue +21 -0
- package/components/navigation/Sidebar.d.ts +23 -7
- package/components/navigation/Sidebar.jsx +38 -56
- package/components/navigation/Sidebar.vue +40 -44
- package/components/navigation/TopBar.d.ts +28 -3
- package/components/navigation/TopBar.jsx +54 -7
- package/components/navigation/TopBar.vue +26 -8
- package/components/navigation/Tree.jsx +1 -1
- package/components/navigation/Tree.vue +9 -4
- package/components/navigation/UserMenu.vue +27 -0
- package/components/navigation/chrome-tones.js +22 -0
- package/components/navigation/nav-model.js +26 -0
- package/components/navigation/navigation.prompt.md +2 -0
- package/components/navigation/theme-store.js +9 -0
- package/components/tabs/CountBadge.vue +12 -0
- package/components/tabs/PanelTabs.vue +7 -3
- package/components/tabs/PillTabs.vue +8 -4
- package/components/tabs/SegmentedControl.d.ts +5 -3
- package/components/tabs/SegmentedControl.jsx +6 -4
- package/components/tabs/SegmentedControl.vue +4 -3
- package/components/tabs/Tabs.d.ts +14 -4
- package/components/tabs/Tabs.jsx +35 -20
- package/components/tabs/Tabs.vue +7 -3
- package/components/tabs/ViewToggle.d.ts +1 -1
- package/components/tabs/tabs.prompt.md +2 -2
- package/package.json +2 -2
- package/readme.md +10 -9
- package/readme.zh-CN.md +8 -7
- package/tokens/alpha.css +3 -1
- package/tokens/colors.css +1 -1
- package/tokens/dark.css +1 -1
- package/tokens/layout.css +8 -5
- package/tokens/tokens.d.ts +24 -0
- package/tokens/tokens.js +42 -6
- package/tokens/tokens.json +97 -11
- package/tokens/typography.css +26 -9
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
/** Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. Rows are control-height at the region's tier (a menu inherits the tier of the control that opened it); `size` overrides.
|
|
4
|
+
/** Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider, selected}]. Rows are control-height at the region's tier (a menu inherits the tier of the control that opened it); `size` overrides.
|
|
5
|
+
* selected: true marks the current choice of a single-choice menu (section switcher, language picker): --bg-hover fill, --weight-medium
|
|
6
|
+
* --text-heading, no checkmark. A menu with any selected item spaces its rows 2px apart (like Sidebar / Tree / DocTree) so a hovered
|
|
7
|
+
* fill never touches the selected one; an action menu (nothing selected) keeps its rows flush. Dividers keep 4px above and below. */
|
|
5
8
|
export function ContextMenu({ items = [], onSelect, size, style }) {
|
|
6
9
|
const [hover, setHover] = React.useState(-1);
|
|
10
|
+
const choice = items.some((it) => it && it.selected);
|
|
7
11
|
return (
|
|
8
|
-
<div role="menu" data-size={size} style={{ background: "var(--bg-surface)", borderRadius: "var(--radius-lg)", border: "1px solid var(--border-card)", boxShadow: "var(--shadow-menu)", padding: 4, minWidth: 148, display: "inline-flex", flexDirection: "column", ...style }}>
|
|
9
|
-
{items.map((it, i) => it.divider ? <div key={i} style={{ height: 1, background: "var(--border-subtle)", margin: "4px 0" }} /> : (
|
|
12
|
+
<div role="menu" data-size={size} style={{ background: "var(--bg-surface)", borderRadius: "var(--radius-lg)", border: "1px solid var(--border-card)", boxShadow: "var(--shadow-menu)", padding: 4, minWidth: 148, display: "inline-flex", flexDirection: "column", gap: choice ? 2 : 0, ...style }}>
|
|
13
|
+
{items.map((it, i) => it.divider ? <div key={i} role="separator" style={{ height: 1, flexShrink: 0, background: "var(--border-subtle)", margin: "4px 0" }} /> : (
|
|
10
14
|
<button key={i} type="button" onClick={() => onSelect && onSelect(it.label)}
|
|
11
15
|
onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}
|
|
12
|
-
role="menuitem" style={{ display: "flex", alignItems: "center", gap: 10, height: "var(--control-h)", padding: "0 10px", boxSizing: "border-box", borderRadius: "var(--radius-md)", border: "none", cursor: "pointer", background: hover === i ? "var(--bg-hover)" : "transparent", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: it.danger ? "var(--danger)" : "var(--text-heading)", textAlign: "left", whiteSpace: "nowrap" }}>
|
|
16
|
+
role={choice ? "menuitemradio" : "menuitem"} aria-checked={choice ? !!it.selected : undefined} style={{ display: "flex", alignItems: "center", gap: 10, height: "var(--control-h)", flexShrink: 0, padding: "0 10px", boxSizing: "border-box", borderRadius: "var(--radius-md)", border: "none", cursor: "pointer", background: it.selected || hover === i ? "var(--bg-hover)" : "transparent", fontFamily: "var(--font-sans)", fontWeight: it.selected ? "var(--weight-medium)" : "var(--weight-regular)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: it.danger ? "var(--danger)" : "var(--text-heading)", textAlign: "left", whiteSpace: "nowrap" }}>
|
|
13
17
|
{it.icon ? <Icon name={it.icon} size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /> : null}{it.label}
|
|
14
18
|
</button>
|
|
15
19
|
))}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref } from 'vue'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
|
-
// Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. Rows are control-height at the region's tier (a menu inherits the tier of the control that opened it); `size` overrides.
|
|
5
|
-
|
|
4
|
+
// Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider, selected}]. Rows are control-height at the region's tier (a menu inherits the tier of the control that opened it); `size` overrides.
|
|
5
|
+
// selected: true marks the current choice of a single-choice menu: --bg-hover fill, --weight-medium --text-heading, no checkmark. A menu with any
|
|
6
|
+
// selected item spaces its rows 2px apart (like Sidebar / Tree / DocTree); an action menu keeps its rows flush. Dividers keep 4px above and below.
|
|
7
|
+
const props = defineProps({ items: { type: Array, default: () => [] }, size: String, style: Object })
|
|
8
|
+
const choice = computed(() => props.items.some(it => it && it.selected))
|
|
6
9
|
const emit = defineEmits(['select'])
|
|
7
10
|
const hover = ref(-1)
|
|
8
11
|
</script>
|
|
9
12
|
<template>
|
|
10
|
-
<div role="menu" :data-size="size" :style="{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-menu)', padding: '4px', minWidth: '148px', display: 'inline-flex', flexDirection: 'column', ...style }">
|
|
13
|
+
<div role="menu" :data-size="size" :style="{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-menu)', padding: '4px', minWidth: '148px', display: 'inline-flex', flexDirection: 'column', gap: choice ? '2px' : 0, ...style }">
|
|
11
14
|
<template v-for="(it, i) in items" :key="i">
|
|
12
|
-
<div v-if="it.divider" style="height:1px
|
|
15
|
+
<div v-if="it.divider" role="separator" :style="{ height: '1px', flexShrink: 0, background: 'var(--border-subtle)', margin: '4px 0' }" />
|
|
13
16
|
<button v-else type="button" @click="emit('select', it.label)" @mouseenter="hover = i" @mouseleave="hover = -1"
|
|
14
|
-
role="menuitem" :style="{ display: 'flex', alignItems: 'center', gap: '10px', height: 'var(--control-h)', padding: '0 10px', boxSizing: 'border-box', borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', background: hover === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: it.danger ? 'var(--danger)' : 'var(--text-heading)', textAlign: 'left', whiteSpace: 'nowrap' }">
|
|
17
|
+
:role="choice ? 'menuitemradio' : 'menuitem'" :aria-checked="choice ? !!it.selected : undefined" :style="{ display: 'flex', alignItems: 'center', gap: '10px', height: 'var(--control-h)', flexShrink: 0, padding: '0 10px', boxSizing: 'border-box', borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', background: it.selected || hover === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontWeight: it.selected ? 'var(--weight-medium)' : 'var(--weight-regular)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: it.danger ? 'var(--danger)' : 'var(--text-heading)', textAlign: 'left', whiteSpace: 'nowrap' }">
|
|
15
18
|
<Icon v-if="it.icon" :name="it.icon" :size="16" :style="{ width: 'var(--control-icon)', height: 'var(--control-icon)' }" />{{ it.label }}
|
|
16
19
|
</button>
|
|
17
20
|
</template>
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
/** Trigger-anchored dropdown menu (reuses ContextMenu's look). Addition — for "Show ⌄"-style buttons and user menus. */
|
|
3
3
|
export interface MenuProps {
|
|
4
4
|
trigger: React.ReactNode;
|
|
5
|
-
items?: { label?: string; icon?: string; danger?: boolean; divider?: boolean }[];
|
|
5
|
+
items?: { label?: string; icon?: string; danger?: boolean; divider?: boolean; /** Current choice of a single-choice menu: --bg-hover fill, --weight-medium --text-heading, no checkmark. Any selected item spaces the rows 2px apart. */ selected?: boolean }[];
|
|
6
6
|
onSelect?: (label: string) => void;
|
|
7
7
|
align?: "start" | "end";
|
|
8
8
|
style?: React.CSSProperties;
|
|
@@ -22,7 +22,7 @@ export function Tooltip({ label, placement = "top", children, style }) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Centered modal dialog with scrim. width defaults to 440. */
|
|
25
|
-
/** Modal dialog. A size region that defaults to lg (
|
|
25
|
+
/** Modal dialog. A size region that defaults to lg (40px controls) whatever the page behind it uses — a dialog does not compete for space; pass `size` to override. 24px padding, footer buttons 8px apart. */
|
|
26
26
|
export function Modal({ open, title, subtitle, onClose, footer, width = 440, size = "lg", children, style }) {
|
|
27
27
|
if (!open) return null;
|
|
28
28
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { px } from '../px.js'
|
|
3
3
|
import IconButton from '../buttons/IconButton.vue'
|
|
4
|
-
// Modal dialog. A size region that defaults to lg (
|
|
4
|
+
// Modal dialog. A size region that defaults to lg (40px controls) whatever the page behind it uses — a dialog does not compete for space; pass `size` to override. 24px padding, footer buttons 8px apart.
|
|
5
5
|
defineProps({ open: Boolean, title: String, subtitle: String, width: { type: [Number, String], default: 440 }, size: { type: String, default: 'lg' }, style: Object })
|
|
6
6
|
const emit = defineEmits(['close'])
|
|
7
7
|
</script>
|
|
@@ -12,8 +12,8 @@ defineProps({ items: { type: Array, default: () => [] }, style: Object })
|
|
|
12
12
|
<span v-if="i < items.length - 1" style="width:1.5px;flex:1;min-height:14px;background:var(--border-control)" />
|
|
13
13
|
</div>
|
|
14
14
|
<div :style="{ display: 'flex', flexDirection: 'column', gap: '2px', paddingBottom: i < items.length - 1 ? '18px' : 0, minWidth: 0, paddingTop: '3px' }">
|
|
15
|
-
<span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)">{{ it.title }}</span>
|
|
16
|
-
<span v-if="it.meta" style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)">{{ it.meta }}</span>
|
|
15
|
+
<span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)">{{ it.title }}</span>
|
|
16
|
+
<span v-if="it.meta" style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted)">{{ it.meta }}</span>
|
|
17
17
|
<span v-if="it.body" style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body);padding-top:4px">{{ it.body }}</span>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Feedback & flow: `Stepper` (wizard circles), `ContextMenu
|
|
1
|
+
Feedback & flow: `Stepper` (wizard circles), `ContextMenu` / `Menu` (action menus flush; `selected: true` on one item makes a single-choice menu with 2px row gaps, no checkmark), `ActivityItem`/`DotActivity` (feeds), `LogViewer` (mono logs).
|
|
2
2
|
|
|
3
3
|
```jsx
|
|
4
4
|
<Stepper current={1} steps={[{label:"Upload & Parse",icon:"upload"},{label:"Comparison",icon:"file-text"},{label:"Confirmation",icon:"check"},{label:"Execution",icon:"play"},{label:"Results",icon:"eye"}]}/>
|
|
@@ -8,7 +8,7 @@ export interface ComboboxProps {
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
error?: boolean;
|
|
10
10
|
width?: number | string;
|
|
11
|
-
/** Size tier: sm
|
|
11
|
+
/** Size tier: sm 28 · md 32 · lg 40. Omit to inherit the enclosing region's tier (the default). */
|
|
12
12
|
size?: "sm" | "md" | "lg";
|
|
13
13
|
onChange?: (value: string) => void;
|
|
14
14
|
style?: React.CSSProperties;
|
|
@@ -7,7 +7,7 @@ export interface DatePickerProps {
|
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
error?: boolean;
|
|
9
9
|
width?: number | string;
|
|
10
|
-
/** Size tier: sm
|
|
10
|
+
/** Size tier: sm 28 · md 32 · lg 40. Omit to inherit the enclosing region's tier (the default). */
|
|
11
11
|
size?: "sm" | "md" | "lg";
|
|
12
12
|
/** BCP 47 locale for the month title, weekday initials and the field label (default "en-US"; e.g. "zh-CN", "ja-JP") */
|
|
13
13
|
locale?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** r8 hairline text input at the region's tier height (sm
|
|
2
|
+
/** r8 hairline text input at the region's tier height (sm 28 · md 32 · lg 40); readOnly = inset fill, disabled = 50% opacity, error = red border+ring. Extends native input props. */
|
|
3
3
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
4
4
|
/** Size tier. Omit to inherit the enclosing region's tier (the default); controls that share a row must share a tier. */
|
|
5
5
|
size?: "sm" | "md" | "lg";
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { inputBase } from "./input-base.js";
|
|
4
4
|
export { inputBase };
|
|
5
5
|
|
|
6
|
-
/** Text input, r8, tier height (sm
|
|
6
|
+
/** Text input, r8, tier height (sm 28 · md 32 · lg 40 — omit `size` to inherit the region's tier). readOnly = inset fill; disabled = 50% opacity; error = red border + ring. */
|
|
7
7
|
export function Input({ readOnly, disabled, error, size, style, ...rest }) {
|
|
8
8
|
const [focus, setFocus] = React.useState(false);
|
|
9
9
|
return <input readOnly={readOnly} disabled={disabled} data-size={size} data-field={readOnly || disabled ? undefined : ""} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
|
|
@@ -30,7 +30,7 @@ export function Textarea({ style, rows = 4, error, disabled, showCount, maxLengt
|
|
|
30
30
|
...(error || over ? { borderColor: "var(--danger)" } : null),
|
|
31
31
|
...(focus && !disabled ? { borderColor: error || over ? "var(--danger)" : "var(--focus-border)", boxShadow: `0 0 0 2px ${error || over ? "var(--danger-bg)" : "var(--focus-ring)"}` } : null),
|
|
32
32
|
...(disabled ? { opacity: .5, cursor: "not-allowed", background: "var(--bg-inset)" } : null), ...style }} {...rest} />
|
|
33
|
-
{showCount ? <span style={{ position: "absolute", right: 10, bottom: 7, fontFamily: "var(--font-
|
|
33
|
+
{showCount ? <span style={{ position: "absolute", right: 10, bottom: 7, fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-xs)", color: over ? "var(--danger)" : "var(--text-muted)" }}>{len}{maxLength != null ? `/${maxLength}` : ""}</span> : null}
|
|
34
34
|
</div>
|
|
35
35
|
);
|
|
36
36
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from 'vue'
|
|
3
3
|
import { inputBase } from './input-base.js'
|
|
4
|
-
// Text input, r8, tier height (sm
|
|
4
|
+
// Text input, r8, tier height (sm 28 · md 32 · lg 40 — omit `size` to inherit the region's tier). v-model. readonly = inset fill; disabled = 50% opacity; error = red border + ring.
|
|
5
5
|
// Multiline variant lives in Textarea.vue.
|
|
6
6
|
const props = defineProps({ modelValue: String, readonly: Boolean, disabled: Boolean, error: Boolean, size: String, style: Object })
|
|
7
7
|
defineEmits(['update:modelValue'])
|
|
@@ -9,7 +9,7 @@ export interface MultiSelectProps {
|
|
|
9
9
|
maxTags?: number;
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
width?: number | string;
|
|
12
|
-
/** Size tier: sm
|
|
12
|
+
/** Size tier: sm 28 · md 32 · lg 40. Omit to inherit the enclosing region's tier (the default). */
|
|
13
13
|
size?: "sm" | "md" | "lg";
|
|
14
14
|
onChange?: (value: string[]) => void;
|
|
15
15
|
style?: React.CSSProperties;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
/** Segmented −/value/+ number stepper at the region's tier height, r8 — "Preview rows" control. */
|
|
3
3
|
export interface NumberStepperProps {
|
|
4
|
-
/** Size tier: sm
|
|
4
|
+
/** Size tier: sm 28 · md 32 · lg 40. Omit to inherit the enclosing region's tier (the default). */
|
|
5
5
|
size?: "sm" | "md" | "lg";
|
|
6
6
|
value?: number;
|
|
7
7
|
onChange?: (next: number) => void;
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
export interface SearchInputProps {
|
|
4
4
|
placeholder?: string;
|
|
5
5
|
bare?: boolean;
|
|
6
|
-
/** Size tier: sm
|
|
6
|
+
/** Size tier: sm 28 · md 32 · lg 40. Omit to inherit the enclosing region's tier (the default). */
|
|
7
7
|
size?: "sm" | "md" | "lg";
|
|
8
8
|
value?: string;
|
|
9
9
|
/** Receives the new string value (consistent with Select / Combobox / MultiSelect) */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Hairline select, r8, at the region's tier height (sm
|
|
2
|
+
/** Hairline select, r8, at the region's tier height (sm 28 · md 32 · lg 40). disabled fills inset like Input's readOnly. */
|
|
3
3
|
export interface SelectProps {
|
|
4
4
|
options?: string[];
|
|
5
5
|
/** Size tier. Omit to inherit the enclosing region's tier (the default); controls that share a row must share a tier. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
/** Native select styled per Keybricks: r8, tier height (sm
|
|
4
|
+
/** Native select styled per Keybricks: r8, tier height (sm 28 · md 32 · lg 40 — omit `size` to inherit the region's tier). disabled = inset fill (matches Input readOnly), 50% opacity; error = red border. */
|
|
5
5
|
export function Select({ options = [], size, value, onChange, placeholder, disabled, error, style }) {
|
|
6
6
|
return (
|
|
7
7
|
<div data-size={size} style={{ position: "relative", display: "inline-flex", ...style }}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import Icon from '../icons/Icon.vue'
|
|
3
|
-
// Native select styled per Keybricks: r8, tier height (sm
|
|
3
|
+
// Native select styled per Keybricks: r8, tier height (sm 28 · md 32 · lg 40 — omit `size` to inherit the region's tier). disabled = inset fill (matches Input readonly), 50% opacity; error = red border.
|
|
4
4
|
const props = defineProps({ modelValue: String, options: { type: Array, default: () => [] }, size: String, placeholder: String, disabled: Boolean, error: Boolean, style: Object })
|
|
5
5
|
defineEmits(['update:modelValue'])
|
|
6
6
|
</script>
|
|
@@ -13,7 +13,7 @@ export function Slider({ value = 50, min = 0, max = 100, step = 1, labels, showV
|
|
|
13
13
|
<div style={{ position: "absolute", left: 0, right: 0, height: 6, borderRadius: "var(--radius-full)", background: "var(--track)" }}></div>
|
|
14
14
|
<div style={{ position: "absolute", left: 0, width: pct + "%", height: 6, borderRadius: "var(--radius-full)", background: "var(--action-primary)" }}></div>
|
|
15
15
|
<span style={{ position: "absolute", left: pct + "%", transform: "translateX(-50%)", width: 16, height: 16, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", border: "2px solid var(--action-primary)", boxSizing: "border-box", boxShadow: drag ? "0 0 0 4px var(--focus-ring), 0 1px 2px rgba(0,0,0,.15)" : "var(--thumb-ring, 0 1px 2px rgba(0,0,0,.15))", transition: "box-shadow var(--dur-fast) var(--ease-out)" }}></span>
|
|
16
|
-
{(drag || showValue) && <span style={{ position: "absolute", left: pct + "%", transform: "translateX(-50%)", bottom: 22, background: "var(--tooltip-bg)", color: "var(--tooltip-fg)", borderRadius: "var(--radius-sm)", padding: "2px 6px", fontFamily: "var(--font-
|
|
16
|
+
{(drag || showValue) && <span style={{ position: "absolute", left: pct + "%", transform: "translateX(-50%)", bottom: 22, background: "var(--tooltip-bg)", color: "var(--tooltip-fg)", borderRadius: "var(--radius-sm)", padding: "2px 6px", fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-xs)", whiteSpace: "nowrap", pointerEvents: "none" }}>{value}{unit}</span>}
|
|
17
17
|
</div>
|
|
18
18
|
{labels && <div style={{ display: "flex", justifyContent: "space-between", paddingTop: 4, fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-muted)" }}><span>{min}{unit}</span><span>{max}{unit}</span></div>}
|
|
19
19
|
</div>
|
|
@@ -16,7 +16,7 @@ onUnmounted(up)
|
|
|
16
16
|
<div style="position:absolute;left:0;right:0;height:6px;border-radius:var(--radius-full);background:var(--track)" />
|
|
17
17
|
<div :style="{ position: 'absolute', left: 0, width: pct + '%', height: '6px', borderRadius: 'var(--radius-full)', background: 'var(--action-primary)' }" />
|
|
18
18
|
<span :style="{ position: 'absolute', left: pct + '%', transform: 'translateX(-50%)', width: '16px', height: '16px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', border: '2px solid var(--action-primary)', boxSizing: 'border-box', boxShadow: drag ? '0 0 0 4px var(--focus-ring), 0 1px 2px rgba(0,0,0,.15)' : 'var(--thumb-ring, 0 1px 2px rgba(0,0,0,.15))', transition: 'box-shadow var(--dur-fast) var(--ease-out)' }" />
|
|
19
|
-
<span v-if="drag || showValue" :style="{ position: 'absolute', left: pct + '%', transform: 'translateX(-50%)', bottom: '22px', background: 'var(--tooltip-bg)', color: 'var(--tooltip-fg)', borderRadius: 'var(--radius-sm)', padding: '2px 6px', fontFamily: 'var(--font-
|
|
19
|
+
<span v-if="drag || showValue" :style="{ position: 'absolute', left: pct + '%', transform: 'translateX(-50%)', bottom: '22px', background: 'var(--tooltip-bg)', color: 'var(--tooltip-fg)', borderRadius: 'var(--radius-sm)', padding: '2px 6px', fontFamily: 'var(--font-sans)', fontVariantNumeric: 'tabular-nums', fontSize: 'var(--text-xs)', whiteSpace: 'nowrap', pointerEvents: 'none' }">{{ modelValue }}{{ unit }}</span>
|
|
20
20
|
</div>
|
|
21
21
|
<div v-if="labels" style="display:flex;justify-content:space-between;padding-top:4px;font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)"><span>{{ min }}{{ unit }}</span><span>{{ max }}{{ unit }}</span></div>
|
|
22
22
|
</div>
|
|
@@ -20,7 +20,7 @@ import { Icon } from "../icons/Icon.jsx";
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
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)"
|
|
23
|
+
const H = "var(--switch-h)", W = "var(--switch-w)", KNOB = "calc(var(--switch-h) - 4px)", GLYPH = "calc(var(--switch-h) * .5)", TEXT = "max(calc(var(--switch-h) * .5), var(--text-glyph-min))" /* text never drops below the glyph floor (12px in CJK) */;
|
|
24
24
|
|
|
25
25
|
const pick = (v, on) => (v && typeof v === "object" ? (on ? v.on : v.off) : v);
|
|
26
26
|
|
|
@@ -60,14 +60,14 @@ export function Switch({ checked = false, onChange, disabled, size, tone = "prim
|
|
|
60
60
|
const glyphText = pick(knobText, checked);
|
|
61
61
|
const knobX = checked ? `calc(100% - ${H} + 2px)` : 2;
|
|
62
62
|
|
|
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:
|
|
63
|
+
const knob = <span aria-hidden="true" style={{ position: "absolute", top: 2, left: knobX, width: KNOB, height: KNOB, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", boxShadow: "0 1px 2px rgba(0,0,0,.18)", transition: "left 160ms ease-out", display: "flex", alignItems: "center", justifyContent: "center", color: track, fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", overflow: "hidden" }}>
|
|
64
64
|
{loading
|
|
65
65
|
? <span style={{ width: GLYPH, height: GLYPH, borderRadius: "50%", border: `1.5px solid ${track}`, borderRightColor: "transparent", animation: "kb-spin 720ms linear infinite" }}></span>
|
|
66
66
|
: glyphIcon ? <Icon name={glyphIcon} size={10} strokeWidth={2.5} style={{ width: GLYPH, height: GLYPH }} />
|
|
67
67
|
: glyphText ? <span>{String(glyphText).slice(0, 1)}</span> : null}
|
|
68
68
|
</span>;
|
|
69
69
|
|
|
70
|
-
const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: 6, display: "flex", alignItems: "center", fontSize:
|
|
70
|
+
const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: 6, display: "flex", alignItems: "center", fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", letterSpacing: ".2px", color: "var(--on-accent)", transition: "opacity 160ms ease-out", pointerEvents: "none", whiteSpace: "nowrap" });
|
|
71
71
|
|
|
72
72
|
const pill = <span role="switch" aria-checked={checked} aria-busy={loading || undefined} aria-disabled={disabled || undefined} tabIndex={inert ? -1 : 0} onClick={flip}
|
|
73
73
|
onKeyDown={e => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); flip(); } }}
|
|
@@ -12,17 +12,20 @@ const props = defineProps({
|
|
|
12
12
|
size: String, tone: { type: String, default: 'primary' },
|
|
13
13
|
onColor: String, offColor: String,
|
|
14
14
|
knobIcon: [String, Object], knobText: [String, Object],
|
|
15
|
-
insideOn: String, insideOff: String, label: String, labelOn: String, labelOff: String,
|
|
15
|
+
insideOn: String, insideOff: String, inlinePrompt: Object, label: String, labelOn: String, labelOff: String,
|
|
16
16
|
beforeChange: Function, style: Object
|
|
17
17
|
})
|
|
18
18
|
const emit = defineEmits(['update:checked', 'change'])
|
|
19
|
-
const H = 'var(--switch-h)', W = 'var(--switch-w)', KNOB = 'calc(var(--switch-h) - 4px)', GLYPH = 'calc(var(--switch-h) * .5)'
|
|
19
|
+
const H = 'var(--switch-h)', W = 'var(--switch-w)', KNOB = 'calc(var(--switch-h) - 4px)', GLYPH = 'calc(var(--switch-h) * .5)', TEXT = 'max(calc(var(--switch-h) * .5), var(--text-glyph-min))' /* text never drops below the glyph floor (12px in CJK) */
|
|
20
20
|
const busy = ref(false); let alive = true; onBeforeUnmount(() => { alive = false })
|
|
21
21
|
const isLoading = computed(() => props.loading || busy.value)
|
|
22
22
|
const inert = computed(() => props.disabled || isLoading.value)
|
|
23
|
-
|
|
23
|
+
// inlinePrompt { on, off } is an alias of the insideOn / insideOff pair (same as Switch.jsx)
|
|
24
|
+
const inOn = computed(() => props.insideOn ?? (props.inlinePrompt ? props.inlinePrompt.on : undefined))
|
|
25
|
+
const inOff = computed(() => props.insideOff ?? (props.inlinePrompt ? props.inlinePrompt.off : undefined))
|
|
26
|
+
const hasInside = computed(() => inOn.value != null || inOff.value != null)
|
|
24
27
|
// 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((
|
|
28
|
+
const width = computed(() => { if (!hasInside.value) return W; const n = Math.max((inOn.value || '').length, (inOff.value || '').length); return `max(${W}, calc(${H} * ${(1 + n * .34).toFixed(2)} + 6px))` })
|
|
26
29
|
const track = computed(() => props.checked ? (props.onColor || (props.tone === 'semantic' ? 'var(--success)' : 'var(--action-primary)')) : (props.offColor || (props.tone === 'semantic' ? 'var(--danger)' : 'var(--border-strong)')))
|
|
27
30
|
const pick = (v) => v && typeof v === 'object' ? (props.checked ? v.on : v.off) : v
|
|
28
31
|
const glyphIcon = computed(() => pick(props.knobIcon))
|
|
@@ -40,7 +43,7 @@ async function flip() {
|
|
|
40
43
|
}
|
|
41
44
|
emit('update:checked', next); emit('change', next)
|
|
42
45
|
}
|
|
43
|
-
const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize:
|
|
46
|
+
const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', letterSpacing: '.2px', color: 'var(--on-accent)', transition: 'opacity 160ms ease-out', pointerEvents: 'none', whiteSpace: 'nowrap', opacity: on === props.checked ? 1 : 0 })
|
|
44
47
|
const cap = (active) => ({ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: active ? 'var(--weight-medium)' : 'var(--weight-regular)', color: active ? 'var(--text-heading)' : 'var(--text-muted)', transition: 'color 160ms ease-out', cursor: inert.value ? 'default' : 'pointer', userSelect: 'none' })
|
|
45
48
|
</script>
|
|
46
49
|
<template>
|
|
@@ -48,9 +51,9 @@ const cap = (active) => ({ fontSize: 'var(--control-text)', lineHeight: 'var(--c
|
|
|
48
51
|
<span v-if="labelOff != null" :style="cap(!checked)" @click="flip">{{ labelOff }}</span>
|
|
49
52
|
<span role="switch" :aria-checked="checked" :aria-busy="isLoading || undefined" :aria-disabled="disabled || undefined" :tabindex="inert ? -1 : 0" @click="flip" @keydown.space.prevent="flip" @keydown.enter.prevent="flip"
|
|
50
53
|
:style="{ position: 'relative', display: 'inline-block', width: width, height: H, borderRadius: 'var(--radius-full)', '--sw-track': track, background: 'var(--sw-bg, var(--sw-track))', transition: 'background 160ms ease-out', cursor: inert ? (isLoading ? 'progress' : 'not-allowed') : 'pointer', flexShrink: 0, verticalAlign: 'middle', outline: 'none' }">
|
|
51
|
-
<span v-if="hasInside" :style="insideStyle('left', true)">{{
|
|
52
|
-
<span v-if="hasInside" :style="insideStyle('right', false)">{{
|
|
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:
|
|
54
|
+
<span v-if="hasInside" :style="insideStyle('left', true)">{{ inOn }}</span>
|
|
55
|
+
<span v-if="hasInside" :style="insideStyle('right', false)">{{ inOff }}</span>
|
|
56
|
+
<span aria-hidden="true" :style="{ position: 'absolute', top: '2px', left: knobX, width: KNOB, height: KNOB, borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', boxShadow: '0 1px 2px rgba(0,0,0,.18)', transition: 'left 160ms ease-out', display: 'flex', alignItems: 'center', justifyContent: 'center', color: track, fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
|
|
54
57
|
<span v-if="isLoading" :style="{ width: GLYPH, height: GLYPH, borderRadius: '50%', border: '1.5px solid ' + track, borderRightColor: 'transparent', animation: 'kb-spin 720ms linear infinite' }"></span>
|
|
55
58
|
<Icon v-else-if="glyphIcon" :name="glyphIcon" :size="10" :stroke-width="2.5" :style="{ width: GLYPH, height: GLYPH }" />
|
|
56
59
|
<span v-else-if="glyphText">{{ glyphText }}</span>
|
|
@@ -15,6 +15,6 @@ const s = computed(() => ({ ...inputBase, height: '90px', resize: 'vertical', pa
|
|
|
15
15
|
<template>
|
|
16
16
|
<div style="position:relative">
|
|
17
17
|
<textarea :rows="rows" :disabled="disabled" :data-size="size" :data-field="disabled ? undefined : ''" :maxlength="showCount ? undefined : maxLength" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" @focus="focus = true" @blur="focus = false" :style="s"></textarea>
|
|
18
|
-
<span v-if="showCount" :style="{ position: 'absolute', right: '10px', bottom: '7px', fontFamily: 'var(--font-
|
|
18
|
+
<span v-if="showCount" :style="{ position: 'absolute', right: '10px', bottom: '7px', fontFamily: 'var(--font-sans)', fontVariantNumeric: 'tabular-nums', fontSize: 'var(--text-xs)', color: over ? 'var(--danger)' : 'var(--text-muted)' }">{{ len }}{{ maxLength != null ? `/${maxLength}` : '' }}</span>
|
|
19
19
|
</div>
|
|
20
20
|
</template>
|
|
@@ -1,4 +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
|
-
// Height, text and padding come from the size tier of the enclosing region (tokens/layout.css): sm
|
|
3
|
+
// Height, text and padding come from the size tier of the enclosing region (tokens/layout.css): sm 28 · md 32 · lg 40.
|
|
4
4
|
export const inputBase = { height: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: '1px solid var(--field-border, var(--border-control))', background: 'var(--bg-surface)', padding: '0 var(--control-px)', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-primary)', outline: 'none', boxSizing: 'border-box', width: '100%' }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
// Keybricks icon set — the source file uses Lucide stroke glyphs (24-grid, stroke 2,
|
|
3
3
|
// round caps/joins, currentColor) rendered at 14/16px. Path data below is from the
|
|
4
|
-
// public Lucide set, matching the glyphs used across Keyfunc UI v8
|
|
4
|
+
// public Lucide set, matching the glyphs used across Keyfunc UI v8, plus thirteen Keybricks
|
|
5
|
+
// glyphs (event … time-restatement, ai, ai-sparkles) drawn to the same rules.
|
|
5
6
|
export const ICON_PATHS = {
|
|
6
7
|
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
7
8
|
bell: '<path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>',
|
|
@@ -74,6 +75,21 @@ export const ICON_PATHS = {
|
|
|
74
75
|
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
75
76
|
"triangle-alert": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 20h16a2 2 0 0 0 1.73-2Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
76
77
|
calendar: '<path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/>',
|
|
78
|
+
// Keybricks glyphs — drawn to the core-set rules (see readme.md › Iconography)
|
|
79
|
+
event: '<path d="M17 2 22 12 17 22 12 12Z"/><path d="M7 2 2 12l5 10"/><path d="M12 2 7 12l5 10"/>',
|
|
80
|
+
consolidation: '<rect width="4" height="4" x="2" y="17" rx="1"/><rect width="4" height="4" x="10" y="17" rx="1"/><rect width="4" height="4" x="18" y="17" rx="1"/><path d="M4 17v-5a2 2 0 0 1 2-2h2"/><path d="M20 17v-5a2 2 0 0 0-2-2h-2"/><path d="M12 17v-4"/><path d="M12 3v6"/><path d="M9 6h6"/>',
|
|
81
|
+
journal: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="M2 8h20"/><path d="M12 8v12"/><path d="M6 12h2"/><path d="M16 16h2"/>',
|
|
82
|
+
reconciliation: '<path d="M3 6h8"/><path d="m15 4 2 2 4-4"/><path d="M3 10h18"/><path d="M3 18h8"/><path d="m15 16 2 2 4-4"/><path d="M3 22h18"/>',
|
|
83
|
+
"view-tree": '<rect width="4" height="4" x="4" y="2" rx="1"/><rect width="4" height="4" x="16" y="10" rx="1"/><rect width="4" height="4" x="16" y="18" rx="1"/><path d="M6 6v4a2 2 0 0 0 2 2h8"/><path d="M6 10v8a2 2 0 0 0 2 2h8"/>',
|
|
84
|
+
"drilldown": '<path d="M12 2v11"/><path d="m8 9 4 4 4-4"/><path d="M19 17H5"/><path d="M16 21H8"/>',
|
|
85
|
+
"add-below": '<path d="M11 2v10"/><path d="M6 7h10"/><path d="M22 9v5a4 4 0 0 1-4 4H2"/><path d="m6 14-4 4 4 4"/>',
|
|
86
|
+
"copy-data": '<path d="M14 22h5a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-5"/><path d="m11 16 4-4-4-4"/><path d="M3 22v-6a4 4 0 0 1 4-4h8"/>',
|
|
87
|
+
"task-execution": '<rect width="8" height="4" x="8" y="2" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M8 14h8"/><path d="m13 11 3 3-3 3"/>',
|
|
88
|
+
"time-future": '<path d="M20 6A10 10 0 1 0 22 12"/><path d="M12 7v5h5"/><path d="M21 2v5h-5"/>',
|
|
89
|
+
"time-restatement": '<path d="M2 12A10 10 0 1 0 4 6"/><path d="M12 7v5h5"/><path d="M3 2v5h5"/>',
|
|
90
|
+
// AI glyphs (2.3.0) — traced from the two reference glyphs, redrawn to core rules (stroke 2, no fill, integer endpoints, >=4 between separate elements)
|
|
91
|
+
ai: '<path d="M9 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-3"/><path d="M16 2c1.2 3 3 4.8 6 6c-3 1.2-4.8 3-6 6c-1.2-3-3-4.8-6-6c3-1.2 4.8-3 6-6Z"/><path d="M9 12c.6 1.5 1.5 2.4 3 3c-1.5.6-2.4 1.5-3 3c-.6-1.5-1.5-2.4-3-3c1.5-.6 2.4-1.5 3-3Z"/>',
|
|
92
|
+
"ai-sparkles": '<path d="M9 2c1.4 3.5 3.5 5.6 7 7c-3.5 1.4-5.6 3.5-7 7c-1.4-3.5-3.5-5.6-7-7c3.5-1.4 5.6-3.5 7-7Z"/><path d="M17 13c.8 2 2 3.2 4 4c-2 .8-3.2 2-4 4c-.8-2-2-3.2-4-4c2-.8 3.2-2 4-4Z"/>',
|
|
77
93
|
};
|
|
78
94
|
|
|
79
95
|
import { FEATHER_PATHS } from "./feather-paths.js";
|
|
@@ -6,3 +6,5 @@ Stroke icon component embedding the Lucide glyphs used across Keybricks; use for
|
|
|
6
6
|
```
|
|
7
7
|
|
|
8
8
|
Sizes in the product: 14 (dense chrome), 16 (nav/buttons), 18 (palette tiles), 22 (node tiles). Color inherits `currentColor`. Categories pair hue + tint bg: source `database/table/layers/grid-3x3`, join `text-search/shuffle`, transform `sliders-horizontal/split/arrow-right-left/menu/funnel/chart-no-axes-column/trending-up/code`, merge `database/merge`, output `table/layers/grid-3x3`.
|
|
9
|
+
|
|
10
|
+
Keybricks glyphs (drawn to the same grid rules): modules `reconciliation`, `consolidation`, `journal`, `event`, `view-tree`; actions `drilldown`, `add-below`, `copy-data`, `task-execution`, `time-future`, `time-restatement`. For hierarchy use `layers`; for change use `repeat`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
// Keybricks icon set — Lucide stroke glyphs (24-grid, stroke 2, round caps/joins, currentColor), rendered at 14/16px.
|
|
3
|
-
// Full path dictionary mirrors icons/Icon.jsx's ICON_PATHS (
|
|
3
|
+
// Full path dictionary mirrors icons/Icon.jsx's ICON_PATHS (81 entries) — see that file for the complete list.
|
|
4
4
|
import { VUE_ICON_PATHS } from './icon-paths.js'
|
|
5
5
|
import { FEATHER_PATHS } from './feather-paths.js'
|
|
6
6
|
const props = defineProps({ name: String, size: { type: Number, default: 16 }, strokeWidth: { type: Number, default: 2 }, color: String, style: Object })
|
|
@@ -70,4 +70,19 @@ export const VUE_ICON_PATHS = {
|
|
|
70
70
|
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
|
71
71
|
"triangle-alert": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 20h16a2 2 0 0 0 1.73-2Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
72
72
|
calendar: '<path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/>',
|
|
73
|
+
// Keybricks glyphs — drawn to the core-set rules (see readme.md › Iconography)
|
|
74
|
+
event: '<path d="M17 2 22 12 17 22 12 12Z"/><path d="M7 2 2 12l5 10"/><path d="M12 2 7 12l5 10"/>',
|
|
75
|
+
consolidation: '<rect width="4" height="4" x="2" y="17" rx="1"/><rect width="4" height="4" x="10" y="17" rx="1"/><rect width="4" height="4" x="18" y="17" rx="1"/><path d="M4 17v-5a2 2 0 0 1 2-2h2"/><path d="M20 17v-5a2 2 0 0 0-2-2h-2"/><path d="M12 17v-4"/><path d="M12 3v6"/><path d="M9 6h6"/>',
|
|
76
|
+
journal: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="M2 8h20"/><path d="M12 8v12"/><path d="M6 12h2"/><path d="M16 16h2"/>',
|
|
77
|
+
reconciliation: '<path d="M3 6h8"/><path d="m15 4 2 2 4-4"/><path d="M3 10h18"/><path d="M3 18h8"/><path d="m15 16 2 2 4-4"/><path d="M3 22h18"/>',
|
|
78
|
+
"view-tree": '<rect width="4" height="4" x="4" y="2" rx="1"/><rect width="4" height="4" x="16" y="10" rx="1"/><rect width="4" height="4" x="16" y="18" rx="1"/><path d="M6 6v4a2 2 0 0 0 2 2h8"/><path d="M6 10v8a2 2 0 0 0 2 2h8"/>',
|
|
79
|
+
"drilldown": '<path d="M12 2v11"/><path d="m8 9 4 4 4-4"/><path d="M19 17H5"/><path d="M16 21H8"/>',
|
|
80
|
+
"add-below": '<path d="M11 2v10"/><path d="M6 7h10"/><path d="M22 9v5a4 4 0 0 1-4 4H2"/><path d="m6 14-4 4 4 4"/>',
|
|
81
|
+
"copy-data": '<path d="M14 22h5a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-5"/><path d="m11 16 4-4-4-4"/><path d="M3 22v-6a4 4 0 0 1 4-4h8"/>',
|
|
82
|
+
"task-execution": '<rect width="8" height="4" x="8" y="2" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M8 14h8"/><path d="m13 11 3 3-3 3"/>',
|
|
83
|
+
"time-future": '<path d="M20 6A10 10 0 1 0 22 12"/><path d="M12 7v5h5"/><path d="M21 2v5h-5"/>',
|
|
84
|
+
"time-restatement": '<path d="M2 12A10 10 0 1 0 4 6"/><path d="M12 7v5h5"/><path d="M3 2v5h5"/>',
|
|
85
|
+
// AI glyphs (2.3.0) — traced from the two reference glyphs, redrawn to core rules (stroke 2, no fill, integer endpoints, >=4 between separate elements)
|
|
86
|
+
ai: '<path d="M9 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h10a4 4 0 0 0 4-4v-3"/><path d="M16 2c1.2 3 3 4.8 6 6c-3 1.2-4.8 3-6 6c-1.2-3-3-4.8-6-6c3-1.2 4.8-3 6-6Z"/><path d="M9 12c.6 1.5 1.5 2.4 3 3c-1.5.6-2.4 1.5-3 3c-.6-1.5-1.5-2.4-3-3c1.5-.6 2.4-1.5 3-3Z"/>',
|
|
87
|
+
"ai-sparkles": '<path d="M9 2c1.4 3.5 3.5 5.6 7 7c-3.5 1.4-5.6 3.5-7 7c-1.4-3.5-3.5-5.6-7-7c3.5-1.4 5.6-3.5 7-7Z"/><path d="M17 13c.8 2 2 3.2 4 4c-2 .8-3.2 2-4 4c-.8-2-2-3.2-4-4c2-.8 3.2-2 4-4Z"/>',
|
|
73
88
|
};
|
package/components/index.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ export * from "./icons/Icon";
|
|
|
73
73
|
export * from "./icons/Unicon";
|
|
74
74
|
export * from "./navigation/Breadcrumb";
|
|
75
75
|
export * from "./navigation/DocTree";
|
|
76
|
+
export * from "./navigation/SidePanel";
|
|
76
77
|
export * from "./navigation/Sidebar";
|
|
77
78
|
export * from "./navigation/TopBar";
|
|
78
79
|
export * from "./navigation/Tree";
|
package/components/index.js
CHANGED
|
@@ -90,10 +90,11 @@ export { ICON_PATHS, Icon } from "./icons/Icon.jsx";
|
|
|
90
90
|
export { Unicon } from "./icons/Unicon.jsx";
|
|
91
91
|
|
|
92
92
|
/* navigation/ */
|
|
93
|
-
export { Breadcrumb, ThemeToggle } from "./navigation/Breadcrumb.jsx";
|
|
93
|
+
export { Breadcrumb, ThemeToggle, applyStoredTheme } from "./navigation/Breadcrumb.jsx";
|
|
94
94
|
export { DocTree } from "./navigation/DocTree.jsx";
|
|
95
|
-
export {
|
|
96
|
-
export {
|
|
95
|
+
export { SidePanel } from "./navigation/SidePanel.jsx";
|
|
96
|
+
export { KEYBRICKS_MGMT, KEYBRICKS_NAV, KEYBRICKS_NAV_FOOTER, SIDEBAR_COLORS, Sidebar } from "./navigation/Sidebar.jsx";
|
|
97
|
+
export { PageHeading, PageToolbar, THEME_OPTIONS, TopBar, UserMenu } from "./navigation/TopBar.jsx";
|
|
97
98
|
export { Tree } from "./navigation/Tree.jsx";
|
|
98
99
|
|
|
99
100
|
/* pipeline/ */
|
|
@@ -106,5 +107,5 @@ export { ZoomControls } from "./pipeline/ZoomControls.jsx";
|
|
|
106
107
|
|
|
107
108
|
/* tabs/ */
|
|
108
109
|
export { SegmentedControl } from "./tabs/SegmentedControl.jsx";
|
|
109
|
-
export { PanelTabs, PillTabs, Tabs } from "./tabs/Tabs.jsx";
|
|
110
|
+
export { CountBadge, PanelTabs, PillTabs, Tabs } from "./tabs/Tabs.jsx";
|
|
110
111
|
export { ViewToggle } from "./tabs/ViewToggle.jsx";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
import { applyStoredTheme } from "./theme-store.js";
|
|
3
4
|
|
|
4
5
|
function Crumb({ label, last, hovered, onSelect }) {
|
|
5
6
|
const [hover, setHover] = React.useState(false);
|
|
@@ -111,10 +112,5 @@ export function ThemeToggle({ size = 34, dark: darkProp, onToggle, style }) {
|
|
|
111
112
|
);
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
/** Reads the persisted theme and applies it before first paint. Call once at app start. */
|
|
115
|
-
export
|
|
116
|
-
if (typeof document === "undefined") return;
|
|
117
|
-
let t = "light";
|
|
118
|
-
try { t = localStorage.getItem("kb-theme") || "light"; } catch (e) {}
|
|
119
|
-
document.documentElement.dataset.theme = t;
|
|
120
|
-
}
|
|
115
|
+
/** Reads the persisted theme and applies it before first paint. Call once at app start. Defined in theme-store.js, shared with the Vue entry. */
|
|
116
|
+
export { applyStoredTheme };
|
|
@@ -62,11 +62,5 @@ const crumbStyle = (i, it) => ({ fontFamily: 'var(--font-sans)', fontSize: 'var(
|
|
|
62
62
|
</nav>
|
|
63
63
|
</template>
|
|
64
64
|
|
|
65
|
-
<!-- applyStoredTheme()
|
|
66
|
-
|
|
67
|
-
export function applyStoredTheme() {
|
|
68
|
-
let t = 'light'
|
|
69
|
-
try { t = localStorage.getItem('kb-theme') || 'light' } catch (e) {}
|
|
70
|
-
document.documentElement.dataset.theme = t
|
|
71
|
-
}
|
|
72
|
-
-->
|
|
65
|
+
<!-- applyStoredTheme() lives in ./theme-store.js (shared with React) and is exported from the Vue entry.
|
|
66
|
+
Call it once in main.js before mounting. -->
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Side panel docked beside the Sidebar (Documents, Management): 240px, same tone as the rail, grows open from 0 and fades its content in.
|
|
3
|
+
* 56px header: title + actions + close. The body scrolls. Vue: #actions slot, @close. */
|
|
4
|
+
export interface SidePanelProps {
|
|
5
|
+
open?: boolean;
|
|
6
|
+
title?: React.ReactNode;
|
|
7
|
+
/** Extra header buttons left of close (e.g. a muted IconButton "maximize-2" to open the full page) */
|
|
8
|
+
actions?: React.ReactNode;
|
|
9
|
+
/** Shows the close button. Vue: @close (hide it with :closable="false"). */
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
closeLabel?: string;
|
|
12
|
+
closeIcon?: string;
|
|
13
|
+
/** Same values as Sidebar — keep the two in step */
|
|
14
|
+
tone?: "recessed" | "flush" | "contrast" | "custom";
|
|
15
|
+
color?: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
/** Size tier of the panel body ("sm" for compact document lists) */
|
|
18
|
+
size?: "sm" | "md" | "lg";
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
style?: React.CSSProperties;
|
|
21
|
+
}
|
|
22
|
+
export declare function SidePanel(props: SidePanelProps): JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { IconButton } from "../buttons/IconButton.jsx";
|
|
3
|
+
import { chromeTone } from "./chrome-tones.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Side panel docked beside the Sidebar (Documents, Management). 240px, same tone as the rail, opens by growing from 0
|
|
7
|
+
* (--dur-slow, --ease-out) while its content fades in from 12px left. 56px header: title + actions + close.
|
|
8
|
+
* Body scrolls; put a SegmentedControl, SearchInput, Tree or rows inside. size="sm" makes its controls compact.
|
|
9
|
+
*/
|
|
10
|
+
export function SidePanel({ open = true, title, actions, onClose, closeLabel = "Close", closeIcon = "x", tone = "recessed", color, width = 240, size, children, style }) {
|
|
11
|
+
const t = chromeTone(tone, color);
|
|
12
|
+
return (
|
|
13
|
+
<div aria-hidden={open ? undefined : "true"} style={{ width: open ? width : 0, flexShrink: 0, overflow: "hidden", height: "100%", transition: "width var(--dur-slow) var(--ease-out)" }}>
|
|
14
|
+
<div data-theme={t.theme} data-size={size} style={{ ...t.vars, width, height: "100%", background: "var(--sb-bg)", borderRight: "1px solid var(--sb-border)", color: "var(--sb-text)", display: "flex", flexDirection: "column", boxSizing: "border-box", opacity: open ? 1 : 0, transform: open ? "none" : "translateX(-12px)", transition: "opacity var(--dur) var(--ease-out), transform var(--dur-slow) var(--ease-out)", pointerEvents: open ? "auto" : "none", ...style }}>
|
|
15
|
+
<div style={{ height: "var(--topbar-h)", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, padding: "0 8px 0 16px", boxSizing: "border-box", flexShrink: 0 }}>
|
|
16
|
+
<span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", fontWeight: "var(--weight-medium)", color: "var(--text-heading)" }}>{title}</span>
|
|
17
|
+
<div data-size="md" style={{ display: "flex", alignItems: "center", gap: 2, flexShrink: 0 }}>{actions}{onClose ? <IconButton icon={closeIcon} muted label={closeLabel} title={closeLabel} onClick={onClose} /> : null}</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div style={{ flex: 1, minHeight: 0, overflow: "auto", display: "flex", flexDirection: "column" }}>{children}</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|