Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ export function HomeGrid() {
Component: (
<Slider
defaultValue={[25, 75]}
getAriaLabel={index => (index === 0 ? "Minimum" : "Maximum")}
getAriaLabel={(index) => (index === 0 ? "Minimum" : "Maximum")}
className="w-48"
/>
),
Expand Down
14 changes: 6 additions & 8 deletions packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { useState } from "react";
import { Slider } from "@cloudflare/kumo";

export function SliderBasicDemo() {
return (
<Slider label="Volume" defaultValue={40} className="w-72" />
);
return <Slider label="Volume" defaultValue={40} className="w-72" />;
}

export function SliderRangeDemo() {
return (
<Slider
label="Price range"
defaultValue={[25, 75]}
getAriaLabel={index => (index === 0 ? "Minimum price" : "Maximum price")}
getAriaLabel={(index) =>
index === 0 ? "Minimum price" : "Maximum price"
}
className="w-72"
/>
);
Expand Down Expand Up @@ -55,15 +55,13 @@ export function SliderControlledDemo() {
<Slider
label="Brightness"
value={value}
onValueChange={next => setValue(next)}
onValueChange={(next) => setValue(next)}
/>
<p className="text-sm text-kumo-subtle">Brightness: {value}%</p>
</div>
);
}

export function SliderDisabledDemo() {
return (
<Slider label="Locked" defaultValue={70} disabled className="w-72" />
);
return <Slider label="Locked" defaultValue={70} disabled className="w-72" />;
}
81 changes: 43 additions & 38 deletions packages/kumo/src/components/slider/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,50 +125,55 @@ export function Slider<Value extends SliderValue = SliderValue>({
{label}
</BaseSlider.Label>
) : null}
<BaseSlider.Control className={sliderVariants({ size })}>
<BaseSlider.Track className="relative h-full">
<BaseSlider.Indicator
className={cn(
"bg-kumo-base shadow-sm ring ring-kumo-line",
innerRadius,
)}
/>
{Array.from({ length: thumbCount }, (_, index) => (
<BaseSlider.Thumb
key={index}
index={isRange ? index : undefined}
getAriaLabel={getAriaLabel}
{/* The padding lives on a wrapper rather than the control: Base UI
positions edge-aligned thumbs as a percentage of the control's full
width, so any control padding pushes them past the track ends. */}
<div className={sliderVariants({ size })}>
<BaseSlider.Control className="h-full">
<BaseSlider.Track className="relative h-full">
<BaseSlider.Indicator
className={cn(
"h-full w-4 cursor-grab outline-none has-focus-visible:ring-2 has-focus-visible:ring-kumo-focus data-disabled:cursor-not-allowed data-dragging:cursor-grabbing",
// Base UI ends the indicator at the thumb's center. Padding
// stretches it over the whole thumb, so it fills the track at
// either end of the range. `box-content` is forced because global
// `* { box-sizing: border-box }` resets would otherwise win.
"box-content! bg-kumo-base pr-2 shadow-sm ring ring-kumo-line",
isRange && "-ml-2 pl-2",
innerRadius,
)}
>
<span
aria-hidden
/>
{Array.from({ length: thumbCount }, (_, index) => (
<BaseSlider.Thumb
key={index}
index={isRange ? index : undefined}
getAriaLabel={getAriaLabel}
className={cn(
"absolute top-1/2 left-1/2 h-1/2 w-0.5 -translate-1/2 rounded-full bg-kumo-contrast/25",
// Grips sit just inside the indicator: at its start for the
// first thumb of a range, at its end otherwise.
isRange && index === 0 ? "ml-[7px]" : "-ml-[7px]",
)}
/>
<span
aria-hidden
className={cn(
// The 8px gap plus the control's 3px padding, so the badge lines up
// with the min and max labels below the control.
"absolute top-full left-1/2 mt-[11px] -translate-x-1/2 rounded bg-kumo-brand px-1.5 font-medium whitespace-nowrap text-white tabular-nums",
textSize,
"h-full w-4 cursor-grab outline-none has-focus-visible:ring-2 has-focus-visible:ring-kumo-focus data-disabled:cursor-not-allowed data-dragging:cursor-grabbing",
innerRadius,
)}
>
<BaseSlider.Value>
{(formattedValues) => formattedValues[index]}
</BaseSlider.Value>
</span>
</BaseSlider.Thumb>
))}
</BaseSlider.Track>
</BaseSlider.Control>
<span
aria-hidden
className="absolute top-1/2 left-1/2 h-1/2 w-0.5 -translate-1/2 rounded-full bg-kumo-contrast/25"
/>
<span
aria-hidden
className={cn(
// The 8px gap plus the control's 3px padding, so the badge lines up
// with the min and max labels below the control.
"absolute top-full left-1/2 mt-[11px] -translate-x-1/2 rounded bg-kumo-brand px-1.5 font-medium whitespace-nowrap text-white tabular-nums",
textSize,
)}
>
<BaseSlider.Value>
{(formattedValues) => formattedValues[index]}
</BaseSlider.Value>
</span>
</BaseSlider.Thumb>
))}
</BaseSlider.Track>
</BaseSlider.Control>
</div>
<div
aria-hidden
className={cn(
Expand Down
Loading