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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ When adding a new item, you will create/edit these files:
| `mc-avatar` | ✅ | ✅ |
| `mc-drawer` | ✅ | ✅ |
| `mc-hover-card` | ✅ | ✅ |
| `mc-slider` | ✅ | |
| `mc-slider` | ✅ | |
| `mc-carousel` | ✅ | ✅ |

# Important Notes
Expand Down
29 changes: 29 additions & 0 deletions packages/web/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,19 @@ export const Index: Record<string, any> = {
},
],
},
'mc-slider': {
name: 'mc-slider',
description: 'A slider component for MicroClub UI',
type: 'registry:component',
files: [
{
path: 'registry/ui/mc-slider.tsx',
content:
'import * as React from \'react\';\nimport { Slider as SliderPrimitive } from \'@base-ui/react/slider\';\n\nimport { cn } from \'@/lib/utils\';\n\nfunction McSlider({\n className,\n defaultValue,\n value,\n min = 0,\n max = 100,\n showValue = true,\n border = false,\n unity,\n onValueChange,\n ...props\n}: SliderPrimitive.Root.Props & {\n showValue?: boolean;\n border?: boolean;\n unity?: string;\n}) {\n const isControlled = value !== undefined;\n\n const [internalValues, setInternalValues] = React.useState<number[]>(\n Array.isArray(defaultValue) ? defaultValue : [min]\n );\n\n const values = isControlled ? (Array.isArray(value) ? value : [value]) : internalValues;\n\n return (\n <SliderPrimitive.Root\n className={cn(\'data-horizontal:w-full data-vertical:h-full mt-12\', className)}\n data-slot="slider"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n thumbAlignment="edge-client-only"\n onValueChange={(newValues, event) => {\n if (!isControlled) {\n setInternalValues(Array.isArray(newValues) ? newValues : [newValues]);\n }\n onValueChange?.(newValues, event);\n }}\n {...props}\n >\n <SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">\n <SliderPrimitive.Track\n data-slot="slider-track"\n className="relative grow overflow-hidden rounded-full bg-muted ring-1 ring-inset ring-ring select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"\n >\n <SliderPrimitive.Indicator\n data-slot="slider-range"\n className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"\n />\n </SliderPrimitive.Track>\n\n {values.map((currentValue, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot="slider-thumb"\n className="group relative block size-3 shrink-0 rounded-full border-2 border-primary bg-muted ring-accent-foreground backdrop-blur-3xl transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 active:bg-primary disabled:pointer-events-none disabled:opacity-50 disabled:bg-blue-500"\n >\n {showValue && (\n <div\n className={cn(\n \'pointer-events-none absolute bottom-full left-1/2 mb-3 -translate-x-1/2 whitespace-nowrap rounded-xl \',\n \'px-3.5 py-2.5 gap-2.5 text-md text-foreground bg-transparent \',\n border && \'ring-1 ring-inset ring-border bg-muted\'\n )}\n >\n {currentValue} {unity}\n </div>\n )}\n </SliderPrimitive.Thumb>\n ))}\n </SliderPrimitive.Control>\n </SliderPrimitive.Root>\n );\n}\n\nexport { McSlider };\n',
type: 'registry:component',
},
],
},
'mc-carousel': {
name: 'mc-carousel',
description: 'A carousel component for MicroClub UI',
Expand Down Expand Up @@ -3029,6 +3042,22 @@ export const Index: Record<string, any> = {
source:
'import { McHoverCard, McHoverCardContent, McHoverCardTrigger } from \'../ui/mc-hover-card\';\n\nexport default function McHoverCardDemo() {\n return (\n <div>\n <McHoverCard>\n <McHoverCardTrigger>\n <button type="button" className="cursor-pointer rounded-full">\n hover me\n </button>\n </McHoverCardTrigger>\n <McHoverCardContent\n textAlign="start"\n title="John Doe"\n subtitle="Software Engineer"\n description="John is a software engineer with 5 years of experience in web development. He loves working with React and TypeScript."\n />\n </McHoverCard>\n </div>\n );\n}\n',
},
'mc-slider-demo': {
name: 'mc-slider-demo',
description: 'Demo for MicroClub Slider',
type: 'registry:example',
files: [
{
path: 'registry/examples/mc-slider-demo.tsx',
content:
'import { McSlider } from \'@/registry/ui/mc-slider\';\n\nexport default function SliderDemo() {\n return <McSlider defaultValue={[75]} max={100} step={1} className="mx-auto w-full max-w-xs" />;\n}\n',
type: 'registry:example',
},
],
component: React.lazy(() => import('@/registry/examples/mc-slider-demo.tsx')),
source:
'import { McSlider } from \'@/registry/ui/mc-slider\';\n\nexport default function SliderDemo() {\n return <McSlider defaultValue={[75]} max={100} step={1} className="mx-auto w-full max-w-xs" />;\n}\n',
},
'mc-carousel-demo': {
name: 'mc-carousel-demo',
description: 'Demo for MicroClub Carousel',
Expand Down
13 changes: 13 additions & 0 deletions packages/web/__registry__/registry.autogenerated.json
Original file line number Diff line number Diff line change
Expand Up @@ -2584,6 +2584,19 @@
],
"type": "registry:component"
},
{
"name": "mc-slider",
"title": "MicroClub Slider",
"description": "A slider component for MicroClub UI",
"dependencies": ["@base-ui/react"],
"files": [
{
"path": "registry/ui/mc-slider.tsx",
"type": "registry:component"
}
],
"type": "registry:component"
},
{
"name": "mc-carousel",
"title": "MicroClub Carousel",
Expand Down
6 changes: 3 additions & 3 deletions packages/web/content/docs/components/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ description: Explore all available UI components in the MicroClub UI library.
<Card title="MC Hover Card" href="/docs/components/mc-hover-card">
A card that appears on hover with additional information.
</Card>
<Card title="MC Slider" href="/docs/components/mc-slider">
A component for selecting a value from a range.
</Card>
<Card title="MC Carousel" href="/docs/components/mc-carousel">
A component for cycling through a series of content.
</Card>
Expand All @@ -119,7 +122,4 @@ description: Explore all available UI components in the MicroClub UI library.
<Card title="MC Progress" href="/docs/components/mc-progress">
A component that displays progress of an operation.
</Card>
<Card title="MC Slider" href="/docs/components/mc-slider">
A component for selecting a value from a range.
</Card>
</Cards>
124 changes: 120 additions & 4 deletions packages/web/content/docs/components/mc-slider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,123 @@ title: MC Slider
description: A component for selecting a value from a range.
---

<Callout type="warn">
This component is coming soon. Check our [contributing guide](/docs/introduction#contributing) for
more information.
</Callout>
## Overview

The `mc-slider` is a core component of the MicroClub UI library. Built on the **@base-ui/react** slider primitive, it provides an accessible, keyboard-friendly range control with MicroClub's signature styling via **Tailwind CSS v4**.

As part of the MicroClub library, this component is designed for **instant deployment into your codebase**, giving you full ownership of its look and behavior.

## Preview

<Tabs items={['preview', 'code']}>
<Tab>
<ComponentPreview name="mc-slider-demo" />
</Tab>
<Tab>
<ComponentSource name="mc-slider-demo" />
</Tab>
</Tabs>

## Add to Your Project

Deploy the `mc-slider` directly to your `components/ui` directory:

<Tabs items={['npm', 'pnpm', 'yarn', 'bun']}>
<Tab value="npm">npx mcoli-ui@latest add mc-slider</Tab>
<Tab value="pnpm">pnpm dlx mcoli-ui@latest add mc-slider</Tab>
<Tab value="yarn">yarn dlx mcoli-ui@latest add mc-slider</Tab>
<Tab value="bun">bunx mcoli-ui@latest add mc-slider</Tab>
</Tabs>

### Manual Setup

If you prefer manual control:

<Steps>

<Step>Install the core dependencies</Step>

<Tabs items={['npm', 'pnpm', 'yarn', 'bun']}>
<Tab value="npm">npm install @base-ui/react</Tab>
<Tab value="pnpm">pnpm add @base-ui/react</Tab>
<Tab value="yarn">yarn add @base-ui/react</Tab>
<Tab value="bun">bun add @base-ui/react</Tab>
</Tabs>

<Step>Copy the source code</Step>

<ComponentSource name="mc-slider" />

<Step>Adjust import paths</Step>

Ensure your `@/lib/utils` or equivalent classname helper is correctly imported.

</Steps>

## Usage

```tsx
import { McSlider } from '@/components/ui/mc-slider';

export default function Example() {
return <McSlider defaultValue={[50]} max={100} step={1} />;
}
```

## Examples

### Basic

```tsx
<McSlider defaultValue={[40]} max={100} step={1} />
```

### Range

```tsx
<McSlider defaultValue={[20, 80]} max={100} step={1} />
```

### With value labels

```tsx
<McSlider defaultValue={[60]} max={100} step={1} unity="%" />
```

### With border and custom label styling

```tsx
<McSlider defaultValue={[35]} max={100} step={1} showValue border unity="°C" />
```

### Vertical

```tsx
<McSlider defaultValue={[60]} max={100} orientation="vertical" className="h-48" />
```

### Disabled

```tsx
<McSlider defaultValue={[35]} max={100} disabled />
```

## API Reference

### Props

| Prop | Type | Default | Description |
| -------------- | ---------------------------- | -------------- | --------------------------------------------------- |
| `defaultValue` | `number[]` | `undefined` | Initial value(s) for the slider |
| `value` | `number[]` | `undefined` | Controlled value(s) |
| `min` | `number` | `0` | Minimum slider value |
| `max` | `number` | `100` | Maximum slider value |
| `step` | `number` | `1` | Increment step |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Slider orientation |
| `disabled` | `boolean` | `false` | Disables interaction |
| `showValue` | `boolean` | `true` | Shows the floating value badge above each thumb |
| `border` | `boolean` | `false` | Adds a bordered value badge style |
| `unity` | `string` | `undefined` | Optional suffix displayed next to the current value |
| `className` | `string` | `undefined` | Additional CSS classes |

All other props from `@base-ui/react/slider` are supported.
4 changes: 2 additions & 2 deletions packages/web/content/docs/components/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
"mc-avatar",
"mc-drawer",
"mc-hover-card",
"mc-slider",
"mc-carousel",
"---Coming Soon---",
"mc-data-table",
"mc-progress",
"mc-slider"
"mc-progress"
]
}
15 changes: 15 additions & 0 deletions packages/web/public/r/mc-slider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "mc-slider",
"title": "MicroClub Slider",
"description": "A slider component for MicroClub UI",
"dependencies": ["@base-ui/react"],
"files": [
{
"path": "registry/ui/mc-slider.tsx",
"content": "import * as React from 'react';\nimport { Slider as SliderPrimitive } from '@base-ui/react/slider';\n\nimport { cn } from '@/lib/utils';\n\nfunction McSlider({\n className,\n defaultValue,\n value,\n min = 0,\n max = 100,\n showValue = true,\n border = false,\n unity,\n onValueChange,\n ...props\n}: SliderPrimitive.Root.Props & {\n showValue?: boolean;\n border?: boolean;\n unity?: string;\n}) {\n const isControlled = value !== undefined;\n\n const [internalValues, setInternalValues] = React.useState<number[]>(\n Array.isArray(defaultValue) ? defaultValue : [min]\n );\n\n const values = isControlled ? (Array.isArray(value) ? value : [value]) : internalValues;\n\n return (\n <SliderPrimitive.Root\n className={cn('data-horizontal:w-full data-vertical:h-full mt-12', className)}\n data-slot=\"slider\"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n thumbAlignment=\"edge-client-only\"\n onValueChange={(newValues, event) => {\n if (!isControlled) {\n setInternalValues(Array.isArray(newValues) ? newValues : [newValues]);\n }\n onValueChange?.(newValues, event);\n }}\n {...props}\n >\n <SliderPrimitive.Control className=\"relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col\">\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className=\"relative grow overflow-hidden rounded-full bg-muted ring-1 ring-inset ring-ring select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1\"\n >\n <SliderPrimitive.Indicator\n data-slot=\"slider-range\"\n className=\"bg-primary select-none data-horizontal:h-full data-vertical:w-full\"\n />\n </SliderPrimitive.Track>\n\n {values.map((currentValue, index) => (\n <SliderPrimitive.Thumb\n key={index}\n data-slot=\"slider-thumb\"\n className=\"group relative block size-3 shrink-0 rounded-full border-2 border-primary bg-muted ring-accent-foreground backdrop-blur-3xl transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 active:bg-primary disabled:pointer-events-none disabled:opacity-50 disabled:bg-blue-500\"\n >\n {showValue && (\n <div\n className={cn(\n 'pointer-events-none absolute bottom-full left-1/2 mb-3 -translate-x-1/2 whitespace-nowrap rounded-xl ',\n 'px-3.5 py-2.5 gap-2.5 text-md text-foreground bg-transparent ',\n border && 'ring-1 ring-inset ring-border bg-muted'\n )}\n >\n {currentValue} {unity}\n </div>\n )}\n </SliderPrimitive.Thumb>\n ))}\n </SliderPrimitive.Control>\n </SliderPrimitive.Root>\n );\n}\n\nexport { McSlider };\n",
"type": "registry:component"
}
],
"type": "registry:component"
}
13 changes: 13 additions & 0 deletions packages/web/public/r/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -2584,6 +2584,19 @@
],
"type": "registry:component"
},
{
"name": "mc-slider",
"title": "MicroClub Slider",
"description": "A slider component for MicroClub UI",
"dependencies": ["@base-ui/react"],
"files": [
{
"path": "registry/ui/mc-slider.tsx",
"type": "registry:component"
}
],
"type": "registry:component"
},
{
"name": "mc-carousel",
"title": "MicroClub Carousel",
Expand Down
5 changes: 5 additions & 0 deletions packages/web/registry/examples/mc-slider-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { McSlider } from '@/registry/ui/mc-slider';

export default function SliderDemo() {
return <McSlider defaultValue={[75]} max={100} step={1} className="mx-auto w-full max-w-xs" />;
}
13 changes: 13 additions & 0 deletions packages/web/registry/registry-examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,19 @@ export const examples: Registry['items'] = [
],
registryDependencies: [`${REGISTRY_URL}/r/mc-hover-card.json`],
},
{
name: 'mc-slider-demo',
type: 'registry:example',
title: 'MicroClub Slider Demo',
description: 'Demo for MicroClub Slider',
files: [
{
path: 'examples/mc-slider-demo.tsx',
type: 'registry:example',
},
],
registryDependencies: [`${REGISTRY_URL}/r/mc-slider.json`],
},
{
name: 'mc-carousel-demo',
type: 'registry:example',
Expand Down
13 changes: 13 additions & 0 deletions packages/web/registry/registry-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,19 @@ export const ui: Registry['items'] = [
],
dependencies: ['@base-ui/react'],
},
{
name: 'mc-slider',
type: 'registry:component',
title: 'MicroClub Slider',
description: 'A slider component for MicroClub UI',
files: [
{
path: 'ui/mc-slider.tsx',
type: 'registry:component',
},
],
dependencies: ['@base-ui/react'],
},
{
name: 'mc-carousel',
type: 'registry:component',
Expand Down
82 changes: 82 additions & 0 deletions packages/web/registry/ui/mc-slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from 'react';
import { Slider as SliderPrimitive } from '@base-ui/react/slider';

import { cn } from '@/lib/utils';

function McSlider({
className,
defaultValue,
value,
min = 0,
max = 100,
showValue = true,
border = false,
unity,
onValueChange,
...props
}: SliderPrimitive.Root.Props & {
showValue?: boolean;
border?: boolean;
unity?: string;
}) {
const isControlled = value !== undefined;

const [internalValues, setInternalValues] = React.useState<number[]>(
Array.isArray(defaultValue) ? defaultValue : [min]
);

const values = isControlled ? (Array.isArray(value) ? value : [value]) : internalValues;

return (
<SliderPrimitive.Root
className={cn('data-horizontal:w-full data-vertical:h-full mt-12', className)}
data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
thumbAlignment="edge-client-only"
onValueChange={(newValues, event) => {
if (!isControlled) {
setInternalValues(Array.isArray(newValues) ? newValues : [newValues]);
}
onValueChange?.(newValues, event);
}}
{...props}
>
<SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
<SliderPrimitive.Track
data-slot="slider-track"
className="relative grow overflow-hidden rounded-full bg-muted ring-1 ring-inset ring-ring select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"
>
<SliderPrimitive.Indicator
data-slot="slider-range"
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
/>
</SliderPrimitive.Track>

{values.map((currentValue, index) => (
<SliderPrimitive.Thumb
key={index}
data-slot="slider-thumb"
className="group relative block size-3 shrink-0 rounded-full border-2 border-primary bg-muted ring-accent-foreground backdrop-blur-3xl transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 active:bg-primary disabled:pointer-events-none disabled:opacity-50 disabled:bg-blue-500"
>
{showValue && (
<div
className={cn(
'pointer-events-none absolute bottom-full left-1/2 mb-3 -translate-x-1/2 whitespace-nowrap rounded-xl ',
'px-3.5 py-2.5 gap-2.5 text-md text-foreground bg-transparent ',
border && 'ring-1 ring-inset ring-border bg-muted'
)}
>
{currentValue} {unity}
</div>
)}
</SliderPrimitive.Thumb>
))}
</SliderPrimitive.Control>
</SliderPrimitive.Root>
);
}

export { McSlider };
Loading
Loading