From 56784c94f95b16a7d92ff149406d537708bf4926 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Thu, 7 May 2020 08:14:21 -0400 Subject: [PATCH] Color picker progress --- package-lock.json | 33 +++- package.json | 1 + src/components/color-picker/color-picker.scss | 114 +++++++++--- src/components/color-picker/color-picker.tsx | 168 ++++++++++++++---- src/components/color-picker/readme.md | 14 ++ src/components/input/input.tsx | 2 +- src/components/input/readme.md | 5 + src/utilities/math.ts | 8 + 8 files changed, 272 insertions(+), 73 deletions(-) create mode 100644 src/utilities/math.ts diff --git a/package-lock.json b/package-lock.json index 44dc66843..1a9ec66e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 32be6c541..f2f8fdcc8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/color-picker/color-picker.scss b/src/components/color-picker/color-picker.scss index 2c0e0a8c1..da1a25045 100644 --- a/src/components/color-picker/color-picker.scss +++ b/src/components/color-picker/color-picker.scss @@ -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; } diff --git a/src/components/color-picker/color-picker.tsx b/src/components/color-picker/color-picker.tsx index 5677ca7f8..39d56e88b 100644 --- a/src/components/color-picker/color-picker.tsx +++ b/src/components/color-picker/color-picker.tsx @@ -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 (
(this.trigger = el)} class="sl-color-picker">
Trigger
-
(this.menu = el)} class="sl-color-picker__menu"> -
+
-
- -
+
+
+
+ +
+ +
+
+ +
+
-
-
-
-
- + + # +
-
+
- +
-
+
- +
-
+
- +
- +
- +
- +
-
+
- +
diff --git a/src/components/color-picker/readme.md b/src/components/color-picker/readme.md index 2d1edad45..9de074cff 100644 --- a/src/components/color-picker/readme.md +++ b/src/components/color-picker/readme.md @@ -8,6 +8,20 @@ +## 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 +``` + ---------------------------------------------- diff --git a/src/components/input/input.tsx b/src/components/input/input.tsx index b630cd676..76d53817c 100644 --- a/src/components/input/input.tsx +++ b/src/components/input/input.tsx @@ -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; diff --git a/src/components/input/readme.md b/src/components/input/readme.md index 736ae59e2..8f2a87532 100644 --- a/src/components/input/readme.md +++ b/src/components/input/readme.md @@ -200,6 +200,10 @@ Type: `Promise` ## Dependencies +### Used by + + - [sl-color-picker](../color-picker) + ### Depends on - [sl-icon](../icon) @@ -208,6 +212,7 @@ Type: `Promise` ```mermaid graph TD; sl-input --> sl-icon + sl-color-picker --> sl-input style sl-input fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/utilities/math.ts b/src/utilities/math.ts new file mode 100644 index 000000000..4ad34c6f8 --- /dev/null +++ b/src/utilities/math.ts @@ -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; +}