Skip to content

Commit

Permalink
feat: add Slider component (#59)
Browse files Browse the repository at this point in the history
* feat: add Slider component

* refactor(wedges): add default Label text color

* refactor(wedges): update Tooltip arrow SVG

* feat(docs): add docUtils for common table props

* feat(wedges): export Slider.Root

* docs: add Slider docs
  • Loading branch information
brankoconjic committed Jan 30, 2024
1 parent 12d7970 commit c5b48fd
Show file tree
Hide file tree
Showing 21 changed files with 1,923 additions and 539 deletions.
5 changes: 5 additions & 0 deletions .changeset/nine-wasps-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lemonsqueezy/wedges": patch
---

add default Label text color
5 changes: 5 additions & 0 deletions .changeset/poor-pillows-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lemonsqueezy/wedges": patch
---

update Tooltip arrow SVG
5 changes: 5 additions & 0 deletions .changeset/spotty-ducks-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lemonsqueezy/wedges": patch
---

add Slider component
54 changes: 47 additions & 7 deletions apps/docs/src/components/PropsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import { Tooltip } from "@lemonsqueezy/wedges";

import { createLabelDocs, type LabelDocsParams } from "@/lib/docUtils";
import { cn } from "@/lib/utils";

type ContentItem = {
Expand All @@ -26,23 +27,62 @@ export const PropsTable = React.forwardRef<
isOptions?: boolean;
isUtility?: boolean;
sort?: boolean;
includeCommonDocs?: LabelDocsParams;
} & (SortProps | NoSortProps)
>(
(
{ content, isData = false, isOptions = false, isUtility = false, sort = true, ...otherProps },
{
content,
isData = false,
isOptions = false,
isUtility = false,
sort = true,
includeCommonDocs,
...otherProps
},
ref
) => {
// Include label docs?
const {
label = false,
description = false,
tooltip = false,
helperText = false,
required = false,
disabled = false,
before = false,
after = false,
asChild = false,
} = includeCommonDocs ?? {};

const labelDocs = createLabelDocs({
label,
description,
tooltip,
helperText,
required,
disabled,
before,
after,
asChild,
});

// Sort the content array
const sortedContent = React.useMemo(() => {
if (sort) {
return [...content].sort((a, b) => {
const aValue = a[0].value;
const bValue = b[0].value;
const returnContent = [...labelDocs];
returnContent.push(
...[...content].sort((a, b) => {
const aValue = a[0].value;
const bValue = b[0].value;

return aValue?.toString().localeCompare(bValue.toString());
})
);

return aValue?.toString().localeCompare(bValue.toString());
});
return returnContent;
} else {
return content;
return [...labelDocs, ...content];
}
}, [content, sort]);

Expand Down
10 changes: 5 additions & 5 deletions apps/docs/src/config/sidebarConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ export const sidebarConfig: DocsConfig = {
label: "Radio Group",
href: "/components/radio-group",
},
{
label: "Slider",
href: "/components/slider",
new: true,
},
{
label: "Switch",
href: "/components/switch",
Expand Down Expand Up @@ -193,11 +198,6 @@ export const sidebarConfig: DocsConfig = {
href: "/components/number-input",
disabled: true,
},
{
label: "Slider",
href: "/components/slider",
disabled: true,
},
{
label: "Select",
href: "/components/select",
Expand Down
179 changes: 179 additions & 0 deletions apps/docs/src/content/components/slider.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
---
title: Slider
description: An interactive input slider that allows users to select a value from a specified range.
links:
source: https://github.com/lmsqueezy/wedges/blob/main/packages/wedges/src/components/Slider/Slider.tsx
radix: https://radix-ui.com/primitives/docs/components/slider
---
<PreviewComponent name="slider/preview" />

### Usage

```tsx
import { Slider } from "@lemonsqueezy/wedges";
```

```tsx showLineNumbers
<Slider />
```

For more advanced usage, you can use the `Slider.Root` component to compose your own avatar. These components include pre-defined styles and accessiblity features.

```tsx showLineNumbers
<Slider.Root>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb />
</Avatar.Root>
```

### API Reference

The Slider component is built on top of the <a href="https://www.radix-ui.com/primitives/docs/components/slider" rel="nofollow noreferrer" target="_blank">Radix Slider</a> primitive.

#### Slider

The Wedges component of the Slider. In most cases this is the only Slider component you will need to use.

<PropsTable
sort={false}
content={[
[{ value: "defaultValue", description: "The value of the slider when initially rendered. Use when you do not need to control the state of the slider."}, { value: "number[]"}, {}],
[{ value: "value", description: "The controlled value of the slider. Must be used in conjunction with onValueChange."}, { value: "number[]"}, {}],
[{ value: "onValueChange", description: "Event handler called when the value changes."}, { value: "function", description: "onValueChange?(value: number[]): void"}, {}],
[{ value: "onValueCommit", description: "Event handler called when the value changes at the end of an interaction. Useful when you only need to capture a final value e.g. to update a backend service."}, { value: "function", description: "onValueCommit?(value: number[]): void"}, {}],
[{ value: "name", description: "The name of the slider. Submitted with its owning form as part of a name/value pair."}, { value: "string"}, {}],
[{ value: "orientation", description: "The orientation of the slider."}, { value: "enum", description: '"vertical" | "horizontal"'}, { value: '"horizontal"'}],
[{ value: "dir", description: "The reading direction of the slider."}, { value: "enum", description: '"ltr" | "rtl"'}, {}],
[{ value: "inverted", description: "Whether the slider is visually inverted."}, { value: "boolean" }, { value: "false" }],
[{ value: "min", description: "The minimum value for the range."}, { value: "number" }, { value: "0" }],
[{ value: "max", description: "The maximum value for the range."}, { value: "number" }, { value: "100" }],
[{ value: "step", description: "The stepping interval."}, { value: "number" }, { value: "1" }],
[{ value: "minStepsBetweenThumbs", description: "The minimum permitted `step`s between multiple thumbs."}, { value: "number" }, { value: "0" }],
]}
includeCommonDocs={{
label: true,
description: true,
tooltip: true,
helperText: true,
required: true,
disabled: true,
before: true,
after: true,
}}
/>

#### Slider.Root

Contains all the parts of a slider. It will render an `input` for each thumb when used within a `form` to ensure events propagate correctly.

<PropsTable
sort={false}
content={[
[{ value: "defaultValue", description: "The value of the slider when initially rendered. Use when you do not need to control the state of the slider."}, { value: "number[]"}, {}],
[{ value: "value", description: "The controlled value of the slider. Must be used in conjunction with onValueChange."}, { value: "number[]"}, {}],
[{ value: "onValueChange", description: "Event handler called when the value changes."}, { value: "function", description: "onValueChange?(value: number[]): void"}, {}],
[{ value: "onValueCommit", description: "Event handler called when the value changes at the end of an interaction. Useful when you only need to capture a final value e.g. to update a backend service."}, { value: "function", description: "onValueCommit?(value: number[]): void"}, {}],
[{ value: "name", description: "The name of the slider. Submitted with its owning form as part of a name/value pair."}, { value: "string"}, {}],
[{ value: "orientation", description: "The orientation of the slider."}, { value: "enum", description: '"vertical" | "horizontal"'}, { value: '"horizontal"'}],
[{ value: "dir", description: "The reading direction of the slider."}, { value: "enum", description: '"ltr" | "rtl"'}, {}],
[{ value: "inverted", description: "Whether the slider is visually inverted."}, { value: "boolean" }, { value: "false" }],
[{ value: "min", description: "The minimum value for the range."}, { value: "number" }, { value: "0" }],
[{ value: "max", description: "The maximum value for the range."}, { value: "number" }, { value: "100" }],
[{ value: "step", description: "The stepping interval."}, { value: "number" }, { value: "1" }],
[{ value: "minStepsBetweenThumbs", description: "The minimum permitted `step`s between multiple thumbs."}, { value: "number" }, { value: "0" }],
]}
/>

<PropsTable
isData
content={[
[{ value: "[data-disabled]" }, { value: "Present when disabled" }, {}],
[{ value: "[data-orientation]" }, { value: '"vertical" | "horizontal"' }, {}],
]}
/>

#### Slider.Track

The track that contains the `Slider.Range`.

<PropsTable
content={[]}
includeCommonDocs={{
asChild: true
}}
/>

<PropsTable
isData
content={[
[{ value: "[data-disabled]" }, { value: "Present when disabled" }, {}],
[{ value: "[data-orientation]" }, { value: '"vertical" | "horizontal"' }, {}],
]}
/>

#### Slider.Range

The range part. Must live inside `Slider.Track`.

<PropsTable
content={[]}
includeCommonDocs={{
asChild: true
}}
/>

<PropsTable
isData
content={[
[{ value: "[data-disabled]" }, { value: "Present when disabled" }, {}],
[{ value: "[data-orientation]" }, { value: '"vertical" | "horizontal"' }, {}],
]}
/>

#### Slider.Thumb

The range part. Must live inside `Slider.Track`.

<PropsTable
content={[]}
includeCommonDocs={{
asChild: true
}}
/>

<PropsTable
isData
content={[
[{ value: "[data-disabled]" }, { value: "Present when disabled" }, {}],
[{ value: "[data-orientation]" }, { value: '"vertical" | "horizontal"' }, {}],
[{ value: "[data-state]" }, { value: '"active" | "inactive"' }, {}],
]}
/>

### Accessibility

Adheres to the <a href="https://www.w3.org/WAI/ARIA/apg/patterns/slidertwothumb" target="_blank" rel='noreferrer nofollow'>Slider WAI-ARIA</a> design pattern.

### Examples

Use `orientation` prop to create vertical sliders.

<PreviewComponent name="slider/example-1" />

You can disable the slider by using the `disabled` prop.

<PreviewComponent name="slider/example-2" />

Use range values:

<PreviewComponent name="slider/example-3" />

Controlled component:

<PreviewComponent name="slider/example-4" />

Playground:

<PreviewComponent name="slider/example-5" />
Loading

0 comments on commit c5b48fd

Please sign in to comment.