@keyfunc/keybricks-ds 1.0.3 → 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 +176 -36
- package/SKILL.md +6 -4
- package/_adherence.oxlintrc.json +183 -72
- 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 +46 -0
- package/components/_cards/search.card.html +5 -2
- package/components/_cards/segmented-control.card.html +39 -0
- 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 +45 -56
- 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/_shared/view-source.js +9 -4
- 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 +30 -0
- package/components/forms/Radio.jsx +65 -0
- package/components/forms/Radio.vue +17 -9
- package/components/forms/RadioGroup.vue +22 -4
- 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 +32 -24
- package/components/forms/Switch.jsx +85 -35
- package/components/forms/Switch.vue +57 -11
- package/components/forms/Textarea.vue +4 -4
- package/components/forms/forms.prompt.md +4 -1
- package/components/forms/input-base.js +2 -1
- package/components/index.d.ts +2 -2
- package/components/index.js +5 -5
- 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 +11 -4
- package/components/tabs/SegmentedControl.jsx +25 -10
- package/components/tabs/SegmentedControl.vue +19 -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 +4 -2
- package/keybricks.css +4 -0
- package/package.json +6 -3
- package/readme.md +90 -123
- 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
|
@@ -12,17 +12,17 @@ function stop(e) { e.preventDefault(); e.stopPropagation() }
|
|
|
12
12
|
function onDrop(e) { stop(e); dragging.value = false; emit('dropFiles', e.dataTransfer.files) }
|
|
13
13
|
</script>
|
|
14
14
|
<template>
|
|
15
|
-
<div v-if="file" :style="{ height, borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-control)', background: 'var(--bg-inset)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap:
|
|
15
|
+
<div v-if="file" :style="{ height, borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-control)', background: 'var(--bg-inset)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', boxSizing: 'border-box', ...style }">
|
|
16
16
|
<span style="color:var(--success-strong)"><Icon name="file-check" :size="22" :stroke-width="1.5" /></span>
|
|
17
17
|
<span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)">{{ file }}</span>
|
|
18
|
-
<button type="button" @click="$emit('remove')" style="display:flex;align-items:center;gap:4px;border:none;background:transparent;cursor:pointer;color:var(--danger);font-family:var(--font-sans);font-size:var(--text-
|
|
18
|
+
<button type="button" data-link="" @click="$emit('remove')" style="display:flex;align-items:center;gap:4px;border:none;background:transparent;cursor:pointer;color:var(--danger);font-family:var(--font-sans);font-size:var(--text-base);padding:0;text-decoration-line:var(--link-line, none);text-underline-offset:2px"><Icon name="trash-2" :size="13" />Remove</button>
|
|
19
19
|
</div>
|
|
20
20
|
<div v-else @click="!disabled && $emit('click')" @mouseenter="hover = true" @mouseleave="hover = false"
|
|
21
21
|
@dragenter="!disabled && ((e) => { stop(e); dragging = true })($event)" @dragover="!disabled && stop($event)"
|
|
22
22
|
@dragleave="!disabled && ((e) => { stop(e); dragging = false })($event)" @drop="!disabled && onDrop($event)"
|
|
23
|
-
:style="{ height, borderRadius: 'var(--radius-lg)', border: `1px dashed ${toneColor[tone]}`, background: error ? 'var(--danger-bg)' : dragActive ? 'var(--soft-bg)' : 'transparent', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap:
|
|
24
|
-
<span :style="{ color: error ? 'var(--danger)' : dragActive ? 'var(--action-primary)' : 'var(--text-muted)', marginBottom:
|
|
23
|
+
:style="{ height, borderRadius: 'var(--radius-lg)', border: `1px dashed ${toneColor[tone]}`, background: error ? 'var(--danger-bg)' : dragActive ? 'var(--soft-bg)' : 'transparent', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '4px', cursor: disabled ? 'not-allowed' : 'pointer', boxSizing: 'border-box', opacity: disabled ? .5 : 1, transition: 'var(--t-hover)', ...style }">
|
|
24
|
+
<span :style="{ color: error ? 'var(--danger)' : dragActive ? 'var(--action-primary)' : 'var(--text-muted)', marginBottom: '6px' }"><Icon :name="error ? 'triangle-alert' : 'upload'" :size="22" :stroke-width="1.5" /></span>
|
|
25
25
|
<span :style="{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: error ? 'var(--danger-text)' : dragActive ? 'var(--action-primary)' : 'var(--text-heading)' }">{{ error || (dragActive ? 'Drop file to upload' : title) }}</span>
|
|
26
|
-
<span :style="{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-
|
|
26
|
+
<span :style="{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', color: dragActive ? 'var(--action-primary)' : 'var(--text-secondary)' }">{{ hint }}</span>
|
|
27
27
|
</div>
|
|
28
28
|
</template>
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/** Label + control wrapper.
|
|
2
|
+
/** Label + control wrapper. The label uses the tier's control text at weight 500 (text-body); required appends a red " *". */
|
|
3
3
|
export interface FieldProps {
|
|
4
4
|
label: string;
|
|
5
5
|
required?: boolean;
|
|
6
|
+
/** Size tier for the label and the control inside. Omit to inherit the enclosing region's tier (the default). */
|
|
7
|
+
size?: "sm" | "md" | "lg";
|
|
8
|
+
/** Label beside the control instead of above it — the filter-bar layout */
|
|
9
|
+
inline?: boolean;
|
|
6
10
|
/** Muted helper text below the control; hidden when `error` is set */
|
|
7
11
|
hint?: string;
|
|
8
12
|
/** Red error text below the control; also expected to drive the control's own error look */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
/** Form field wrapper:
|
|
4
|
-
export function Field({ label, required, hint, error, children, style }) {
|
|
3
|
+
/** Form field wrapper: label (tier control text, weight 500; red + * when required) above the control, optional hint/error below. `size` sets the tier for the whole field; `inline` puts the label beside the control for filter bars. */
|
|
4
|
+
export function Field({ label, required, hint, error, size, inline, children, style }) {
|
|
5
5
|
return (
|
|
6
|
-
<div style={{ display: "flex", flexDirection: "column", ...style }}>
|
|
7
|
-
<label style={{ paddingBottom:
|
|
6
|
+
<div data-size={size} style={{ display: "flex", flexDirection: inline ? "row" : "column", ...(inline ? { alignItems: "center", gap: "var(--control-gap)" } : null), ...style }}>
|
|
7
|
+
<label style={{ paddingBottom: inline ? 0 : 8, whiteSpace: inline ? "nowrap" : undefined, fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-body)" }}>{label}{required ? <span style={{ color: "var(--danger)" }}> *</span> : null}</label>
|
|
8
8
|
{children}
|
|
9
9
|
{error ? <span style={{ paddingTop: 5, fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", color: "var(--danger)" }}>{error}</span>
|
|
10
10
|
: hint ? <span style={{ paddingTop: 5, fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", color: "var(--text-muted)" }}>{hint}</span> : null}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
// Form field wrapper:
|
|
3
|
-
defineProps({ label: String, required: Boolean, hint: String, error: String, style: Object })
|
|
2
|
+
// Form field wrapper: label (tier control text, weight 500; red + * when required) above the control, optional hint/error below. `size` sets the tier for the whole field; `inline` puts the label beside the control for filter bars.
|
|
3
|
+
defineProps({ label: String, required: Boolean, hint: String, error: String, size: String, inline: Boolean, style: Object })
|
|
4
4
|
</script>
|
|
5
5
|
<template>
|
|
6
|
-
<div :style="{ display: 'flex', flexDirection: 'column', ...style }">
|
|
7
|
-
<label style="
|
|
6
|
+
<div :data-size="size" :style="{ display: 'flex', flexDirection: inline ? 'row' : 'column', ...(inline ? { alignItems: 'center', gap: 'var(--control-gap)' } : null), ...style }">
|
|
7
|
+
<label :style="{ paddingBottom: inline ? 0 : '8px', whiteSpace: inline ? 'nowrap' : undefined, fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: 'var(--text-body)' }">{{ label }}<span v-if="required" style="color:var(--danger)"> *</span></label>
|
|
8
8
|
<slot />
|
|
9
9
|
<span v-if="error" style="padding-top:5px;font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--danger)">{{ error }}</span>
|
|
10
10
|
<span v-else-if="hint" style="padding-top:5px;font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-muted)">{{ hint }}</span>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
/**
|
|
2
|
+
/** r8 hairline text input at the region's tier height (sm 24 · md 32 · lg 36); readOnly = inset fill, disabled = 50% opacity, error = red border+ring. Extends native input props. */
|
|
3
3
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
4
|
+
/** Size tier. Omit to inherit the enclosing region's tier (the default); controls that share a row must share a tier. */
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
4
6
|
readOnly?: boolean;
|
|
5
7
|
disabled?: boolean;
|
|
6
8
|
error?: boolean;
|
|
@@ -9,6 +11,8 @@ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement>
|
|
|
9
11
|
export declare function Input(props: InputProps): JSX.Element;
|
|
10
12
|
/** r8 hairline textarea (default 90px). showCount + maxLength renders a live "n/max" character counter that turns red past the limit. */
|
|
11
13
|
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
14
|
+
/** Size tier for the text; omit to inherit */
|
|
15
|
+
size?: "sm" | "md" | "lg";
|
|
12
16
|
error?: boolean;
|
|
13
17
|
disabled?: boolean;
|
|
14
18
|
showCount?: boolean;
|
|
@@ -3,18 +3,18 @@ import React from "react";
|
|
|
3
3
|
import { inputBase } from "./input-base.js";
|
|
4
4
|
export { inputBase };
|
|
5
5
|
|
|
6
|
-
/** Text input,
|
|
7
|
-
export function Input({ readOnly, disabled, error, style, ...rest }) {
|
|
6
|
+
/** Text input, r8, tier height (sm 24 · md 32 · lg 36 — omit `size` to inherit the region's tier). readOnly = inset fill; disabled = 50% opacity; error = red border + ring. */
|
|
7
|
+
export function Input({ readOnly, disabled, error, size, style, ...rest }) {
|
|
8
8
|
const [focus, setFocus] = React.useState(false);
|
|
9
|
-
return <input readOnly={readOnly} disabled={disabled} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
|
|
9
|
+
return <input readOnly={readOnly} disabled={disabled} data-size={size} data-field={readOnly || disabled ? undefined : ""} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
|
|
10
10
|
style={{ ...inputBase, ...(readOnly ? { background: "var(--bg-inset)", color: "var(--text-secondary)" } : null),
|
|
11
11
|
...(error ? { borderColor: "var(--danger)" } : null),
|
|
12
12
|
...(focus && !readOnly && !disabled ? { borderColor: error ? "var(--danger)" : "var(--focus-border)", boxShadow: `0 0 0 2px ${error ? "var(--danger-bg)" : "var(--focus-ring)"}` } : null),
|
|
13
13
|
...(disabled ? { opacity: .5, cursor: "not-allowed", background: "var(--bg-inset)" } : null), ...style }} {...rest} />;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** Multiline textarea, r8, default 90px tall. showCount + maxLength renders a bottom-right "n/max" counter (red past the limit). */
|
|
17
|
-
export function Textarea({ style, rows = 4, error, disabled, showCount, maxLength, value, defaultValue, onChange, ...rest }) {
|
|
16
|
+
/** Multiline textarea, r8, default 90px tall; text follows the region's tier. showCount + maxLength renders a bottom-right "n/max" counter (red past the limit). */
|
|
17
|
+
export function Textarea({ style, rows = 4, error, disabled, showCount, maxLength, size, value, defaultValue, onChange, ...rest }) {
|
|
18
18
|
const [focus, setFocus] = React.useState(false);
|
|
19
19
|
const [inner, setInner] = React.useState(defaultValue || "");
|
|
20
20
|
const val = value != null ? value : inner;
|
|
@@ -22,11 +22,11 @@ export function Textarea({ style, rows = 4, error, disabled, showCount, maxLengt
|
|
|
22
22
|
const over = maxLength != null && len > maxLength;
|
|
23
23
|
return (
|
|
24
24
|
<div style={{ position: "relative" }}>
|
|
25
|
-
<textarea rows={rows} disabled={disabled} maxLength={showCount ? undefined : maxLength}
|
|
25
|
+
<textarea rows={rows} disabled={disabled} data-size={size} data-field={disabled ? undefined : ""} maxLength={showCount ? undefined : maxLength}
|
|
26
26
|
value={value} defaultValue={value == null ? defaultValue : undefined}
|
|
27
27
|
onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
|
|
28
28
|
onChange={(e) => { if (value == null) setInner(e.target.value); onChange && onChange(e); }}
|
|
29
|
-
style={{ ...inputBase, height: 90, resize: "vertical", paddingBottom: showCount ? 22 : 8,
|
|
29
|
+
style={{ ...inputBase, height: 90, resize: "vertical", paddingTop: 8, paddingBottom: showCount ? 22 : 8,
|
|
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} />
|
|
@@ -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>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
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
|
+
export interface RadioProps {
|
|
4
|
+
checked?: boolean;
|
|
5
|
+
onChange?: (checked: true) => void;
|
|
6
|
+
label?: string;
|
|
7
|
+
/** Muted second line under the label */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Size tier. Omit to inherit the enclosing region's tier (the default). */
|
|
10
|
+
size?: "sm" | "md" | "lg";
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
export declare function Radio(props: RadioProps): JSX.Element;
|
|
15
|
+
/** Single choice from a small set. Arrow keys move between enabled options. */
|
|
16
|
+
export interface RadioGroupProps {
|
|
17
|
+
options?: Array<string | { label: string; value: string; description?: string; disabled?: boolean }>;
|
|
18
|
+
value?: string;
|
|
19
|
+
onChange?: (value: string) => void;
|
|
20
|
+
/** Horizontal layout */
|
|
21
|
+
row?: boolean;
|
|
22
|
+
/** Size tier for the whole group. Omit to inherit (the default). */
|
|
23
|
+
size?: "sm" | "md" | "lg";
|
|
24
|
+
/** plain (default) · cards — each option is a bordered selectable card */
|
|
25
|
+
variant?: "plain" | "cards";
|
|
26
|
+
/** Disables the whole group */
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
}
|
|
30
|
+
export declare function RadioGroup(props: RadioGroupProps): JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "../icons/Icon.jsx";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Radio — circle at the region's tier (sm 14 · md/lg 16, --check-size), 1.5px border, filled action-blue dot when selected.
|
|
6
|
+
* hover darkens the ring; keyboard focus shows the 2px focus ring; disabled = 50% opacity.
|
|
7
|
+
* description renders a muted second line under the label (settings-style choices).
|
|
8
|
+
*/
|
|
9
|
+
export function Radio({ checked, onChange, label, description, size, disabled, style }) {
|
|
10
|
+
const d = { box: "var(--check-size)", dot: "calc(var(--check-size) / 2)" };
|
|
11
|
+
const [hover, setHover] = React.useState(false);
|
|
12
|
+
const [focus, setFocus] = React.useState(false);
|
|
13
|
+
const ring = checked ? "var(--action-primary)" : hover && !disabled ? "var(--text-muted)" : "var(--checkbox-border)";
|
|
14
|
+
return (
|
|
15
|
+
<label role="radio" data-size={size} aria-checked={!!checked} aria-disabled={disabled || undefined} tabIndex={disabled ? -1 : 0}
|
|
16
|
+
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
|
|
17
|
+
onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); !disabled && onChange && onChange(true); } }}
|
|
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 }}
|
|
19
|
+
onClick={() => !disabled && onChange && onChange(true)}>
|
|
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)" }}>
|
|
21
|
+
{checked ? <span style={{ width: d.dot, height: d.dot, borderRadius: "var(--radius-full)", background: "var(--action-primary)" }}></span> : null}
|
|
22
|
+
</span>
|
|
23
|
+
{label || description ? <span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
|
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}
|
|
26
|
+
</span> : null}
|
|
27
|
+
</label>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* RadioGroup — single choice from a small set. Vertical (default) or `row`.
|
|
33
|
+
* options: string[] | { label, value, description?, disabled? }[]
|
|
34
|
+
* Arrow keys move the selection between enabled options (WAI-ARIA radiogroup).
|
|
35
|
+
* variant="cards" renders each option as a bordered selectable card (plan pickers, migration strategies).
|
|
36
|
+
*/
|
|
37
|
+
export function RadioGroup({ options = [], value, onChange, row, size, variant = "plain", disabled, style }) {
|
|
38
|
+
const opts = options.map((o) => (typeof o === "string" ? { label: o, value: o } : o));
|
|
39
|
+
const enabled = opts.filter((o) => !o.disabled && !disabled);
|
|
40
|
+
const onKey = (e) => {
|
|
41
|
+
if (!["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft"].includes(e.key) || !enabled.length) return;
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
const i = enabled.findIndex((o) => o.value === value);
|
|
44
|
+
const next = e.key === "ArrowDown" || e.key === "ArrowRight" ? enabled[(i + 1) % enabled.length] : enabled[(i - 1 + enabled.length) % enabled.length];
|
|
45
|
+
onChange && onChange(next.value);
|
|
46
|
+
};
|
|
47
|
+
if (variant === "cards") {
|
|
48
|
+
return (
|
|
49
|
+
<div role="radiogroup" data-size={size} onKeyDown={onKey} style={{ display: "flex", flexDirection: row ? "row" : "column", gap: 8, ...style }}>
|
|
50
|
+
{opts.map((o) => {
|
|
51
|
+
const on = value === o.value, off = o.disabled || disabled;
|
|
52
|
+
return <div key={o.value} onClick={() => !off && onChange && onChange(o.value)}
|
|
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)" }}>
|
|
54
|
+
<Radio checked={on} onChange={() => onChange && onChange(o.value)} label={o.label} description={o.description} disabled={off} style={{ opacity: 1, cursor: "inherit" }} />
|
|
55
|
+
</div>;
|
|
56
|
+
})}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return (
|
|
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)} />)}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
// Radio —
|
|
4
|
-
|
|
5
|
-
const props = defineProps({ checked: Boolean, label: String,
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
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
|
+
// description renders a muted second line under the label.
|
|
5
|
+
const props = defineProps({ checked: Boolean, label: String, description: String, size: String, disabled: Boolean, style: Object })
|
|
6
6
|
const emit = defineEmits(['change'])
|
|
7
|
-
const d =
|
|
7
|
+
const d = { box: 'var(--check-size)', dot: 'calc(var(--check-size) / 2)' }
|
|
8
|
+
const hover = ref(false), focus = ref(false)
|
|
9
|
+
const ring = computed(() => props.checked ? 'var(--action-primary)' : hover.value && !props.disabled ? 'var(--text-muted)' : 'var(--checkbox-border)')
|
|
10
|
+
const pick = () => { if (!props.disabled) emit('change', true) }
|
|
8
11
|
</script>
|
|
9
12
|
<template>
|
|
10
|
-
<label
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
<label role="radio" :data-size="size" :aria-checked="checked" :aria-disabled="disabled || undefined" :tabindex="disabled ? -1 : 0"
|
|
14
|
+
@mouseenter="hover = true" @mouseleave="hover = false" @focus="focus = true" @blur="focus = false" @click="pick" @keydown.space.prevent="pick" @keydown.enter.prevent="pick"
|
|
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>
|
|
18
|
+
</span>
|
|
19
|
+
<span v-if="label || description" style="display:flex;flex-direction:column;gap:2px">
|
|
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>
|
|
13
22
|
</span>
|
|
14
|
-
<span v-if="label" :style="{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: size === 'sm' ? 'var(--text-xs)' : 'var(--text-sm)', lineHeight: 'var(--leading-sm)', color: 'var(--text-body)' }">{{ label }}</span>
|
|
15
23
|
</label>
|
|
16
24
|
</template>
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
2
3
|
import Radio from './Radio.vue'
|
|
3
|
-
// Vertical or
|
|
4
|
-
|
|
4
|
+
// RadioGroup — single choice. Vertical (default) or `row`. options: string[] | { label, value, description?, disabled? }[].
|
|
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: String, variant: { type: String, default: 'plain' }, disabled: Boolean, style: Object })
|
|
5
7
|
const emit = defineEmits(['update:modelValue'])
|
|
8
|
+
const opts = computed(() => props.options.map(o => typeof o === 'string' ? { label: o, value: o } : o))
|
|
9
|
+
const enabled = computed(() => opts.value.filter(o => !o.disabled && !props.disabled))
|
|
10
|
+
function onKey(e) {
|
|
11
|
+
if (!['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'].includes(e.key) || !enabled.value.length) return
|
|
12
|
+
e.preventDefault()
|
|
13
|
+
const i = enabled.value.findIndex(o => o.value === props.modelValue), n = enabled.value.length
|
|
14
|
+
const next = (e.key === 'ArrowDown' || e.key === 'ArrowRight') ? enabled.value[(i + 1) % n] : enabled.value[(i - 1 + n) % n]
|
|
15
|
+
emit('update:modelValue', next.value)
|
|
16
|
+
}
|
|
17
|
+
const pick = (o) => { if (!(o.disabled || props.disabled)) emit('update:modelValue', o.value) }
|
|
6
18
|
</script>
|
|
7
19
|
<template>
|
|
8
|
-
<div :style="{ display: 'flex', flexDirection: row ? 'row' : 'column', gap:
|
|
9
|
-
<
|
|
20
|
+
<div v-if="variant === 'cards'" role="radiogroup" :data-size="size" @keydown="onKey" :style="{ display: 'flex', flexDirection: row ? 'row' : 'column', gap: '8px', ...style }">
|
|
21
|
+
<div v-for="o in opts" :key="o.value" @click="pick(o)"
|
|
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" :disabled="o.disabled || disabled" :style="{ opacity: 1, cursor: 'inherit' }" @change="pick(o)" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
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)" />
|
|
10
28
|
</div>
|
|
11
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
|
}
|