diff --git a/apps/www/src/content/docs/components/slider/demo.ts b/apps/www/src/content/docs/components/slider/demo.ts index 2189d546..787c29cf 100644 --- a/apps/www/src/content/docs/components/slider/demo.ts +++ b/apps/www/src/content/docs/components/slider/demo.ts @@ -1,67 +1,72 @@ -"use client"; +'use client'; -import { getPropsString } from "@/lib/utils"; +import { getPropsString } from '@/lib/utils'; export const getCode = (props: any) => { return ``; }; 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: ``, + name: 'Single', + code: `` }, { - name: "Range", - code: ``, - }, - ], + name: 'Range', + code: `` + } + ] }; export const controlDemo = { - type: "code", + type: 'code', tabs: [ { - name: "Single", + name: 'Single', code: `function ControlledRangeSlider() { const [value, setValue] = React.useState(50); - + return ( - setValue(newValue as number)} /> Value {value} ); -}`, +}` }, { - name: "Range", + name: 'Range', code: `function ControlledRangeSlider() { const [value, setValue] = React.useState([25, 75]); - + return ( - setValue(newValue as [number, number])} /> @@ -69,7 +74,25 @@ export const controlDemo = { Upper {value[1]} ); -}`, - }, - ], +}` + } + ] +}; + +export const thumbSizeDemo = { + type: 'code', + code: ` + + +` }; diff --git a/apps/www/src/content/docs/components/slider/index.mdx b/apps/www/src/content/docs/components/slider/index.mdx index 842260ba..23154482 100644 --- a/apps/www/src/content/docs/components/slider/index.mdx +++ b/apps/www/src/content/docs/components/slider/index.mdx @@ -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"; @@ -29,6 +30,12 @@ A controlled slider that maintains and updates its state through React's useStat +### Thumb Size + +Different thumb sizes for various use cases and visual preferences. + + + ## Accessibility The Slider component follows WAI-ARIA guidelines for the [Slider Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider/). diff --git a/apps/www/src/content/docs/components/slider/props.ts b/apps/www/src/content/docs/components/slider/props.ts index 01a53b55..06b6a99c 100644 --- a/apps/www/src/content/docs/components/slider/props.ts +++ b/apps/www/src/content/docs/components/slider/props.ts @@ -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]; @@ -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; diff --git a/packages/raystack/components/slider/slider.module.css b/packages/raystack/components/slider/slider.module.css index d6661572..9c97439f 100644 --- a/packages/raystack/components/slider/slider.module.css +++ b/packages/raystack/components/slider/slider.module.css @@ -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 { @@ -35,7 +33,6 @@ justify-content: center; outline: none; transform: translate(-50%, -50%); - top: 50%; } .thumb:active { @@ -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; } diff --git a/packages/raystack/components/slider/slider.tsx b/packages/raystack/components/slider/slider.tsx index 13f22d40..10a09be9 100644 --- a/packages/raystack/components/slider/slider.tsx +++ b/packages/raystack/components/slider/slider.tsx @@ -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'; @@ -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, +export const Slider = forwardRef< + ElementRef, SliderProps >( ( @@ -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]; @@ -101,14 +108,27 @@ export const Slider = React.forwardRef< {defaultVal.map((_, i) => (
- - {getLabel(i) &&
{getLabel(i)}
} + {isThumbSmall ? ( +
+ ) : ( + + )} + {getLabel(i) && ( + + {getLabel(i)} + + )}
))}