Skip to content

Commit

Permalink
New Builder: Scroll Area (melt-ui#908)
Browse files Browse the repository at this point in the history
Co-authored-by: tglide <26071571+TGlide@users.noreply.github.com>
Co-authored-by: anatolzak <53095479+anatolzak@users.noreply.github.com>
  • Loading branch information
3 people authored and lolcabanon committed Apr 20, 2024
1 parent b9d9ba3 commit b9e8486
Show file tree
Hide file tree
Showing 23 changed files with 1,809 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-dryers-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@melt-ui/svelte": minor
---

New Builder: Scroll Area
62 changes: 62 additions & 0 deletions src/docs/content/builders/scroll-area.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: Scroll Area
description: Provides consistent scrollbars across platforms.
---

<script>
import { APIReference, Preview, Callout } from '$docs/components'
import { A } from '$docs/components/markdown';
export let snippets
export let previews
export let schemas
</script>

## Anatomy

- **Root**: The container that wraps all parts of the scroll area
- **Viewport**: A container that wraps the scrollable content
- **Content**: The scrollable content
- **Corner**: The corner element displayed when both scrollbars are visible
- **ScrollbarX**: The track of the X scrollbar
- **ThumbX**: The thumb of the X scrollbar
- **ScrollbarY**: The track of the Y scrollbar
- **ThumbY**: The thumb of the Y scrollbar

## Examples

### Always

When the `type` is set to `always`, the scrollbars will always be visible.

<Preview code={snippets.always} variant="dark" size="auto">
<svelte:component this={previews.always} />
</Preview>

### Auto

When the `type` is set to `auto`, the scrollbars will only be visible when the content overflows.

<Preview code={snippets.auto} variant="dark" size="auto">
<svelte:component this={previews.auto} />
</Preview>

### Hover

When the `type` is set to `hover`, the scrollbars will only be visible when the mouse is over the
scroll area.

<Preview code={snippets.hover} variant="dark" size="auto">
<svelte:component this={previews.hover} />
</Preview>

### Scroll

When the `type` is set to `scroll`, the scrollbars will only be visible when the user is scrolling.

<Preview code={snippets.scroll} variant="dark" size="auto">
<svelte:component this={previews.scroll} />
</Preview>

## API Reference

<APIReference {schemas} />
2 changes: 2 additions & 0 deletions src/docs/data/builders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { tableOfContentsData } from './table-of-contents.js';
import { toolbarData } from './toolbar.js';
import { tooltipData } from './tooltip.js';
import { datePickerData } from './date-picker.js';
import { scrollAreaData } from './scroll-area.js';

export type BuilderData = {
schemas?: APISchema[];
Expand Down Expand Up @@ -65,6 +66,7 @@ export const builderMap = {
progress: progressData,
'radio-group': radioGroupData,
'range-calendar': rangeCalendarData,
'scroll-area': scrollAreaData,
select: selectData,
separator: separatorData,
slider: sliderData,
Expand Down
181 changes: 181 additions & 0 deletions src/docs/data/builders/scroll-area.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { ATTRS } from '$docs/constants.js';
import { builderSchema, elementSchema } from '$docs/utils/index.js';
import type { BuilderData } from './index.js';
import { scrollAreaIdParts } from '$lib/builders/scroll-area/create.js';

/**
* Props that are also returned in the form of stores via the `options` property.
*/
const OPTION_PROPS = [
{
name: 'type',
type: ['"auto"', '"always"', '"scroll"', '"hover"'],
default: '"hover"',
description: 'Determins when the scrollbar should be visible.',
},
{
name: 'hideDelay',
type: 'number',
default: '600',
description:
'When the `type` is `"scroll"` or `"hover"`, this determines how long the scrollbar should be visible after the user either stops scrolling or stops hovering over the scroll area.',
},
{
name: 'dir',
type: ['ltr', 'rtl'],
default: 'ltr',
description: 'The reading direction of the scroll area.',
},
];

const BUILDER_NAME = 'scroll area';

const builder = builderSchema(BUILDER_NAME, {
ids: scrollAreaIdParts,
title: 'createScrollArea',
props: OPTION_PROPS,
elements: [
{
name: 'root',
description: 'The container that wraps all parts of the scroll area.',
},
{
name: 'viewport',
description:
'A container that wraps the scrollable content and is used to calculate the scrollbar size.',
},
{
name: 'content',
description: 'The scrollable content.',
},
{
name: 'scrollbarX',
description: 'The track of the horizontal scrollbar.',
},
{
name: 'thumbX',
description: 'The thumb of the horizontal scrollbar.',
},
{
name: 'scrollbarY',
description: 'The track of the vertical scrollbar.',
},
{
name: 'thumbY',
description: 'The thumb of the vertical scrollbar.',
},
],
options: OPTION_PROPS,
});

const root = elementSchema('root', {
description: 'The container that wraps all parts of the scroll area.',
dataAttributes: [
{
name: 'data-melt-scroll-area',
value: ATTRS.MELT('scroll area root'),
},
],
});

const viewport = elementSchema('viewport', {
description:
'The container that wraps the scrollable content and is used to calculate the scrollbar size.',
dataAttributes: [
{
name: 'data-melt-scroll-area-viewport',
value: ATTRS.MELT('scroll area viewport'),
},
],
});

const content = elementSchema('content', {
description: 'The scrollable content. This is the element that will be scrolled.',
dataAttributes: [
{
name: 'data-melt-scroll-area-content',
value: ATTRS.MELT('scroll area content'),
},
],
});

const scrollbarX = elementSchema('scrollbarX', {
description: 'The track of the horizontal scrollbar.',
dataAttributes: [
{
name: 'data-state',
value: '`"visible" | "hidden"`',
},
{
name: 'data-melt-scroll-area-scrollbar',
value: ATTRS.MELT('scroll area scrollbar'),
},
],
events: ['pointerdown', 'pointerup', 'pointermove'],
});
const thumbX = elementSchema('thumbX', {
description: 'The thumb of the horizontal scrollbar.',
dataAttributes: [
{
name: 'data-state',
value: '`"visible" | "hidden"`',
},
{
name: 'data-melt-scroll-area-thumb',
value: ATTRS.MELT('scroll area thumb'),
},
],
events: ['pointerdown', 'pointerup'],
});

const scrollbarY = elementSchema('scrollbarY', {
description: 'The track of the vertical scrollbar.',
dataAttributes: [
{
name: 'data-state',
value: '`"visible" | "hidden"`',
},
{
name: 'data-melt-scroll-area-scrollbar',
value: ATTRS.MELT('scroll area scrollbar'),
},
],
events: ['pointerdown', 'pointerup', 'pointermove'],
});

const thumbY = elementSchema('thumbY', {
description: 'The thumb of the vertical scrollbar.',
dataAttributes: [
{
name: 'data-state',
value: '`"visible" | "hidden"`',
},
{
name: 'data-melt-scroll-area-thumb',
value: ATTRS.MELT('scroll area thumb'),
},
],
events: ['pointerdown', 'pointerup'],
});

const schemas: BuilderData['schemas'] = [
builder,
root,
viewport,
content,
scrollbarY,
thumbY,
scrollbarX,
thumbX,
];

const features: BuilderData['features'] = [
'Scrolling behavior is native to the platform',
'Scrollbar is positioned above the content so no layout shifting occurs',
'Keyboard controls are unaffected and follow platform conventions',
];

export const scrollAreaData: BuilderData = {
schemas,
features,
};
32 changes: 32 additions & 0 deletions src/docs/previews/scroll-area/always/tailwind/flavors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const flavors = [
'Vanilla',
'Chocolate',
'Strawberry',
'Mint Chocolate Chip',
'Cookies and Cream',
'Rocky Road',
'Pistachio',
'Neapolitan',
'Butter Pecan',
'Salted Caramel',
'Coffee',
'Mango',
'Raspberry Ripple',
'Lemon Sorbet',
'Green Tea',
'Coconut',
'Black Cherry',
'Banana',
'Almond Fudge',
'Cinnamon',
'Blueberry Cheesecake',
'Tiramisu',
'Red Velvet',
'Matcha',
'Peanut Butter Cup',
'Cookie Dough',
'Rum Raisin',
'Birthday Cake',
'Lychee',
'Honey Lavender',
];
40 changes: 40 additions & 0 deletions src/docs/previews/scroll-area/always/tailwind/index.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import { createScrollArea, melt } from '$lib/index.js';
import { flavors } from './flavors.js';
const {
elements: { root, content, viewport, corner, scrollbarY, thumbY },
} = createScrollArea({
type: 'always',
dir: 'ltr',
});
</script>

<div
use:melt={$root}
class="relative h-72 w-56 overflow-hidden rounded-md border border-neutral-700 bg-neutral-800/90 text-white shadow-lg"
>
<div use:melt={$viewport} class="h-full w-full rounded-[inherit]">
<div use:melt={$content}>
<div class="p-7">
<h4 class="mb-4 font-semibold leading-none">Endless Flavors</h4>
{#each flavors as flavor (flavor)}
<div class="text-sm">
{flavor}
</div>
<div role="separator" class="my-2 h-px w-full bg-magnum-800/30" />
{/each}
</div>
</div>
</div>
<div
use:melt={$scrollbarY}
class="flex h-full w-2.5 touch-none select-none border-l border-l-transparent bg-neutral-300/10 p-px transition-colors"
>
<div
use:melt={$thumbY}
class="relative flex-1 rounded-full bg-neutral-300/50"
/>
</div>
<div use:melt={$corner} />
</div>
32 changes: 32 additions & 0 deletions src/docs/previews/scroll-area/auto/tailwind/flavors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const flavors = [
'Vanilla',
'Chocolate',
'Strawberry',
'Mint Chocolate Chip',
'Cookies and Cream',
'Rocky Road',
'Pistachio',
'Neapolitan',
'Butter Pecan',
'Salted Caramel',
'Coffee',
'Mango',
'Raspberry Ripple',
'Lemon Sorbet',
'Green Tea',
'Coconut',
'Black Cherry',
'Banana',
'Almond Fudge',
'Cinnamon',
'Blueberry Cheesecake',
'Tiramisu',
'Red Velvet',
'Matcha',
'Peanut Butter Cup',
'Cookie Dough',
'Rum Raisin',
'Birthday Cake',
'Lychee',
'Honey Lavender',
];
Loading

0 comments on commit b9e8486

Please sign in to comment.