mirror of
https://github.com/shoelace-style/shoelace.git
synced 2026-01-12 11:09:13 +00:00
Add format toggle and update styles
This commit is contained in:
@@ -15,12 +15,14 @@ Color pickers allow the user to select a color.
|
||||
Use the `opacity` attribute to enable the opacity slider. When this is enabled, the value will be displayed as HEXA, RGBA, or HSLA based on `format`.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker opacity format="hsl"></sl-color-picker>
|
||||
<sl-color-picker opacity></sl-color-picker>
|
||||
```
|
||||
|
||||
### Formats
|
||||
|
||||
Set the color picker's format with the `format` attribute. Valid options include `hex`, `rgb`, and `hsl`. Note that the color picker will accept any parsable format (including CSS color names) regardless of this option.
|
||||
Set the color picker's format with the `format` attribute. Valid options include `hex`, `rgb`, and `hsl`. Note that the color picker's input will accept any parsable format (including CSS color names) regardless of this option.
|
||||
|
||||
To prevent users from toggling the format themselves, add the `no-toggle` attribute.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker format="hex" value="#4a90e2"></sl-color-picker>
|
||||
@@ -28,6 +30,17 @@ Set the color picker's format with the `format` attribute. Valid options include
|
||||
<sl-color-picker format="hsl" value="hsl(290, 87%, 47%)"></sl-color-picker>
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Use the `size` attribute to change the color picker's trigger size.
|
||||
|
||||
```html preview
|
||||
<sl-color-picker size="small"></sl-color-picker>
|
||||
<sl-color-picker size="medium"></sl-color-picker>
|
||||
<sl-color-picker size="large"></sl-color-picker>
|
||||
```
|
||||
|
||||
|
||||
### Inline
|
||||
|
||||
The color picker can be rendered inline instead of in a dropdown using the `inline` attribute.
|
||||
|
||||
@@ -8,6 +8,14 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
|
||||
|
||||
## Next
|
||||
|
||||
- 🚨 BREAKING CHANGE: Refactored `sl-select` to improve accessibility [#216](https://github.com/shoelace-style/shoelace/issues/216)
|
||||
- Removed the internal `sl-input` because it was causing problems with a11y and virtual keyboards
|
||||
- Removed `input`, `prefix` and `suffix` parts
|
||||
- 🚨 BREAKING CHANGE: Removed `copy-button` part from `sl-color-picker` since copying is now done by clicking the preview
|
||||
- Added `getFormattedValue()` method to `sl-color-picker` so you can retrieve the current value in any format
|
||||
- Fixed a bug where moving the mouse while `sl-dropdown` is closing would remove focus from the trigger
|
||||
- Fixed a bug where `sl-menu-item` didn't set a default color set in the dark theme
|
||||
- Fixed a bug where `sl-color-picker` preview wouldn't update in Safari
|
||||
- Removed `sl-blur` and `sl-focus` events from `sl-menu` since menus can't have focus since 2.0.0-beta.22
|
||||
|
||||
## 2.0.0-beta.24
|
||||
|
||||
16
src/components.d.ts
vendored
16
src/components.d.ts
vendored
@@ -268,6 +268,10 @@ export namespace Components {
|
||||
* The format to use for the display value. If opacity is enabled, these will translate to HEXA, RGBA, and HSLA respectively. The color picker will always accept user input in any format (including CSS color names) and convert it to the desired format.
|
||||
*/
|
||||
"format": 'hex' | 'rgb' | 'hsl';
|
||||
/**
|
||||
* Returns the current value as a string in the specified format.
|
||||
*/
|
||||
"getFormattedValue": (format?: 'hex' | 'hexa' | 'rgb' | 'rgba' | 'hsl' | 'hsla') => Promise<string>;
|
||||
/**
|
||||
* Enable this option to prevent the panel from being clipped when the component is placed inside a container with `overflow: auto|scroll`.
|
||||
*/
|
||||
@@ -284,6 +288,10 @@ export namespace Components {
|
||||
* The input's name attribute.
|
||||
*/
|
||||
"name": string;
|
||||
/**
|
||||
* Removes the format toggle.
|
||||
*/
|
||||
"noToggle": boolean;
|
||||
/**
|
||||
* Whether to show the opacity slider.
|
||||
*/
|
||||
@@ -1915,6 +1923,10 @@ declare namespace LocalJSX {
|
||||
* The input's name attribute.
|
||||
*/
|
||||
"name"?: string;
|
||||
/**
|
||||
* Removes the format toggle.
|
||||
*/
|
||||
"noToggle"?: boolean;
|
||||
/**
|
||||
* Emitted after the color picker closes and all transitions are complete.
|
||||
*/
|
||||
@@ -2689,7 +2701,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"name"?: string;
|
||||
/**
|
||||
* Emitted when the control loses focus
|
||||
* Emitted when the control loses focus.
|
||||
*/
|
||||
"onSl-blur"?: (event: CustomEvent<any>) => void;
|
||||
/**
|
||||
@@ -2697,7 +2709,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"onSl-change"?: (event: CustomEvent<any>) => void;
|
||||
/**
|
||||
* Emitted when the control gains focus
|
||||
* Emitted when the control gains focus.
|
||||
*/
|
||||
"onSl-focus"?: (event: CustomEvent<any>) => void;
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
--grid-height: 200px;
|
||||
--grid-handle-size: 16px;
|
||||
--slider-height: 12px;
|
||||
--slider-handle-size: 16px;
|
||||
--slider-handle-size: 14px;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -131,11 +131,17 @@
|
||||
|
||||
.color-picker__preview {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: var(--sl-input-height-small);
|
||||
width: 3.125rem;
|
||||
height: var(--sl-input-height-small);
|
||||
border-radius: 50%;
|
||||
margin-left: var(--sl-spacing-medium);
|
||||
border: none;
|
||||
border-radius: var(--sl-input-border-radius-small);
|
||||
background: none;
|
||||
margin-left: var(--sl-spacing-small);
|
||||
cursor: copy;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -145,8 +151,15 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background-color: currentColor;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
|
||||
// We use a custom property in lieu of currentColor because of https://bugs.webkit.org/show_bug.cgi?id=216780
|
||||
background-color: var(--preview-color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: var(--sl-focus-ring-box-shadow);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,10 +169,44 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
border: solid 1px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.color-picker__copy-feedback {
|
||||
width: calc(var(--sl-input-height-small) / 2);
|
||||
height: calc(var(--sl-input-height-small) / 2);
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.33);
|
||||
border-radius: var(--sl-border-radius-circle);
|
||||
opacity: 0;
|
||||
|
||||
&.color-picker__copy-feedback--visible {
|
||||
animation: copied 1s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes copied {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.6);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker__user-input {
|
||||
display: flex;
|
||||
padding: 0 var(--sl-spacing-small) var(--sl-spacing-small) var(--sl-spacing-small);
|
||||
@@ -170,15 +217,11 @@
|
||||
}
|
||||
|
||||
sl-button {
|
||||
flex: 0 0 auto;
|
||||
margin-left: var(--sl-spacing-medium);
|
||||
width: 3.125rem;
|
||||
margin-left: var(--sl-spacing-small);
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker__copy-button {
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
.color-picker__swatches {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
@@ -254,19 +297,19 @@
|
||||
&.color-dropdown__trigger--small {
|
||||
width: var(--sl-input-height-small);
|
||||
height: var(--sl-input-height-small);
|
||||
border-radius: var(--sl-border-radius-circle);
|
||||
border-radius: var(--sl-input-border-radius-small);
|
||||
}
|
||||
|
||||
&.color-dropdown__trigger--medium {
|
||||
width: var(--sl-input-height-medium);
|
||||
height: var(--sl-input-height-medium);
|
||||
border-radius: var(--sl-border-radius-circle);
|
||||
border-radius: var(--sl-input-border-radius-medium);
|
||||
}
|
||||
|
||||
&.color-dropdown__trigger--large {
|
||||
width: var(--sl-input-height-large);
|
||||
height: var(--sl-input-height-large);
|
||||
border-radius: var(--sl-border-radius-circle);
|
||||
border-radius: var(--sl-input-border-radius-large);
|
||||
}
|
||||
|
||||
&::before {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { clamp } from '../../utilities/math';
|
||||
*
|
||||
* @part base - The component's base wrapper.
|
||||
* @part trigger - The color picker's dropdown trigger.
|
||||
* @part copy-button - The copy button.
|
||||
* @part swatches - The container that holds swatches.
|
||||
* @part swatch - Each individual swatch.
|
||||
* @part grid - The color grid.
|
||||
@@ -19,7 +18,7 @@ import { clamp } from '../../utilities/math';
|
||||
* @part slider-handle - Hue and opacity slider handles.
|
||||
* @part preview - The preview color.
|
||||
* @part input - The text input.
|
||||
* @part copy-button - The copy button.
|
||||
* @part format-button - The toggle format button.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
@@ -29,11 +28,11 @@ import { clamp } from '../../utilities/math';
|
||||
})
|
||||
export class ColorPicker {
|
||||
bypassValueParse = false;
|
||||
copyButton: HTMLSlButtonElement;
|
||||
dropdown: HTMLSlDropdownElement;
|
||||
input: HTMLSlInputElement;
|
||||
lastValueEmitted: string;
|
||||
menu: HTMLElement;
|
||||
input: HTMLSlInputElement;
|
||||
previewButton: HTMLButtonElement;
|
||||
trigger: HTMLButtonElement;
|
||||
|
||||
@Element() host: HTMLSlColorPickerElement;
|
||||
@@ -43,7 +42,7 @@ export class ColorPicker {
|
||||
@State() saturation = 100;
|
||||
@State() lightness = 100;
|
||||
@State() alpha = 100;
|
||||
@State() showCopyCheckmark = false;
|
||||
@State() showCopyFeedback = false;
|
||||
|
||||
/** The current color. */
|
||||
@Prop({ mutable: true, reflect: true }) value = '#ffffff';
|
||||
@@ -53,7 +52,7 @@ export class ColorPicker {
|
||||
* respectively. The color picker will always accept user input in any format (including CSS color names) and convert
|
||||
* it to the desired format.
|
||||
*/
|
||||
@Prop() format: 'hex' | 'rgb' | 'hsl' = 'hex';
|
||||
@Prop({ mutable: true }) format: 'hex' | 'rgb' | 'hsl' = 'hex';
|
||||
|
||||
/** Set to true to render the color picker inline rather than inside a dropdown. */
|
||||
@Prop() inline = false;
|
||||
@@ -61,6 +60,9 @@ export class ColorPicker {
|
||||
/** Determines the size of the color picker's trigger. This has no effect on inline color pickers. */
|
||||
@Prop() size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
/** Removes the format toggle. */
|
||||
@Prop() noToggle = false;
|
||||
|
||||
/** The input's name attribute. */
|
||||
@Prop({ reflect: true }) name = '';
|
||||
|
||||
@@ -123,6 +125,16 @@ export class ColorPicker {
|
||||
/** Emitted after the color picker closes and all transitions are complete. */
|
||||
@Event({ eventName: 'sl-after-hide' }) slAfterHide: EventEmitter;
|
||||
|
||||
@Watch('format')
|
||||
handleFormatChange() {
|
||||
this.syncValues();
|
||||
}
|
||||
|
||||
@Watch('opacity')
|
||||
handleOpacityChange() {
|
||||
this.alpha = 100;
|
||||
}
|
||||
|
||||
@Watch('value')
|
||||
handleValueChange(newValue: string, oldValue: string) {
|
||||
if (!this.bypassValueParse) {
|
||||
@@ -150,6 +162,7 @@ export class ColorPicker {
|
||||
this.handleAlphaInput = this.handleAlphaInput.bind(this);
|
||||
this.handleAlphaKeyDown = this.handleAlphaKeyDown.bind(this);
|
||||
this.handleCopy = this.handleCopy.bind(this);
|
||||
this.handleFormatToggle = this.handleFormatToggle.bind(this);
|
||||
this.handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
|
||||
this.handleDrag = this.handleDrag.bind(this);
|
||||
this.handleDropdownAfterHide = this.handleDropdownAfterHide.bind(this);
|
||||
@@ -177,6 +190,35 @@ export class ColorPicker {
|
||||
this.syncValues();
|
||||
}
|
||||
|
||||
/** Returns the current value as a string in the specified format. */
|
||||
@Method()
|
||||
async getFormattedValue(format: 'hex' | 'hexa' | 'rgb' | 'rgba' | 'hsl' | 'hsla' = 'hex') {
|
||||
const currentColor = this.parseColor(
|
||||
`hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha / 100})`
|
||||
);
|
||||
|
||||
if (!currentColor) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case 'hex':
|
||||
return currentColor.hex;
|
||||
case 'hexa':
|
||||
return currentColor.hexa;
|
||||
case 'rgb':
|
||||
return currentColor.rgb.string;
|
||||
case 'rgba':
|
||||
return currentColor.rgba.string;
|
||||
case 'hsl':
|
||||
return currentColor.hsl.string;
|
||||
case 'hsla':
|
||||
return currentColor.hsla.string;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
||||
@Method()
|
||||
async reportValidity() {
|
||||
@@ -208,12 +250,18 @@ export class ColorPicker {
|
||||
handleCopy() {
|
||||
this.input.select().then(() => {
|
||||
document.execCommand('copy');
|
||||
this.copyButton.setFocus();
|
||||
this.showCopyCheckmark = true;
|
||||
setTimeout(() => (this.showCopyCheckmark = false), 1000);
|
||||
this.previewButton.focus();
|
||||
this.showCopyFeedback = true;
|
||||
this.previewButton.addEventListener('animationend', () => (this.showCopyFeedback = false), { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
handleFormatToggle() {
|
||||
const formats = ['hex', 'rgb', 'hsl'];
|
||||
const nextIndex = (formats.indexOf(this.format) + 1) % formats.length;
|
||||
this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl';
|
||||
}
|
||||
|
||||
handleHueInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.hue = clamp(Number(target.value), 0, 360);
|
||||
@@ -435,6 +483,7 @@ export class ColorPicker {
|
||||
handleDropdownAfterHide(event: CustomEvent) {
|
||||
event.stopPropagation();
|
||||
this.slAfterHide.emit();
|
||||
this.showCopyFeedback = false;
|
||||
}
|
||||
|
||||
normalizeColorString(colorString: string) {
|
||||
@@ -582,6 +631,7 @@ export class ColorPicker {
|
||||
}
|
||||
|
||||
setLetterCase(string: string) {
|
||||
if (typeof string !== 'string') return '';
|
||||
return this.uppercase ? string.toUpperCase() : string.toLowerCase();
|
||||
}
|
||||
|
||||
@@ -713,13 +763,25 @@ export class ColorPicker {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
<button
|
||||
ref={el => (this.previewButton = el)}
|
||||
type="button"
|
||||
part="preview"
|
||||
class="color-picker__preview color-picker__transparent-bg"
|
||||
style={{
|
||||
color: `hsla(${this.hue}deg, ${this.saturation}%, ${this.lightness}%, ${this.alpha / 100})`
|
||||
'--preview-color': `hsla(${this.hue}deg, ${this.saturation}%, ${this.lightness}%, ${this.alpha / 100})`
|
||||
}}
|
||||
/>
|
||||
onClick={this.handleCopy}
|
||||
>
|
||||
<sl-icon
|
||||
name="check"
|
||||
class={{
|
||||
'color-picker__copy-feedback': true,
|
||||
'color-picker__copy-feedback--visible': this.showCopyFeedback,
|
||||
'color-picker__copy-feedback--dark': this.lightness > 50
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="color-picker__user-input">
|
||||
@@ -738,17 +800,12 @@ export class ColorPicker {
|
||||
onKeyDown={this.handleInputKeyDown}
|
||||
onSl-change={this.handleInputChange}
|
||||
/>
|
||||
<sl-button
|
||||
ref={el => (this.copyButton = el)}
|
||||
exportparts="base:copy-button"
|
||||
slot="suffix"
|
||||
class="color-picker__copy-button"
|
||||
size="small"
|
||||
circle
|
||||
onClick={this.handleCopy}
|
||||
>
|
||||
<sl-icon name={this.showCopyCheckmark ? 'check2' : 'clipboard'} />
|
||||
</sl-button>
|
||||
|
||||
{!this.noToggle && (
|
||||
<sl-button part="format-button" size="small" onClick={this.handleFormatToggle}>
|
||||
{this.setLetterCase(this.format)}
|
||||
</sl-button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{this.swatches && (
|
||||
|
||||
Reference in New Issue
Block a user