mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 12:09:26 +00:00
Color picker progress
This commit is contained in:
33
package-lock.json
generated
33
package-lock.json
generated
@@ -355,6 +355,23 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ansi-wrap": {
|
||||
@@ -1017,19 +1034,17 @@
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-support": {
|
||||
"version": "1.1.3",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@popperjs/core": "^2.1.1",
|
||||
"@stencil/core": "^1.12.6",
|
||||
"@stencil/sass": "^1.1.1",
|
||||
"color-convert": "^2.0.1",
|
||||
"feather-icons": "^4.28.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
|
||||
@@ -11,19 +11,17 @@
|
||||
|
||||
.sl-color-picker__menu {
|
||||
// position: absolute;
|
||||
max-height: 50vh;
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: var(--sl-font-size-medium);
|
||||
font-weight: var(--sl-font-weight-normal);
|
||||
color: var(--color);
|
||||
background-color: var(--sl-color-white);
|
||||
border: solid 1px var(--sl-color-gray-90);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--sl-border-radius-medium);
|
||||
box-shadow: var(--sl-shadow-large);
|
||||
padding-top: var(--sl-spacing-x-small);
|
||||
padding-bottom: var(--sl-spacing-x-small);
|
||||
// opacity: 0;
|
||||
transition: var(--sl-transition-fast) opacity;
|
||||
user-select: none;
|
||||
|
||||
// &[hidden] {
|
||||
// display: none;
|
||||
@@ -38,7 +36,8 @@
|
||||
position: relative;
|
||||
width: var(--grid-width);
|
||||
height: var(--grid-height);
|
||||
margin-bottom: 8px;
|
||||
border-top-left-radius: var(--sl-border-radius-medium);
|
||||
border-top-right-radius: var(--sl-border-radius-medium);
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
@@ -49,6 +48,8 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(to right, rgb(255, 255, 255), rgba(255, 255, 255, 0));
|
||||
border-top-left-radius: calc(var(--sl-border-radius-medium) - 1px);
|
||||
border-top-right-radius: calc(var(--sl-border-radius-medium) - 1px);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@@ -58,6 +59,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(to top, rgb(0, 0, 0), rgba(0, 0, 0, 0));
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,17 +68,47 @@
|
||||
width: var(--sight-size);
|
||||
height: var(--sight-size);
|
||||
border-radius: 50%;
|
||||
border: solid 1px rgba(0, 0, 0, 0.125);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.125), inset 0 0 0 1px rgba(0, 0, 0, 0.125);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25), inset 0 0 0 1px rgba(0, 0, 0, 0.25);
|
||||
border: solid 2px white;
|
||||
margin-top: calc(var(--sight-size) / -2);
|
||||
margin-left: calc(var(--sight-size) / -2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sl-color-picker__hue {
|
||||
.sl-color-picker__controls {
|
||||
padding: var(--sl-spacing-small);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sl-color-picker__sliders {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.sl-color-picker__slider {
|
||||
position: relative;
|
||||
height: 12px;
|
||||
height: 10px;
|
||||
border-radius: var(--sl-border-radius-pill);
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: var(--sl-spacing-small);
|
||||
}
|
||||
}
|
||||
|
||||
.sl-color-picker__slider-handle {
|
||||
position: absolute;
|
||||
top: calc(50% - var(--slider-size) / 2);
|
||||
width: var(--slider-size);
|
||||
height: var(--slider-size);
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
|
||||
margin-left: calc(var(--slider-size) / -2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sl-color-picker__hue {
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
rgb(255, 0, 0) 0%,
|
||||
@@ -87,18 +119,13 @@
|
||||
rgb(255, 0, 255) 83%,
|
||||
rgb(255, 0, 0) 100%
|
||||
);
|
||||
margin-bottom: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sl-color-picker__alpha {
|
||||
position: relative;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #eee 75%),
|
||||
linear-gradient(45deg, transparent 75%, #eee 75%), linear-gradient(45deg, #eee 25%, transparent 25%);
|
||||
background-size: 12px 12px;
|
||||
background-position: 0 0, 0 0, -6px -6px, 6px 6px;
|
||||
background-size: 10px 10px;
|
||||
background-position: 0 0, 0 0, -5px -5px, 5px 5px;
|
||||
|
||||
.sl-color-picker__alpha-gradient {
|
||||
position: absolute;
|
||||
@@ -106,32 +133,63 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.sl-color-picker__slider {
|
||||
position: absolute;
|
||||
top: calc(50% - var(--slider-size) / 2);
|
||||
width: var(--slider-size);
|
||||
height: var(--slider-size);
|
||||
background-color: white;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
.sl-color-picker__preview {
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #eee 75%),
|
||||
linear-gradient(45deg, transparent 75%, #eee 75%), linear-gradient(45deg, #eee 25%, transparent 25%);
|
||||
background-size: 10px 10px;
|
||||
background-position: 0 0, 0 0, -5px -5px, 5px 5px;
|
||||
border-radius: 50%;
|
||||
margin-left: calc(var(--slider-size) / -2);
|
||||
cursor: pointer;
|
||||
margin-left: var(--sl-spacing-medium);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background-color: currentColor;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.sl-color-picker__preview-color {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
border: solid 1px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.sl-color-picker__input {
|
||||
padding: 0 var(--sl-spacing-small) var(--sl-spacing-small) var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
.sl-color-picker__swatches {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
grid-gap: 6px;
|
||||
justify-items: center;
|
||||
border-top: solid 1px var(--sl-color-gray-90);
|
||||
padding: var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
.sl-color-picker__swatch {
|
||||
flex: 0 0 auto;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
border: solid 1px rgba(0, 0, 0, 0.125);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Component, h } from '@stencil/core';
|
||||
import { Component, State, h } from '@stencil/core';
|
||||
import convert from 'color-convert';
|
||||
import { clamp } from '../../utilities/math';
|
||||
|
||||
@Component({
|
||||
tag: 'sl-color-picker',
|
||||
@@ -9,87 +11,183 @@ export class ColorPicker {
|
||||
menu: HTMLElement;
|
||||
trigger: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.handleHueInput = this.handleHueInput.bind(this);
|
||||
this.handleSaturationInput = this.handleSaturationInput.bind(this);
|
||||
this.handleLightnessInput = this.handleLightnessInput.bind(this);
|
||||
this.handleOpacityInput = this.handleOpacityInput.bind(this);
|
||||
}
|
||||
|
||||
@State() hue = 0;
|
||||
@State() saturation = 100;
|
||||
@State() lightness = 50;
|
||||
@State() opacity = 100;
|
||||
|
||||
handleHueInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.hue = clamp(Number(target.value), 0, 360);
|
||||
}
|
||||
|
||||
handleSaturationInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.saturation = clamp(Number(target.value), 0, 100);
|
||||
}
|
||||
|
||||
handleLightnessInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.lightness = clamp(Number(target.value), 0, 100);
|
||||
}
|
||||
|
||||
handleOpacityInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.opacity = clamp(Number(target.value), 0, 100);
|
||||
}
|
||||
|
||||
render() {
|
||||
const hsl = [this.hue, this.saturation, this.lightness];
|
||||
const rgb = convert.hsl.rgb(hsl);
|
||||
const hex = convert.hsl.hex(hsl);
|
||||
|
||||
// const x = clamp(this.saturation, 0, 100);
|
||||
// const y = 100 - this.lightness * 100;
|
||||
|
||||
const x = Math.abs((this.saturation * 260) / 100);
|
||||
const y = Math.abs((220 - this.lightness * 220) / 100);
|
||||
|
||||
// console.log(x / 100, y / 100);
|
||||
|
||||
return (
|
||||
<div ref={el => (this.trigger = el)} class="sl-color-picker">
|
||||
<div class="sl-color-picker__trigger">Trigger</div>
|
||||
|
||||
<div ref={el => (this.menu = el)} class="sl-color-picker__menu">
|
||||
<div class="sl-color-picker__grid" style={{ backgroundColor: 'red' }}>
|
||||
<div
|
||||
class="sl-color-picker__grid"
|
||||
style={{
|
||||
backgroundColor: `hsl(${this.hue}deg, 100%, 50%)`
|
||||
}}
|
||||
>
|
||||
<div class="sl-color-picker__grid-gradient" />
|
||||
<span
|
||||
class="sl-color-picker__sight"
|
||||
style={{
|
||||
transform: `translate(100px, 100px)`
|
||||
top: `${y}px`, // TODO: %
|
||||
left: `${x}px` // TODO: %
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__hue">
|
||||
<span
|
||||
class="sl-color-picker__slider"
|
||||
style={{
|
||||
transform: 'translateX(20px)'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="sl-color-picker__controls">
|
||||
<div class="sl-color-picker__sliders">
|
||||
<div class="sl-color-picker__hue sl-color-picker__slider">
|
||||
<span
|
||||
class="sl-color-picker__slider-handle"
|
||||
style={{
|
||||
left: `${this.hue === 0 ? 0 : 100 / (360 / this.hue)}%`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__alpha sl-color-picker__slider">
|
||||
<div
|
||||
class="sl-color-picker__alpha-gradient"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(
|
||||
to right,
|
||||
hsl(${this.hue}deg, ${this.saturation}%, ${this.lightness}%, 0%) 0%,
|
||||
hsl(${this.hue}deg, ${this.saturation}%, ${this.lightness}%) 100%
|
||||
)`
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
class="sl-color-picker__slider-handle"
|
||||
style={{
|
||||
left: `${this.opacity}%`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__alpha">
|
||||
<div
|
||||
class="sl-color-picker__alpha-gradient"
|
||||
class="sl-color-picker__preview"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(to right, rgba(255, 0, 0, 0) 0%, rgb(255, 0, 0) 100%)`
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
class="sl-color-picker__slider"
|
||||
style={{
|
||||
transform: 'translateX(60px)'
|
||||
color: `hsla(${this.hue}deg, ${this.saturation}%, ${this.lightness}%, ${this.opacity}%)`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__preview"></div>
|
||||
|
||||
<div class="sl-color-picker__inputs">
|
||||
<div class="sl-color-picker__input sl-color-picker__input--hex">
|
||||
<label>Hex</label>
|
||||
<input type="text" pattern="[a-fA-F\d]+" />
|
||||
<sl-input size="small" type="text" pattern="[a-fA-F\d]+" value={hex}>
|
||||
<span slot="prefix">#</span>
|
||||
</sl-input>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgba">
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgb">
|
||||
<label>R</label>
|
||||
<input type="number" min="0" max="255" />
|
||||
<sl-input size="small" type="number" min={0} max={255} inputmode="numeric" value={rgb[0]} />
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgba">
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgb">
|
||||
<label>G</label>
|
||||
<input type="number" min="0" max="255" />
|
||||
<sl-input size="small" type="number" min={0} max={255} inputmode="numeric" value={rgb[1]} />
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgba">
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgb">
|
||||
<label>B</label>
|
||||
<input type="number" min="0" max="255" />
|
||||
<sl-input size="small" type="number" min={0} max={255} inputmode="numeric" value={rgb[2]} />
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--hsl">
|
||||
<label>H</label>
|
||||
<input type="number" min="0" max="255" />
|
||||
<sl-input
|
||||
size="small"
|
||||
type="number"
|
||||
min={0}
|
||||
max={360}
|
||||
inputmode="numeric"
|
||||
value={this.hue.toString()}
|
||||
onInput={this.handleHueInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--hsl">
|
||||
<label>S</label>
|
||||
<input type="number" min="0" max="255" />
|
||||
<sl-input
|
||||
size="small"
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
inputmode="numeric"
|
||||
value={this.saturation.toString()}
|
||||
onInput={this.handleSaturationInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--hsl">
|
||||
<label>L</label>
|
||||
<input type="number" min="0" max="255" />
|
||||
<sl-input
|
||||
size="small"
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
inputmode="numeric"
|
||||
value={this.lightness.toString()}
|
||||
onInput={this.handleLightnessInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sl-color-picker__input sl-color-picker__input--rgba">
|
||||
<div class="sl-color-picker__input sl-color-picker__input--alpha">
|
||||
<label>A</label>
|
||||
<input type="number" min="0" max="100" />
|
||||
<sl-input
|
||||
size="small"
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
inputmode="numeric"
|
||||
value={this.opacity.toString()}
|
||||
onInput={this.handleOpacityInput}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,20 @@
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Depends on
|
||||
|
||||
- [sl-input](../input)
|
||||
|
||||
### Graph
|
||||
```mermaid
|
||||
graph TD;
|
||||
sl-color-picker --> sl-input
|
||||
sl-input --> sl-icon
|
||||
style sl-color-picker fill:#f9f,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export class Input {
|
||||
@Prop() name = '';
|
||||
|
||||
/** The input's value attribute. */
|
||||
@Prop({ mutable: true }) value = '';
|
||||
@Prop({ mutable: true }) value: string = '';
|
||||
|
||||
/** The input's placeholder text. */
|
||||
@Prop() placeholder: string;
|
||||
|
||||
@@ -200,6 +200,10 @@ Type: `Promise<void>`
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Used by
|
||||
|
||||
- [sl-color-picker](../color-picker)
|
||||
|
||||
### Depends on
|
||||
|
||||
- [sl-icon](../icon)
|
||||
@@ -208,6 +212,7 @@ Type: `Promise<void>`
|
||||
```mermaid
|
||||
graph TD;
|
||||
sl-input --> sl-icon
|
||||
sl-color-picker --> sl-input
|
||||
style sl-input fill:#f9f,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
|
||||
8
src/utilities/math.ts
Normal file
8
src/utilities/math.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
//
|
||||
// Ensures a number stays within a minimum and maximum value
|
||||
//
|
||||
export function clamp(value: number, min: number, max: number) {
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
Reference in New Issue
Block a user