Skip to content

Commit 4f54ebf

Browse files
committed
add ColorField component
1 parent bb814f4 commit 4f54ebf

6 files changed

Lines changed: 261 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1616
- `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly`
1717
- `<RadioButton />`
1818
- `hideIndicator` property: hide the radio inout indicator but click on children can be processed via `onChange` event
19+
- `<ColorField />`
20+
- input component for colors, uses the configured palette by default but it also allows to enter custom colors
1921

2022
### Fixed
2123

src/common/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { decode } from "he";
33
import { invisibleZeroWidthCharacters } from "./utils/characters";
44
import { colorCalculateDistance } from "./utils/colorCalculateDistance";
55
import decideContrastColorValue from "./utils/colorDecideContrastvalue";
6-
import { getEnabledColorsFromPalette, textToColorHash } from "./utils/colorHash";
6+
import { getEnabledColorPropertiesFromPalette, getEnabledColorsFromPalette, textToColorHash } from "./utils/colorHash";
77
import getColorConfiguration from "./utils/getColorConfiguration";
88
import { getScrollParent } from "./utils/getScrollParent";
99
import { getGlobalVar, setGlobalVar } from "./utils/globalVars";
@@ -22,6 +22,7 @@ export const utils = {
2222
setGlobalVar,
2323
getScrollParent,
2424
getEnabledColorsFromPalette,
25+
getEnabledColorPropertiesFromPalette,
2526
textToColorHash,
2627
reduceToText,
2728
decodeHtmlEntities: decode,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react";
2+
import { Meta, StoryFn } from "@storybook/react";
3+
4+
import textFieldTest from "../TextField/stories/TextField.stories";
5+
6+
import { ColorField } from "./ColorField";
7+
8+
export default {
9+
title: "Forms/ColorField",
10+
component: ColorField,
11+
argTypes: {
12+
...textFieldTest.argTypes,
13+
},
14+
} as Meta<typeof ColorField>;
15+
16+
const Template: StoryFn<typeof ColorField> = (args) => <ColorField {...args}></ColorField>;
17+
18+
export const Default = Template.bind({});
19+
Default.args = {
20+
onChange: (e) => {
21+
alert(e.target.value);
22+
},
23+
};
24+
25+
export const NoPalettePresets = Template.bind({});
26+
NoPalettePresets.args = {
27+
colorWeightFilter: [],
28+
paletteGroupFilter: [],
29+
allowCustomColor: true,
30+
onChange: (e) => {
31+
alert(e.target.value);
32+
},
33+
};
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import React, { CSSProperties } from "react";
2+
import classNames from "classnames";
3+
4+
import { utils } from "../../common";
5+
import { ColorWeight, PaletteGroup } from "../../common/utils/colorHash";
6+
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
7+
import { ContextOverlay } from "../ContextOverlay";
8+
import { FieldSet } from "../Form";
9+
import { RadioButton } from "../RadioButton/RadioButton";
10+
import { Spacing } from "../Separation/Spacing";
11+
import { Tag, TagList } from "../Tag";
12+
import { TextField, TextFieldProps } from "../TextField";
13+
import { Tooltip } from "../Tooltip/Tooltip";
14+
import { WhiteSpaceContainer } from "../Typography";
15+
16+
export interface ColorFieldProps extends Omit<TextFieldProps, "invisibleCharacterWarning"> {
17+
/**
18+
* Any color can be selected, not only from the configured color palette.
19+
*/
20+
allowCustomColor?: boolean;
21+
/**
22+
* What color weights should be included in the set of allowed colors.
23+
*/
24+
colorWeightFilter?: ColorWeight[];
25+
/**
26+
* What palette groups should be included in the set of allowed colors.
27+
*/
28+
paletteGroupFilter?: PaletteGroup[];
29+
}
30+
31+
/**
32+
* Text input field.
33+
*/
34+
export const ColorField = ({
35+
className = "",
36+
allowCustomColor = false,
37+
colorWeightFilter = [100, 300, 700, 900],
38+
paletteGroupFilter = ["layout"],
39+
defaultValue,
40+
value,
41+
onChange,
42+
fullWidth = false,
43+
...otherTextFieldProps
44+
}: ColorFieldProps) => {
45+
const ref = React.useRef(null);
46+
const [colorValue, setColorValue] = React.useState<string>(defaultValue || value || "#000000");
47+
48+
let allowedPaletteColors, disableNativePicker, disabled;
49+
const updateConfig = () => {
50+
allowedPaletteColors = utils.getEnabledColorPropertiesFromPalette({
51+
includePaletteGroup: paletteGroupFilter,
52+
includeColorWeight: colorWeightFilter,
53+
minimalColorDistance: 0, // we use all allowed colors, and do not check distances between them
54+
});
55+
56+
disableNativePicker =
57+
colorWeightFilter.length > 0 && paletteGroupFilter.length > 0 && allowedPaletteColors.length > 0;
58+
disabled = (!disableNativePicker && !allowCustomColor) || otherTextFieldProps.disabled;
59+
};
60+
updateConfig();
61+
React.useEffect(() => {
62+
updateConfig();
63+
}, [allowCustomColor, colorWeightFilter, paletteGroupFilter, otherTextFieldProps]);
64+
65+
React.useEffect(() => {
66+
setColorValue(defaultValue || value || "#000000");
67+
}, [defaultValue, value]);
68+
69+
const forwardOnChange = (forwardedEvent: React.ChangeEvent<HTMLInputElement>) => {
70+
setColorValue(forwardedEvent.target.value);
71+
if (onChange) {
72+
onChange(forwardedEvent);
73+
}
74+
};
75+
76+
const colorInput = (
77+
<TextField
78+
inputRef={ref}
79+
className={classNames(`${eccgui}-colorfield`, className, {
80+
[`${eccgui}-colorfield--custom-picker`]: disableNativePicker,
81+
})}
82+
// we cannot use `color` type for the custom picker because we do not have control over it then
83+
type={!disableNativePicker ? "color" : "text"}
84+
readOnly={disableNativePicker}
85+
disabled={disabled}
86+
value={colorValue}
87+
fullWidth={fullWidth}
88+
{...otherTextFieldProps}
89+
onChange={
90+
!disableNativePicker
91+
? (e: React.ChangeEvent<HTMLInputElement>) => {
92+
forwardOnChange(e);
93+
}
94+
: undefined
95+
}
96+
style={{ ...otherTextFieldProps.style, [`--eccgui-colorfield-background`]: colorValue } as CSSProperties}
97+
/>
98+
);
99+
100+
return disableNativePicker && !disabled ? (
101+
<ContextOverlay
102+
fill={fullWidth}
103+
content={
104+
<WhiteSpaceContainer
105+
paddingTop={"small"}
106+
paddingRight={"small"}
107+
paddingBottom={"small"}
108+
paddingLeft={"small"}
109+
className={`${eccgui}-colorfield__picker`}
110+
>
111+
{allowCustomColor && (
112+
<>
113+
<TextField
114+
type={"color"}
115+
value={colorValue}
116+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
117+
forwardOnChange(e);
118+
}}
119+
/>
120+
<Spacing size={"small"} />
121+
</>
122+
)}
123+
<FieldSet>
124+
<TagList
125+
className={`${eccgui}-colorfield__palette ${eccgui}-colorfield__palette--${
126+
colorWeightFilter.length >= 3 ? colorWeightFilter.length * 2 : "8"
127+
}col`}
128+
>
129+
{allowedPaletteColors!.map((color: [string, string], idx: number) => [
130+
<RadioButton
131+
className={`${eccgui}-colorfield__palette__color`}
132+
hideIndicator
133+
value={color[1]}
134+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
135+
forwardOnChange(e);
136+
}}
137+
>
138+
<Tooltip key={idx} content={color[0].replace(`${eccgui}-color-palette-`, "")}>
139+
<Tag
140+
large
141+
style={{ [`--eccgui-colorfield-palette-color`]: color[1] } as CSSProperties}
142+
>
143+
{color[1]}
144+
</Tag>
145+
</Tooltip>
146+
</RadioButton>,
147+
// Looks like we cannot force some type of line break in the tag list via CSS only
148+
(idx + 1) % (colorWeightFilter.length >= 3 ? colorWeightFilter.length * 2 : 8) ===
149+
0 && (
150+
<>
151+
<br className={`${eccgui}-colorfield__palette-linebreak`} />
152+
</>
153+
),
154+
])}
155+
</TagList>
156+
</FieldSet>
157+
</WhiteSpaceContainer>
158+
}
159+
>
160+
{colorInput}
161+
</ContextOverlay>
162+
) : (
163+
colorInput
164+
);
165+
};
166+
167+
export default ColorField;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.#{$eccgui}-colorfield {
2+
cursor: default;
3+
4+
&:not(.#{$ns}-fill) {
5+
width: 100%;
6+
max-width: 4 * $eccgui-size-textfield-height-regular;
7+
}
8+
9+
.#{$ns}-input {
10+
color: var(--#{$eccgui}-colorfield-background);
11+
cursor: inherit;
12+
background-color: var(--#{$eccgui}-colorfield-background);
13+
14+
&[type="color"] {
15+
&::-webkit-color-swatch-wrapper {
16+
display: none;
17+
}
18+
19+
&::-moz-color-swatch {
20+
display: none;
21+
}
22+
}
23+
}
24+
25+
.#{$ns}-input-left-container {
26+
top: 1px;
27+
left: 1px !important;
28+
height: calc(100% - 2px);
29+
background-color: $eccgui-color-textfield-background;
30+
}
31+
.#{$ns}-input-action {
32+
top: 1px;
33+
right: 1px !important;
34+
height: calc(100% - 2px);
35+
background-color: $eccgui-color-textfield-background;
36+
}
37+
}
38+
39+
.#{$eccgui}-colorfield__palette {
40+
& > li:has(.#{$eccgui}-colorfield__palette-linebreak) {
41+
display: block;
42+
width: 100%;
43+
height: 0;
44+
margin: 0;
45+
overflow: hidden;
46+
}
47+
}
48+
49+
.#{$eccgui}-colorfield__palette__color {
50+
margin: 0;
51+
.#{$eccgui}-tag__item {
52+
width: 3rem;
53+
color: var(--#{$eccgui}-colorfield-palette-color) !important;
54+
background-color: var(--#{$eccgui}-colorfield-palette-color) !important;
55+
}
56+
}

src/components/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
@import "./Tabs/tabs";
3333
@import "./Tag/tag";
3434
@import "./TextField/textfield";
35+
@import "./ColorField/colorfield";
3536
@import "./TagInput/taginput";
3637
@import "./Toolbar/toolbar";
3738
@import "./Tooltip/tooltip";

0 commit comments

Comments
 (0)