Skip to content

Commit

Permalink
feat(range-slider): add component (VIV-1488) (#1584)
Browse files Browse the repository at this point in the history
* feat(range-slider): add component (VIV-1488)

* Add range-slider to vivid-vue

* Add component override

* Update snapshot

* Update snapshot

* Update snapshots

* Refactor track-start position

* Fix keyboard navigation in vertical orientation

* Use logical properties

---------

Co-authored-by: James Taylor <[email protected]>
  • Loading branch information
RichardHelm and TaylorJ76 authored Feb 28, 2024
1 parent 5537b64 commit 0074811
Show file tree
Hide file tree
Showing 32 changed files with 1,856 additions and 10 deletions.
5 changes: 5 additions & 0 deletions apps/docs/_data/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@
"title": "File Picker",
"markdown": "./libs/components/src/lib/file-picker/README.md"
},
{
"title": "Range Slider",
"status": "alpha",
"markdown": "./libs/components/src/lib/range-slider/README.md"
},
{
"title": "Split Button",
"markdown": "./libs/components/src/lib/split-button/README.md"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<VRangeSlider
:start="start"
:end="end"
@update:start="start = $event"
@update:end="end = $event"
/>
<!-- In Vue 3 you can use v-model shorthand instead of @update:start and @update:end
<VRangeSlider v-model:start="start" v-model:end="end" />
-->
</template>

<script setup lang="ts">
import { VRangeSlider } from '@vonage/vivid-vue';
import { ref } from 'vue';
const start = ref('0');
const end = ref('10');
</script>
21 changes: 21 additions & 0 deletions apps/vue-docs/docs/examples/range-slider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Range Slider Examples

## Range Slider
<code-tab>
<template #example>
<RangeSliderExample />
</template>
<template #code>

```vue
<!--@include: ./components/range-slider/RangeSliderExample.vue -->
```
</template>
</code-tab>

<script setup lang="ts">
import CodeTab from '../custom/CodeTab.vue';
import { defineClientComponent } from 'vitepress';

const RangeSliderExample = defineClientComponent(() => import('./components/range-slider/RangeSliderExample.vue'));
</script>
1 change: 1 addition & 0 deletions libs/components/src/lib/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export * from './progress-ring/definition';
export * from './progress/definition';
export * from './radio-group/definition';
export * from './radio/definition';
export * from './range-slider/definition';
export * from './select/definition';
export * from './selectable-box/definition';
export * from './side-drawer/definition';
Expand Down
212 changes: 212 additions & 0 deletions libs/components/src/lib/range-slider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Range Slider

The range slider component allows users to select a range. It includes two thumb controls that can be dragged to set the start and end values.

```js
<script type="module">import '@vonage/vivid/range-slider';</script>
```

```html preview
<vwc-range-slider></vwc-range-slider>
```

## Members

### Min

The lowest value allowed for the range.

- Type: `number`
- Default: `0`

```html preview blocks
<div>
<vwc-range-slider id="slider" min="-10"></vwc-range-slider>
</div>
<div>Current range: <span id="start"></span> to <span id="end"></span></div>

<script>
const slider = document.querySelector('#slider');
const start = document.querySelector('#start');
const end = document.querySelector('#end');
const updateDescription = () => {
start.innerText = slider.start;
end.innerText = slider.end;
}
customElements.whenDefined('vwc-range-slider').then(updateDescription);
slider.addEventListener('change', updateDescription);
</script>
```

### Max

The highest value allowed for the range.

- Type: `number`
- Default: `0`

```html preview blocks
<div>
<vwc-range-slider id="slider" max="20"></vwc-range-slider>
</div>
<div>Current range: <span id="start"></span> to <span id="end"></span></div>

<script>
const slider = document.querySelector('#slider');
const start = document.querySelector('#start');
const end = document.querySelector('#end');
const updateDescription = () => {
start.innerText = slider.start;
end.innerText = slider.end;
}
customElements.whenDefined('vwc-range-slider').then(updateDescription);
slider.addEventListener('change', updateDescription);
</script>
```

### Step

Sets the granularity with which values can be incremented/decremented.

- Type: `number`
- Default: `1`

```html preview blocks
<div>
<vwc-range-slider id="slider" step="0.5"></vwc-range-slider>
</div>
<div>Current range: <span id="start"></span> to <span id="end"></span></div>

<script>
const slider = document.querySelector('#slider');
const start = document.querySelector('#start');
const end = document.querySelector('#end');
const updateDescription = () => {
start.innerText = slider.start;
end.innerText = slider.end;
}
customElements.whenDefined('vwc-range-slider').then(updateDescription);
slider.addEventListener('change', updateDescription);
</script>
```

### Orientation

When used vertically, the range slider fills the height of its container.

- Type: `'horizontal'` | `'vertical'`
- Default: `'horizontal'`

```html preview center 300px
<vwc-range-slider orientation="vertical"></vwc-range-slider>
```

### Markers

Toggles markers display.

- Type: `boolean`
- Default: `false`

```html preview blocks
<vwc-range-slider markers></vwc-range-slider>
```

### Connotation

- Type: `'accent'` | `'cta'`
- Default: `'accent'`

```html preview blocks
<vwc-range-slider connotation="cta"></vwc-range-slider>
```

### Disabled

Toggle the `disabled` member to disable/enable the slider.

- Type: `boolean`
- Default: `false`

```html preview blocks
<vwc-range-slider disabled></vwc-range-slider>
```

### Start

The lower value of the range.

- Type: `string`
- Default: `min`

```html preview blocks
<vwc-range-slider start="5"></vwc-range-slider>
```

### End

The upper value of the range.

- Type: `string`
- Default: `max`

```html preview blocks
<vwc-range-slider end="5"></vwc-range-slider>
```

## Events

<div class="table-wrapper">

| Name | Description |
|-------------|---------------------------------------------|
| input | When either the start or end value changes. |
| change | When either the start or end value changes. |
| input:start | When the start value changes |
| input:end | When the end value changes |

</div>

## Accessibility

Both thumbs have a `role` of `slider`, which needs an accessible label. By default, they use a localized version of "min" and "max".
You can change the labels by setting the `aria-start-label` and `aria-end-label` attributes.

You can set the `valueTextFormatter` member to customize how values will be formatted for the thumbs' `aria-valuetext` attribute.

## Use Cases

```html preview
<div>
<vwc-range-slider id="slider" min="0" max="7200" end="7200" step="15"></vwc-range-slider>
</div>
<div>Duration from <strong><span id="start"></span> to <span id="end"></span></strong></div>
<script>
const slider = document.querySelector('#slider');
const start = document.querySelector('#start');
const end = document.querySelector('#end');
const formatValue = (value) => {
const totalSeconds = Number.parseFloat(value);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
const updateDescription = () => {
start.innerText = formatValue(slider.start);
end.innerText = formatValue(slider.end);
};
customElements.whenDefined('vwc-range-slider').then(updateDescription);
slider.valueTextFormatter = formatValue;
slider.addEventListener('change', updateDescription);
</script>
```
28 changes: 28 additions & 0 deletions libs/components/src/lib/range-slider/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { FoundationElementDefinition } from '@microsoft/fast-foundation';
import { registerFactory } from '../../shared/design-system';
import styles from './range-slider.scss?inline';

import { RangeSlider } from './range-slider';
import { RangeSliderTemplate as template } from './range-slider.template';

export const rangeSliderDefinition =
RangeSlider.compose<FoundationElementDefinition>({
baseName: 'range-slider',
template: template as any,
styles,
shadowOptions: {
delegatesFocus: true,
},
});

/**
* @internal
*/
export const rangeSliderRegistries = [rangeSliderDefinition()];

/**
* Registers the range-slider element with the design system.
*
* @param prefix - the prefix to use for the component name
*/
export const registerRangeSlider = registerFactory(rangeSliderRegistries);
3 changes: 3 additions & 0 deletions libs/components/src/lib/range-slider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { registerRangeSlider } from './definition';

registerRangeSlider();
4 changes: 4 additions & 0 deletions libs/components/src/lib/range-slider/locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface RangeSliderLocale {
startThumbLabel: string;
endThumbLabel: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FormAssociated, FoundationElement } from '@microsoft/fast-foundation';

class _RangeSlider extends FoundationElement {}
// eslint-disable-next-line @typescript-eslint/naming-convention
interface _RangeSlider extends FormAssociated {}

export class FormAssociatedRangeSlider extends FormAssociated(_RangeSlider) {
proxy = document.createElement('input');
}
9 changes: 9 additions & 0 deletions libs/components/src/lib/range-slider/range-slider.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use "../slider/partials/variables" as variables;

@import "../slider/slider";

.control {
// Unlike slider, track-start should not be aligned to the start of the track
#{variables.$track-start-inset-inline-start}: auto;
#{variables.$track-start-inset-block-start}: auto;
}
Loading

0 comments on commit 0074811

Please sign in to comment.