Skip to content

Commit

Permalink
Merge pull request #107 from eliassebastian/feat/settings-slider-text…
Browse files Browse the repository at this point in the history
…-input

Feat/settings slider text input
  • Loading branch information
ddiu8081 authored Nov 7, 2023
2 parents 8823e38 + fce92e6 commit adabf77
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/components/ui/SettingsInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default ({ settings, editing, value, setValue }: Props) => {
<input
type="text"
value={value()}
class="w-full mt-1 bg-transparent border border-base px-2 py-1 focus:border-base-100 transition-colors-200"
class="w-full mt-1 bg-transparent border border-base px-2 py-1 focus:border-base-100 transition-colors-200"
onChange={e => setValue(e.currentTarget.value)}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/SettingsSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export default ({ settings, editing, value, setValue }: Props) => {
step={sliderSettings.step}
/>
)}
{!editing() && value() && (
{!editing() && value() !== undefined && (
<div>{value()}</div>
)}
{!editing() && !value() && (
{!editing() && value() === undefined && (
<SettingsNotDefined />
)}
</div>
Expand Down
114 changes: 104 additions & 10 deletions src/components/ui/base/Slider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as slider from '@zag-js/slider'
import { normalizeProps, useMachine } from '@zag-js/solid'
import { createMemo, createUniqueId, mergeProps } from 'solid-js'
import { createMemo, createSignal, createUniqueId, mergeProps } from 'solid-js'
import type { Accessor } from 'solid-js'

interface Props {
Expand All @@ -9,22 +9,30 @@ interface Props {
max: number
step: number
disabled?: boolean
canEditSliderViaInput?: boolean
setValue: (v: number) => void
}

export const Slider = (selectProps: Props) => {
const props = mergeProps({
min: 0,
max: 10,
step: 1,
disabled: false,
}, selectProps)
const props = mergeProps(
{
min: 0,
max: 10,
step: 1,
disabled: false,
canEditSliderViaInput: true,
},
selectProps,
)

const formatSliderValue = (value: number) => {
if (!value) return 0

return Number.isInteger(value) ? value : parseFloat(value.toFixed(2))
}

const [input, setInput] = createSignal(props.value(), { equals: false })

const [state, send] = useMachine(slider.machine({
id: createUniqueId(),
value: props.value(),
Expand All @@ -33,15 +41,72 @@ export const Slider = (selectProps: Props) => {
step: props.step,
disabled: props.disabled,
onChange: (details) => {
details && details.value && props.setValue(formatSliderValue(details.value))
if (!details) return

const value = formatSliderValue(details.value)
props.setValue(value)
setInput(value)
},
}))

const api = createMemo(() => slider.connect(state, send, normalizeProps))

return (
<div {...api().rootProps}>
<div class="text-xs op-50 fb items-center">
<div class="text-xs op-50 focus-within:op-100 fb items-center">
<div />
<output {...api().outputProps}>{formatSliderValue(api().value)}</output>
{!props.canEditSliderViaInput && (
<output {...api().outputProps}>
{formatSliderValue(api().value)}
</output>
)}
{props.canEditSliderViaInput && (
<input
type="text"
spellcheck={false}
autocomplete="off"
autocorrect="off"
aria-valuemax={props.max}
aria-valuemin={props.min}
aria-valuenow={input()}
aria-controls={api().hiddenInputProps.id}
aria-live="off"
aria-label="Enter value to adjust slider"
data-scope="slider"
class="bg-transparent border border-transparent w-[80px] text-right px-2 py-1 hover:border-base focus:border-base-100 transition-colors-200"
value={input()}
onInput={(e) => {
const target = e.target
if (!target) return

let value = Number(target.value)

if (Number.isNaN(value)) value = props.value()

api().setValue(value)
}}
onBlur={(e) => {
const target = e.target
if (!target) return

let value = Number(target.value)

if (Number.isNaN(value)) value = props.value()

value = adjustValueToStep(
value,
props.step,
props.min,
props.max,
)

setInput(value)
}}
onKeyUp={(e) => {
if (e.key === 'Enter') e.currentTarget.blur()
}}
/>
)}
</div>
<div class="mt-2" {...api().controlProps}>
<div {...api().trackProps}>
Expand All @@ -54,3 +119,32 @@ export const Slider = (selectProps: Props) => {
</div>
)
}

/**
* Adjusts the given value to the nearest multiple of 'step'
* and ensures that the result lies within the range [min, max].
*
* @param value - The value to be adjusted.
* @param step - The step size to which the value should be adjusted.
* @param min - The minimum allowable value.
* @param max - The maximum allowable value.
*
* @returns The adjusted value.
*/
function adjustValueToStep(
value: number,
step: number,
min: number,
max: number,
) {
// Adjust the value to the nearest step
const adjustedValue = Math.round((value - min) / step) * step + min

// Clamp the value to the min and max
const boundedValue = Math.min(Math.max(adjustedValue, min), max)

// Round the value to the nearest decimal place
const decimalPlaces = (step.toString().split('.')[1] || []).length

return parseFloat(boundedValue.toFixed(decimalPlaces))
}

0 comments on commit adabf77

Please sign in to comment.