From ce6df47b0f3173681339d83046c261793182cf95 Mon Sep 17 00:00:00 2001 From: nanda Date: Fri, 2 Oct 2026 12:45:35 -0700 Subject: [PATCH 1/2] feat(slider): add Slider component Adds a Slider built on Base UI's Slider for picking a number, or a range between two numbers. A recessed track holds a raised indicator with grip lines at its ends, each thumb's value shows in a badge below the track, and the minimum and maximum sit at either end. Supports sm and base sizes, Intl.NumberFormat formatting, and Base UI's value, step, and change props. Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/slider-component.md | 5 + .../src/components/SidebarNav.tsx | 1 + .../src/components/demos/HomeGrid.tsx | 13 ++ .../src/components/demos/SliderDemo.tsx | 81 ++++++++ .../src/pages/components/slider.mdx | 150 ++++++++++++++ packages/kumo/package.json | 4 + .../scripts/component-registry/discovery.ts | 1 + .../scripts/component-registry/metadata.ts | 38 ++++ packages/kumo/src/components/slider/index.ts | 9 + .../src/components/slider/slider.test.tsx | 95 +++++++++ .../kumo/src/components/slider/slider.tsx | 187 ++++++++++++++++++ packages/kumo/src/index.ts | 9 + packages/kumo/vite.config.ts | 1 + 13 files changed, 594 insertions(+) create mode 100644 .changeset/slider-component.md create mode 100644 packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx create mode 100644 packages/kumo-docs-astro/src/pages/components/slider.mdx create mode 100644 packages/kumo/src/components/slider/index.ts create mode 100644 packages/kumo/src/components/slider/slider.test.tsx create mode 100644 packages/kumo/src/components/slider/slider.tsx diff --git a/.changeset/slider-component.md b/.changeset/slider-component.md new file mode 100644 index 000000000..7e4a5c4e7 --- /dev/null +++ b/.changeset/slider-component.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/kumo": minor +--- + +Add `Slider` component for picking a number, or a range between two numbers, by dragging a thumb along a track. Built on Base UI's Slider: pass an array to `value` or `defaultValue` for a range. Shows each thumb's value in a badge below the track and the minimum and maximum at either end (`showValue` / `showRange`), formats them with `format`, and supports `sm` and `base` sizes. diff --git a/packages/kumo-docs-astro/src/components/SidebarNav.tsx b/packages/kumo-docs-astro/src/components/SidebarNav.tsx index 2c6268cc2..6d1266c01 100644 --- a/packages/kumo-docs-astro/src/components/SidebarNav.tsx +++ b/packages/kumo-docs-astro/src/components/SidebarNav.tsx @@ -75,6 +75,7 @@ const componentItems: NavItem[] = [ { label: "Sensitive Input", href: "/components/sensitive-input" }, { label: "Sidebar", href: "/components/sidebar" }, { label: "Skeleton Line", href: "/components/skeleton-line" }, + { label: "Slider", href: "/components/slider" }, { label: "Switch", href: "/components/switch" }, { label: "Table", href: "/components/table" }, { label: "Table of Contents", href: "/components/table-of-contents" }, diff --git a/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx b/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx index 68f928ca6..4b7c8bf68 100644 --- a/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx +++ b/packages/kumo-docs-astro/src/components/demos/HomeGrid.tsx @@ -30,6 +30,7 @@ import { Select, SensitiveInput, SkeletonLine, + Slider, Switch, Table, TableOfContents, @@ -91,6 +92,7 @@ const componentRoutes: Record = { select: "/components/select", "sensitive-input": "/components/sensitive-input", "skeleton-line": "/components/skeleton-line", + slider: "/components/slider", switch: "/components/switch", table: "/components/table", "table-of-contents": "/components/table-of-contents", @@ -558,6 +560,17 @@ export function HomeGrid() { ), }, + { + name: "Slider", + id: "slider", + Component: ( + (index === 0 ? "Minimum" : "Maximum")} + className="w-48" + /> + ), + }, { name: "DatePicker", id: "date-picker", diff --git a/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx b/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx new file mode 100644 index 000000000..0de4be83a --- /dev/null +++ b/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx @@ -0,0 +1,81 @@ +import { useState } from "react"; +import { Slider } from "@cloudflare/kumo"; + +export function SliderBasicDemo() { + return ( + + ); +} + +export function SliderRangeDemo() { + return ( + (index === 0 ? "Minimum price" : "Maximum price")} + className="w-72" + /> + ); +} + +export function SliderStepDemo() { + return ( + + ); +} + +export function SliderFormatDemo() { + return ( + + ); +} + +export function SliderSizesDemo() { + return ( +
+ + +
+ ); +} + +export function SliderHiddenLabelsDemo() { + return ( + + ); +} + +export function SliderControlledDemo() { + const [value, setValue] = useState(50); + + return ( +
+ setValue(next)} + /> +

Brightness: {value}%

+
+ ); +} + +export function SliderDisabledDemo() { + return ( + + ); +} diff --git a/packages/kumo-docs-astro/src/pages/components/slider.mdx b/packages/kumo-docs-astro/src/pages/components/slider.mdx new file mode 100644 index 000000000..4c9c39b71 --- /dev/null +++ b/packages/kumo-docs-astro/src/pages/components/slider.mdx @@ -0,0 +1,150 @@ +--- +layout: ~/layouts/MdxDocLayout.astro +title: "Slider" +description: "An input for picking a number, or a range between two numbers, by dragging a thumb along a track." +sourceFile: "components/slider" +--- + +import ComponentExample from "~/components/docs/ComponentExample.astro"; +import ComponentSection from "~/components/docs/ComponentSection.astro"; +import PropsTable from "~/components/docs/PropsTable.astro"; +import { + SliderBasicDemo, + SliderRangeDemo, + SliderStepDemo, + SliderFormatDemo, + SliderSizesDemo, + SliderHiddenLabelsDemo, + SliderControlledDemo, + SliderDisabledDemo, +} from "~/components/demos/SliderDemo"; + +{/* Hero Demo */} + + + + + + + +{/* Installation */} + + + +## Installation + +### Barrel + +```tsx +import { Slider } from "@cloudflare/kumo"; +``` + +### Granular + +```tsx +import { Slider } from "@cloudflare/kumo/components/slider"; +``` + + + +{/* Usage */} + + + +## Usage + +```tsx +import { Slider } from "@cloudflare/kumo"; + +export default function Example() { + return ; +} +``` + +`Slider` is built on [Base UI's Slider](https://base-ui.com/react/components/slider). Props such as `value`, `defaultValue`, `min`, `max`, `step`, `format`, and `onValueChange` are passed through to it. + + + +{/* Examples */} + + + +## Examples + +### Basic Slider + +

A single thumb, with the value in a badge below it and the minimum and maximum at either end.

+ + + + +### Range + +

Pass an array to `value` or `defaultValue` to render one thumb per value. Use `getAriaLabel` to give each thumb its own accessible name.

+ + + + +### Custom Range and Step + +

Set `min`, `max`, and `step` to constrain the values.

+ + + + +### Formatted Values + +

`format` takes `Intl.NumberFormat` options and applies to the badges, the range labels, and the value announced to assistive technology.

+ + + + +### Sizes + +

Use `size="sm"` for dense layouts.

+ + + + +### Hidden Value and Range + +

Set `showValue={false}` and `showRange={false}` when the value is shown elsewhere.

+ + + + +### Controlled + +

Pass `value` and `onValueChange` to control the slider.

+ + + + +### Disabled + + + + + +
+ +{/* Accessibility */} + + + +## Accessibility + +- Each thumb is a native range input, so arrow keys, Page Up/Page Down, and Home/End work by default. +- `label` names the slider and its thumb. Without a visible label, pass `getAriaLabel`. +- The value badges and range labels are hidden from assistive technology, because each thumb already announces its formatted value. + + + +{/* API Reference */} + + + +## API Reference + + + diff --git a/packages/kumo/package.json b/packages/kumo/package.json index 1c3b890ac..37545af05 100644 --- a/packages/kumo/package.json +++ b/packages/kumo/package.json @@ -208,6 +208,10 @@ "types": "./dist/components/sidebar.d.ts", "import": "./dist/components/sidebar.js" }, + "./components/slider": { + "types": "./dist/components/slider.d.ts", + "import": "./dist/components/slider.js" + }, "./components/surface": { "types": "./dist/components/surface.d.ts", "import": "./dist/components/surface.js" diff --git a/packages/kumo/scripts/component-registry/discovery.ts b/packages/kumo/scripts/component-registry/discovery.ts index 59eb7d4f5..d5e4294d0 100644 --- a/packages/kumo/scripts/component-registry/discovery.ts +++ b/packages/kumo/scripts/component-registry/discovery.ts @@ -56,6 +56,7 @@ export const CATEGORY_MAP: Record = { "input-group": "Input", radio: "Input", select: "Input", + slider: "Input", switch: "Input", // Layout grid: "Layout", diff --git a/packages/kumo/scripts/component-registry/metadata.ts b/packages/kumo/scripts/component-registry/metadata.ts index 904c83330..f72204c59 100644 --- a/packages/kumo/scripts/component-registry/metadata.ts +++ b/packages/kumo/scripts/component-registry/metadata.ts @@ -289,6 +289,44 @@ export const ADDITIONAL_COMPONENT_PROPS: Record< description: "Minimum value of the meter (default: 0)", }, }, + Slider: { + value: { + type: "number | readonly number[]", + description: + "Controlled value. Pass an array to render one thumb per value (a range).", + }, + defaultValue: { + type: "number | readonly number[]", + description: + "Uncontrolled initial value. Pass an array to render one thumb per value (a range).", + }, + onValueChange: { + type: "(value: number | readonly number[], eventDetails: SliderRoot.ChangeEventDetails) => void", + description: + "Called when the value changes while dragging or from the keyboard", + }, + min: { + type: "number", + description: "Minimum value of the slider (default: 0)", + }, + max: { + type: "number", + description: "Maximum value of the slider (default: 100)", + }, + step: { + type: "number", + description: "Amount the value changes per step (default: 1)", + }, + format: { + type: "Intl.NumberFormatOptions", + description: + "Formats the value badges, range labels, and the value announced to assistive technology", + }, + disabled: { + type: "boolean", + description: "Whether the slider is disabled", + }, + }, Tooltip: { content: { type: "ReactNode", diff --git a/packages/kumo/src/components/slider/index.ts b/packages/kumo/src/components/slider/index.ts new file mode 100644 index 000000000..b6d97a997 --- /dev/null +++ b/packages/kumo/src/components/slider/index.ts @@ -0,0 +1,9 @@ +export { + Slider, + sliderVariants, + KUMO_SLIDER_VARIANTS, + KUMO_SLIDER_DEFAULT_VARIANTS, + type SliderProps, + type KumoSliderSize, + type KumoSliderVariantsProps, +} from "./slider"; diff --git a/packages/kumo/src/components/slider/slider.test.tsx b/packages/kumo/src/components/slider/slider.test.tsx new file mode 100644 index 000000000..26cd846ef --- /dev/null +++ b/packages/kumo/src/components/slider/slider.test.tsx @@ -0,0 +1,95 @@ +import { describe, it, expect, vi } from "vite-plus/test"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { Slider } from "./slider"; + +describe("Slider", () => { + it("should have correct display name", () => { + expect(Slider.displayName).toBe("Slider"); + }); + + it("labels the thumb with the visible label", () => { + render(); + + const thumb = screen.getByLabelText("Volume", { selector: "input" }); + expect(thumb.getAttribute("type")).toBe("range"); + expect(thumb.getAttribute("aria-valuenow")).toBe("40"); + }); + + it("renders one thumb per value for a range", () => { + render( + (index === 0 ? "Minimum" : "Maximum")} + />, + ); + + expect(screen.getByLabelText("Minimum").getAttribute("aria-valuenow")).toBe( + "25", + ); + expect(screen.getByLabelText("Maximum").getAttribute("aria-valuenow")).toBe( + "75", + ); + }); + + it("shows the value badges and the min and max labels by default", () => { + render(); + + expect(screen.getByText("2")).toBeTruthy(); + expect(screen.getByText("0")).toBeTruthy(); + expect(screen.getByText("5")).toBeTruthy(); + }); + + it("hides the value badges and range labels when turned off", () => { + render( + , + ); + + expect(screen.queryByText("2")).toBeNull(); + expect(screen.queryByText("0")).toBeNull(); + expect(screen.queryByText("5")).toBeNull(); + }); + + it("formats values with the format option", () => { + render( + , + ); + + expect(screen.getByText("50%")).toBeTruthy(); + expect(screen.getByText("0%")).toBeTruthy(); + expect(screen.getByText("100%")).toBeTruthy(); + }); + + it("reports changes from the keyboard", () => { + const onValueChange = vi.fn(); + render( + , + ); + + fireEvent.keyDown(screen.getByLabelText("Volume", { selector: "input" }), { + key: "ArrowRight", + }); + + expect(onValueChange.mock.calls[0]?.[0]).toBe(41); + }); + + it("applies the size variant to the track", () => { + const { container } = render( + , + ); + + expect(container.querySelector(".h-6")).toBeTruthy(); + }); +}); diff --git a/packages/kumo/src/components/slider/slider.tsx b/packages/kumo/src/components/slider/slider.tsx new file mode 100644 index 000000000..d87b058cc --- /dev/null +++ b/packages/kumo/src/components/slider/slider.tsx @@ -0,0 +1,187 @@ +import { Slider as BaseSlider } from "@base-ui/react/slider"; +import { type ReactNode } from "react"; +import { cn } from "../../utils/cn"; + +/** Slider size definitions mapping sizes to their track heights. */ +export const KUMO_SLIDER_VARIANTS = { + size: { + sm: { + classes: "h-6", + description: "Compact slider for dense layouts", + }, + base: { + classes: "h-8", + description: "Default slider size", + }, + }, +} as const; + +export const KUMO_SLIDER_DEFAULT_VARIANTS = { + size: "base", +} as const; + +export type KumoSliderSize = keyof typeof KUMO_SLIDER_VARIANTS.size; + +export interface KumoSliderVariantsProps { + /** + * Height of the slider track. + * - `"sm"` — Compact slider for dense layouts + * - `"base"` — Default slider size + * @default "base" + */ + size?: KumoSliderSize; +} + +export function sliderVariants({ + size = KUMO_SLIDER_DEFAULT_VARIANTS.size, +}: KumoSliderVariantsProps = {}) { + return cn( + "rounded-lg bg-kumo-recessed p-[3px] ring ring-kumo-line", + KUMO_SLIDER_VARIANTS.size[size].classes, + ); +} + +type SliderValue = number | readonly number[]; + +/** + * Slider component props. + * + * @example + * ```tsx + * + * + * ``` + */ +export interface SliderProps + extends + Omit, "children">, + KumoSliderVariantsProps { + /** Label displayed above the slider track. */ + label?: ReactNode; + /** + * Whether to show each thumb's value in a badge below the track. + * @default true + */ + showValue?: boolean; + /** + * Whether to show the minimum and maximum values at the ends of the track. + * @default true + */ + showRange?: boolean; + /** + * Accessible name for each thumb. Use it when there is no visible `label`, + * or to tell the thumbs of a range slider apart. + */ + getAriaLabel?: (index: number) => string; +} + +function countThumbs(value: SliderValue | undefined) { + return Array.isArray(value) ? value.length : 1; +} + +/** + * Lets people pick a number, or a range between two numbers, by dragging a + * thumb along a track. Pass an array to `value` or `defaultValue` for a range. + * + * @example + * ```tsx + * + * ``` + */ +export function Slider({ + label, + showValue = true, + showRange = true, + size = KUMO_SLIDER_DEFAULT_VARIANTS.size, + getAriaLabel, + className, + min = 0, + max = 100, + format, + locale, + value, + defaultValue, + ...props +}: SliderProps) { + const thumbCount = countThumbs(value ?? defaultValue); + const isRange = thumbCount > 1; + const formatter = new Intl.NumberFormat(locale, format); + const textSize = size === "sm" ? "text-xs" : "text-sm"; + + return ( + + {label ? ( + + {label} + + ) : null} + + + + {Array.from({ length: thumbCount }, (_, index) => ( + + + {showValue ? ( + + + {(formattedValues) => formattedValues[index]} + + + ) : null} + + ))} + + + {showRange ? ( +
+ {formatter.format(min)} + {formatter.format(max)} +
+ ) : null} +
+ ); +} + +Slider.displayName = "Slider"; diff --git a/packages/kumo/src/index.ts b/packages/kumo/src/index.ts index c9879f224..19d49eec1 100644 --- a/packages/kumo/src/index.ts +++ b/packages/kumo/src/index.ts @@ -373,6 +373,15 @@ export { type KumoLayerDialogSize, type KumoLayerDialogVerticalAlign, } from "./components/layer-dialog"; +export { + Slider, + sliderVariants, + KUMO_SLIDER_VARIANTS, + KUMO_SLIDER_DEFAULT_VARIANTS, + type SliderProps, + type KumoSliderSize, + type KumoSliderVariantsProps, +} from "./components/slider"; // PLOP_INJECT_EXPORT // Utils diff --git a/packages/kumo/vite.config.ts b/packages/kumo/vite.config.ts index c0a27fd3b..96d3bb4d1 100644 --- a/packages/kumo/vite.config.ts +++ b/packages/kumo/vite.config.ts @@ -131,6 +131,7 @@ const packEntries = { __dirname, "src/components/tag-input/index.ts", ), + "components/slider": resolve(__dirname, "src/components/slider/index.ts"), // PLOP_INJECT_COMPONENT_ENTRY // Utils entry point utils: resolve(__dirname, "src/utils/index.ts"), From 4859ed03b9ea5b150adfdf45c9a4fc79f641e3c4 Mon Sep 17 00:00:00 2001 From: nanda Date: Mon, 5 Oct 2026 08:27:41 -0700 Subject: [PATCH 2/2] fix(slider): refine styles and props - Use rounded-md outer and rounded inner corners for the sm size. - Darken the thumb grip. - Align the value badge with the min and max labels. - Always show the value badge and range labels; remove showValue and showRange. - Honor a caller's thumbAlignment, keeping edge as the default. - Remove vertical orientation support. Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/slider-component.md | 2 +- .../src/components/demos/SliderDemo.tsx | 12 --- .../src/pages/components/slider.mdx | 8 -- .../src/components/slider/slider.test.tsx | 45 +++++---- .../kumo/src/components/slider/slider.tsx | 93 +++++++++---------- 5 files changed, 75 insertions(+), 85 deletions(-) diff --git a/.changeset/slider-component.md b/.changeset/slider-component.md index 7e4a5c4e7..ded3c21b4 100644 --- a/.changeset/slider-component.md +++ b/.changeset/slider-component.md @@ -2,4 +2,4 @@ "@cloudflare/kumo": minor --- -Add `Slider` component for picking a number, or a range between two numbers, by dragging a thumb along a track. Built on Base UI's Slider: pass an array to `value` or `defaultValue` for a range. Shows each thumb's value in a badge below the track and the minimum and maximum at either end (`showValue` / `showRange`), formats them with `format`, and supports `sm` and `base` sizes. +Add `Slider` component for picking a number, or a range between two numbers, by dragging a thumb along a track. Built on Base UI's Slider: pass an array to `value` or `defaultValue` for a range. Shows each thumb's value in a badge below the track and the minimum and maximum at either end, formats them with `format`, and supports `sm` and `base` sizes. diff --git a/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx b/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx index 0de4be83a..fa61afa24 100644 --- a/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx +++ b/packages/kumo-docs-astro/src/components/demos/SliderDemo.tsx @@ -47,18 +47,6 @@ export function SliderSizesDemo() { ); } -export function SliderHiddenLabelsDemo() { - return ( - - ); -} - export function SliderControlledDemo() { const [value, setValue] = useState(50); diff --git a/packages/kumo-docs-astro/src/pages/components/slider.mdx b/packages/kumo-docs-astro/src/pages/components/slider.mdx index 4c9c39b71..13444948f 100644 --- a/packages/kumo-docs-astro/src/pages/components/slider.mdx +++ b/packages/kumo-docs-astro/src/pages/components/slider.mdx @@ -14,7 +14,6 @@ import { SliderStepDemo, SliderFormatDemo, SliderSizesDemo, - SliderHiddenLabelsDemo, SliderControlledDemo, SliderDisabledDemo, } from "~/components/demos/SliderDemo"; @@ -106,13 +105,6 @@ export default function Example() { -### Hidden Value and Range - -

Set `showValue={false}` and `showRange={false}` when the value is shown elsewhere.

- - - - ### Controlled

Pass `value` and `onValueChange` to control the slider.

diff --git a/packages/kumo/src/components/slider/slider.test.tsx b/packages/kumo/src/components/slider/slider.test.tsx index 26cd846ef..9cb65b67f 100644 --- a/packages/kumo/src/components/slider/slider.test.tsx +++ b/packages/kumo/src/components/slider/slider.test.tsx @@ -31,7 +31,7 @@ describe("Slider", () => { ); }); - it("shows the value badges and the min and max labels by default", () => { + it("shows the value badges and the min and max labels", () => { render(); expect(screen.getByText("2")).toBeTruthy(); @@ -39,22 +39,6 @@ describe("Slider", () => { expect(screen.getByText("5")).toBeTruthy(); }); - it("hides the value badges and range labels when turned off", () => { - render( - , - ); - - expect(screen.queryByText("2")).toBeNull(); - expect(screen.queryByText("0")).toBeNull(); - expect(screen.queryByText("5")).toBeNull(); - }); - it("formats values with the format option", () => { render( { expect(onValueChange.mock.calls[0]?.[0]).toBe(41); }); + it("aligns the thumbs to the track edges by default", () => { + render(); + + const thumb = screen.getByLabelText("Volume", { selector: "input" }) + .parentElement as HTMLElement; + expect(thumb.style.getPropertyValue("--position")).not.toBe(""); + }); + + it("lets callers override the thumb alignment", () => { + render(); + + const thumb = screen.getByLabelText("Volume", { selector: "input" }) + .parentElement as HTMLElement; + expect(thumb.style.getPropertyValue("--position")).toBe(""); + }); + + it("stays horizontal even when an orientation is passed", () => { + const props = { orientation: "vertical" } as object; + render(); + + expect( + screen + .getByLabelText("Volume", { selector: "input" }) + .getAttribute("aria-orientation"), + ).toBe("horizontal"); + }); + it("applies the size variant to the track", () => { const { container } = render( , diff --git a/packages/kumo/src/components/slider/slider.tsx b/packages/kumo/src/components/slider/slider.tsx index d87b058cc..90417e958 100644 --- a/packages/kumo/src/components/slider/slider.tsx +++ b/packages/kumo/src/components/slider/slider.tsx @@ -6,11 +6,11 @@ import { cn } from "../../utils/cn"; export const KUMO_SLIDER_VARIANTS = { size: { sm: { - classes: "h-6", + classes: "h-6 rounded-md", description: "Compact slider for dense layouts", }, base: { - classes: "h-8", + classes: "h-8 rounded-lg", description: "Default slider size", }, }, @@ -36,7 +36,7 @@ export function sliderVariants({ size = KUMO_SLIDER_DEFAULT_VARIANTS.size, }: KumoSliderVariantsProps = {}) { return cn( - "rounded-lg bg-kumo-recessed p-[3px] ring ring-kumo-line", + "bg-kumo-recessed p-[3px] ring ring-kumo-line", KUMO_SLIDER_VARIANTS.size[size].classes, ); } @@ -54,20 +54,10 @@ type SliderValue = number | readonly number[]; */ export interface SliderProps extends - Omit, "children">, + Omit, "children" | "orientation">, KumoSliderVariantsProps { /** Label displayed above the slider track. */ label?: ReactNode; - /** - * Whether to show each thumb's value in a badge below the track. - * @default true - */ - showValue?: boolean; - /** - * Whether to show the minimum and maximum values at the ends of the track. - * @default true - */ - showRange?: boolean; /** * Accessible name for each thumb. Use it when there is no visible `label`, * or to tell the thumbs of a range slider apart. @@ -90,10 +80,11 @@ function countThumbs(value: SliderValue | undefined) { */ export function Slider({ label, - showValue = true, - showRange = true, size = KUMO_SLIDER_DEFAULT_VARIANTS.size, getAriaLabel, + // Keeps the thumbs, and the grips inside them, within the track at either + // end of the range. + thumbAlignment = "edge", className, min = 0, max = 100, @@ -107,6 +98,8 @@ export function Slider({ const isRange = thumbCount > 1; const formatter = new Intl.NumberFormat(locale, format); const textSize = size === "sm" ? "text-xs" : "text-sm"; + // One step smaller than the track radius, so the inset corners stay concentric. + const innerRadius = size === "sm" ? "rounded" : "rounded-md"; return ( ({ max={max} format={format} locale={locale} - // Keeps the thumbs, and the grips inside them, within the track at - // either end of the range. - thumbAlignment="edge" + thumbAlignment={thumbAlignment} + // The track, badges, and range labels are laid out for a horizontal slider only. + orientation="horizontal" className={cn( "flex w-full flex-col gap-2 data-disabled:opacity-50", className, @@ -134,52 +127,58 @@ export function Slider({ ) : null} - + {Array.from({ length: thumbCount }, (_, index) => ( - {showValue ? ( - - - {(formattedValues) => formattedValues[index]} - - - ) : null} + + + {(formattedValues) => formattedValues[index]} + + ))} - {showRange ? ( -
- {formatter.format(min)} - {formatter.format(max)} -
- ) : null} +
+ {formatter.format(min)} + {formatter.format(max)} +
); }