Skip to content

Commit

Permalink
Range input
Browse files Browse the repository at this point in the history
  • Loading branch information
tborychowski committed Oct 13, 2023
1 parent 6ec9fb1 commit cfa120a
Show file tree
Hide file tree
Showing 16 changed files with 462 additions and 131 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
Changelog
=========

## v9.2.0 *(2023-10-13)*
- New component: `Range`.


## v9.1.2, v9.1.1, v9.1.0 *(2023-09-27)*
- New components: `InputRating`, `Tag`, `InputTag`, `InputTime`.
- Add `hideTip` and more, to `Popover`.
- Add `useNativeOnMobile` to `InputDate`.
- Fix `Popover` z-index (so that it shows over dialogs)
- Many other smaller bugfixes and improvements.


## v9.0.5 *(2023-09-22)*
- Reduce `Dialog's` `z-index` so that the popups from the dialog show up on top of it.

Expand Down
1 change: 1 addition & 0 deletions docs-src/components/input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './input-tag';
export * from './input-time';
export * from './input-text';
export * from './radio';
export * from './range';
export * from './select';
export * from './textarea';
export * from './toggle';
80 changes: 80 additions & 0 deletions docs-src/components/input/range/Range.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<h2>Range</h2>
<p>A wrapper around the native range input.</p>

<h3>Default</h3>
<Range />

<h3>Disabled</h3>
<Range disabled />

<h3>With tooltip hidden</h3>
<Range hideTooltip />

<h3>With different constraints</h3>
<Range min="10" max="100" step="5" />

<h3>With label</h3>
<Range label="Slide to the right" />

<h3>With label and info text</h3>
<Range label="Write some text" info="This is some info for you" />

<h3>With label and error and live validation</h3>
<Range label="Move the slider" {error} on:change="{onChange}" value="5" />

<h3>With label, info, and error</h3>
<Range label="Move the slider" info="Don't make any mistakes!" error="You did not slide!" />

<h3>Label on the left</h3>
<Range label="Label is on the left" labelOnTheLeft="true"/>



<CodeExample html="{exampleHtml}" />

<API props="{apiProps}"/>


<script>
import { Range } from '../../../../src';
import { API } from '../../../api-table';
import { CodeExample } from '../../../code-example';
const apiProps = [
{ name: 'class', type: 'string', description: 'Additional css class name to be added to the component.' },
{ name: 'disabled', description: 'Make the input disabled.' },
{ name: 'id', type: 'string', description: 'Assign ID to the underlying input.' },
{ name: 'info', type: 'string', description: 'Show info message above the input.' },
{ name: 'error', type: 'string', description: 'Error message to show above the input.' },
{ name: 'hideTooltip', description: 'If present, the value tooltip will not be shown.' },
{ name: 'name', type: 'string', description: 'Assign title to the underlying input.' },
{ name: 'label', type: 'string', description: 'Label for the input.' },
{ name: 'labelOnTheLeft', type: ['true', 'false'], default: 'false', description: 'Put label to the left of the input (instead of at the top). Usually in longer forms, to align labels and inputs, hence input also gets <em>width: 100%</em>, as it will be constraint by the form container.' },
{ name: 'max', type: ['number'], default: '10', description: 'Max value of the input.' },
{ name: 'min', type: ['number'], default: '0', description: 'Min value of the input.' },
{ name: 'step', type: ['number'], default: '1', description: 'Step value of the input.' },
{ name: 'title', type: 'string', description: 'Assign title to the underlying input.' },
{ name: 'value', type: 'string', description: 'Initial value of the input.' },
{ name: 'bind:element', type: 'element', description: 'Exposes the HTML element of the component.' },
{ name: 'bind:inputElement', type: 'element', description: 'Exposes the HTML element of the underlying input.' },
{ name: 'on:change', type: 'function', description: 'Triggered when the value changes.' },
{ name: 'on:input', type: 'function', description: 'Triggered when input value is edited.' },
];
const exampleHtml = `
<Range autogrow on:change="{onChange}" error="Invalid text" />
<script>
function onChange (e) {
console.log('value', e.target.value);
}
&lt;/script>
`;
let error = 'Move to 6.';
function onChange (e) {
error = e.target.value === '6' ? '' : 'Move to 6.';
}
</script>
1 change: 1 addition & 0 deletions docs-src/components/input/range/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Range } from './Range.svelte';
1 change: 1 addition & 0 deletions docs-src/nav/Nav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<NavItem name="Input Text" {active} />
<NavItem name="Input Time" {active} />
<NavItem name="Radio" {active} />
<NavItem name="Range" {active} />
<NavItem name="Select" {active} />
<NavItem name="Textarea" {active} />
<NavItem name="Toggle" {active} />
Expand Down
4 changes: 4 additions & 0 deletions docs-src/pages/changelog.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<h1>Changelog</h1>
<h2>v9.2.0 <em>(2023-10-13)</em></h2>
<ul>
<li>New component: <code>Range</code>.</li>
</ul>
<h2>v9.1.2, v9.1.1, v9.1.0 <em>(2023-09-27)</em></h2>
<ul>
<li>New components: <code>InputRating</code>, <code>Tag</code>, <code>InputTag</code>, <code>InputTime</code>.</li>
Expand Down
248 changes: 128 additions & 120 deletions docs/docs.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/ui.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/dialog/Dialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function onDocKeydown (e) {
function freezeBody (freeze) {
if (freeze) {
scrollPos = window.pageYOffset;
scrollPos = window.scrollY;
document.body.classList.add('has-dialog');
document.body.style.top = `-${scrollPos}px`;
}
Expand Down
1 change: 1 addition & 0 deletions src/input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './input-time';
export * from './input-text';
export * from './label';
export * from './radio';
export * from './range';
export * from './select';
export * from './textarea';
export * from './toggle';
14 changes: 9 additions & 5 deletions src/input/input.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@ input[type=color] {


/* label on the left */
.radio.label-on-the-left,
.button-toggle.input.label-on-the-left,
.textarea.label-on-the-left,
.toggle.label-on-the-left,
.input.label-on-the-left {
.radio.label-on-the-left,
.button-toggle.input.label-on-the-left,
.textarea.label-on-the-left,
.toggle.label-on-the-left,
.range.label-on-the-left,
.input.label-on-the-left {
display: inline-flex;
flex-flow: row;
align-items: flex-start;
width: 100%;
gap: 1rem;
}
.label-on-the-left .label { padding-inline: 0; width: max-content; white-space: nowrap; }
.label-on-the-left .range-inner,
.label-on-the-left .input-inner { flex: 1; }


Expand All @@ -86,10 +88,12 @@ input[type=color] {

.input,
.textarea textarea:disabled,
.range input:disabled,
.input select:disabled,
.input input:disabled { opacity: 1; color: var(--ui-color-text); }

.input .input-inner:has(:disabled),
.range .range-inner:has(:disabled),
.textarea .textarea-inner:has(:disabled) { opacity: 0.7; }

.input input:not([type=radio]) { width: 100%; border: none; }
Expand Down
84 changes: 84 additions & 0 deletions src/input/range/Range.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.range {
position: relative;
width: 25ch;
vertical-align: top;
--range-size: calc(var(--ui-button-height) / 3 * 2);
}

.range .range-inner {
display: flex;
flex-flow: column;
gap: 0.5rem;
flex: 1;
padding: 0.75rem 0;
position: relative;
}

.range input {
-webkit-appearance: none;
appearance: none;
margin: 0;
width: 100%;
height: 0.5rem;
border-radius: 5rem;
padding-inline: 0;
border: 1px solid var(--ui-color-border);
background: var(--ui-color-background-input);
background-image: linear-gradient(var(--ui-color-highlight), var(--ui-color-highlight));
background-size: 70% 100%;
background-repeat: no-repeat;
background-clip: padding-box;
}

.range input::-webkit-slider-runnable-track {
-webkit-appearance: none;
display: flex;
align-items: center;
height: 0.5rem;
border-radius: 0.5rem;
}

.range input::-moz-range-track {
display: flex;
align-items: center;
background-color: var(--ui-color-background-input);
height: 0.5rem;
border-radius: 0.5rem;
}

.range input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
background-color: var(--ui-color-text);
height: var(--range-size);
width: var(--range-size);
border-radius: 5rem;
box-shadow: 0 1px 3px #000c;
}

.range input::-moz-range-thumb {
background-color: var(--ui-color-text);
height: var(--range-size);
width: var(--range-size);
border-radius: 5rem;
box-shadow: 0 1px 3px #000c;
}

/* Cannot be merged, as it won't work in safari */
.range:not(.disabled) input::-webkit-slider-thumb:active { transform: scale(0.9); }
.range:not(.disabled) input::-moz-slider-thumb:active { transform: scale(0.9); }


.range-tooltip {
position: absolute;
bottom: 0;
width: calc(100% - var(--range-size) - 2px);
margin-inline: calc(var(--range-size) / 2 + 1px);
transform: translateY(calc(var(--range-size) * -1 - 0.3rem));
display: none;
}

.range-tooltip .tooltip-plate { display: inline-flex; position: relative; transform: translateX(-50%); }

.range:not(.disabled):focus-within .range-tooltip,
.range:not(.disabled):active .range-tooltip { display: block; }
77 changes: 77 additions & 0 deletions src/input/range/Range.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<div
class="range {className}"
class:has-error="{error}"
class:label-on-the-left="{labelOnTheLeft === true || labelOnTheLeft === 'true'}"
class:disabled
{title}
bind:this="{element}">

<Label {label} {disabled} for="{_id}"/>
<Info msg="{info}" />

<div class="range-inner" class:disabled>
<InputError id="{errorMessageId}" msg="{error}" />
<input
type="range"
{name}
{disabled}
{min}
{max}
{step}
id="{_id}"
style="background-size: {progress}% 100%;"
aria-invalid="{error}"
aria-errormessage="{error ? errorMessageId : undefined}"
bind:this="{inputElement}"
bind:value="{value}"
on:change
on:input>

{#if !hideTooltip}
<div class="range-tooltip">
<div class="popover-plate popover-top tooltip-plate opened" style="left: {progress}%;">
<div class="popover tooltip" role="tooltip">
<div class="popover-content tooltip-content">{value}</div>
</div>
</div>
</div>
{/if}
</div>
</div>


<script>
import { guid } from '../../utils';
import { Info } from '../../info-bar';
import { InputError } from '../input-error';
import { Label } from '../label';
let className = '';
export { className as class };
export let id = '';
export let disabled = false;
export let label = '';
export let error = undefined;
export let info = undefined;
export let title = undefined;
export let name = undefined;
export let labelOnTheLeft = false;
export let min = 0;
export let max = 10;
export let step = 1;
export let value = min;
export let hideTooltip = false;
export let element = undefined;
export let inputElement = undefined;
$:_id = id || name || guid();
$:progress = (value - min) / (max - min) * 100;
const errorMessageId = guid();
</script>
1 change: 1 addition & 0 deletions src/input/range/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Range } from './Range.svelte';
8 changes: 4 additions & 4 deletions tests/input/Radio.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ test('Radio', async () => {
const cmp = getByTitle(props.title);
expect(cmp).toBeInTheDocument();
expect(cmp).toHaveClass('test-class');
expect(cmp).toHaveAttribute('id', 'Radio1');
expect(cmp).toHaveAttribute('id', props.id);


let err = cmp.querySelector('.info-bar-error');
Expand All @@ -51,7 +51,7 @@ test('Radio', async () => {

const lbl = cmp.querySelector('label');
expect(lbl).toBeInTheDocument();
expect(lbl).toHaveAttribute('for', 'Radio1');
expect(lbl).toHaveAttribute('for', props.id);
expect(lbl).toHaveTextContent(props.label);


Expand All @@ -62,8 +62,8 @@ test('Radio', async () => {
expect(inp0).not.toBeChecked();
expect(inp1).not.toBeChecked();

expect(inp0).toHaveAttribute('name', 'Radio1');
expect(inp1).toHaveAttribute('name', 'Radio1');
expect(inp0).toHaveAttribute('name', props.name);
expect(inp1).toHaveAttribute('name', props.name);

expect(inp0).toBeDisabled();
expect(inp1).not.toBeDisabled();
Expand Down
Loading

0 comments on commit cfa120a

Please sign in to comment.