- Move root-level picker, input, and menu components into lowercase domain folders - Update component namespace linting for kebab-case app domains - Document FSD-lite component structure in AGENTS.md
92 lines
2.2 KiB
Vue
92 lines
2.2 KiB
Vue
<script setup lang="ts">
|
|
import { inputNumberValue } from '@open-pencil/vue'
|
|
import { usePickerSliderUI } from '@/components/ui/picker-slider'
|
|
|
|
type PickerSliderDisplay = {
|
|
value?: number
|
|
min?: number
|
|
max?: number
|
|
step?: number
|
|
format?: (value: number) => string | number
|
|
parse?: (value: number) => number
|
|
}
|
|
|
|
interface PickerSliderProps {
|
|
label: string
|
|
modelValue: number
|
|
min: number
|
|
max: number
|
|
step?: number
|
|
display?: PickerSliderDisplay
|
|
gradientStyle?: string
|
|
checkerboard?: boolean
|
|
thumbFill?: string
|
|
ui?: Partial<
|
|
Record<'root' | 'label' | 'track' | 'gradient' | 'range' | 'thumb' | 'input', string>
|
|
>
|
|
}
|
|
|
|
const {
|
|
label,
|
|
modelValue,
|
|
min,
|
|
max,
|
|
step = 1,
|
|
display,
|
|
gradientStyle,
|
|
checkerboard = false,
|
|
thumbFill = '#fff',
|
|
ui
|
|
} = defineProps<PickerSliderProps>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: number]
|
|
}>()
|
|
|
|
const cls = usePickerSliderUI({ checkerboard, ui })
|
|
|
|
function numberValue(): string | number {
|
|
const value = display?.value ?? modelValue
|
|
return display?.format ? display.format(value) : value
|
|
}
|
|
|
|
function handleNumberChange(value: number) {
|
|
emit('update:modelValue', display?.parse ? display.parse(value) : value)
|
|
}
|
|
|
|
function thumbLeft(): string {
|
|
const range = max - min
|
|
const ratio = range === 0 ? 0 : (modelValue - min) / range
|
|
const clampedRatio = Math.max(0, Math.min(1, ratio))
|
|
return `calc(${clampedRatio * 100}% - ${clampedRatio * 14}px)`
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div :class="cls.root">
|
|
<span :class="cls.label">{{ label }}</span>
|
|
<div :class="cls.track">
|
|
<div :class="cls.gradient" :style="gradientStyle" />
|
|
<input
|
|
type="range"
|
|
:class="cls.range"
|
|
:min="min"
|
|
:max="max"
|
|
:step="step"
|
|
:value="modelValue"
|
|
@input="emit('update:modelValue', inputNumberValue($event))"
|
|
/>
|
|
<div :class="cls.thumb" :style="{ left: thumbLeft(), background: thumbFill }" />
|
|
</div>
|
|
<input
|
|
type="number"
|
|
:class="cls.input"
|
|
:min="display?.min ?? min"
|
|
:max="display?.max ?? max"
|
|
:step="display?.step ?? step"
|
|
:value="numberValue()"
|
|
@change="handleNumberChange(inputNumberValue($event))"
|
|
/>
|
|
</div>
|
|
</template>
|