Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Slider): enable support for form-associated #1369

Merged
merged 1 commit into from
Dec 11, 2024
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
8 changes: 8 additions & 0 deletions packages/beeq/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,10 @@ export namespace Components {
* A number representing the min value of the slider.
*/
"min": number;
/**
* Name of the form control. Submitted with the form as part of a name/value pair
*/
"name": string;
/**
* A number representing the step of the slider. ⚠️ Please notice that the value (or list of values if the slider type is `range`) will be rounded to the nearest multiple of `step`.
*/
Expand Down Expand Up @@ -7417,6 +7421,10 @@ declare namespace LocalJSX {
* A number representing the min value of the slider.
*/
"min"?: number;
/**
* Name of the form control. Submitted with the form as part of a name/value pair
*/
"name"?: string;
/**
* Handler to be called when the slider loses focus
*/
Expand Down
138 changes: 116 additions & 22 deletions packages/beeq/src/components/slider/_storybook/bq-slider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const meta: Meta = {
docs: {
page: mdx,
},
layout: 'centered',
},
argTypes: {
'debounce-time': { control: 'number' },
Expand All @@ -22,6 +21,7 @@ const meta: Meta = {
gap: { control: 'number' },
max: { control: 'number' },
min: { control: 'number' },
name: { control: 'text' },
step: { control: 'number' },
type: { control: 'inline-radio', options: ['single', 'range'] },
value: { control: 'object' },
Expand All @@ -39,6 +39,7 @@ const meta: Meta = {
gap: 0,
max: 100,
min: 0,
name: 'bq-slider',
step: 1,
type: 'single',
value: undefined,
Expand All @@ -48,38 +49,45 @@ export default meta;

type Story = StoryObj;

const Template = (args: Args) => html`
<div class="is-96">
<bq-slider
debounce-time=${ifDefined(args['debounce-time'])}
?disabled=${args.disabled}
?enable-value-indicator=${args['enable-value-indicator']}
?enable-tooltip=${args['enable-tooltip']}
?tooltip-always-visible=${args['tooltip-always-visible']}
gap=${ifDefined(args.gap)}
max=${ifDefined(args.max)}
min=${ifDefined(args.min)}
step=${ifDefined(args.step)}
type=${ifDefined(args.type)}
value=${ifDefined(JSON.stringify(args.value))}
@bqBlur=${args.bqBlur}
@bqChange=${args.bqChange}
@bqFocus=${args.bqFocus}
>
${args.text}
</bq-slider>
</div>
const Template = (args: Args) => html` <div class="is-96">${SliderTemplate(args)}</div> `;

const SliderTemplate = (args: Args) => html`
<bq-slider
debounce-time=${ifDefined(args['debounce-time'])}
?disabled=${args.disabled}
?enable-value-indicator=${args['enable-value-indicator']}
?enable-tooltip=${args['enable-tooltip']}
?tooltip-always-visible=${args['tooltip-always-visible']}
gap=${ifDefined(args.gap)}
max=${ifDefined(args.max)}
min=${ifDefined(args.min)}
name=${ifDefined(args.name)}
step=${ifDefined(args.step)}
type=${ifDefined(args.type)}
value=${ifDefined(JSON.stringify(args.value))}
@bqBlur=${args.bqBlur}
@bqChange=${args.bqChange}
@bqFocus=${args.bqFocus}
>
${args.text}
</bq-slider>
`;

export const Default: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
value: 30,
},
};

export const Range: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
value: [30, 70],
type: 'range',
Expand All @@ -88,6 +96,9 @@ export const Range: Story = {

export const Disabled: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
disabled: true,
value: [30, 70],
Expand All @@ -97,6 +108,9 @@ export const Disabled: Story = {

export const ValueIndicator: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
'enable-value-indicator': true,
value: [30, 70],
Expand All @@ -107,6 +121,9 @@ export const ValueIndicator: Story = {
export const MinMaxStep: Story = {
name: 'Min, Max, Step',
render: Template,
parameters: {
layout: 'centered',
},
args: {
'enable-value-indicator': true,
max: 10,
Expand All @@ -118,6 +135,9 @@ export const MinMaxStep: Story = {

export const Gap: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
'enable-value-indicator': true,
gap: 10,
Expand All @@ -131,6 +151,9 @@ export const Gap: Story = {

export const DecimalValues: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
'enable-value-indicator': true,
max: 1,
Expand All @@ -143,6 +166,9 @@ export const DecimalValues: Story = {

export const WithTooltip: Story = {
render: Template,
parameters: {
layout: 'centered',
},
args: {
'enable-tooltip': true,
gap: 10,
Expand All @@ -153,3 +179,71 @@ export const WithTooltip: Story = {
value: [30, 70],
},
};

export const WithForm: Story = {
render: (args: Args) => {
const handleFormSubmit = (ev: Event) => {
ev.preventDefault();
const form = ev.target as HTMLFormElement;
const formData = new FormData(form);
const formValues = Object.fromEntries(formData.entries());

const codeElement = document.getElementById('form-data');
if (!codeElement) return;

codeElement.textContent = JSON.stringify(formValues, null, 2);
};

return html`
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/[email protected]/styles/night-owl.min.css" />
<div class="grid auto-cols-auto grid-cols-1 gap-y-l sm:grid-cols-2 sm:gap-x-l">
<bq-card>
<h4 class="m-be-m">Taxi information</h4>
<form class="flex flex-col gap-y-m" @submit=${handleFormSubmit}>
<label class="flex items-center gap-x-s"> Number of seats </label>
${SliderTemplate({ ...args, name: 'numSeats', max: 8, value: 3 })}
<!-- Range -->
<label class="flex items-center gap-x-s m-bs-m"> Price range (€) </label>
${SliderTemplate({ ...args, name: 'priceRange', value: [30, 70], type: 'range' })}
<div class="flex justify-end gap-x-s m-bs-l">
<bq-button appearance="secondary" type="reset">Cancel</bq-button>
<bq-button type="submit">Save</bq-button>
</div>
</form>
</bq-card>
<bq-card class="[&::part(wrapper)]:h-full">
<h4 class="m-be-m">Form Data</h4>
<div class="language-javascript overflow-x-scroll whitespace-pre rounded-s">
// Handle form submit<br />
const form = ev.target as HTMLFormElement;<br />
const formData = new FormData(form);<br />
const formValues = Object.fromEntries(formData.entries());
</div>
<pre>
<code id="form-data" class="rounded-s">
{ // submit the form to see the data here }
</code>
</pre>
</bq-card>
</div>

<script type="module">
import hljs from 'https://unpkg.com/@highlightjs/[email protected]/es/highlight.min.js';
import javascript from 'https://unpkg.com/@highlightjs/[email protected]/es/languages/javascript.min.js';

hljs.registerLanguage('javascript', javascript);
hljs.highlightAll();

document.querySelectorAll('div.language-javascript').forEach((block) => {
hljs.highlightElement(block);
});
</script>
`;
},
args: {
'enable-value-indicator': true,
max: 100,
min: 0,
step: 1,
},
};
20 changes: 14 additions & 6 deletions packages/beeq/src/components/slider/bq-slider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Element, Event, EventEmitter, h, Prop, State, Watch } from '@stencil/core';
import { AttachInternals, Component, Element, Event, EventEmitter, h, Prop, State, Watch } from '@stencil/core';

import { TSliderType, TSliderValue } from './bq-slider.types';
import { clamp, debounce, isNil, isString, TDebounce } from '../../shared/utils';
Expand Down Expand Up @@ -51,6 +51,7 @@ import { clamp, debounce, isNil, isString, TDebounce } from '../../shared/utils'
@Component({
tag: 'bq-slider',
styleUrl: './scss/bq-slider.scss',
formAssociated: true,
shadow: {
delegatesFocus: true,
},
Expand All @@ -70,6 +71,7 @@ export class BqSlider {
// Reference to host HTML element
// ===================================

@AttachInternals() internals!: ElementInternals;
@Element() el!: HTMLBqSliderElement;

// State() variables
Expand Down Expand Up @@ -109,6 +111,9 @@ export class BqSlider {
/** A number representing the min value of the slider. */
@Prop({ reflect: true }) min = 0;

/** Name of the form control. Submitted with the form as part of a name/value pair */
@Prop({ reflect: true }) name: string;

/**
* A number representing the step of the slider.
* ⚠️ Please notice that the value (or list of values if the slider type is `range`) will be rounded to the nearest multiple of `step`.
Expand Down Expand Up @@ -176,10 +181,6 @@ export class BqSlider {
// Ordered by their natural call order
// =====================================

connectedCallback() {
this.init();
}

componentWillLoad() {
this.init();
}
Expand All @@ -192,6 +193,10 @@ export class BqSlider {
this.runUpdates();
}

formAssociatedCallback() {
this.internals?.setFormValue(`${this.value}`);
}

// Listeners
// ==============

Expand Down Expand Up @@ -258,7 +263,9 @@ export class BqSlider {

// Sync the prop value.
// This will trigger the `@Watch('value')` method and emit the `bqChange` event.
this.value = this.isRangeType ? [this.minValue, this.maxValue] : this.minValue;
const { internals, isRangeType, maxValue, minValue } = this;
this.value = isRangeType ? [minValue, maxValue] : minValue;
internals?.setFormValue(isRangeType ? JSON.stringify(this.value) : this.value.toString());
};

private calculatePercent = (value: number) => {
Expand Down Expand Up @@ -385,6 +392,7 @@ export class BqSlider {
disabled={this.disabled}
min={this.min}
max={this.max}
name={this.name}
step={this.step}
ref={refCallback}
onInput={(ev) => this.handleInputChange(type, ev)}
Expand Down
1 change: 1 addition & 0 deletions packages/beeq/src/components/slider/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Sliders provide a visual representation of adjustable content, enabling users to
| `gap` | `gap` | A number representing the amount to remain between the minimum and maximum values (only for range type). | `number` | `0` |
| `max` | `max` | A number representing the max value of the slider. | `number` | `100` |
| `min` | `min` | A number representing the min value of the slider. | `number` | `0` |
| `name` | `name` | Name of the form control. Submitted with the form as part of a name/value pair | `string` | `undefined` |
| `step` | `step` | A number representing the step of the slider. ⚠️ Please notice that the value (or list of values if the slider type is `range`) will be rounded to the nearest multiple of `step`. | `number` | `1` |
| `tooltipAlwaysVisible` | `tooltip-always-visible` | If `true`, a tooltip will always display the progress value. It relies on enableTooltip and if enableTooltip is false, tooltipAlwaysVisible cannot be true. | `boolean` | `false` |
| `type` | `type` | It defines the type of slider to display | `"range" \| "single"` | `'single'` |
Expand Down
1 change: 0 additions & 1 deletion packages/beeq/src/components/switch/bq-switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ export class BqSwitch {
}

formAssociatedCallback() {
console.log('formAssociatedCallback...');
this.setFormValue(this.checked);
this.updateFormValidity();
}
Expand Down