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
85 changes: 54 additions & 31 deletions apps/www/src/content/docs/components/slider/demo.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,98 @@
"use client";
'use client';

import { getPropsString } from "@/lib/utils";
import { getPropsString } from '@/lib/utils';

export const getCode = (props: any) => {
return `<Slider${getPropsString(props)}/>`;
};

export const playground = {
type: "playground",
type: 'playground',
controls: {
defaultValue: { type: "number", initialValue: 50 },
min: { type: "number", defaultValue: 0, min: 0, max: 99 },
max: { type: "number", defaultValue: 100, min: 1, max: 100 },
step: { type: "number", defaultValue: 1, min: 0, max: 100 },
label: { type: "text", initialValue: "Slider Label" },
defaultValue: { type: 'number', initialValue: 50 },
thumbSize: {
type: 'select',
initialValue: 'large',
options: ['small', 'large']
},
min: { type: 'number', defaultValue: 0, min: 0, max: 99 },
max: { type: 'number', defaultValue: 100, min: 1, max: 100 },
step: { type: 'number', defaultValue: 1, min: 0, max: 100 },
label: { type: 'text', initialValue: 'Slider Label' }
},
getCode,
getCode
};

export const variantDemo = {
type: "code",
type: 'code',
tabs: [
{
name: "Single",
code: `<Slider variant="single" label="Value" defaultValue={50} />`,
name: 'Single',
code: `<Slider variant="single" label="Value" defaultValue={50} />`
},
{
name: "Range",
code: `<Slider variant="range" label={["Min", "Max"]} defaultValue={[20, 80]} />`,
},
],
name: 'Range',
code: `<Slider variant="range" label={["Min", "Max"]} defaultValue={[20, 80]} />`
}
]
};
export const controlDemo = {
type: "code",
type: 'code',
tabs: [
{
name: "Single",
name: 'Single',
code: `function ControlledRangeSlider() {
const [value, setValue] = React.useState(50);

return (
<Flex direction="column" gap="medium" align="center" style={{ width: "400px" }}>
<Slider
variant="single"
value={value}
<Slider
variant="single"
value={value}
label="Value"
onChange={(newValue) => setValue(newValue as number)}
/>
<Text>Value {value}</Text>
</Flex>
);
}`,
}`
},
{
name: "Range",
name: 'Range',
code: `function ControlledRangeSlider() {
const [value, setValue] = React.useState([25, 75]);

return (
<Flex direction="column" gap="medium" align="center" style={{ width: "400px" }}>
<Slider
variant="range"
value={value}
<Slider
variant="range"
value={value}
label={["Lower", "Upper"]}
onChange={(newValue) => setValue(newValue as [number, number])}
/>
<Text>Lower {value[0]}</Text>
<Text>Upper {value[1]}</Text>
</Flex>
);
}`,
},
],
}`
}
]
};

export const thumbSizeDemo = {
type: 'code',
code: `<Flex direction="column" gap="extra-large" align="center" style={{ width: "400px" }}>
<Slider
variant="single"
label="Large Thumb"
defaultValue={50}
thumbSize="large"
/>
<Slider
variant="single"
label="Small Thumb"
defaultValue={50}
thumbSize="small"
/>
</Flex>`
};
9 changes: 8 additions & 1 deletion apps/www/src/content/docs/components/slider/index.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
title: Slider
description: A control that allows users to select a value or range from a given range.
tag: update
---

import { playground, variantDemo, controlDemo } from "./demo.ts";
import { playground, variantDemo, controlDemo, thumbSizeDemo } from "./demo.ts";

<Demo data={playground} />

Expand All @@ -29,6 +30,12 @@ A controlled slider that maintains and updates its state through React's useStat

<Demo data={controlDemo} />

### Thumb Size

Different thumb sizes for various use cases and visual preferences.

<Demo data={thumbSizeDemo} />

## Accessibility

The Slider component follows WAI-ARIA guidelines for the [Slider Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider/).
Expand Down
8 changes: 7 additions & 1 deletion apps/www/src/content/docs/components/slider/props.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface SliderProps {
/** The type of slider. */
variant?: "single" | "range";
variant?: 'single' | 'range';

/** Controlled value - number for single, [number, number] for range. */
value?: number | [number, number];
Expand Down Expand Up @@ -32,6 +32,12 @@ export interface SliderProps {
*/
label?: string | [string, string];

/**
* Size of the slider thumb.
* @default "large"
*/
thumbSize?: 'small' | 'large';

/** Callback when value changes. */
onChange?: (value: number | [number, number]) => void;

Expand Down
34 changes: 22 additions & 12 deletions packages/raystack/components/slider/slider.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@
.track {
position: relative;
flex-grow: 1;
height: var(--rs-space-1);
height: var(--rs-space-2);
background-color: var(--rs-color-background-neutral-secondary);
border-radius: var(--rs-radius-full);
margin: 0 12px;
margin: 0 var(--rs-space-4);
}

.range {
position: absolute;
height: 100%;
background-color: var(--rs-color-background-accent-emphasis);
border-radius: var(--rs-radius-full);
}

.thumb {
Expand All @@ -35,7 +33,6 @@
justify-content: center;
outline: none;
transform: translate(-50%, -50%);
top: 50%;
}

.thumb:active {
Expand All @@ -47,35 +44,48 @@
outline: none;
}

.thumb:hover {
border-color: var(--rs-color-border-accent-emphasis-hover);
}

.thumb svg {
width: 24px;
height: 24px;
width: 32px;
height: 28px;
fill: var(--rs-color-background-base-primary);
margin-top: var(--rs-space-1);
}

.thumb:hover svg {
fill: var(--rs-color-background-base-secondary);
}

.thumb:hover {
border-color: var(--rs-color-border-accent-emphasis-hover);
.thumbSmall {
width: 8px;
height: 16px;
border-radius: var(--rs-radius-full);
background-color: var(--rs-color-background-base-primary);
border: 1px solid var(--rs-color-border-base-tertiary);
box-shadow: var(--rs-shadow-soft);
}

.label {
position: absolute;
top: calc(-1 * var(--rs-space-7));
top: calc(-1 * (var(--rs-space-7) + 1px));
left: 50%;
padding: var(--rs-space-1) var(--rs-space-2);
padding: var(--rs-space-2);
color: var(--rs-color-foreground-base-primary);
background-color: var(--rs-color-background-base-primary);
border: 0.5px solid var(--rs-color-border-base-primary);
border-radius: var(--rs-radius-2);
font-size: var(--rs-font-size-mini);
transform: translateX(-50%);
white-space: nowrap;
box-shadow: var(--rs-shadow-soft);
}

.thumb[data-size="small"] .label {
top: calc(-1 * (var(--rs-space-7)));
}

.slider-variant-single .range {
left: 0;
}
36 changes: 28 additions & 8 deletions packages/raystack/components/slider/slider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
'use client';

import { type VariantProps, cva } from 'class-variance-authority';
import { type VariantProps, cva, cx } from 'class-variance-authority';
import { Slider as SliderPrimitive } from 'radix-ui';
import * as React from 'react';
import { type ComponentPropsWithoutRef } from 'react';
import {
type ComponentPropsWithoutRef,
type ElementRef,
forwardRef
} from 'react';
import { Text } from '../text';
import styles from './slider.module.css';
import { ThumbIcon } from './thumb';

Expand Down Expand Up @@ -34,10 +38,11 @@ export interface SliderProps
onChange?: (value: number | [number, number]) => void;
'aria-label'?: string;
'aria-valuetext'?: string;
thumbSize?: 'small' | 'large';
}

export const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
export const Slider = forwardRef<
ElementRef<typeof SliderPrimitive.Root>,
SliderProps
>(
(
Expand All @@ -53,11 +58,13 @@ export const Slider = React.forwardRef<
onChange,
'aria-label': ariaLabel,
'aria-valuetext': ariaValueText,
thumbSize = 'large',
...props
},
ref
) => {
const isRange = variant === 'range';
const isThumbSmall = thumbSize === 'small';
const defaultVal = isRange
? (defaultValue as [number, number]) || [min, max]
: [(defaultValue as number) || min];
Expand Down Expand Up @@ -101,14 +108,27 @@ export const Slider = React.forwardRef<
{defaultVal.map((_, i) => (
<SliderPrimitive.Thumb
key={i}
className={styles.thumb}
className={cx(styles.thumb)}
asChild
aria-label={getLabel(i) || `Thumb ${i + 1}`}
aria-valuetext={getAriaValueText(i)}
data-size={thumbSize}
>
<div>
<ThumbIcon />
{getLabel(i) && <div className={styles.label}>{getLabel(i)}</div>}
{isThumbSmall ? (
<div className={styles.thumbSmall} />
) : (
<ThumbIcon />
)}
{getLabel(i) && (
<Text
className={styles.label}
size={isThumbSmall ? 'micro' : 'mini'}
weight='medium'
>
{getLabel(i)}
</Text>
)}
</div>
</SliderPrimitive.Thumb>
))}
Expand Down