Skip to content

Commit 5a14b6a

Browse files
bjarnefiOvergaard
andauthored
feat: Advanced color slider story (#1076)
* Add advanced color slider story * Update color in opacity slider * Fallback * Update slider gradient for saturation and lightness * Remove comment * Color property --------- Co-authored-by: Jacob Overgaard <[email protected]>
1 parent 4c3bbfa commit 5a14b6a

File tree

2 files changed

+203
-7
lines changed

2 files changed

+203
-7
lines changed

packages/uui-color-slider/lib/uui-color-slider.element.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { LitElement, html, css } from 'lit';
2+
import { Colord } from 'colord';
23
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
34
import { property } from 'lit/decorators.js';
45
import { styleMap } from 'lit/directives/style-map.js';
@@ -13,8 +14,7 @@ import { UUIColorSliderEvent } from './UUIColorSliderEvent';
1314
import { LabelMixin } from '@umbraco-ui/uui-base/lib/mixins';
1415

1516
export type UUIColorSliderOrientation = 'horizontal' | 'vertical';
16-
//TODO implement saturation and lightness types for color slider
17-
export type UUIColorSliderType = 'hue' | 'opacity';
17+
export type UUIColorSliderType = 'hue' | 'opacity' | 'saturation' | 'lightness';
1818

1919
/**
2020
* @element uui-color-slider
@@ -107,11 +107,29 @@ export class UUIColorSliderElement extends LabelMixin('label', LitElement) {
107107
willUpdate(changedProperties: Map<string, any>) {
108108
if (changedProperties.has('type')) {
109109
if (this.type === 'hue') {
110-
this.max = 360;
111-
this.precision = 1;
110+
this.max = this.max ?? 360;
111+
} else if (this.type === 'saturation') {
112+
this.max = this.max ?? 100;
113+
} else if (this.type === 'lightness') {
114+
this.max = this.max ?? 100;
112115
} else if (this.type === 'opacity') {
113-
this.max = 100;
114-
this.precision = 1;
116+
this.max = this.max ?? 100;
117+
}
118+
119+
this.precision = this.precision ?? 1;
120+
121+
if (this.color) {
122+
const colord = new Colord(this.color);
123+
const { h, s, l } = colord.toHsl();
124+
125+
const gradient =
126+
this.type === 'saturation'
127+
? `linear-gradient(to ${this.vertical ? 'top' : 'right'}, hsl(${h}, 0%, ${l}%), hsl(${h}, 100%, ${l}%))`
128+
: this.type === 'lightness'
129+
? `linear-gradient(to ${this.vertical ? 'top' : 'right'}, hsl(${h}, ${s}%, 0%), hsl(${h}, ${s}%, 100%))`
130+
: null;
131+
132+
this.style.setProperty('--uui-slider-background-image', gradient);
115133
}
116134
}
117135
}

packages/uui-color-slider/lib/uui-color-slider.story.ts

+179-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import '.';
22
import readme from '../README.md?raw';
33
import { html } from 'lit';
4+
import { ifDefined } from 'lit/directives/if-defined.js';
5+
import { styleMap } from 'lit/directives/style-map.js';
46
import type { Meta, StoryObj } from '@storybook/web-components';
7+
import { useState } from '@storybook/preview-api';
58
import { spread } from '../../../storyhelpers';
9+
import { repeat } from 'lit/directives/repeat.js';
10+
11+
import { colord, HslaColor } from 'colord';
12+
13+
import type { UUIColorSliderElement } from '@umbraco-ui/uui-color-slider/lib';
614

715
const meta: Meta = {
816
id: 'uui-color-slider',
917
component: 'uui-color-slider',
1018
title: 'Inputs/Color/Color Slider',
1119
argTypes: {
1220
type: {
13-
options: ['hue', 'opacity'],
21+
options: ['hue', 'opacity', 'saturation', 'lightness'],
1422
control: { type: 'select' },
1523
},
1624
},
@@ -53,3 +61,173 @@ export const Vertical: Story = {
5361
vertical: true,
5462
},
5563
};
64+
65+
export const Advanced: Story = {
66+
render: () => {
67+
const sliders = [
68+
{
69+
label: 'H',
70+
type: 'hue',
71+
color: '#0075ff',
72+
value: 0,
73+
min: 0,
74+
max: 360,
75+
},
76+
{
77+
label: 'S',
78+
type: 'saturation',
79+
color: '#0075ff',
80+
value: 100,
81+
min: 0,
82+
max: 100,
83+
},
84+
{
85+
label: 'L',
86+
type: 'lightness',
87+
color: '#0075ff',
88+
value: 50,
89+
min: 0,
90+
max: 100,
91+
},
92+
{
93+
label: 'A',
94+
type: 'opacity',
95+
color: '#0075ff',
96+
value: 1,
97+
min: 0,
98+
max: 1,
99+
precision: 2,
100+
},
101+
];
102+
103+
const [value, setValue] = useState({ h: 0, s: 100, l: 50, a: 1 });
104+
105+
function handleSliderChange(e: Event, slider: any) {
106+
e.stopPropagation();
107+
108+
const element = e.target as UUIColorSliderElement;
109+
110+
if (isNaN(element.value)) return;
111+
112+
const newColor: HslaColor = {
113+
h: value.h,
114+
s: value.s,
115+
l: value.l,
116+
a: value.a,
117+
};
118+
119+
if (slider.type === 'hue') {
120+
newColor.h = element.value;
121+
} else if (slider.type === 'saturation') {
122+
newColor.s = element.value;
123+
} else if (slider.type === 'lightness') {
124+
newColor.l = element.value;
125+
} else if (slider.type === 'opacity') {
126+
newColor.a = element.value;
127+
}
128+
129+
slider.value = element.value;
130+
131+
setValue({ h: newColor.h, s: newColor.s, l: newColor.l, a: newColor.a });
132+
}
133+
134+
function handleInputChange(e: Event, slider: any) {
135+
e.stopPropagation();
136+
137+
const input = e.target as HTMLInputElement;
138+
139+
let newValue = parseFloat(input.value);
140+
141+
if (isNaN(newValue)) {
142+
newValue = 0;
143+
input.value = '0';
144+
}
145+
146+
const newColor: HslaColor = {
147+
h: value.h,
148+
s: value.s,
149+
l: value.l,
150+
a: value.a,
151+
};
152+
153+
if (slider.type === 'hue') {
154+
newColor.h = newValue;
155+
} else if (slider.type === 'saturation') {
156+
newColor.s = newValue;
157+
} else if (slider.type === 'lightness') {
158+
newColor.l = newValue;
159+
} else if (slider.type === 'opacity') {
160+
newColor.a = newValue;
161+
}
162+
163+
slider.value = newValue;
164+
165+
setValue({ h: newColor.h, s: newColor.s, l: newColor.l, a: newColor.a });
166+
}
167+
168+
/** Generates a hex string from HSL values. Hue must be 0-360. All other arguments must be 0-100. */
169+
function getHexString(
170+
hue: number,
171+
saturation: number,
172+
lightness: number,
173+
alpha = 100,
174+
) {
175+
const color = colord(
176+
`hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha / 100})`,
177+
);
178+
if (!color.isValid()) {
179+
return '';
180+
}
181+
182+
return color.toHex();
183+
}
184+
185+
return html` <div style="display: flex; gap: 20px;">
186+
<div style="display: flex; flex-direction: column; gap: 10px;">
187+
${repeat(sliders, (slider: any) => {
188+
return html`<div style="display: flex; gap: 10px 20px;">
189+
<label>${slider.label}</label>
190+
<uui-color-slider
191+
.type=${slider.type}
192+
.value=${slider.value}
193+
.min=${slider.min}
194+
.max=${slider.max}
195+
.color=${slider.type !== 'hue'
196+
? getHexString(value.h, value.s, value.l)
197+
: undefined}
198+
?precision=${ifDefined(slider.precision)}
199+
@change=${(e: Event) => handleSliderChange(e, slider)}
200+
style=${styleMap({
201+
'--uui-slider-background-image':
202+
slider.type === 'saturation'
203+
? `linear-gradient(to right, hsl(${value.h}, 0%, ${value.l}%), hsl(${value.h}, 100%, ${value.l}%))`
204+
: slider.type === 'lightness'
205+
? `linear-gradient(to right, hsl(${value.h}, ${value.s}%, 0%), hsl(${value.h}, ${value.s}%, ${slider.value}%))`
206+
: undefined,
207+
width: '400px',
208+
})}>
209+
</uui-color-slider>
210+
<uui-input
211+
type="number"
212+
.min=${slider.min}
213+
.max=${slider.max}
214+
.step=${slider.precision > 1 ? slider.max / 10 : 1}
215+
.value=${slider.value}
216+
@change=${(e: Event) => handleInputChange(e, slider)}
217+
style="width: 60px;">
218+
</uui-input>
219+
</div>`;
220+
})}
221+
</div>
222+
<div
223+
style="width: 100px; height: 100px;
224+
border: 1px solid var(--uui-color-border-standalone);
225+
background-image: linear-gradient(45deg, var(--uui-palette-grey) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--uui-palette-grey) 75%), linear-gradient(45deg, transparent 75%, var(--uui-palette-grey) 75%), linear-gradient(45deg, var(--uui-palette-grey) 25%, transparent 25%);
226+
background-size: 10px 10px;
227+
background-position: 0 0, 0 0, -5px -5px, 5px 5px;">
228+
<div
229+
style="width: 100%; height: 100%; background-color: hsla(${value.h}, ${value.s}%, ${value.l}%, ${value.a});"></div>
230+
</div>
231+
</div>`;
232+
},
233+
};

0 commit comments

Comments
 (0)