From 91913f4fbddbc7990426ad1d769f88a748c2c92c Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:28:27 -0500 Subject: [PATCH] breaking: Slider `type` for `number` or `number[]` value (#1032) --- .changeset/dry-spiders-learn.md | 5 + .../lib/bits/accordion/accordion.svelte.ts | 2 +- .../lib/bits/slider/components/slider.svelte | 15 +- .../src/lib/bits/slider/slider.svelte.ts | 416 +++++++++++++++--- packages/bits-ui/src/lib/bits/slider/types.ts | 219 +++++---- .../src/tests/slider/slider-range-test.svelte | 8 +- ...r-test.svelte => slider-test-multi.svelte} | 8 +- .../tests/src/tests/slider/slider.test.ts | 14 +- sites/docs/content/components/slider.md | 79 +++- .../api-ref/prop-type-content.svelte | 4 +- .../src/lib/components/api-section.svelte | 2 +- sites/docs/src/lib/components/demos/index.ts | 1 + .../demos/slider-demo-multiple.svelte | 30 ++ .../lib/components/demos/slider-demo.svelte | 28 +- .../slider/slider-root-on-value-change.md | 3 + .../src/lib/content/api-reference/helpers.ts | 2 +- .../lib/content/api-reference/slider.api.ts | 21 +- .../docs/src/routes/(main)/sink/+page.svelte | 2 +- 18 files changed, 648 insertions(+), 211 deletions(-) create mode 100644 .changeset/dry-spiders-learn.md rename packages/tests/src/tests/slider/{slider-test.svelte => slider-test-multi.svelte} (81%) create mode 100644 sites/docs/src/lib/components/demos/slider-demo-multiple.svelte diff --git a/.changeset/dry-spiders-learn.md b/.changeset/dry-spiders-learn.md new file mode 100644 index 000000000..10953a5e8 --- /dev/null +++ b/.changeset/dry-spiders-learn.md @@ -0,0 +1,5 @@ +--- +"bits-ui": patch +--- + +breaking: `Slider.Root` now requires a `type` prop to specify whether the slider should be a `"single"` or `"multiple"` slider, which determines whether the value and change function arguments should be of type `number` or `number[]` diff --git a/packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts b/packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts index 0bec3e6e7..a081d1974 100644 --- a/packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts +++ b/packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts @@ -48,7 +48,7 @@ class AccordionBaseState { this.#ref = props.ref; useRefById({ - id: props.id, + id: this.#id, ref: this.#ref, }); diff --git a/packages/bits-ui/src/lib/bits/slider/components/slider.svelte b/packages/bits-ui/src/lib/bits/slider/components/slider.svelte index 4ef4cf618..0fadb6855 100644 --- a/packages/bits-ui/src/lib/bits/slider/components/slider.svelte +++ b/packages/bits-ui/src/lib/bits/slider/components/slider.svelte @@ -1,5 +1,5 @@
- + {#snippet children({ thumbs, ticks })} diff --git a/packages/tests/src/tests/slider/slider-test.svelte b/packages/tests/src/tests/slider/slider-test-multi.svelte similarity index 81% rename from packages/tests/src/tests/slider/slider-test.svelte rename to packages/tests/src/tests/slider/slider-test-multi.svelte index 01b65fd65..39708807e 100644 --- a/packages/tests/src/tests/slider/slider-test.svelte +++ b/packages/tests/src/tests/slider/slider-test-multi.svelte @@ -1,7 +1,7 @@
- + {#snippet children({ thumbs, ticks })} diff --git a/packages/tests/src/tests/slider/slider.test.ts b/packages/tests/src/tests/slider/slider.test.ts index e5e0900c0..f8f63f744 100644 --- a/packages/tests/src/tests/slider/slider.test.ts +++ b/packages/tests/src/tests/slider/slider.test.ts @@ -3,19 +3,19 @@ import { render } from "@testing-library/svelte"; import { axe } from "jest-axe"; import { describe, it } from "vitest"; import { getTestKbd, setupUserEvents } from "../utils.js"; -import SliderTest, { type SliderTestProps } from "./slider-test.svelte"; -import SliderRangeTest, { type SliderRangeTestProps } from "./slider-range-test.svelte"; +import SliderMultiTest, { type SliderMultiTestProps } from "./slider-test-multi.svelte"; +import SliderRangeTest, { type SliderMultiRangeTestProps } from "./slider-range-test.svelte"; const kbd = getTestKbd(); -function renderSlider(props: SliderTestProps = {}) { - return render(SliderTest, { ...props }); +function renderSlider(props: SliderMultiTestProps = {}) { + return render(SliderMultiTest, { ...props }); } -function renderSliderRange(props: SliderRangeTestProps = {}) { +function renderSliderRange(props: SliderMultiRangeTestProps = {}) { return render(SliderRangeTest, { ...props }); } -function setup(props: SliderTestProps = {}, kind: "default" | "range" = "default") { +function setup(props: SliderMultiTestProps = {}, kind: "default" | "range" = "default") { const user = setupUserEvents(); // eslint-disable-next-line @typescript-eslint/no-explicit-any let returned: any; @@ -31,7 +31,7 @@ function setup(props: SliderTestProps = {}, kind: "default" | "range" = "default describe("slider (default)", () => { it("should have no accessibility violations", async () => { - const { container } = render(SliderTest); + const { container } = render(SliderMultiTest); expect(await axe(container)).toHaveNoViolations(); }); diff --git a/sites/docs/content/components/slider.md b/sites/docs/content/components/slider.md index d5868a030..c57885d7d 100644 --- a/sites/docs/content/components/slider.md +++ b/sites/docs/content/components/slider.md @@ -4,7 +4,7 @@ description: Allows users to select a value from a continuous range by sliding a --- @@ -36,16 +36,22 @@ Bits UI provides primitives that enable you to build your own custom slider comp Here's an example of how you might create a reusable `MySlider` component. -```svelte title="MySlider.svelte" +```svelte title="MyMultiSlider.svelte" - + + {#snippet children({ thumbs, ticks })} {#each thumbs as index} @@ -65,10 +71,12 @@ You can then use the `MySlider` component in your application like so: - + + ``` ## Managing Value State @@ -82,12 +90,12 @@ For seamless state synchronization, use Svelte's `bind:value` directive. This me ```svelte - + - + ``` @@ -105,10 +113,11 @@ For more granular control or to perform additional logic on state changes, use t ```svelte { myValue = v; @@ -138,10 +147,10 @@ To implement controlled state: ```svelte - (myValue = v)}> + (myValue = v)}> ``` @@ -180,10 +189,11 @@ If the `value` prop has more than one value, the slider will render multiple thu - + {#snippet children({ ticks, thumbs })} @@ -200,12 +210,44 @@ If the `value` prop has more than one value, the slider will render multiple thu To determine the number of ticks that will be rendered, you can simply divide the `max` value by the `step` value. +## Single Type + +Set the `type` prop to `"single"` to allow only one accordion item to be open at a time. + +```svelte /type="single"/ + +``` + + + +{#snippet preview()} + +{/snippet} + + + +## Multiple Type + +Set the `type` prop to `"multiple"` to allow multiple accordion items to be open at the same time. + +```svelte /type="multiple"/ + +``` + + + +{#snippet preview()} + +{/snippet} + + + ## Vertical Orientation You can use the `orientation` prop to change the orientation of the slider, which defaults to `"horizontal"`. ```svelte - + ``` @@ -215,7 +257,7 @@ You can use the `orientation` prop to change the orientation of the slider, whic You can use the `dir` prop to change the reading direction of the slider, which defaults to `"ltr"`. ```svelte - + ``` @@ -227,7 +269,7 @@ By default, the slider will sort the values from smallest to largest, so if you You can disable this behavior by setting the `autoSort` prop to `false`. ```svelte - + ``` @@ -245,12 +287,15 @@ Here's an example of how you might do that: import MySlider from "$lib/components/MySlider.svelte"; let expectedIncome = $state([50, 100]); + let desiredIncome = $state(50);
- + + + ``` diff --git a/sites/docs/src/lib/components/api-ref/prop-type-content.svelte b/sites/docs/src/lib/components/api-ref/prop-type-content.svelte index 598f4448a..d20ac3414 100644 --- a/sites/docs/src/lib/components/api-ref/prop-type-content.svelte +++ b/sites/docs/src/lib/components/api-ref/prop-type-content.svelte @@ -54,7 +54,7 @@ preventScroll={false} side="top" sideOffset={10} - class="z-50 rounded-card border border-border bg-background p-4 shadow-popover" + class="focus-override z-50 rounded-card border border-border bg-background p-4 shadow-popover outline-none" >
@@ -72,7 +72,7 @@ > {@const TypeDef = typeDef}
diff --git a/sites/docs/src/lib/components/api-section.svelte b/sites/docs/src/lib/components/api-section.svelte index 50c1c9e35..2893c61d0 100644 --- a/sites/docs/src/lib/components/api-section.svelte +++ b/sites/docs/src/lib/components/api-section.svelte @@ -19,7 +19,7 @@ class="inline-flex h-[29px] items-center justify-center rounded-button bg-accent px-3 font-mono text-[17px] font-medium leading-tight tracking-tight dark:text-neutral-900" >

- {$page.data.title.replaceAll(" ", "")}.{schema.title}

diff --git a/sites/docs/src/lib/components/demos/index.ts b/sites/docs/src/lib/components/demos/index.ts index 37ff648ac..6a059e362 100644 --- a/sites/docs/src/lib/components/demos/index.ts +++ b/sites/docs/src/lib/components/demos/index.ts @@ -47,6 +47,7 @@ export { default as ScrollAreaDemo } from "./scroll-area-demo.svelte"; export { default as ScrollAreaDemoCustom } from "./scroll-area-demo-custom.svelte"; export { default as SeparatorDemo } from "./separator-demo.svelte"; export { default as SliderDemo } from "./slider-demo.svelte"; +export { default as SliderDemoMultiple } from "./slider-demo-multiple.svelte"; export { default as SwitchDemo } from "./switch-demo.svelte"; export { default as SwitchDemoCustom } from "./switch-demo-custom.svelte"; export { default as TabsDemo } from "./tabs-demo.svelte"; diff --git a/sites/docs/src/lib/components/demos/slider-demo-multiple.svelte b/sites/docs/src/lib/components/demos/slider-demo-multiple.svelte new file mode 100644 index 000000000..fa2ceecd7 --- /dev/null +++ b/sites/docs/src/lib/components/demos/slider-demo-multiple.svelte @@ -0,0 +1,30 @@ + + +
+ + {#snippet children({ thumbs })} + + + + {#each thumbs as index} + + {/each} + {/snippet} + +
diff --git a/sites/docs/src/lib/components/demos/slider-demo.svelte b/sites/docs/src/lib/components/demos/slider-demo.svelte index ee6a02883..56cd3e599 100644 --- a/sites/docs/src/lib/components/demos/slider-demo.svelte +++ b/sites/docs/src/lib/components/demos/slider-demo.svelte @@ -2,23 +2,27 @@ import { Slider } from "bits-ui"; import { cn } from "$lib/utils/styles.js"; - let value = [50]; + let value = $state(50);
- - {#snippet children({ thumbs })} - + + {#snippet children()} + - {#each thumbs as index} - - {/each} + {/snippet}
diff --git a/sites/docs/src/lib/content/api-reference/extended-types/slider/slider-root-on-value-change.md b/sites/docs/src/lib/content/api-reference/extended-types/slider/slider-root-on-value-change.md index a404482b1..8ad2c4fe0 100644 --- a/sites/docs/src/lib/content/api-reference/extended-types/slider/slider-root-on-value-change.md +++ b/sites/docs/src/lib/content/api-reference/extended-types/slider/slider-root-on-value-change.md @@ -1,3 +1,6 @@ ```ts +// type="single" +(value: number) => void +// type="multiple" (value: number[]) => void ``` diff --git a/sites/docs/src/lib/content/api-reference/helpers.ts b/sites/docs/src/lib/content/api-reference/helpers.ts index cb52386b2..508c84bf4 100644 --- a/sites/docs/src/lib/content/api-reference/helpers.ts +++ b/sites/docs/src/lib/content/api-reference/helpers.ts @@ -543,7 +543,7 @@ export const dirProp = createEnumProp({ definition: DirProp, options: ["ltr", "rtl"], description: "The reading direction of the app.", - default: "ltr", + default: "'ltr'", }); export const orientationDataAttr = createEnumDataAttr({ diff --git a/sites/docs/src/lib/content/api-reference/slider.api.ts b/sites/docs/src/lib/content/api-reference/slider.api.ts index 41a7f3d2a..3e9b69a2c 100644 --- a/sites/docs/src/lib/content/api-reference/slider.api.ts +++ b/sites/docs/src/lib/content/api-reference/slider.api.ts @@ -5,7 +5,7 @@ import type { SliderTickPropsWithoutHTML, } from "bits-ui"; import { SliderRootOnValueChangeProp } from "./extended-types/slider/index.js"; -import { OrientationProp } from "./extended-types/shared/index.js"; +import { OrientationProp, SingleOrMultipleProp } from "./extended-types/shared/index.js"; import { controlledValueProp, createApiSchema, @@ -14,6 +14,7 @@ import { createEnumProp, createFunctionProp, createNumberProp, + createUnionProp, dirProp, withChildProps, } from "$lib/content/api-reference/helpers.js"; @@ -23,10 +24,18 @@ const root = createApiSchema({ title: "Root", description: "The root slider component which contains the remaining slider components.", props: { + type: createUnionProp({ + options: ["'single'", "'multiple'"], + description: + "The type of the slider. If set to `'multiple'`, the slider will allow multiple thumbs and the `value` will be an array of numbers.", + required: true, + definition: SingleOrMultipleProp, + }), value: { - default: "[]", - type: "number[]", - description: "The current value of the slider.", + default: "0", + type: "number", + description: + "The current value of the slider. If the `type` is set to `'multiple'`, this should be an array of numbers and will default to an empty array.", bindable: true, }, onValueChange: createFunctionProp({ @@ -53,7 +62,7 @@ const root = createApiSchema({ }), orientation: createEnumProp({ options: ["horizontal", "vertical"], - default: '"horizontal"', + default: "'horizontal'", description: "The orientation of the slider.", definition: OrientationProp, }), @@ -65,7 +74,7 @@ const root = createApiSchema({ autoSort: createBooleanProp({ default: C.TRUE, description: - "Whether to automatically sort the values in the array when moving thumbs past one another.", + "Whether to automatically sort the values in the array when moving thumbs past one another. This is only applicable to the `'multiple'` type.", }), ...withChildProps({ elType: "HTMLSpanElement" }), }, diff --git a/sites/docs/src/routes/(main)/sink/+page.svelte b/sites/docs/src/routes/(main)/sink/+page.svelte index f4f3a4de1..a80edf4b0 100644 --- a/sites/docs/src/routes/(main)/sink/+page.svelte +++ b/sites/docs/src/routes/(main)/sink/+page.svelte @@ -31,7 +31,7 @@
- + {#snippet children({ thumbs })}