Skip to content
Open
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
17 changes: 17 additions & 0 deletions sandbox/forms/form-demo-with-cc-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '../../src/components/cc-input-date/cc-input-date.js';
import '../../src/components/cc-input-number/cc-input-number.js';
import '../../src/components/cc-input-text/cc-input-text.js';
import '../../src/components/cc-picker/cc-picker.js';
import '../../src/components/cc-range-selector/cc-range-selector.js';
import '../../src/components/cc-select/cc-select.js';
import '../../src/components/cc-toggle/cc-toggle.js';
import { formSubmit } from '../../src/lib/form/form-submit-directive.js';
Expand All @@ -30,6 +31,16 @@ const PICKER_OPTIONS = [
{ body: 'George Harrison', value: 'HARRISON' },
];

const RANGE_SELECTOR_OPTIONS = [
{ body: `L`, value: 'lun' },
{ body: `M`, value: 'mar' },
{ body: `M`, value: 'mer' },
{ body: `J`, value: 'jeu' },
{ body: `V`, value: 'ven' },
{ body: `S`, value: 'sam' },
{ body: `D`, value: 'dim' },
];

export class FormDemoWithCcComponents extends LitElement {
render() {
return html`
Expand All @@ -43,6 +54,12 @@ export class FormDemoWithCcComponents extends LitElement {
<cc-input-text label="Email address" name="email" type="email" required></cc-input-text>
<cc-select label="Favorite color" name="color" .options=${COLOR_OPTIONS} required></cc-select>
<cc-picker label="Find the intruder" name="star" .options=${PICKER_OPTIONS} required></cc-picker>
<cc-range-selector
label="Select a range"
name="planning"
.options=${RANGE_SELECTOR_OPTIONS}
required
></cc-range-selector>

<cc-button primary type="submit">Submit</cc-button>
</form>
Expand Down
193 changes: 193 additions & 0 deletions src/components/cc-range-selector-option/cc-range-selector-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { LitElement, css, html } from 'lit';
import { classMap } from 'lit/directives/class-map.js';

/**
* A tile component that can be used to display a range selection state.
*
* This component is specifically designed for cc-range-selector and is not meant to be used standalone.
* It is a presentational component that displays visual states based on properties set by its parent.
* All interactive behaviors (click handling, keyboard navigation, etc.) are managed by the parent cc-range-selector component.
*
* @cssdisplay inline-flex
*
* @slot body - Content displayed as the main part of the tile. This slot should contain the option's label or visual content and should not be empty.
*/
export class CcRangeSelectorOption extends LitElement {
static get properties() {
return {
disabled: { type: Boolean, reflect: true },
dragging: { type: Boolean, reflect: true },
error: { type: Boolean, reflect: true },
pointer: { type: Boolean, reflect: true },
readonly: { type: Boolean, reflect: true },
selected: { type: Boolean, reflect: true },
};
}

constructor() {
super();

/** @type {boolean} Whether the component should be disabled (default: 'false') */
this.disabled = false;

/** @type {boolean} Whether the option is within a drag selection range (default: 'false') */
this.dragging = false;

/** @type {boolean} Whether the component should be in error mode when not disabled nor readonly (default: 'false') */
this.error = false;

/** @type {boolean} Whether to show a pointer cursor for interactive states (default: 'false') */
this.pointer = false;

/** @type {boolean} Whether the component should be readonly when not disabled (default: 'false') */
this.readonly = false;

/** @type {boolean} Whether the option is currently selected when not dragging (default: 'false') */
this.selected = false;
}

/**
* Renders the option with appropriate visual states based on its properties.
* Calculates CSS classes for disabled, readonly, error, dragging, selected, and pointer states.
* @return {import('lit').TemplateResult}
*/
render() {
// Calculate CSS classes based on component state
// State priority: disabled > readonly > error > dragging > selected
// Note: dragging state takes visual priority over selected state during drag operations
const classes = {
disabled: this.disabled,
readonly: !this.disabled && this.readonly,
error: !this.disabled && !this.readonly && this.error,
dragging: this.dragging,
selected: !this.dragging && this.selected, // Selected styling is hidden during dragging
};

return html`
<div class="wrapper ${classMap(classes)}">
<slot name="body"></slot>
</div>
`;
}

static get styles() {
return [
// language=CSS
css`
/* region global */
:host {
--cc-icon-size: 1.25em;

border-radius: var(--cc-border-radius-default, 0.25em);
display: inline-flex;
overflow: hidden;
width: fit-content;
}

.wrapper {
align-items: stretch;
display: flex;
flex: 1 1 auto;
line-height: 1.5;
}
/* endregion */

/* region body section */
::slotted([slot='body']) {
background-color: var(--cc-color-bg-neutral, #f5f5f5);
border: 0.125em dotted var(--cc-color-bg-neutral, #f5f5f5);
color: var(--cc-color-text-default, #262626);
display: inline-block;
flex: 1 1 auto;
padding: 0.25em 0.5em;
}
/* endregion */

/* region common states */
.disabled ::slotted([slot='body']) {
background-color: var(--cc-color-bg-default, #fff);
border-color: var(--cc-color-bg-neutral, #f5f5f5);
border-style: solid;
color: var(--cc-color-text-disabled, #595959);
}

.readonly ::slotted([slot='body']) {
background-color: var(--cc-color-bg-neutral-active, #d9d9d9);
border-color: var(--cc-color-bg-neutral-active, #d9d9d9);
}

.selected ::slotted([slot='body']) {
background-color: var(--cc-color-bg-primary, #3569aa);
border-color: var(--cc-color-bg-primary, #3569aa);
color: var(--cc-color-text-inverted, #fff);
}

.dragging ::slotted([slot='body']) {
background-color: var(--cc-color-bg-primary-weaker, #e6eff8);
border-color: var(--cc-color-bg-primary, #3569aa);
color: var(--cc-color-text-primary-strong, #002c9d);
user-select: none;
}

.error ::slotted([slot='body']) {
background-color: var(--cc-color-bg-danger-weaker, #ffe4e1);
border-color: var(--cc-color-bg-danger-weaker, #ffe4e1);
color: var(--cc-color-text-danger, #be242d);
}
/* endregion */

/* region selected & disabled */
.selected.disabled ::slotted([slot='body']) {
background-color: var(--color-grey-60, #737373);
border-color: var(--color-grey-60, #737373);
color: var(--cc-color-text-inverted, #fff);
}
/* endregion */

/* region selected & readonly */
.selected.readonly ::slotted([slot='body']) {
background-color: var(--cc-color-bg-primary-weak, #cedcff);
border-color: var(--cc-color-bg-primary-weak, #cedcff);
color: var(--cc-color-text-primary-strong, #002c9d);
}
/* endregion */

/* region selected & error */
.selected.error ::slotted([slot='body']) {
background-color: var(--cc-color-bg-danger, #be242d);
border-color: var(--cc-color-bg-danger, #be242d);
color: var(--cc-color-text-inverted, #fff);
}
/* endregion */

/* region dragging & error */
.dragging.error ::slotted([slot='body']) {
border-color: var(--cc-color-bg-danger, #be242d);
}
/* endregion */

/* region hover */
/* Hover state only applies when option is in its default interactive state
(not disabled, readonly, error, selected, or dragging) */
.wrapper:not(.selected, .dragging, .disabled, .readonly, .error) :hover::slotted([slot='body']) {
background-color: var(--cc-color-bg-neutral-hovered, #e7e7e7);
border-color: var(--cc-color-bg-neutral-hovered, #e7e7e7);
}

.wrapper.error:not(.selected, .dragging) :hover::slotted([slot='body']) {
background-color: var(--cc-color-bg-danger-weak, #fbc8c2);
border-color: var(--cc-color-bg-danger-weak, #fbc8c2);
}
/* endregion */

/* region pointer */
:host([pointer]) {
cursor: pointer;
}
/* endregion */
`,
];
}
}

window.customElements.define('cc-range-selector-option', CcRangeSelectorOption);
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { makeStory } from '../../stories/lib/make-story.js';
import './cc-range-selector-option.js';

export default {
tags: ['autodocs'],
title: '🧬 Atoms/<cc-range-selector-option>',
component: 'cc-range-selector-option',
};

const conf = {
component: 'cc-range-selector-option',
};

export const defaultStates = makeStory(conf, {
displayMode: 'flex-wrap',
items: [
{
pointer: true,
innerHTML: `
<span slot="body">Default</span>
`,
},
{
disabled: true,
innerHTML: `
<span slot="body">Disabled</span>
`,
},
{
readonly: true,
innerHTML: `
<span slot="body">Readonly</span>
`,
},
{
error: true,
pointer: true,
innerHTML: `
<span slot="body">Error</span>
`,
},
],
});

export const selectedStates = makeStory(conf, {
displayMode: 'flex-wrap',
items: [
{
selected: true,
pointer: true,
innerHTML: `
<span slot="body">Default</span>
`,
},
{
disabled: true,
selected: true,
innerHTML: `
<span slot="body">Disabled</span>
`,
},
{
readonly: true,
selected: true,
innerHTML: `
<span slot="body">Readonly</span>
`,
},
{
error: true,
selected: true,
innerHTML: `
<span slot="body">Error</span>
`,
},
],
});

export const draggingStates = makeStory(conf, {
displayMode: 'flex-wrap',
items: [
{
dragging: true,
innerHTML: `
<span slot="body">Default</span>
`,
},
{
dragging: true,
error: true,
innerHTML: `
<span slot="body">Error</span>
`,
},
],
});
17 changes: 17 additions & 0 deletions src/components/cc-range-selector/cc-range-selector.events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CcEvent } from '../../lib/events.js';

/**
* Dispatched when the custom option button is clicked.
* The detail contains the current selection value (string for single mode, string array for range mode).
* @extends {CcEvent<string|string[]>}
*/
export class CcRangeSelectorSelectCustom extends CcEvent {
static TYPE = 'cc-range-selector-select-custom';

/**
* @param {string|string[]} detail
*/
constructor(detail) {
super(CcRangeSelectorSelectCustom.TYPE, detail);
}
}
Loading