Add ColorInput component (#455)
* Add ColorInput component * Use valid color on input blur * Darken input text and add labels
This commit is contained in:
parent
7f6e1f420e
commit
7ae52f1164
@ -7,6 +7,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-picker-control-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.color-picker-triangle-shadow {
|
.color-picker-triangle-shadow {
|
||||||
width: 0px;
|
width: 0px;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
@ -33,13 +38,17 @@
|
|||||||
padding: 15px 9px 9px 15px;
|
padding: 15px 9px 9px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.colors-gallery {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.color-picker-swatch {
|
.color-picker-swatch {
|
||||||
|
position: relative;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
float: left;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0px 6px 6px 0px;
|
margin: 0px 6px 6px 0px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -69,17 +78,20 @@
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
border-radius: 4px 0px 0px 4px;
|
border-radius: 4px 0px 0px 4px;
|
||||||
float: left;
|
color: #495057;
|
||||||
color: #868e96;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-input-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.color-picker-input {
|
.color-picker-input {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #868e96;
|
color: #343a40;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
@ -92,9 +104,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-label-swatch {
|
.color-picker-label-swatch {
|
||||||
height: 24px;
|
height: 30px;
|
||||||
width: 24px;
|
width: 30px;
|
||||||
display: inline-block;
|
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,12 @@ const Picker = function({
|
|||||||
color: string | undefined;
|
color: string | undefined;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [innerValue, setInnerValue] = React.useState(color);
|
|
||||||
React.useEffect(() => {
|
|
||||||
setInnerValue(color);
|
|
||||||
}, [color]);
|
|
||||||
return (
|
return (
|
||||||
<div className="color-picker">
|
<div className="color-picker">
|
||||||
<div className="color-picker-triangle-shadow"></div>
|
<div className="color-picker-triangle-shadow"></div>
|
||||||
<div className="color-picker-triangle"></div>
|
<div className="color-picker-triangle"></div>
|
||||||
<div className="color-picker-content">
|
<div className="color-picker-content">
|
||||||
|
<div className="colors-gallery">
|
||||||
{colors.map(color => (
|
{colors.map(color => (
|
||||||
<div
|
<div
|
||||||
className="color-picker-swatch"
|
className="color-picker-swatch"
|
||||||
@ -42,26 +39,53 @@ const Picker = function({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
<ColorInput
|
||||||
|
color={color}
|
||||||
|
onChange={color => {
|
||||||
|
onChange(color);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function ColorInput({
|
||||||
|
color,
|
||||||
|
onChange
|
||||||
|
}: {
|
||||||
|
color: string | undefined;
|
||||||
|
onChange: (color: string) => void;
|
||||||
|
}) {
|
||||||
|
const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
|
||||||
|
const [innerValue, setInnerValue] = React.useState(color);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setInnerValue(color);
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="color-input-container">
|
||||||
<div className="color-picker-hash">#</div>
|
<div className="color-picker-hash">#</div>
|
||||||
<div style={{ position: "relative" }}>
|
|
||||||
<input
|
<input
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
className="color-picker-input"
|
className="color-picker-input"
|
||||||
|
aria-label="Hex color code"
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
if (value.match(/^([0-9a-f]{3}|[0-9a-f]{6}|transparent)$/)) {
|
if (value.match(colorRegex)) {
|
||||||
onChange(value === "transparent" ? "transparent" : "#" + value);
|
onChange(value === "transparent" ? "transparent" : "#" + value);
|
||||||
}
|
}
|
||||||
setInnerValue(value);
|
setInnerValue(value);
|
||||||
}}
|
}}
|
||||||
value={(innerValue || "").replace(/^#/, "")}
|
value={(innerValue || "").replace(/^#/, "")}
|
||||||
|
onPaste={e => onChange(e.clipboardData.getData("text"))}
|
||||||
|
onBlur={() => setInnerValue(color)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ clear: "both" }}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export function ColorPicker({
|
export function ColorPicker({
|
||||||
type,
|
type,
|
||||||
@ -69,17 +93,27 @@ export function ColorPicker({
|
|||||||
onChange
|
onChange
|
||||||
}: {
|
}: {
|
||||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||||
color: string | null;
|
color: string | undefined;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [isActive, setActive] = React.useState(false);
|
const [isActive, setActive] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className="color-picker-control-container">
|
||||||
<button
|
<button
|
||||||
className="color-picker-label-swatch"
|
className="color-picker-label-swatch"
|
||||||
|
aria-label="Change color"
|
||||||
style={color ? { backgroundColor: color } : undefined}
|
style={color ? { backgroundColor: color } : undefined}
|
||||||
onClick={() => setActive(!isActive)}
|
onClick={() => setActive(!isActive)}
|
||||||
/>
|
/>
|
||||||
|
<ColorInput
|
||||||
|
color={color}
|
||||||
|
onChange={color => {
|
||||||
|
onChange(color);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<React.Suspense fallback="">
|
<React.Suspense fallback="">
|
||||||
{isActive ? (
|
{isActive ? (
|
||||||
<Popover onCloseRequest={() => setActive(false)}>
|
<Popover onCloseRequest={() => setActive(false)}>
|
||||||
@ -93,13 +127,6 @@ export function ColorPicker({
|
|||||||
</Popover>
|
</Popover>
|
||||||
) : null}
|
) : null}
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="color-picker-swatch-input"
|
|
||||||
value={color || ""}
|
|
||||||
onPaste={e => onChange(e.clipboardData.getData("text"))}
|
|
||||||
onChange={e => onChange(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user