Marks as dots inside the track: ticks, a selector's options and a slider's reading on the track's midline and the ring's centre line, coloured by the fill or the handle over them; the selector ring fills to its option
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { motion, useTransform } from "motion/react"
|
||||
import { useEffect, useRef } from "react"
|
||||
import { type MotionValue, motion, useTransform } from "motion/react"
|
||||
import { useEffect, useId, useRef } from "react"
|
||||
|
||||
import type { WidgetDef } from "@/client"
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
showTitle,
|
||||
text,
|
||||
} from "./ui/core/config"
|
||||
import { tickIntervals } from "./ui/core/controls"
|
||||
import {
|
||||
CENTRE_LABEL,
|
||||
CENTRE_SWATCH,
|
||||
@@ -34,7 +35,6 @@ import {
|
||||
RING_INSET,
|
||||
RING_MARK,
|
||||
RING_READING,
|
||||
RING_READING_STROKE,
|
||||
RING_STROKE,
|
||||
ringArc,
|
||||
ringPoint,
|
||||
@@ -78,10 +78,13 @@ type Dial = {
|
||||
/** Written out; empty for a colour, whose handle is the reading. */
|
||||
reading: (value: unknown) => string
|
||||
unit?: string
|
||||
/** A value arc from the start to the reading: a slider and a gauge. */
|
||||
/** A value arc from the start to the reading: a slider, a selector and a
|
||||
* gauge. */
|
||||
fill?: boolean
|
||||
handle?: boolean
|
||||
marks?: number[]
|
||||
/** Dots along the sweep, as fractions of it: a selector's options, a
|
||||
* slider's ticks. */
|
||||
stops?: number[]
|
||||
hue?: boolean
|
||||
}
|
||||
|
||||
@@ -109,8 +112,9 @@ function dialOf(widget: WidgetDef, live: unknown): Dial {
|
||||
const option = options[index as number]
|
||||
return option ? (option.label ?? text(option.value)) : "—"
|
||||
},
|
||||
fill: true,
|
||||
handle: true,
|
||||
marks: options.map((_, index) => at(index) ?? 0.5),
|
||||
stops: options.map((_, index) => at(index) ?? 0.5),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +163,8 @@ function dialOf(widget: WidgetDef, live: unknown): Dial {
|
||||
const digits = (String(step).split(".")[1] ?? "").length
|
||||
const fit = (value: number) =>
|
||||
Number(Math.min(max, Math.max(min, value)).toFixed(digits))
|
||||
// The tile's scale, as dots: the same intervals its labels are cut into.
|
||||
const intervals = tickIntervals((max - min) / step)
|
||||
return {
|
||||
value: typeof live === "number" ? live : min,
|
||||
at: (value) => fractionOf(value as number, min, max),
|
||||
@@ -170,6 +176,10 @@ function dialOf(widget: WidgetDef, live: unknown): Dial {
|
||||
unit,
|
||||
fill: true,
|
||||
handle: true,
|
||||
stops:
|
||||
cfg.ticks !== false && max > min
|
||||
? Array.from({ length: intervals + 1 }, (_, index) => index / intervals)
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,14 +343,16 @@ export function Ring({
|
||||
// Written to the slider's own decimals and in its unit: the two are read
|
||||
// against each other.
|
||||
const other = beside.message ? dial.reading(read?.value ?? null) : null
|
||||
// The same reading, placed: a bar across the band where it sits on the
|
||||
// slider's scale, held to the sweep at either end.
|
||||
// The same reading, placed: a dot where it sits on the slider's scale,
|
||||
// held to the sweep at either end.
|
||||
const actual = typeof read?.value === "number" ? dial.at(read.value) : null
|
||||
const across = useTransform(useEased(actual ?? 0), (fraction) => {
|
||||
const from = ringPoint(size, fraction, -RING_READING / 2)
|
||||
const to = ringPoint(size, fraction, RING_READING / 2)
|
||||
return `M${from.x} ${from.y}L${to.x} ${to.y}`
|
||||
})
|
||||
const readAt = useEased(actual ?? 0)
|
||||
const readX = useTransform(readAt, (fraction) => ringPoint(size, fraction).x)
|
||||
const readY = useTransform(readAt, (fraction) => ringPoint(size, fraction).y)
|
||||
const readDot = actual === null ? null : { x: readX, y: readY }
|
||||
// What the dots are drawn under: the value arc and the handle, which
|
||||
// repaint whatever part of a dot they cover (`RingDots`).
|
||||
const id = useId().replace(/[^\w-]/g, "")
|
||||
const whole = ringArc(size, 0, 1)
|
||||
const name = widget.title || text(cfg.target, widget.id)
|
||||
|
||||
@@ -397,38 +409,23 @@ export function Ring({
|
||||
className="dui-ring-track"
|
||||
/>
|
||||
)}
|
||||
<RingDots size={size} stops={dial.stops} reading={readDot} />
|
||||
{/* Revealed rather than re-pathed, as the gauge tile's arc is: `d`
|
||||
does not ease, and `pathLength` makes the reveal the fraction. */}
|
||||
{dial.fill ? (
|
||||
<motion.path
|
||||
d={whole}
|
||||
fill="none"
|
||||
strokeWidth={RING_STROKE}
|
||||
strokeLinecap="round"
|
||||
className="dui-ring-fill"
|
||||
style={{ pathLength: along, opacity: shown }}
|
||||
/>
|
||||
) : null}
|
||||
{dial.marks?.map((mark, index) => {
|
||||
const point = ringPoint(size, mark)
|
||||
return (
|
||||
<circle
|
||||
// Two options may share a label; position is the identity.
|
||||
key={`mark-${index}`}
|
||||
cx={point.x}
|
||||
cy={point.y}
|
||||
r={RING_MARK / 2}
|
||||
className="dui-ring-mark"
|
||||
<>
|
||||
<motion.path
|
||||
d={whole}
|
||||
fill="none"
|
||||
strokeWidth={RING_STROKE}
|
||||
strokeLinecap="round"
|
||||
className="dui-ring-fill"
|
||||
style={{ pathLength: along, opacity: shown }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{/* Under the handle, so where the two meet the value being set wins. */}
|
||||
{actual !== null ? (
|
||||
<motion.path
|
||||
d={across}
|
||||
strokeWidth={RING_READING_STROKE}
|
||||
className="dui-ring-reading"
|
||||
/>
|
||||
<g className="dui-ring-reached" mask={`url(#${id}-fill)`}>
|
||||
<RingDots size={size} stops={dial.stops} reading={readDot} />
|
||||
</g>
|
||||
</>
|
||||
) : null}
|
||||
{dial.handle && at !== null ? (
|
||||
<motion.circle
|
||||
@@ -439,7 +436,36 @@ export function Ring({
|
||||
style={dial.hue ? { fill: cssOf(current as Triple) } : undefined}
|
||||
/>
|
||||
) : null}
|
||||
{readDot && dial.handle && at !== null ? (
|
||||
<g className="dui-ring-on-handle" clipPath={`url(#${id}-handle)`}>
|
||||
<RingDots size={size} reading={readDot} />
|
||||
</g>
|
||||
) : null}
|
||||
</g>
|
||||
<defs>
|
||||
{/* The value arc again, as a stencil: a dot it has reached is
|
||||
drawn in the colour that reads on it, to the pixel it covers. */}
|
||||
<mask
|
||||
id={`${id}-fill`}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={size}
|
||||
height={size}
|
||||
>
|
||||
<motion.path
|
||||
d={whole}
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
strokeWidth={RING_STROKE}
|
||||
strokeLinecap="round"
|
||||
style={{ pathLength: along, opacity: shown }}
|
||||
/>
|
||||
</mask>
|
||||
<clipPath id={`${id}-handle`}>
|
||||
<motion.circle cx={handleX} cy={handleY} r={RING_HANDLE / 2} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
{turns || editing ? (
|
||||
<path
|
||||
d={whole}
|
||||
@@ -527,6 +553,51 @@ export function Ring({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The ring's dots: its stops, and what a slider reads beside its value.
|
||||
*
|
||||
* Drawn on the ring's centre line, inside the band, and drawn up to three
|
||||
* times: on the track, again through the value arc as a stencil, and the
|
||||
* reading once more through the handle. Each copy is in the colour that reads
|
||||
* on what it sits on, so a dot changes colour exactly where the arc or the
|
||||
* handle passes over it — and the reading still shows on the handle.
|
||||
*/
|
||||
function RingDots({
|
||||
size,
|
||||
stops = [],
|
||||
reading,
|
||||
}: {
|
||||
size: number
|
||||
stops?: number[]
|
||||
reading: { x: MotionValue<number>; y: MotionValue<number> } | null
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{stops.map((stop, index) => {
|
||||
const point = ringPoint(size, stop)
|
||||
return (
|
||||
<circle
|
||||
// Two options may share a label; position is the identity.
|
||||
key={`stop-${index}`}
|
||||
cx={point.x}
|
||||
cy={point.y}
|
||||
r={RING_MARK / 2}
|
||||
className="dui-ring-stop"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{reading ? (
|
||||
<motion.circle
|
||||
cx={reading.x}
|
||||
cy={reading.y}
|
||||
r={RING_READING / 2}
|
||||
className="dui-ring-reading"
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The colour ring's track: every hue in order along the sweep, red at both
|
||||
* ends. Drawn as short strokes — an SVG stroke takes no conic gradient — each
|
||||
|
||||
@@ -254,6 +254,11 @@ export function useSliderDrag({
|
||||
fraction,
|
||||
/** Where the mark sits along the track, 0..1 and held to it; null for none. */
|
||||
markAt: mark === null ? null : markAt,
|
||||
/** The scale's ticks as dots in the track, 0..1, in either orientation. */
|
||||
stops:
|
||||
ticks && span > 0
|
||||
? Array.from({ length: intervals + 1 }, (_, index) => index / intervals)
|
||||
: [],
|
||||
inputProps: {
|
||||
type: "range" as const,
|
||||
min,
|
||||
|
||||
@@ -412,43 +412,162 @@
|
||||
translate: -50% 50%;
|
||||
}
|
||||
|
||||
/* A second value on the track, such as what a setpoint is set against: a bar
|
||||
across the track, on the handle's own travel so the two line up at the same
|
||||
value, and longer than the handle so it still shows when they meet. Under
|
||||
the input, so the hit area is the input's alone. */
|
||||
.dui-slider-mark {
|
||||
/* The colours of the dots inside a slider's track and along the ring, by what
|
||||
each copy sits on: the track, the fill, the handle. A look whose handle is
|
||||
not the page colour restates the last. */
|
||||
.dui-slider,
|
||||
.dui-ring {
|
||||
--dui-stop: var(--muted-foreground);
|
||||
--dui-stop-reached: color-mix(
|
||||
in srgb,
|
||||
var(--primary-foreground) 70%,
|
||||
transparent
|
||||
);
|
||||
--dui-reading: var(--foreground);
|
||||
--dui-reading-reached: var(--primary-foreground);
|
||||
--dui-reading-on-handle: var(--foreground);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dots inside the track, on its midline: a tick's stop, and what a slider reads
|
||||
* beside its value — such as the room's temperature beside a setpoint — as a
|
||||
* larger one in a stronger colour. Both sit on the handle's own travel, so a
|
||||
* dot lines up with the handle at the same value.
|
||||
*
|
||||
* Drawn up to three times, each copy in the colour that reads on what it sits
|
||||
* on: on the track; again over the fill, cut to the fill's extent; and the
|
||||
* reading once more over the handle, cut to the handle. So a dot changes
|
||||
* colour exactly where the fill or the handle passes it, as the ring's do.
|
||||
* After the input only for its `:disabled`, as none of them takes a pointer.
|
||||
*/
|
||||
.dui-slider-dots,
|
||||
.dui-slider-reached,
|
||||
.dui-slider-on-thumb {
|
||||
position: absolute;
|
||||
background: var(--foreground);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="horizontal"] .dui-slider-mark {
|
||||
.dui-slider-dots {
|
||||
overflow: hidden;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="horizontal"] .dui-slider-dots {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
height: var(--dui-track-size, 0.5rem);
|
||||
translate: 0 -50%;
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="vertical"] .dui-slider-dots {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: var(--dui-track-size, 0.5rem);
|
||||
translate: -50% 0;
|
||||
}
|
||||
|
||||
.dui-slider-reached {
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="horizontal"] .dui-slider-reached {
|
||||
clip-path: inset(0 calc((1 - var(--dui-fraction, 0)) * 100%) 0 0);
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="vertical"] .dui-slider-reached {
|
||||
clip-path: inset(calc((1 - var(--dui-fraction, 0)) * 100%) 0 0 0);
|
||||
}
|
||||
|
||||
/* Over the handle, which comes before it. */
|
||||
.dui-slider-on-thumb {
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="horizontal"] .dui-slider-on-thumb {
|
||||
clip-path: circle(
|
||||
calc(var(--dui-thumb) / 2) at
|
||||
calc(
|
||||
var(--dui-thumb) /
|
||||
2 +
|
||||
var(--dui-fraction, 0) *
|
||||
(100% - var(--dui-thumb))
|
||||
)
|
||||
50%
|
||||
);
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="vertical"] .dui-slider-on-thumb {
|
||||
clip-path: circle(
|
||||
calc(var(--dui-thumb) / 2) at 50%
|
||||
calc(
|
||||
100% -
|
||||
var(--dui-thumb) /
|
||||
2 -
|
||||
var(--dui-fraction, 0) *
|
||||
(100% - var(--dui-thumb))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.dui-slider-stop,
|
||||
.dui-slider-reading {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dui-slider-stop {
|
||||
width: calc(var(--dui-track-size, 0.5rem) / 2);
|
||||
height: calc(var(--dui-track-size, 0.5rem) / 2);
|
||||
background: var(--dui-stop);
|
||||
}
|
||||
|
||||
.dui-slider-reading {
|
||||
width: calc(var(--dui-track-size, 0.5rem) * 0.75);
|
||||
height: calc(var(--dui-track-size, 0.5rem) * 0.75);
|
||||
background: var(--dui-reading);
|
||||
}
|
||||
|
||||
.dui-slider-reached .dui-slider-stop {
|
||||
background: var(--dui-stop-reached);
|
||||
}
|
||||
|
||||
.dui-slider-reached .dui-slider-reading {
|
||||
background: var(--dui-reading-reached);
|
||||
}
|
||||
|
||||
.dui-slider-on-thumb .dui-slider-reading {
|
||||
background: var(--dui-reading-on-handle);
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="horizontal"]
|
||||
:is(.dui-slider-stop, .dui-slider-reading) {
|
||||
top: 50%;
|
||||
left: calc(
|
||||
var(--dui-thumb) /
|
||||
2 +
|
||||
var(--dui-mark, 0) *
|
||||
var(--dui-at, 0) *
|
||||
(100% - var(--dui-thumb))
|
||||
);
|
||||
width: 2px;
|
||||
height: calc(var(--dui-thumb) + 0.5rem);
|
||||
translate: -50% -50%;
|
||||
}
|
||||
|
||||
.dui-slider[data-orientation="vertical"] .dui-slider-mark {
|
||||
.dui-slider[data-orientation="vertical"]
|
||||
:is(.dui-slider-stop, .dui-slider-reading) {
|
||||
left: 50%;
|
||||
bottom: calc(
|
||||
var(--dui-thumb) /
|
||||
2 +
|
||||
var(--dui-mark, 0) *
|
||||
var(--dui-at, 0) *
|
||||
(100% - var(--dui-thumb))
|
||||
);
|
||||
width: calc(var(--dui-thumb) + 0.5rem);
|
||||
height: 2px;
|
||||
translate: -50% 50%;
|
||||
}
|
||||
|
||||
.dui-slider-input:disabled ~ .dui-slider-mark {
|
||||
.dui-slider-input:disabled ~ .dui-slider-dots,
|
||||
.dui-slider-input:disabled ~ .dui-slider-on-thumb {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
@@ -685,13 +804,25 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dui-ring-mark {
|
||||
fill: var(--muted-foreground);
|
||||
/* The ring's dots, in the colours the slider's track uses for its own. */
|
||||
.dui-ring-stop {
|
||||
fill: var(--dui-stop);
|
||||
}
|
||||
|
||||
/* What a slider reads beside its value, as the tile's track marks it. */
|
||||
.dui-ring-reading {
|
||||
stroke: var(--foreground);
|
||||
fill: var(--dui-reading);
|
||||
}
|
||||
|
||||
.dui-ring-reached .dui-ring-stop {
|
||||
fill: var(--dui-stop-reached);
|
||||
}
|
||||
|
||||
.dui-ring-reached .dui-ring-reading {
|
||||
fill: var(--dui-reading-reached);
|
||||
}
|
||||
|
||||
.dui-ring-on-handle .dui-ring-reading {
|
||||
fill: var(--dui-reading-on-handle);
|
||||
}
|
||||
|
||||
/* Page dots, one per face, in the ring's gap. The current one changes over
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* What a slider marks inside its track: a dot per tick, and what it reads
|
||||
* beside its value as a larger one.
|
||||
*
|
||||
* The same markup in every look — the dots are geometry, and `core.css` draws
|
||||
* them in the look's tokens — so it lives here rather than three times over.
|
||||
* The copies over the fill and over the handle are what let a dot change
|
||||
* colour exactly where either passes it.
|
||||
*/
|
||||
import { type MotionStyle, type MotionValue, motion } from "motion/react"
|
||||
|
||||
export function SliderMarks({
|
||||
stops,
|
||||
mark,
|
||||
}: {
|
||||
/** Where each tick sits along the track, 0..1. */
|
||||
stops: number[]
|
||||
/** Where the reading sits, 0..1; null for none. */
|
||||
mark: MotionValue<number> | null
|
||||
}) {
|
||||
if (stops.length === 0 && mark === null) return null
|
||||
const dots = (
|
||||
<>
|
||||
{stops.map((at) => (
|
||||
<span
|
||||
key={at}
|
||||
className="dui-slider-stop"
|
||||
style={{ "--dui-at": at } as React.CSSProperties}
|
||||
/>
|
||||
))}
|
||||
{mark ? (
|
||||
<motion.span
|
||||
className="dui-slider-reading"
|
||||
style={{ "--dui-at": mark } as MotionStyle}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<span className="dui-slider-dots" aria-hidden>
|
||||
{dots}
|
||||
<span className="dui-slider-reached">{dots}</span>
|
||||
</span>
|
||||
{mark ? (
|
||||
<span className="dui-slider-on-thumb" aria-hidden>
|
||||
<motion.span
|
||||
className="dui-slider-reading"
|
||||
style={{ "--dui-at": mark } as MotionStyle}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -22,13 +22,12 @@ export const RING_SWEEP = 300
|
||||
export const RING_STROKE = 16
|
||||
/** The handle's diameter. */
|
||||
export const RING_HANDLE = 28
|
||||
/** A dropdown's option marks, as a diameter. */
|
||||
/** A stop on the ring — a selector's option, a slider's tick — as a dot this
|
||||
* wide on the ring's centre line. */
|
||||
export const RING_MARK = 6
|
||||
/** What a slider reads beside its value, marked on the ring: a bar across the
|
||||
* band this long and this wide, longer than the handle so it still shows
|
||||
* where the two meet. */
|
||||
export const RING_READING = RING_HANDLE + 8
|
||||
export const RING_READING_STROKE = 3
|
||||
/** What a slider reads beside its value, as a larger dot on the centre line:
|
||||
* told from a stop by its size and its stronger colour. */
|
||||
export const RING_READING = 10
|
||||
/** How long the ring waits after the last notch before it publishes. */
|
||||
export const RING_SETTLE_MS = 300
|
||||
/** The gap's line — a slider's title and value, when what it reads is in
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
} from "../core/contract"
|
||||
import { useSegmented, useSliderDrag, useSwitch } from "../core/controls"
|
||||
import { useLook } from "../core/look"
|
||||
import { SliderMarks } from "../core/marks"
|
||||
import { LOOK } from "../core/motion"
|
||||
|
||||
export function Button({
|
||||
@@ -65,7 +66,8 @@ export function Switch(props: SwitchProps) {
|
||||
}
|
||||
|
||||
export function Slider(props: SliderProps) {
|
||||
const { fraction, inputProps, marks, markAt, trackRef } = useSliderDrag(props)
|
||||
const { fraction, inputProps, marks, markAt, stops, trackRef } =
|
||||
useSliderDrag(props)
|
||||
const orientation = props.orientation ?? "horizontal"
|
||||
return (
|
||||
<motion.div
|
||||
@@ -85,15 +87,7 @@ export function Slider(props: SliderProps) {
|
||||
the keyboard, the pointer and every `aria-`, and owes the look
|
||||
nothing. */}
|
||||
<input {...inputProps} className="dui-slider-input" />
|
||||
{/* Under the handle, so where the two meet the value being set wins;
|
||||
after the input only for its `:disabled`, as it takes no pointer. */}
|
||||
{markAt === null ? null : (
|
||||
<motion.span
|
||||
className="dui-slider-mark"
|
||||
aria-hidden
|
||||
style={{ "--dui-mark": markAt } as MotionStyle}
|
||||
/>
|
||||
)}
|
||||
<SliderMarks stops={stops} mark={markAt} />
|
||||
<span className="dui-slider-thumb" aria-hidden />
|
||||
</div>
|
||||
{marks.length > 0 ? (
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
useSwitch,
|
||||
} from "../core/controls"
|
||||
import { useLook } from "../core/look"
|
||||
import { SliderMarks } from "../core/marks"
|
||||
import { LOOK } from "../core/motion"
|
||||
|
||||
export function Button({
|
||||
@@ -76,7 +77,8 @@ export function Switch(props: SwitchProps) {
|
||||
}
|
||||
|
||||
export function Slider(props: SliderProps) {
|
||||
const { fraction, inputProps, marks, markAt, trackRef } = useSliderDrag(props)
|
||||
const { fraction, inputProps, marks, markAt, stops, trackRef } =
|
||||
useSliderDrag(props)
|
||||
const orientation = props.orientation ?? "horizontal"
|
||||
return (
|
||||
<motion.div
|
||||
@@ -96,15 +98,7 @@ export function Slider(props: SliderProps) {
|
||||
the keyboard, the pointer and every `aria-`, and owes the look
|
||||
nothing. */}
|
||||
<input {...inputProps} className="dui-slider-input" />
|
||||
{/* Under the handle, so where the two meet the value being set wins;
|
||||
after the input only for its `:disabled`, as it takes no pointer. */}
|
||||
{markAt === null ? null : (
|
||||
<motion.span
|
||||
className="dui-slider-mark"
|
||||
aria-hidden
|
||||
style={{ "--dui-mark": markAt } as MotionStyle}
|
||||
/>
|
||||
)}
|
||||
<SliderMarks stops={stops} mark={markAt} />
|
||||
<span className="dui-slider-thumb" aria-hidden />
|
||||
</div>
|
||||
{marks.length > 0 ? (
|
||||
|
||||
@@ -435,6 +435,11 @@
|
||||
filter: drop-shadow(0 2px 8px rgb(0 0 0 / 0.35));
|
||||
}
|
||||
|
||||
/* A reading's dot on the handle, which is white here in either theme. */
|
||||
[data-look="glass"] :is(.dui-ring, .dui-slider) {
|
||||
--dui-reading-on-handle: rgb(0 0 0 / 0.75);
|
||||
}
|
||||
|
||||
[data-look="glass"] .dui-ring-swatch {
|
||||
stroke: rgb(255 255 255 / 0.85);
|
||||
stroke-width: 2px;
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
useSwitch,
|
||||
} from "../core/controls"
|
||||
import { useLook } from "../core/look"
|
||||
import { SliderMarks } from "../core/marks"
|
||||
import { LOOK } from "../core/motion"
|
||||
|
||||
/** The state layer plus whatever presses are still fading. */
|
||||
@@ -105,7 +106,8 @@ export function Switch(props: SwitchProps) {
|
||||
}
|
||||
|
||||
export function Slider(props: SliderProps) {
|
||||
const { fraction, inputProps, marks, markAt, trackRef } = useSliderDrag(props)
|
||||
const { fraction, inputProps, marks, markAt, stops, trackRef } =
|
||||
useSliderDrag(props)
|
||||
const orientation = props.orientation ?? "horizontal"
|
||||
return (
|
||||
<motion.div
|
||||
@@ -125,15 +127,7 @@ export function Slider(props: SliderProps) {
|
||||
the keyboard, the pointer and every `aria-`, and owes the look
|
||||
nothing. */}
|
||||
<input {...inputProps} className="dui-slider-input" />
|
||||
{/* Under the handle, so where the two meet the value being set wins;
|
||||
after the input only for its `:disabled`, as it takes no pointer. */}
|
||||
{markAt === null ? null : (
|
||||
<motion.span
|
||||
className="dui-slider-mark"
|
||||
aria-hidden
|
||||
style={{ "--dui-mark": markAt } as MotionStyle}
|
||||
/>
|
||||
)}
|
||||
<SliderMarks stops={stops} mark={markAt} />
|
||||
<span className="dui-slider-thumb" aria-hidden />
|
||||
</div>
|
||||
{marks.length > 0 ? (
|
||||
|
||||
@@ -334,6 +334,11 @@
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
/* A reading's dot on the handle, which is primary here. */
|
||||
[data-look="material"] :is(.dui-ring, .dui-slider) {
|
||||
--dui-reading-on-handle: var(--primary-foreground);
|
||||
}
|
||||
|
||||
[data-look="material"] .dui-ring-swatch {
|
||||
stroke: var(--card);
|
||||
stroke-width: 3px;
|
||||
|
||||
Reference in New Issue
Block a user