@keyfunc/keybricks-ds 1.0.4 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPLOY.md +4 -4
- package/DESIGN.md +154 -53
- package/INTEGRATION.md +174 -35
- package/SKILL.md +6 -4
- package/_adherence.oxlintrc.json +164 -68
- package/_scripts/build-index.mjs +9 -0
- package/_scripts/build-tokens.mjs +5 -2
- package/assets/icons/icon-index.js +1 -1
- package/assets/icons/icon-index.json +1 -1
- package/components/_cards/accordion.card.html +2 -2
- package/components/_cards/action-chip.card.html +8 -3
- package/components/_cards/activity.card.html +2 -2
- package/components/_cards/alert.card.html +2 -2
- package/components/_cards/app-chrome.card.html +4 -4
- package/components/_cards/banner.card.html +1 -1
- package/components/_cards/breadcrumb.card.html +2 -2
- package/components/_cards/button-1-base.card.html +2 -2
- package/components/_cards/button-2-states.card.html +14 -14
- package/components/_cards/button-3-sizes-icons.card.html +1 -1
- package/components/_cards/button-4-group.card.html +5 -5
- package/components/_cards/canvas-zoom-controls.card.html +1 -1
- package/components/_cards/cards-indicators.card.html +2 -2
- package/components/_cards/charts-1-sparkline.card.html +2 -2
- package/components/_cards/charts-10-gauge.card.html +4 -4
- package/components/_cards/charts-11-funnel.card.html +2 -2
- package/components/_cards/charts-12-treemap.card.html +2 -2
- package/components/_cards/charts-14-radar.card.html +1 -1
- package/components/_cards/charts-15-distribution.card.html +1 -1
- package/components/_cards/charts-16-pareto.card.html +2 -2
- package/components/_cards/charts-17-candlestick.card.html +2 -2
- package/components/_cards/charts-18-sankey.card.html +2 -2
- package/components/_cards/charts-19-chord.card.html +2 -2
- package/components/_cards/charts-2-line.card.html +2 -2
- package/components/_cards/charts-20-sunburst.card.html +2 -2
- package/components/_cards/charts-21-worldmap.card.html +2 -2
- package/components/_cards/charts-22-gantt.card.html +2 -2
- package/components/_cards/charts-23-bar3d.card.html +2 -2
- package/components/_cards/charts-24-pie3d.card.html +2 -2
- package/components/_cards/charts-25-surface3d.card.html +2 -2
- package/components/_cards/charts-26-scatter3d.card.html +2 -2
- package/components/_cards/charts-27-ribbon3d.card.html +2 -2
- package/components/_cards/charts-3-bar.card.html +2 -2
- package/components/_cards/charts-4-barlist.card.html +2 -2
- package/components/_cards/charts-5-donut.card.html +3 -3
- package/components/_cards/charts-6-scatter.card.html +2 -2
- package/components/_cards/charts-7-heatmap.card.html +2 -2
- package/components/_cards/charts-8-waterfall.card.html +2 -2
- package/components/_cards/charts-9-bullet.card.html +2 -2
- package/components/_cards/charts-99-fullpage.card.html +5 -5
- package/components/_cards/checkbox.card.html +4 -4
- package/components/_cards/codeblock.card.html +1 -1
- package/components/_cards/combobox.card.html +3 -2
- package/components/_cards/context-menu.card.html +3 -2
- package/components/_cards/date-picker.card.html +5 -2
- package/components/_cards/diffvalue.card.html +2 -2
- package/components/_cards/divider.card.html +3 -3
- package/components/_cards/drawer.card.html +2 -2
- package/components/_cards/dropdown-menu.card.html +6 -6
- package/components/_cards/dropzone.card.html +1 -1
- package/components/_cards/empty-state.card.html +5 -5
- package/components/_cards/filter-bar.card.html +5 -5
- package/components/_cards/icon-button.card.html +2 -2
- package/components/_cards/icons-1-core.card.html +2 -2
- package/components/_cards/icons-2-feather.card.html +3 -3
- package/components/_cards/icons-3-unicons.card.html +3 -3
- package/components/_cards/input.card.html +5 -2
- package/components/_cards/kbd.card.html +2 -2
- package/components/_cards/keyvalue.card.html +2 -2
- package/components/_cards/listrow.card.html +2 -2
- package/components/_cards/loading.card.html +2 -2
- package/components/_cards/log-viewer.card.html +2 -2
- package/components/_cards/modal.card.html +9 -9
- package/components/_cards/multi-select.card.html +3 -2
- package/components/_cards/number-stepper.card.html +3 -2
- package/components/_cards/pagination.card.html +3 -2
- package/components/_cards/people.card.html +3 -3
- package/components/_cards/pipeline-node.card.html +2 -2
- package/components/_cards/pipeline-palette.card.html +4 -4
- package/components/_cards/radio.card.html +2 -2
- package/components/_cards/search.card.html +5 -2
- package/components/_cards/segmented-control.card.html +2 -2
- package/components/_cards/select.card.html +4 -4
- package/components/_cards/slider.card.html +1 -1
- package/components/_cards/smart-table.card.html +1 -1
- package/components/_cards/spreadsheet.card.html +2 -2
- package/components/_cards/stepper.card.html +2 -2
- package/components/_cards/table.card.html +103 -0
- package/components/_cards/tabs.card.html +3 -2
- package/components/_cards/tag.card.html +5 -2
- package/components/_cards/textarea.card.html +2 -2
- package/components/_cards/timeline.card.html +1 -1
- package/components/_cards/toast.card.html +4 -4
- package/components/_cards/toggles.card.html +3 -3
- package/components/_cards/tooltip.card.html +5 -5
- package/components/_cards/tree.card.html +2 -2
- package/components/_shared/src-buttons.js +1 -1
- package/components/_shared/src-charts.js +1 -1
- package/components/_shared/src-data.js +1 -1
- package/components/_shared/src-display.js +1 -1
- package/components/_shared/src-feedback.js +1 -1
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-navigation.js +1 -1
- package/components/_shared/src-pipeline.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/_vue/index.mjs +9 -5
- package/components/buttons/ActionChip.d.ts +3 -1
- package/components/buttons/ActionChip.jsx +4 -4
- package/components/buttons/ActionChip.vue +4 -4
- package/components/buttons/Button.d.ts +5 -2
- package/components/buttons/Button.jsx +15 -18
- package/components/buttons/Button.vue +17 -18
- package/components/buttons/ButtonGroup.vue +1 -1
- package/components/buttons/IconButton.d.ts +2 -1
- package/components/buttons/IconButton.jsx +9 -7
- package/components/buttons/IconButton.vue +10 -8
- package/components/buttons/buttons.prompt.md +6 -5
- package/components/charts/Bar3D.jsx +2 -2
- package/components/charts/Bar3D.vue +2 -2
- package/components/charts/BarChart.jsx +4 -4
- package/components/charts/BarChart.vue +4 -4
- package/components/charts/BarList.jsx +1 -1
- package/components/charts/BarList.vue +1 -1
- package/components/charts/BoxPlot.jsx +1 -1
- package/components/charts/BoxPlot.vue +2 -2
- package/components/charts/BulletChart.jsx +1 -1
- package/components/charts/BulletChart.vue +1 -1
- package/components/charts/CandlestickChart.jsx +2 -2
- package/components/charts/CandlestickChart.vue +3 -3
- package/components/charts/ChartKit.jsx +5 -5
- package/components/charts/ChartLegend.vue +16 -0
- package/components/charts/ChartTooltip.vue +16 -0
- package/components/charts/ChordChart.jsx +2 -2
- package/components/charts/ChordChart.vue +2 -2
- package/components/charts/GanttChart.jsx +2 -2
- package/components/charts/GanttChart.vue +2 -2
- package/components/charts/GaugeChart.jsx +2 -2
- package/components/charts/GaugeChart.vue +2 -2
- package/components/charts/HeatmapChart.jsx +2 -2
- package/components/charts/HeatmapChart.vue +2 -2
- package/components/charts/Histogram.jsx +2 -2
- package/components/charts/Histogram.vue +3 -3
- package/components/charts/LineChart.jsx +1 -1
- package/components/charts/LineChart.vue +3 -3
- package/components/charts/ParetoChart.jsx +3 -3
- package/components/charts/ParetoChart.vue +4 -4
- package/components/charts/PlotGrid.vue +18 -0
- package/components/charts/RadarChart.jsx +1 -1
- package/components/charts/RadarChart.vue +1 -1
- package/components/charts/Ribbon3D.jsx +2 -2
- package/components/charts/Ribbon3D.vue +2 -2
- package/components/charts/SankeyChart.jsx +2 -2
- package/components/charts/SankeyChart.vue +2 -2
- package/components/charts/Scatter3D.jsx +1 -1
- package/components/charts/Scatter3D.vue +1 -1
- package/components/charts/ScatterChart.jsx +3 -3
- package/components/charts/ScatterChart.vue +4 -4
- package/components/charts/Sparkline.jsx +1 -1
- package/components/charts/Sparkline.vue +1 -1
- package/components/charts/SunburstChart.jsx +1 -1
- package/components/charts/SunburstChart.vue +1 -1
- package/components/charts/Surface3D.jsx +2 -2
- package/components/charts/Surface3D.vue +2 -2
- package/components/charts/TreemapChart.jsx +2 -2
- package/components/charts/TreemapChart.vue +2 -2
- package/components/charts/WaterfallChart.jsx +2 -2
- package/components/charts/WaterfallChart.vue +3 -3
- package/components/charts/WorldMap.jsx +2 -2
- package/components/charts/WorldMap.vue +2 -2
- package/components/charts/XLabels.vue +11 -0
- package/components/data/CodeBlock.jsx +1 -1
- package/components/data/CodeBlock.vue +1 -1
- package/components/data/DiffValue.jsx +1 -1
- package/components/data/DiffValue.vue +1 -1
- package/components/data/FilterBar.d.ts +2 -0
- package/components/data/FilterBar.jsx +10 -9
- package/components/data/FilterBar.vue +9 -8
- package/components/data/KeyValue.jsx +3 -3
- package/components/data/KeyValue.vue +3 -3
- package/components/data/ListRow.jsx +2 -2
- package/components/data/ListRow.vue +2 -2
- package/components/data/Pagination.d.ts +5 -1
- package/components/data/Pagination.jsx +16 -12
- package/components/data/Pagination.vue +14 -9
- package/components/data/SmartTable.d.ts +4 -1
- package/components/data/SmartTable.jsx +16 -14
- package/components/data/SmartTable.vue +25 -22
- package/components/data/Spreadsheet.d.ts +2 -0
- package/components/data/Spreadsheet.jsx +12 -11
- package/components/data/Spreadsheet.vue +14 -13
- package/components/data/Table.d.ts +47 -0
- package/components/data/Table.jsx +85 -0
- package/components/data/Table.vue +63 -0
- package/components/data/data.prompt.md +3 -3
- package/components/display/Avatar.vue +3 -2
- package/components/display/AvatarGroup.vue +3 -2
- package/components/display/Badge.vue +1 -1
- package/components/display/Divider.vue +3 -3
- package/components/display/Donut.vue +3 -2
- package/components/display/IconTile.vue +2 -1
- package/components/display/Kbd.vue +1 -1
- package/components/display/ProgressBar.jsx +1 -1
- package/components/display/ProgressBar.vue +1 -1
- package/components/display/StatCard.vue +1 -1
- package/components/display/Tag.d.ts +3 -1
- package/components/display/Tag.jsx +4 -4
- package/components/display/Tag.vue +4 -4
- package/components/feedback/Accordion.jsx +2 -2
- package/components/feedback/Accordion.vue +2 -2
- package/components/feedback/ActivityItem.d.ts +3 -1
- package/components/feedback/ActivityItem.jsx +5 -5
- package/components/feedback/ActivityItem.vue +5 -4
- package/components/feedback/Alert.jsx +4 -4
- package/components/feedback/Alert.vue +3 -3
- package/components/feedback/Banner.jsx +5 -5
- package/components/feedback/Banner.vue +5 -5
- package/components/feedback/ContextMenu.d.ts +3 -1
- package/components/feedback/ContextMenu.jsx +5 -5
- package/components/feedback/ContextMenu.vue +5 -5
- package/components/feedback/DotActivity.vue +3 -3
- package/components/feedback/Drawer.d.ts +2 -0
- package/components/feedback/Drawer.jsx +6 -6
- package/components/feedback/Drawer.vue +22 -15
- package/components/feedback/EmptyState.vue +2 -2
- package/components/feedback/LogViewer.vue +1 -1
- package/components/feedback/Modal.d.ts +2 -0
- package/components/feedback/Modal.jsx +12 -11
- package/components/feedback/Modal.vue +10 -8
- package/components/feedback/Skeleton.vue +2 -1
- package/components/feedback/Spinner.vue +2 -1
- package/components/feedback/Stepper.vue +1 -1
- package/components/feedback/Timeline.jsx +1 -1
- package/components/feedback/Timeline.vue +1 -1
- package/components/feedback/Toast.vue +4 -4
- package/components/feedback/ToastStack.vue +11 -0
- package/components/feedback/Tooltip.vue +19 -0
- package/components/feedback/feedback.prompt.md +4 -4
- package/components/forms/Checkbox.d.ts +3 -2
- package/components/forms/Checkbox.jsx +7 -7
- package/components/forms/Checkbox.vue +8 -8
- package/components/forms/Combobox.d.ts +2 -0
- package/components/forms/Combobox.jsx +12 -10
- package/components/forms/Combobox.vue +11 -10
- package/components/forms/DatePicker.d.ts +4 -0
- package/components/forms/DatePicker.jsx +16 -12
- package/components/forms/DatePicker.vue +15 -11
- package/components/forms/Dropzone.jsx +2 -2
- package/components/forms/Dropzone.vue +5 -5
- package/components/forms/Field.d.ts +5 -1
- package/components/forms/Field.jsx +4 -4
- package/components/forms/Field.vue +4 -4
- package/components/forms/Input.d.ts +5 -1
- package/components/forms/Input.jsx +7 -7
- package/components/forms/Input.vue +3 -3
- package/components/forms/MultiSelect.d.ts +2 -0
- package/components/forms/MultiSelect.jsx +13 -11
- package/components/forms/MultiSelect.vue +12 -11
- package/components/forms/NumberStepper.d.ts +3 -1
- package/components/forms/NumberStepper.jsx +7 -7
- package/components/forms/NumberStepper.vue +8 -7
- package/components/forms/Radio.d.ts +5 -3
- package/components/forms/Radio.jsx +13 -15
- package/components/forms/Radio.vue +9 -10
- package/components/forms/RadioGroup.vue +5 -5
- package/components/forms/SearchInput.d.ts +3 -1
- package/components/forms/SearchInput.jsx +6 -6
- package/components/forms/SearchInput.vue +7 -6
- package/components/forms/Select.d.ts +3 -2
- package/components/forms/Select.jsx +6 -7
- package/components/forms/Select.vue +6 -6
- package/components/forms/Slider.jsx +3 -3
- package/components/forms/Slider.vue +3 -3
- package/components/forms/Switch.d.ts +2 -2
- package/components/forms/Switch.jsx +20 -22
- package/components/forms/Switch.vue +15 -14
- package/components/forms/Textarea.vue +4 -4
- package/components/forms/forms.prompt.md +1 -1
- package/components/forms/input-base.js +2 -1
- package/components/index.d.ts +1 -2
- package/components/index.js +3 -4
- package/components/navigation/Breadcrumb.jsx +3 -3
- package/components/navigation/Breadcrumb.vue +4 -4
- package/components/navigation/DocTree.jsx +3 -3
- package/components/navigation/DocTree.vue +3 -3
- package/components/navigation/PageHeading.vue +1 -1
- package/components/navigation/PageToolbar.vue +7 -6
- package/components/navigation/Sidebar.jsx +1 -1
- package/components/navigation/Sidebar.vue +3 -3
- package/components/navigation/ThemeToggle.vue +2 -1
- package/components/navigation/TopBar.d.ts +3 -1
- package/components/navigation/TopBar.jsx +6 -6
- package/components/navigation/Tree.jsx +5 -5
- package/components/navigation/Tree.vue +5 -5
- package/components/pipeline/ConfigPanel.d.ts +2 -0
- package/components/pipeline/ConfigPanel.jsx +9 -9
- package/components/pipeline/ConfigPanel.vue +9 -9
- package/components/pipeline/DataPreview.d.ts +3 -1
- package/components/pipeline/DataPreview.jsx +7 -7
- package/components/pipeline/DataPreview.vue +7 -7
- package/components/pipeline/NodeCard.jsx +3 -3
- package/components/pipeline/NodeCard.vue +3 -3
- package/components/pipeline/PaletteItem.vue +2 -2
- package/components/pipeline/PaletteListItem.vue +1 -1
- package/components/pipeline/PalettePanel.jsx +2 -2
- package/components/pipeline/PalettePanel.vue +1 -1
- package/components/pipeline/Port.vue +15 -0
- package/components/pipeline/PortLabel.vue +15 -0
- package/components/pipeline/ZoomControls.vue +1 -1
- package/components/pipeline/port-skin.js +13 -0
- package/components/px.js +4 -0
- package/components/tabs/PanelTabs.vue +5 -4
- package/components/tabs/PillTabs.vue +5 -4
- package/components/tabs/SegmentedControl.d.ts +2 -2
- package/components/tabs/SegmentedControl.jsx +7 -7
- package/components/tabs/SegmentedControl.vue +7 -7
- package/components/tabs/Tabs.d.ts +5 -3
- package/components/tabs/Tabs.jsx +15 -15
- package/components/tabs/Tabs.vue +5 -5
- package/components/tabs/ViewToggle.d.ts +3 -1
- package/components/tabs/ViewToggle.jsx +5 -5
- package/components/tabs/ViewToggle.vue +5 -5
- package/components/tabs/tabs.prompt.md +1 -1
- package/keybricks.css +4 -0
- package/package.json +6 -3
- package/readme.md +90 -122
- package/readme.zh-CN.md +120 -0
- package/tokens/charts.css +20 -17
- package/tokens/colors.css +2 -2
- package/tokens/dark.css +2 -2
- package/tokens/hover.css +32 -0
- package/tokens/interaction.css +3 -1
- package/tokens/layout.css +29 -7
- package/tokens/tokens.d.ts +36 -0
- package/tokens/tokens.js +143 -89
- package/tokens/tokens.json +240 -106
- package/tokens/typography.css +27 -14
- package/components/_cards/brand-visual.card.html +0 -43
- package/components/_cards/datatable.card.html +0 -48
- package/components/_shared/src-brand.js +0 -1
- package/components/brand/BrandVisual.d.ts +0 -17
- package/components/brand/BrandVisual.jsx +0 -29
- package/components/brand/BrandVisual.vue +0 -34
- package/components/brand/_dir.card.html +0 -13
- package/components/brand/brandMotifs.js +0 -210
- package/components/buttons/_dir.card.html +0 -13
- package/components/charts/ChartKit.vue +0 -47
- package/components/charts/_dir.card.html +0 -7
- package/components/data/DataTable.d.ts +0 -28
- package/components/data/DataTable.jsx +0 -32
- package/components/data/DataTable.vue +0 -25
- package/components/data/_dir.card.html +0 -13
- package/components/display/_dir.card.html +0 -13
- package/components/feedback/_dir.card.html +0 -13
- package/components/forms/_dir.card.html +0 -13
- package/components/icons/_dir.card.html +0 -13
- package/components/navigation/_dir.card.html +0 -13
- package/components/pipeline/_dir.card.html +0 -13
- package/components/tabs/_dir.card.html +0 -13
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from 'vue'
|
|
3
3
|
import { inputBase } from './input-base.js'
|
|
4
|
-
// Text input,
|
|
4
|
+
// Text input, r8, tier height (sm 24 · md 32 · lg 36 — 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
|
-
const props = defineProps({ modelValue: String, readonly: Boolean, disabled: Boolean, error: Boolean, style: Object })
|
|
6
|
+
const props = defineProps({ modelValue: String, readonly: Boolean, disabled: Boolean, error: Boolean, size: String, style: Object })
|
|
7
7
|
defineEmits(['update:modelValue'])
|
|
8
8
|
const focus = ref(false)
|
|
9
9
|
const s = computed(() => ({ ...inputBase, ...(props.readonly ? { background: 'var(--bg-inset)', color: 'var(--text-secondary)' } : null),
|
|
@@ -12,5 +12,5 @@ const s = computed(() => ({ ...inputBase, ...(props.readonly ? { background: 'va
|
|
|
12
12
|
...(props.disabled ? { opacity: .5, cursor: 'not-allowed', background: 'var(--bg-inset)' } : null), ...props.style }))
|
|
13
13
|
</script>
|
|
14
14
|
<template>
|
|
15
|
-
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" :readonly="readonly" :disabled="disabled" @focus="focus = true" @blur="focus = false" :style="s" />
|
|
15
|
+
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" :readonly="readonly" :disabled="disabled" :data-size="size" :data-field="readonly || disabled ? undefined : ''" @focus="focus = true" @blur="focus = false" :style="s" />
|
|
16
16
|
</template>
|
|
@@ -9,6 +9,8 @@ export interface MultiSelectProps {
|
|
|
9
9
|
maxTags?: number;
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
width?: number | string;
|
|
12
|
+
/** Size tier: sm 24 · md 32 · lg 36. Omit to inherit the enclosing region's tier (the default). */
|
|
13
|
+
size?: "sm" | "md" | "lg";
|
|
12
14
|
onChange?: (value: string[]) => void;
|
|
13
15
|
style?: React.CSSProperties;
|
|
14
16
|
}
|
|
@@ -3,29 +3,31 @@ import { Icon } from "../icons/Icon.jsx";
|
|
|
3
3
|
import { Checkbox } from "./Checkbox.jsx";
|
|
4
4
|
import { Tag } from "../display/Tag.jsx";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
|
|
7
|
+
|
|
8
|
+
/** Multi-select dropdown: checkbox option list, selected values render as removable tags in the field (overflow collapses to "+n"), clear-all ×. Field, tags and option rows take the region's tier (omit `size` to inherit). */
|
|
9
|
+
export function MultiSelect({ options = [], value = [], placeholder = "Select...", maxTags = 2, disabled, width, size, onChange, style }) {
|
|
8
10
|
const [open, setOpen] = React.useState(false);
|
|
9
11
|
const root = React.useRef(null);
|
|
10
12
|
React.useEffect(() => { const h = (e) => { if (root.current && !root.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, []);
|
|
11
13
|
const toggle = (o) => { const next = value.includes(o) ? value.filter(x => x !== o) : [...value, o]; onChange && onChange(next); };
|
|
12
14
|
const shown = value.slice(0, maxTags), extra = value.length - shown.length;
|
|
13
15
|
return (
|
|
14
|
-
<div ref={root} style={{ position: "relative", width, ...style }}>
|
|
15
|
-
<div onClick={() => !disabled && setOpen(!open)} style={{ minHeight: "var(--h
|
|
16
|
-
{value.length === 0 && <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
16
|
+
<div ref={root} data-size={size} style={{ position: "relative", width, ...style }}>
|
|
17
|
+
<div data-field={disabled ? undefined : ""} onClick={() => !disabled && setOpen(!open)} style={{ minHeight: "var(--control-h)", borderRadius: "var(--radius-md)", border: `1px solid ${open ? "var(--focus-border)" : "var(--field-border, var(--border-control))"}`, boxShadow: open ? "0 0 0 2px var(--focus-ring)" : "none", background: "var(--bg-surface)", padding: "calc((var(--control-h) - var(--tag-h) - 2px) / 2) calc(var(--control-px) + var(--control-icon) * 2 + 8px) calc((var(--control-h) - var(--tag-h) - 2px) / 2) calc(var(--control-px) - 4px)", boxSizing: "border-box", display: "flex", alignItems: "center", flexWrap: "wrap", gap: 4, cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? .5 : 1, position: "relative" }}>
|
|
18
|
+
{value.length === 0 && <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-muted)", paddingLeft: 4 }}>{placeholder}</span>}
|
|
17
19
|
{shown.map(v => <Tag key={v} removable onRemove={() => toggle(v)}>{v}</Tag>)}
|
|
18
20
|
{extra > 0 && <Tag>+{extra}</Tag>}
|
|
19
|
-
<span style={{ position: "absolute", right:
|
|
20
|
-
{value.length > 0 && <button type="button" onClick={(e) => { e.stopPropagation(); onChange && onChange([]); }} style={{ border: "none", background: "transparent", cursor: "pointer", color: "
|
|
21
|
-
<span style={{ display: "inline-flex", transform: open ? "rotate(180deg)" : "none", transition: "transform var(--dur-fast) var(--ease-out)" }}><Icon name="chevron-down" size={
|
|
21
|
+
<span style={{ position: "absolute", right: "var(--control-px)", top: "50%", transform: "translateY(-50%)", display: "flex", alignItems: "center", gap: 4, color: "var(--text-muted)" }}>
|
|
22
|
+
{value.length > 0 && <button type="button" aria-label="Clear" data-ink="" onClick={(e) => { e.stopPropagation(); onChange && onChange([]); }} style={{ border: "none", background: "transparent", cursor: "pointer", color: "var(--ink, var(--text-muted))", display: "flex", padding: 0 }}><Icon name="x" size={16} style={tierIcon} /></button>}
|
|
23
|
+
<span style={{ display: "inline-flex", transform: open ? "rotate(180deg)" : "none", transition: "transform var(--dur-fast) var(--ease-out)" }}><Icon name="chevron-down" size={16} style={tierIcon} /></span>
|
|
22
24
|
</span>
|
|
23
25
|
</div>
|
|
24
26
|
{open && <div style={{ position: "absolute", top: "calc(100% + 4px)", left: 0, right: 0, zIndex: "var(--z-menu)", background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-md)", boxShadow: "var(--shadow-menu)", padding: 4, maxHeight: 208, overflowY: "auto" }}>
|
|
25
|
-
{options.map(o => <div key={o} role="option" aria-selected={value.includes(o)} tabIndex={0} onClick={() => toggle(o)} style={{ display: "flex", alignItems: "center", gap: 8, padding: "
|
|
27
|
+
{options.map(o => <div key={o} role="option" aria-selected={value.includes(o)} tabIndex={0} onClick={() => toggle(o)} style={{ display: "flex", alignItems: "center", gap: 8, height: "var(--control-h)", padding: "0 10px", boxSizing: "border-box", borderRadius: "var(--radius-sm)", cursor: "pointer", background: "transparent" }}
|
|
26
28
|
onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-hover)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
|
|
27
|
-
<Checkbox
|
|
28
|
-
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
29
|
+
<Checkbox checked={value.includes(o)} onChange={() => toggle(o)} />
|
|
30
|
+
<span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", color: "var(--text-heading)" }}>{o}</span>
|
|
29
31
|
</div>)}
|
|
30
32
|
</div>}
|
|
31
33
|
</div>
|
|
@@ -3,8 +3,9 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
4
|
import Checkbox from './Checkbox.vue'
|
|
5
5
|
import Tag from '../display/Tag.vue'
|
|
6
|
-
// Multi-select dropdown: checkbox options; picks render as removable tags with "+n" overflow and clear-all ×.
|
|
7
|
-
const props = defineProps({ options: { type: Array, default: () => [] }, modelValue: { type: Array, default: () => [] }, placeholder: { type: String, default: 'Select...' }, maxTags: { type: Number, default: 2 }, disabled: Boolean, width: [Number, String], style: Object })
|
|
6
|
+
// Multi-select dropdown: checkbox options; picks render as removable tags with "+n" overflow and clear-all ×. Field, tags and option rows take the region's tier (omit `size` to inherit).
|
|
7
|
+
const props = defineProps({ options: { type: Array, default: () => [] }, modelValue: { type: Array, default: () => [] }, placeholder: { type: String, default: 'Select...' }, maxTags: { type: Number, default: 2 }, disabled: Boolean, width: [Number, String], size: String, style: Object })
|
|
8
|
+
const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
|
|
8
9
|
const emit = defineEmits(['update:modelValue'])
|
|
9
10
|
const open = ref(false), root = ref(null)
|
|
10
11
|
function toggle(o) { emit('update:modelValue', props.modelValue.includes(o) ? props.modelValue.filter(x => x !== o) : [...props.modelValue, o]) }
|
|
@@ -15,20 +16,20 @@ onMounted(() => document.addEventListener('mousedown', onDoc))
|
|
|
15
16
|
onUnmounted(() => document.removeEventListener('mousedown', onDoc))
|
|
16
17
|
</script>
|
|
17
18
|
<template>
|
|
18
|
-
<div ref="root" :style="{ position: 'relative', width: typeof width === 'number' ? width + 'px' : width, ...style }">
|
|
19
|
-
<div @click="!disabled && (open = !open)" :style="{ minHeight: 'var(--h
|
|
20
|
-
<span v-if="modelValue.length === 0" style="font-family:var(--font-sans);font-size:var(--text-
|
|
19
|
+
<div ref="root" :data-size="size" :style="{ position: 'relative', width: typeof width === 'number' ? width + 'px' : width, ...style }">
|
|
20
|
+
<div :data-field="disabled ? undefined : ''" @click="!disabled && (open = !open)" :style="{ minHeight: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: `1px solid ${open ? 'var(--focus-border)' : 'var(--field-border, var(--border-control))'}`, boxShadow: open ? '0 0 0 2px var(--focus-ring)' : 'none', background: 'var(--bg-surface)', padding: 'calc((var(--control-h) - var(--tag-h) - 2px) / 2) calc(var(--control-px) + var(--control-icon) * 2 + 8px) calc((var(--control-h) - var(--tag-h) - 2px) / 2) calc(var(--control-px) - 4px)', boxSizing: 'border-box', display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '4px', cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? .5 : 1, position: 'relative' }">
|
|
21
|
+
<span v-if="modelValue.length === 0" style="font-family:var(--font-sans);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking);color:var(--text-muted);padding-left:4px">{{ placeholder }}</span>
|
|
21
22
|
<Tag v-for="v in shown" :key="v" removable @remove="toggle(v)">{{ v }}</Tag>
|
|
22
23
|
<Tag v-if="extra > 0">+{{ extra }}</Tag>
|
|
23
|
-
<span style="position:absolute;right:
|
|
24
|
-
<button v-if="modelValue.length" type="button" @click.stop="emit('update:modelValue', [])" style="border:none;background:transparent;cursor:pointer;color:
|
|
25
|
-
<span :style="{ display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }"><Icon name="chevron-down" :size="
|
|
24
|
+
<span style="position:absolute;right:var(--control-px);top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:4px;color:var(--text-muted)">
|
|
25
|
+
<button v-if="modelValue.length" type="button" aria-label="Clear" data-ink="" @click.stop="emit('update:modelValue', [])" style="border:none;background:transparent;cursor:pointer;color:var(--ink, var(--text-muted));display:flex;padding:0"><Icon name="x" :size="16" :style="tierIcon" /></button>
|
|
26
|
+
<span :style="{ display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }"><Icon name="chevron-down" :size="16" :style="tierIcon" /></span>
|
|
26
27
|
</span>
|
|
27
28
|
</div>
|
|
28
29
|
<div v-if="open" style="position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:var(--z-menu);background:var(--bg-surface);border:1px solid var(--border-card);border-radius:var(--radius-md);box-shadow:var(--shadow-menu);padding:4px;max-height:208px;overflow-y:auto">
|
|
29
|
-
<div v-for="o in options" :key="o" role="option" :aria-selected="modelValue.includes(o)" tabindex="0" @click="toggle(o)" class="kb-ms-row" style="display:flex;align-items:center;gap:8px;padding:
|
|
30
|
-
<Checkbox
|
|
31
|
-
<span style="font-family:var(--font-sans);font-size:var(--text-
|
|
30
|
+
<div v-for="o in options" :key="o" role="option" :aria-selected="modelValue.includes(o)" tabindex="0" @click="toggle(o)" class="kb-ms-row" style="display:flex;align-items:center;gap:8px;height:var(--control-h);padding:0 10px;box-sizing:border-box;border-radius:var(--radius-sm);cursor:pointer">
|
|
31
|
+
<Checkbox :checked="modelValue.includes(o)" @change="toggle(o)" />
|
|
32
|
+
<span style="font-family:var(--font-sans);font-size:var(--control-text);letter-spacing:var(--control-tracking);color:var(--text-heading)">{{ o }}</span>
|
|
32
33
|
</div>
|
|
33
34
|
</div>
|
|
34
35
|
</div>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Segmented −/value/+ number stepper
|
|
2
|
+
/** Segmented −/value/+ number stepper at the region's tier height, r8 — "Preview rows" control. */
|
|
3
3
|
export interface NumberStepperProps {
|
|
4
|
+
/** Size tier: sm 24 · md 32 · lg 36. Omit to inherit the enclosing region's tier (the default). */
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
4
6
|
value?: number;
|
|
5
7
|
onChange?: (next: number) => void;
|
|
6
8
|
step?: number;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
/** Segmented − / value / + stepper,
|
|
5
|
-
export function NumberStepper({ value = 100, onChange, step = 10, min = 0, max, disabled, style }) {
|
|
4
|
+
/** Segmented − / value / + stepper, r8, tier height ("Preview rows" — omit `size` to inherit the region's tier). Buttons disable themselves at min/max. */
|
|
5
|
+
export function NumberStepper({ value = 100, onChange, step = 10, min = 0, max, disabled, size, style }) {
|
|
6
6
|
const atMin = value <= min, atMax = max != null && value >= max;
|
|
7
|
-
const seg = (dis) => ({ width:
|
|
7
|
+
const seg = (dis) => ({ width: "var(--control-h)", display: "flex", alignItems: "center", justifyContent: "center", cursor: dis ? "not-allowed" : "pointer", color: dis ? "var(--text-muted)" : "var(--text-body)", alignSelf: "stretch", background: dis ? "transparent" : "var(--wash, transparent)", transition: "var(--t-hover)", border: "none", padding: 0 });
|
|
8
8
|
return (
|
|
9
|
-
<div style={{ display: "inline-flex", alignItems: "center", height:
|
|
10
|
-
<button type="button" disabled={disabled || atMin} style={seg(disabled || atMin)} onClick={() => onChange && onChange(Math.max(min, value - step))}><Icon name="minus" size={
|
|
11
|
-
<span style={{ minWidth: 34, textAlign: "center", fontFamily: "var(--font-sans)", fontSize: "var(--text-
|
|
12
|
-
<button type="button" disabled={disabled || atMax} style={seg(disabled || atMax)} onClick={() => onChange && onChange(max != null ? Math.min(max, value + step) : value + step)}><Icon name="plus" size={
|
|
9
|
+
<div data-size={size} data-field={disabled ? undefined : ""} style={{ display: "inline-flex", alignItems: "center", height: "var(--control-h)", borderRadius: "var(--radius-md)", border: "1px solid var(--field-border, var(--border-control))", overflow: "hidden", background: "var(--bg-surface)", boxSizing: "border-box", opacity: disabled ? .5 : 1, ...style }}>
|
|
10
|
+
<button type="button" disabled={disabled || atMin} data-wash="" aria-label="Decrease" style={seg(disabled || atMin)} onClick={() => onChange && onChange(Math.max(min, value - step))}><Icon name="minus" size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></button>
|
|
11
|
+
<span style={{ minWidth: 34, textAlign: "center", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", fontVariantNumeric: "tabular-nums", color: "var(--text-primary)", borderLeft: "1px solid var(--border-control)", borderRight: "1px solid var(--border-control)", alignSelf: "stretch", display: "inline-flex", alignItems: "center", justifyContent: "center", padding: "0 6px" }}>{value}</span>
|
|
12
|
+
<button type="button" disabled={disabled || atMax} data-wash="" aria-label="Increase" style={seg(disabled || atMax)} onClick={() => onChange && onChange(max != null ? Math.min(max, value + step) : value + step)}><Icon name="plus" size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></button>
|
|
13
13
|
</div>
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import Icon from '../icons/Icon.vue'
|
|
4
|
-
// Segmented − / value / + stepper,
|
|
5
|
-
const props = defineProps({ modelValue: { type: Number, default: 100 }, step: { type: Number, default: 10 }, min: { type: Number, default: 0 }, max: Number, disabled: Boolean, style: Object })
|
|
4
|
+
// Segmented − / value / + stepper, r8, tier height ("Preview rows" — omit `size` to inherit the region's tier). Buttons disable themselves at min/max.
|
|
5
|
+
const props = defineProps({ modelValue: { type: Number, default: 100 }, step: { type: Number, default: 10 }, min: { type: Number, default: 0 }, max: Number, disabled: Boolean, size: String, style: Object })
|
|
6
|
+
const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
|
|
6
7
|
const emit = defineEmits(['update:modelValue'])
|
|
7
8
|
const atMin = computed(() => props.modelValue <= props.min)
|
|
8
9
|
const atMax = computed(() => props.max != null && props.modelValue >= props.max)
|
|
9
|
-
function seg(dis) { return { width:
|
|
10
|
+
function seg(dis) { return { width: 'var(--control-h)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: dis ? 'not-allowed' : 'pointer', color: dis ? 'var(--text-muted)' : 'var(--text-body)', alignSelf: 'stretch', background: dis ? 'transparent' : 'var(--wash, transparent)', transition: 'var(--t-hover)', border: 'none', padding: 0 } }
|
|
10
11
|
</script>
|
|
11
12
|
<template>
|
|
12
|
-
<div :style="{ display: 'inline-flex', alignItems: 'center', height:
|
|
13
|
-
<button type="button" :disabled="disabled || atMin" :style="seg(disabled || atMin)" @click="emit('update:modelValue', Math.max(min, modelValue - step))"><Icon name="minus" :size="
|
|
14
|
-
<span style="min-width:34px;text-align:center;font-family:var(--font-sans);font-size:var(--text-
|
|
15
|
-
<button type="button" :disabled="disabled || atMax" :style="seg(disabled || atMax)" @click="emit('update:modelValue', max != null ? Math.min(max, modelValue + step) : modelValue + step)"><Icon name="plus" :size="
|
|
13
|
+
<div :data-size="size" :data-field="disabled ? undefined : ''" :style="{ display: 'inline-flex', alignItems: 'center', height: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: '1px solid var(--field-border, var(--border-control))', overflow: 'hidden', background: 'var(--bg-surface)', boxSizing: 'border-box', opacity: disabled ? .5 : 1, ...style }">
|
|
14
|
+
<button type="button" :disabled="disabled || atMin" data-wash="" aria-label="Decrease" :style="seg(disabled || atMin)" @click="emit('update:modelValue', Math.max(min, modelValue - step))"><Icon name="minus" :size="16" :style="tierIcon" /></button>
|
|
15
|
+
<span style="min-width:34px;text-align:center;font-family:var(--font-sans);font-size:var(--control-text);letter-spacing:var(--control-tracking);font-variant-numeric:tabular-nums;color:var(--text-primary);border-left:1px solid var(--border-control);border-right:1px solid var(--border-control);align-self:stretch;display:inline-flex;align-items:center;justify-content:center;padding:0 6px">{{ modelValue }}</span>
|
|
16
|
+
<button type="button" :disabled="disabled || atMax" data-wash="" aria-label="Increase" :style="seg(disabled || atMax)" @click="emit('update:modelValue', max != null ? Math.min(max, modelValue + step) : modelValue + step)"><Icon name="plus" :size="16" :style="tierIcon" /></button>
|
|
16
17
|
</div>
|
|
17
18
|
</template>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Radio —
|
|
2
|
+
/** Radio — circle at the region's tier (sm 14 · md/lg 16); filled action-blue dot when selected. Hover darkens the ring, focus shows the 2px ring. */
|
|
3
3
|
export interface RadioProps {
|
|
4
4
|
checked?: boolean;
|
|
5
5
|
onChange?: (checked: true) => void;
|
|
6
6
|
label?: string;
|
|
7
7
|
/** Muted second line under the label */
|
|
8
8
|
description?: string;
|
|
9
|
-
|
|
9
|
+
/** Size tier. Omit to inherit the enclosing region's tier (the default). */
|
|
10
|
+
size?: "sm" | "md" | "lg";
|
|
10
11
|
disabled?: boolean;
|
|
11
12
|
style?: React.CSSProperties;
|
|
12
13
|
}
|
|
@@ -18,7 +19,8 @@ export interface RadioGroupProps {
|
|
|
18
19
|
onChange?: (value: string) => void;
|
|
19
20
|
/** Horizontal layout */
|
|
20
21
|
row?: boolean;
|
|
21
|
-
|
|
22
|
+
/** Size tier for the whole group. Omit to inherit (the default). */
|
|
23
|
+
size?: "sm" | "md" | "lg";
|
|
22
24
|
/** plain (default) · cards — each option is a bordered selectable card */
|
|
23
25
|
variant?: "plain" | "cards";
|
|
24
26
|
/** Disables the whole group */
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
const radioDims = { sm: { box: 13, dot: 6, font: "var(--text-xs)" }, md: { box: 16, dot: 8, font: "var(--text-sm)" } };
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
|
-
* Radio —
|
|
5
|
+
* Radio — circle at the region's tier (sm 14 · md/lg 16, --check-size), 1.5px border, filled action-blue dot when selected.
|
|
8
6
|
* hover darkens the ring; keyboard focus shows the 2px focus ring; disabled = 50% opacity.
|
|
9
7
|
* description renders a muted second line under the label (settings-style choices).
|
|
10
8
|
*/
|
|
11
|
-
export function Radio({ checked, onChange, label, description, size
|
|
12
|
-
const d =
|
|
9
|
+
export function Radio({ checked, onChange, label, description, size, disabled, style }) {
|
|
10
|
+
const d = { box: "var(--check-size)", dot: "calc(var(--check-size) / 2)" };
|
|
13
11
|
const [hover, setHover] = React.useState(false);
|
|
14
12
|
const [focus, setFocus] = React.useState(false);
|
|
15
13
|
const ring = checked ? "var(--action-primary)" : hover && !disabled ? "var(--text-muted)" : "var(--checkbox-border)";
|
|
16
14
|
return (
|
|
17
|
-
<label role="radio" aria-checked={!!checked} aria-disabled={disabled || undefined} tabIndex={disabled ? -1 : 0}
|
|
15
|
+
<label role="radio" data-size={size} aria-checked={!!checked} aria-disabled={disabled || undefined} tabIndex={disabled ? -1 : 0}
|
|
18
16
|
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
|
|
19
17
|
onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); !disabled && onChange && onChange(true); } }}
|
|
20
|
-
style={{ display: "inline-flex", alignItems: description ? "flex-start" : "center", verticalAlign: "middle", lineHeight: "var(--leading
|
|
18
|
+
style={{ display: "inline-flex", alignItems: description ? "flex-start" : "center", verticalAlign: "middle", lineHeight: "var(--control-leading)", gap: "var(--control-gap)", cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? .5 : 1, outline: "none", ...style }}
|
|
21
19
|
onClick={() => !disabled && onChange && onChange(true)}>
|
|
22
|
-
<span style={{ width: d.box, height: d.box, marginTop: description ?
|
|
20
|
+
<span style={{ width: d.box, height: d.box, marginTop: description ? 2 : 0, borderRadius: "var(--radius-full)", boxSizing: "border-box", border: `1.5px solid ${ring}`, background: "var(--bg-surface)", boxShadow: focus && !disabled ? "0 0 0 2px var(--focus-ring)" : "none", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, transition: "var(--t-hover)" }}>
|
|
23
21
|
{checked ? <span style={{ width: d.dot, height: d.dot, borderRadius: "var(--radius-full)", background: "var(--action-primary)" }}></span> : null}
|
|
24
22
|
</span>
|
|
25
23
|
{label || description ? <span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
|
26
|
-
{label ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-
|
|
27
|
-
{description ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "
|
|
24
|
+
{label ? <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-regular)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-body)" }}>{label}</span> : null}
|
|
25
|
+
{description ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", color: "var(--text-muted)" }}>{description}</span> : null}
|
|
28
26
|
</span> : null}
|
|
29
27
|
</label>
|
|
30
28
|
);
|
|
@@ -36,7 +34,7 @@ export function Radio({ checked, onChange, label, description, size = "md", disa
|
|
|
36
34
|
* Arrow keys move the selection between enabled options (WAI-ARIA radiogroup).
|
|
37
35
|
* variant="cards" renders each option as a bordered selectable card (plan pickers, migration strategies).
|
|
38
36
|
*/
|
|
39
|
-
export function RadioGroup({ options = [], value, onChange, row, size
|
|
37
|
+
export function RadioGroup({ options = [], value, onChange, row, size, variant = "plain", disabled, style }) {
|
|
40
38
|
const opts = options.map((o) => (typeof o === "string" ? { label: o, value: o } : o));
|
|
41
39
|
const enabled = opts.filter((o) => !o.disabled && !disabled);
|
|
42
40
|
const onKey = (e) => {
|
|
@@ -48,20 +46,20 @@ export function RadioGroup({ options = [], value, onChange, row, size = "md", va
|
|
|
48
46
|
};
|
|
49
47
|
if (variant === "cards") {
|
|
50
48
|
return (
|
|
51
|
-
<div role="radiogroup" onKeyDown={onKey} style={{ display: "flex", flexDirection: row ? "row" : "column", gap: 8, ...style }}>
|
|
49
|
+
<div role="radiogroup" data-size={size} onKeyDown={onKey} style={{ display: "flex", flexDirection: row ? "row" : "column", gap: 8, ...style }}>
|
|
52
50
|
{opts.map((o) => {
|
|
53
51
|
const on = value === o.value, off = o.disabled || disabled;
|
|
54
52
|
return <div key={o.value} onClick={() => !off && onChange && onChange(o.value)}
|
|
55
53
|
style={{ flex: row ? 1 : "none", padding: "10px 12px", borderRadius: "var(--radius-md)", border: `1px solid ${on ? "var(--action-primary)" : "var(--border-card)"}`, background: on ? "var(--soft-bg)" : "var(--bg-surface)", cursor: off ? "not-allowed" : "pointer", opacity: off ? .5 : 1, transition: "var(--t-hover)" }}>
|
|
56
|
-
<Radio checked={on} onChange={() => onChange && onChange(o.value)} label={o.label} description={o.description}
|
|
54
|
+
<Radio checked={on} onChange={() => onChange && onChange(o.value)} label={o.label} description={o.description} disabled={off} style={{ opacity: 1, cursor: "inherit" }} />
|
|
57
55
|
</div>;
|
|
58
56
|
})}
|
|
59
57
|
</div>
|
|
60
58
|
);
|
|
61
59
|
}
|
|
62
60
|
return (
|
|
63
|
-
<div role="radiogroup" onKeyDown={onKey} style={{ display: "flex", flexDirection: row ? "row" : "column", gap: row ?
|
|
64
|
-
{opts.map((o) => <Radio key={o.value} label={o.label} description={o.description}
|
|
61
|
+
<div role="radiogroup" data-size={size} onKeyDown={onKey} style={{ display: "flex", flexDirection: row ? "row" : "column", gap: row ? "var(--group-gap)" : 10, ...style }}>
|
|
62
|
+
{opts.map((o) => <Radio key={o.value} label={o.label} description={o.description} disabled={o.disabled || disabled} checked={value === o.value} onChange={() => onChange && onChange(o.value)} />)}
|
|
65
63
|
</div>
|
|
66
64
|
);
|
|
67
65
|
}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed } from 'vue'
|
|
3
|
-
// Radio —
|
|
3
|
+
// Radio — circle at the region's tier (sm 14 · md/lg 16, --check-size), 1.5px border, filled dot when selected. Hover darkens the ring, focus shows the 2px ring, disabled = 50% opacity.
|
|
4
4
|
// description renders a muted second line under the label.
|
|
5
|
-
const
|
|
6
|
-
const props = defineProps({ checked: Boolean, label: String, description: String, size: { type: String, default: 'md' }, disabled: Boolean, style: Object })
|
|
5
|
+
const props = defineProps({ checked: Boolean, label: String, description: String, size: String, disabled: Boolean, style: Object })
|
|
7
6
|
const emit = defineEmits(['change'])
|
|
8
|
-
const d =
|
|
7
|
+
const d = { box: 'var(--check-size)', dot: 'calc(var(--check-size) / 2)' }
|
|
9
8
|
const hover = ref(false), focus = ref(false)
|
|
10
9
|
const ring = computed(() => props.checked ? 'var(--action-primary)' : hover.value && !props.disabled ? 'var(--text-muted)' : 'var(--checkbox-border)')
|
|
11
10
|
const pick = () => { if (!props.disabled) emit('change', true) }
|
|
12
11
|
</script>
|
|
13
12
|
<template>
|
|
14
|
-
<label role="radio" :aria-checked="checked" :aria-disabled="disabled || undefined" :tabindex="disabled ? -1 : 0"
|
|
13
|
+
<label role="radio" :data-size="size" :aria-checked="checked" :aria-disabled="disabled || undefined" :tabindex="disabled ? -1 : 0"
|
|
15
14
|
@mouseenter="hover = true" @mouseleave="hover = false" @focus="focus = true" @blur="focus = false" @click="pick" @keydown.space.prevent="pick" @keydown.enter.prevent="pick"
|
|
16
|
-
:style="{ display: 'inline-flex', alignItems: description ? 'flex-start' : 'center', verticalAlign: 'middle', lineHeight: 'var(--leading
|
|
17
|
-
<span :style="{ width: d.box
|
|
18
|
-
<span v-if="checked" :style="{ width: d.dot
|
|
15
|
+
:style="{ display: 'inline-flex', alignItems: description ? 'flex-start' : 'center', verticalAlign: 'middle', lineHeight: 'var(--control-leading)', gap: 'var(--control-gap)', cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? .5 : 1, outline: 'none', ...style }">
|
|
16
|
+
<span :style="{ width: d.box, height: d.box, marginTop: description ? '2px' : 0, borderRadius: 'var(--radius-full)', boxSizing: 'border-box', border: `1.5px solid ${ring}`, background: 'var(--bg-surface)', boxShadow: focus && !disabled ? '0 0 0 2px var(--focus-ring)' : 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transition: 'var(--t-hover)' }">
|
|
17
|
+
<span v-if="checked" :style="{ width: d.dot, height: d.dot, borderRadius: 'var(--radius-full)', background: 'var(--action-primary)' }"></span>
|
|
19
18
|
</span>
|
|
20
19
|
<span v-if="label || description" style="display:flex;flex-direction:column;gap:2px">
|
|
21
|
-
<span v-if="label"
|
|
22
|
-
<span v-if="description" style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:
|
|
20
|
+
<span v-if="label" style="font-family:var(--font-sans);font-weight:var(--weight-regular);font-size:var(--control-text);line-height:var(--control-leading);letter-spacing:var(--control-tracking);color:var(--text-body)">{{ label }}</span>
|
|
21
|
+
<span v-if="description" style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted)">{{ description }}</span>
|
|
23
22
|
</span>
|
|
24
23
|
</label>
|
|
25
24
|
</template>
|
|
@@ -3,7 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
import Radio from './Radio.vue'
|
|
4
4
|
// RadioGroup — single choice. Vertical (default) or `row`. options: string[] | { label, value, description?, disabled? }[].
|
|
5
5
|
// Arrow keys move between enabled options. variant="cards" renders bordered selectable cards. v-model.
|
|
6
|
-
const props = defineProps({ options: { type: Array, default: () => [] }, modelValue: String, row: Boolean, size:
|
|
6
|
+
const props = defineProps({ options: { type: Array, default: () => [] }, modelValue: String, row: Boolean, size: String, variant: { type: String, default: 'plain' }, disabled: Boolean, style: Object })
|
|
7
7
|
const emit = defineEmits(['update:modelValue'])
|
|
8
8
|
const opts = computed(() => props.options.map(o => typeof o === 'string' ? { label: o, value: o } : o))
|
|
9
9
|
const enabled = computed(() => opts.value.filter(o => !o.disabled && !props.disabled))
|
|
@@ -17,13 +17,13 @@ function onKey(e) {
|
|
|
17
17
|
const pick = (o) => { if (!(o.disabled || props.disabled)) emit('update:modelValue', o.value) }
|
|
18
18
|
</script>
|
|
19
19
|
<template>
|
|
20
|
-
<div v-if="variant === 'cards'" role="radiogroup" @keydown="onKey" :style="{ display: 'flex', flexDirection: row ? 'row' : 'column', gap: '8px', ...style }">
|
|
20
|
+
<div v-if="variant === 'cards'" role="radiogroup" :data-size="size" @keydown="onKey" :style="{ display: 'flex', flexDirection: row ? 'row' : 'column', gap: '8px', ...style }">
|
|
21
21
|
<div v-for="o in opts" :key="o.value" @click="pick(o)"
|
|
22
22
|
:style="{ flex: row ? 1 : 'none', padding: '10px 12px', borderRadius: 'var(--radius-md)', border: `1px solid ${modelValue === o.value ? 'var(--action-primary)' : 'var(--border-card)'}`, background: modelValue === o.value ? 'var(--soft-bg)' : 'var(--bg-surface)', cursor: (o.disabled || disabled) ? 'not-allowed' : 'pointer', opacity: (o.disabled || disabled) ? .5 : 1, transition: 'var(--t-hover)' }">
|
|
23
|
-
<Radio :checked="modelValue === o.value" :label="o.label" :description="o.description" :
|
|
23
|
+
<Radio :checked="modelValue === o.value" :label="o.label" :description="o.description" :disabled="o.disabled || disabled" :style="{ opacity: 1, cursor: 'inherit' }" @change="pick(o)" />
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
26
|
-
<div v-else role="radiogroup" @keydown="onKey" :style="{ display: 'flex', flexDirection: row ? 'row' : 'column', gap:
|
|
27
|
-
<Radio v-for="o in opts" :key="o.value" :label="o.label" :description="o.description" :
|
|
26
|
+
<div v-else role="radiogroup" :data-size="size" @keydown="onKey" :style="{ display: 'flex', flexDirection: row ? 'row' : 'column', gap: row ? 'var(--group-gap)' : '10px', ...style }">
|
|
27
|
+
<Radio v-for="o in opts" :key="o.value" :label="o.label" :description="o.description" :disabled="o.disabled || disabled" :checked="modelValue === o.value" @change="pick(o)" />
|
|
28
28
|
</div>
|
|
29
29
|
</template>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Search field. Default:
|
|
2
|
+
/** Search field at the region's tier height. Default: r8 hairline card filter. bare: borderless top-bar flavor. Shows a clear (×) button once there's a value; uncontrolled if `value` is omitted. */
|
|
3
3
|
export interface SearchInputProps {
|
|
4
4
|
placeholder?: string;
|
|
5
5
|
bare?: boolean;
|
|
6
|
+
/** Size tier: sm 24 · md 32 · lg 36. Omit to inherit the enclosing region's tier (the default). */
|
|
7
|
+
size?: "sm" | "md" | "lg";
|
|
6
8
|
value?: string;
|
|
7
9
|
/** Receives the new string value (consistent with Select / Combobox / MultiSelect) */
|
|
8
10
|
onChange?: (value: string) => void;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
/** Bordered search field (
|
|
5
|
-
export function SearchInput({ placeholder = "Search...", bare, value, onChange, disabled, style }) {
|
|
4
|
+
/** Bordered search field (r8, tier height — omit `size` to inherit the region's tier) with leading search glyph; bare=true renders the borderless top-bar flavor. Shows a clear (×) button once there's a value. */
|
|
5
|
+
export function SearchInput({ placeholder = "Search...", bare, size, value, onChange, disabled, style }) {
|
|
6
6
|
const [inner, setInner] = React.useState("");
|
|
7
7
|
const val = value != null ? value : inner;
|
|
8
8
|
const set = (v) => { if (value == null) setInner(v); onChange && onChange(v); };
|
|
9
9
|
return (
|
|
10
|
-
<div style={{ display: "flex", alignItems: "center", gap: bare ? 12 : 8, opacity: disabled ? .5 : 1, ...(bare ? null : { border: "1px solid var(--border-control)", borderRadius: "var(--radius-
|
|
11
|
-
<span style={{ color: "var(--text-muted)" }}><Icon name="search" size={
|
|
10
|
+
<div data-size={size} data-field={bare || disabled ? undefined : ""} style={{ display: "flex", alignItems: "center", gap: bare ? 12 : 8, height: "var(--control-h)", boxSizing: "border-box", opacity: disabled ? .5 : 1, ...(bare ? null : { border: "1px solid var(--field-border, var(--border-control))", borderRadius: "var(--radius-md)", padding: "0 var(--control-px)", background: "var(--bg-surface)" }), ...style }}>
|
|
11
|
+
<span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Icon name="search" size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></span>
|
|
12
12
|
<input value={val} disabled={disabled} onChange={(e) => set(e.target.value)} placeholder={placeholder}
|
|
13
|
-
style={{ border: "none", outline: "none", background: "transparent", flex: 1, minWidth: 0, fontFamily: "var(--font-sans)", fontSize:
|
|
14
|
-
{val && !disabled ? <button type="button" onClick={() => set("")} style={{ border: "none", background: "transparent", cursor: "pointer", color: "var(--text-muted)", display: "flex", padding: 0 }}><Icon name="x" size={
|
|
13
|
+
style={{ border: "none", outline: "none", background: "transparent", flex: 1, minWidth: 0, padding: 0, fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-primary)", cursor: disabled ? "not-allowed" : "text" }} />
|
|
14
|
+
{val && !disabled ? <button type="button" onClick={() => set("")} aria-label="Clear" data-ink="" style={{ border: "none", background: "transparent", cursor: "pointer", color: "var(--ink, var(--text-muted))", display: "flex", padding: 0 }}><Icon name="x" size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></button> : null}
|
|
15
15
|
</div>
|
|
16
16
|
);
|
|
17
17
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import Icon from '../icons/Icon.vue'
|
|
3
|
-
// Bordered search field (
|
|
4
|
-
defineProps({ modelValue: { type: String, default: '' }, placeholder: { type: String, default: 'Search...' }, bare: Boolean, disabled: Boolean, style: Object })
|
|
3
|
+
// Bordered search field (r8, tier height — omit `size` to inherit the region's tier) with leading search glyph; bare=true renders the borderless top-bar flavor. Shows a clear (×) button once there's a value.
|
|
4
|
+
defineProps({ modelValue: { type: String, default: '' }, placeholder: { type: String, default: 'Search...' }, bare: Boolean, size: String, disabled: Boolean, style: Object })
|
|
5
|
+
const tierIcon = { width: 'var(--control-icon)', height: 'var(--control-icon)' }
|
|
5
6
|
const emit = defineEmits(['update:modelValue'])
|
|
6
7
|
</script>
|
|
7
8
|
<template>
|
|
8
|
-
<div :style="{ display: 'flex', alignItems: 'center', gap: bare ?
|
|
9
|
-
<span style="color:var(--text-muted)"><Icon name="search" :size="
|
|
9
|
+
<div :data-size="size" :data-field="bare || disabled ? undefined : ''" :style="{ display: 'flex', alignItems: 'center', gap: bare ? '12px' : '8px', height: 'var(--control-h)', boxSizing: 'border-box', opacity: disabled ? .5 : 1, ...(bare ? null : { border: '1px solid var(--field-border, var(--border-control))', borderRadius: 'var(--radius-md)', padding: '0 var(--control-px)', background: 'var(--bg-surface)' }), ...style }">
|
|
10
|
+
<span style="color:var(--text-muted);display:inline-flex"><Icon name="search" :size="16" :style="tierIcon" /></span>
|
|
10
11
|
<input :value="modelValue" :disabled="disabled" @input="emit('update:modelValue', $event.target.value)" :placeholder="placeholder"
|
|
11
|
-
:style="{ border: 'none', outline: 'none', background: 'transparent', flex: 1, minWidth: 0, fontFamily: 'var(--font-sans)', fontSize:
|
|
12
|
-
<button v-if="modelValue && !disabled" type="button" @click="emit('update:modelValue', '')" style="border:none;background:transparent;cursor:pointer;color:var(--text-muted);display:flex;padding:0"><Icon name="x" :size="
|
|
12
|
+
:style="{ border: 'none', outline: 'none', background: 'transparent', flex: 1, minWidth: 0, padding: 0, fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-primary)', cursor: disabled ? 'not-allowed' : 'text' }" />
|
|
13
|
+
<button v-if="modelValue && !disabled" type="button" aria-label="Clear" data-ink="" @click="emit('update:modelValue', '')" style="border:none;background:transparent;cursor:pointer;color:var(--ink, var(--text-muted));display:flex;padding:0"><Icon name="x" :size="16" :style="tierIcon" /></button>
|
|
13
14
|
</div>
|
|
14
15
|
</template>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Hairline select
|
|
2
|
+
/** Hairline select, r8, at the region's tier height (sm 24 · md 32 · lg 36). 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. */
|
|
6
|
+
size?: "sm" | "md" | "lg";
|
|
6
7
|
value?: string;
|
|
7
8
|
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
8
9
|
/** Shown as a disabled, muted-color first option when no value is set */
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Icon } from "../icons/Icon.jsx";
|
|
3
3
|
|
|
4
|
-
/** Native select styled per Keybricks
|
|
5
|
-
export function Select({ options = [], size
|
|
6
|
-
const sm = size === "sm";
|
|
4
|
+
/** Native select styled per Keybricks: r8, tier height (sm 24 · md 32 · lg 36 — omit `size` to inherit the region's tier). disabled = inset fill (matches Input readOnly), 50% opacity; error = red border. */
|
|
5
|
+
export function Select({ options = [], size, value, onChange, placeholder, disabled, error, style }) {
|
|
7
6
|
return (
|
|
8
|
-
<div style={{ position: "relative", display: "inline-flex", ...style }}>
|
|
9
|
-
<select value={value} onChange={onChange} disabled={disabled}
|
|
10
|
-
style={{ appearance: "none", WebkitAppearance: "none", height:
|
|
7
|
+
<div data-size={size} style={{ position: "relative", display: "inline-flex", ...style }}>
|
|
8
|
+
<select value={value} onChange={onChange} disabled={disabled} data-field={disabled ? undefined : ""}
|
|
9
|
+
style={{ appearance: "none", WebkitAppearance: "none", height: "var(--control-h)", borderRadius: "var(--radius-md)", border: `1px solid ${error ? "var(--danger)" : "var(--field-border, var(--border-control))"}`, background: disabled ? "var(--bg-inset)" : "var(--bg-surface)", padding: "0 calc(var(--control-px) + var(--control-icon) + 6px) 0 var(--control-px)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", color: disabled ? "var(--text-secondary)" : value ? "var(--text-primary)" : "var(--text-muted)", outline: "none", cursor: disabled ? "not-allowed" : "pointer", width: "100%", opacity: disabled ? .5 : 1 }}>
|
|
11
10
|
{placeholder ? <option value="" disabled hidden>{placeholder}</option> : null}
|
|
12
11
|
{options.map((o) => <option key={o} value={o}>{o}</option>)}
|
|
13
12
|
</select>
|
|
14
|
-
<span style={{ position: "absolute", right:
|
|
13
|
+
<span style={{ position: "absolute", right: "var(--control-px)", top: "50%", transform: "translateY(-50%)", pointerEvents: "none", color: "var(--text-muted)", display: "inline-flex" }}><Icon name="chevron-down" size={16} style={{ width: "var(--control-icon)", height: "var(--control-icon)" }} /></span>
|
|
15
14
|
</div>
|
|
16
15
|
);
|
|
17
16
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import Icon from '../icons/Icon.vue'
|
|
3
|
-
// Native select styled per Keybricks
|
|
4
|
-
const props = defineProps({ modelValue: String, options: { type: Array, default: () => [] }, size:
|
|
3
|
+
// Native select styled per Keybricks: r8, tier height (sm 24 · md 32 · lg 36 — omit `size` to inherit the region's tier). disabled = inset fill (matches Input readonly), 50% opacity; error = red border.
|
|
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>
|
|
7
7
|
<template>
|
|
8
|
-
<div :style="{ position: 'relative', display: 'inline-flex', ...style }">
|
|
9
|
-
<select :value="modelValue" @change="$emit('update:modelValue', $event.target.value)" :disabled="disabled"
|
|
10
|
-
:style="{ appearance: 'none', height:
|
|
8
|
+
<div :data-size="size" :style="{ position: 'relative', display: 'inline-flex', ...style }">
|
|
9
|
+
<select :value="modelValue" @change="$emit('update:modelValue', $event.target.value)" :disabled="disabled" :data-field="disabled ? undefined : ''"
|
|
10
|
+
:style="{ appearance: 'none', height: 'var(--control-h)', borderRadius: 'var(--radius-md)', border: `1px solid ${error ? 'var(--danger)' : 'var(--field-border, var(--border-control))'}`, background: disabled ? 'var(--bg-inset)' : 'var(--bg-surface)', padding: '0 calc(var(--control-px) + var(--control-icon) + 6px) 0 var(--control-px)', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', letterSpacing: 'var(--control-tracking)', color: disabled ? 'var(--text-secondary)' : modelValue ? 'var(--text-primary)' : 'var(--text-muted)', outline: 'none', cursor: disabled ? 'not-allowed' : 'pointer', width: '100%', opacity: disabled ? .5 : 1 }">
|
|
11
11
|
<option v-if="placeholder" value="" disabled hidden>{{ placeholder }}</option>
|
|
12
12
|
<option v-for="o in options" :key="o" :value="o">{{ o }}</option>
|
|
13
13
|
</select>
|
|
14
|
-
<span :style="{ position: 'absolute', right:
|
|
14
|
+
<span :style="{ position: 'absolute', right: 'var(--control-px)', top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: 'var(--text-muted)', display: 'inline-flex' }"><Icon name="chevron-down" :size="16" :style="{ width: 'var(--control-icon)', height: 'var(--control-icon)' }" /></span>
|
|
15
15
|
</div>
|
|
16
16
|
</template>
|
|
@@ -9,11 +9,11 @@ export function Slider({ value = 50, min = 0, max = 100, step = 1, labels, showV
|
|
|
9
9
|
React.useEffect(() => { if (!drag) return; const mv = (e) => set(e.clientX), up = () => setDrag(false); document.addEventListener("mousemove", mv); document.addEventListener("mouseup", up); return () => { document.removeEventListener("mousemove", mv); document.removeEventListener("mouseup", up); }; }, [drag]);
|
|
10
10
|
return (
|
|
11
11
|
<div style={{ width, opacity: disabled ? .5 : 1, ...style }}>
|
|
12
|
-
<div ref={track} onMouseDown={disabled ? undefined : (e) => { setDrag(true); set(e.clientX); }} style={{ position: "relative", height: 20, display: "flex", alignItems: "center", cursor: disabled ? "not-allowed" : "pointer" }}>
|
|
12
|
+
<div ref={track} data-thumb={disabled ? undefined : ""} onMouseDown={disabled ? undefined : (e) => { setDrag(true); set(e.clientX); }} style={{ position: "relative", height: 20, display: "flex", alignItems: "center", cursor: disabled ? "not-allowed" : "pointer" }}>
|
|
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
|
-
<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: "0 1px 2px rgba(0,0,0,.15)" }}></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-mono)", fontSize: "var(--text-
|
|
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-mono)", 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>
|
|
@@ -12,11 +12,11 @@ onUnmounted(up)
|
|
|
12
12
|
</script>
|
|
13
13
|
<template>
|
|
14
14
|
<div :style="{ width: typeof width === 'number' ? width + 'px' : width, opacity: disabled ? .5 : 1, ...style }">
|
|
15
|
-
<div ref="track" @mousedown="!disabled && (drag = true, set($event.clientX))" :style="{ position: 'relative', height: '20px', display: 'flex', alignItems: 'center', cursor: disabled ? 'not-allowed' : 'pointer' }">
|
|
15
|
+
<div ref="track" :data-thumb="disabled ? undefined : ''" @mousedown="!disabled && (drag = true, set($event.clientX))" :style="{ position: 'relative', height: '20px', display: 'flex', alignItems: 'center', cursor: disabled ? 'not-allowed' : 'pointer' }">
|
|
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
|
-
<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: '0 1px 2px rgba(0,0,0,.15)' }" />
|
|
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-mono)', fontSize: 'var(--text-
|
|
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-mono)', 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>
|