@eccenca/gui-elements 25.1.0-rc.1 → 25.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +1 -1
  3. package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
  4. package/dist/cjs/common/index.js +1 -0
  5. package/dist/cjs/common/index.js.map +1 -1
  6. package/dist/cjs/common/utils/CssCustomProperties.js.map +1 -1
  7. package/dist/cjs/common/utils/colorHash.js +26 -12
  8. package/dist/cjs/common/utils/colorHash.js.map +1 -1
  9. package/dist/cjs/components/ColorField/ColorField.js +114 -0
  10. package/dist/cjs/components/ColorField/ColorField.js.map +1 -0
  11. package/dist/cjs/components/RadioButton/RadioButton.js +5 -2
  12. package/dist/cjs/components/RadioButton/RadioButton.js.map +1 -1
  13. package/dist/cjs/components/TextField/useTextValidation.js +17 -8
  14. package/dist/cjs/components/TextField/useTextValidation.js.map +1 -1
  15. package/dist/cjs/components/index.js +1 -0
  16. package/dist/cjs/components/index.js.map +1 -1
  17. package/dist/cjs/extensions/codemirror/CodeMirror.js +40 -14
  18. package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
  19. package/dist/cjs/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js +23 -0
  20. package/dist/cjs/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js.map +1 -0
  21. package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js +5 -2
  22. package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -1
  23. package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +1 -1
  24. package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
  25. package/dist/esm/common/index.js +2 -1
  26. package/dist/esm/common/index.js.map +1 -1
  27. package/dist/esm/common/utils/CssCustomProperties.js.map +1 -1
  28. package/dist/esm/common/utils/colorHash.js +26 -13
  29. package/dist/esm/common/utils/colorHash.js.map +1 -1
  30. package/dist/esm/components/ColorField/ColorField.js +140 -0
  31. package/dist/esm/components/ColorField/ColorField.js.map +1 -0
  32. package/dist/esm/components/RadioButton/RadioButton.js +6 -2
  33. package/dist/esm/components/RadioButton/RadioButton.js.map +1 -1
  34. package/dist/esm/components/TextField/useTextValidation.js +39 -8
  35. package/dist/esm/components/TextField/useTextValidation.js.map +1 -1
  36. package/dist/esm/components/index.js +1 -0
  37. package/dist/esm/components/index.js.map +1 -1
  38. package/dist/esm/extensions/codemirror/CodeMirror.js +42 -16
  39. package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
  40. package/dist/esm/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js +47 -0
  41. package/dist/esm/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js.map +1 -0
  42. package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js +16 -2
  43. package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -1
  44. package/dist/types/common/index.d.ts +2 -1
  45. package/dist/types/common/utils/CssCustomProperties.d.ts +2 -2
  46. package/dist/types/common/utils/colorHash.d.ts +5 -4
  47. package/dist/types/components/ColorField/ColorField.d.ts +30 -0
  48. package/dist/types/components/RadioButton/RadioButton.d.ts +8 -2
  49. package/dist/types/components/index.d.ts +1 -0
  50. package/dist/types/extensions/codemirror/CodeMirror.d.ts +12 -9
  51. package/dist/types/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.d.ts +24 -0
  52. package/dist/types/extensions/codemirror/toolbars/markdown.toolbar.d.ts +2 -0
  53. package/package.json +1 -1
  54. package/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx +1 -1
  55. package/src/common/index.ts +2 -1
  56. package/src/common/utils/CssCustomProperties.ts +5 -3
  57. package/src/common/utils/colorHash.ts +38 -20
  58. package/src/components/ColorField/ColorField.stories.tsx +72 -0
  59. package/src/components/ColorField/ColorField.test.tsx +101 -0
  60. package/src/components/ColorField/ColorField.tsx +200 -0
  61. package/src/components/ColorField/_colorfield.scss +67 -0
  62. package/src/components/RadioButton/RadioButton.tsx +15 -3
  63. package/src/components/RadioButton/radiobutton.scss +13 -0
  64. package/src/components/TextField/stories/TextField.stories.tsx +23 -0
  65. package/src/components/TextField/tests/useTextValidation.test.tsx +83 -0
  66. package/src/components/TextField/useTextValidation.ts +17 -8
  67. package/src/components/index.scss +1 -0
  68. package/src/components/index.ts +1 -0
  69. package/src/extensions/codemirror/CodeMirror.stories.tsx +9 -4
  70. package/src/extensions/codemirror/CodeMirror.tsx +71 -26
  71. package/src/extensions/codemirror/tests/CodeEditor.test.tsx +138 -0
  72. package/src/extensions/codemirror/tests/EditorAppearanceConfigMenu.test.tsx +131 -0
  73. package/src/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.tsx +59 -0
  74. package/src/extensions/codemirror/toolbars/markdown.toolbar.tsx +17 -3
@@ -6,10 +6,10 @@ import { colorCalculateDistance } from "./colorCalculateDistance";
6
6
  import CssCustomProperties from "./CssCustomProperties";
7
7
 
8
8
  type ColorOrFalse = Color | false;
9
- type ColorWeight = 100 | 300 | 500 | 700 | 900;
10
- type PaletteGroup = "identity" | "semantic" | "layout" | "extra";
9
+ export type ColorWeight = 100 | 300 | 500 | 700 | 900;
10
+ export type PaletteGroup = "identity" | "semantic" | "layout" | "extra";
11
11
 
12
- interface getEnabledColorsProps {
12
+ export interface getEnabledColorsProps {
13
13
  /** Specify the palette groups used to define the set of colors. */
14
14
  includePaletteGroup?: PaletteGroup[];
15
15
  /** Use only some weights of a color tint. */
@@ -21,20 +21,43 @@ interface getEnabledColorsProps {
21
21
  }
22
22
 
23
23
  const getEnabledColorsFromPaletteCache = new Map<string, Color[]>();
24
+ const getEnabledColorPropertiesFromPaletteCache = new Map<string, [string, string][]>();
24
25
 
25
- export function getEnabledColorsFromPalette({
26
+ export function getEnabledColorsFromPalette(props: getEnabledColorsProps): Color[] {
27
+ const configId = JSON.stringify({
28
+ includePaletteGroup: props.includePaletteGroup,
29
+ includeColorWeight: props.includeColorWeight,
30
+ });
31
+
32
+ if (getEnabledColorsFromPaletteCache.has(configId)) {
33
+ return getEnabledColorsFromPaletteCache.get(configId)!;
34
+ }
35
+
36
+ const colorPropertiesFromPalette = Object.values(getEnabledColorPropertiesFromPalette(props));
37
+
38
+ getEnabledColorsFromPaletteCache.set(
39
+ configId,
40
+ colorPropertiesFromPalette.map((color) => {
41
+ return Color(color[1]);
42
+ })
43
+ );
44
+
45
+ return getEnabledColorsFromPaletteCache.get(configId)!;
46
+ }
47
+
48
+ export function getEnabledColorPropertiesFromPalette({
26
49
  includePaletteGroup = ["layout"],
27
50
  includeColorWeight = [100, 300, 500, 700, 900],
28
- // TODO (planned for later): includeMixedColors = false,
51
+ // (planned for later): includeMixedColors = false,
29
52
  minimalColorDistance = COLORMINDISTANCE,
30
- }: getEnabledColorsProps): Color[] {
53
+ }: getEnabledColorsProps): [string, string][] {
31
54
  const configId = JSON.stringify({
32
55
  includePaletteGroup,
33
56
  includeColorWeight,
34
57
  });
35
58
 
36
- if (getEnabledColorsFromPaletteCache.has(configId)) {
37
- return getEnabledColorsFromPaletteCache.get(configId)!;
59
+ if (getEnabledColorPropertiesFromPaletteCache.has(configId)) {
60
+ return getEnabledColorPropertiesFromPaletteCache.get(configId)!;
38
61
  }
39
62
 
40
63
  const colorsFromPalette = new CssCustomProperties({
@@ -50,18 +73,18 @@ export function getEnabledColorsFromPalette({
50
73
  const weight = parseInt(tint[2], 10) as ColorWeight;
51
74
  return includePaletteGroup.includes(group) && includeColorWeight.includes(weight);
52
75
  },
53
- removeDashPrefix: false,
76
+ removeDashPrefix: true,
54
77
  returnObject: true,
55
78
  }).customProperties();
56
79
 
57
- const colorsFromPaletteValues = Object.values(colorsFromPalette) as string[];
80
+ const colorsFromPaletteValues = Object.entries(colorsFromPalette) as [string, string][];
58
81
 
59
82
  const colorsFromPaletteWithEnoughDistance =
60
83
  minimalColorDistance > 0
61
- ? colorsFromPaletteValues.reduce((enoughDistance: string[], color: string) => {
84
+ ? colorsFromPaletteValues.reduce((enoughDistance: [string, string][], color: [string, string]) => {
62
85
  if (enoughDistance.includes(color)) {
63
86
  return enoughDistance.filter((checkColor) => {
64
- const distance = colorCalculateDistance({ color1: color, color2: checkColor });
87
+ const distance = colorCalculateDistance({ color1: color[1], color2: checkColor[1] });
65
88
  return checkColor === color || (distance && minimalColorDistance <= distance);
66
89
  });
67
90
  } else {
@@ -70,14 +93,9 @@ export function getEnabledColorsFromPalette({
70
93
  }, colorsFromPaletteValues)
71
94
  : colorsFromPaletteValues;
72
95
 
73
- getEnabledColorsFromPaletteCache.set(
74
- configId,
75
- colorsFromPaletteWithEnoughDistance.map((color: string) => {
76
- return Color(color);
77
- })
78
- );
96
+ getEnabledColorPropertiesFromPaletteCache.set(configId, colorsFromPaletteWithEnoughDistance);
79
97
 
80
- return getEnabledColorsFromPaletteCache.get(configId)!;
98
+ return getEnabledColorPropertiesFromPaletteCache.get(configId)!;
81
99
  }
82
100
 
83
101
  function getColorcode(text: string): ColorOrFalse {
@@ -148,7 +166,7 @@ export function textToColorHash({
148
166
  }
149
167
 
150
168
  function stringToIntegerHash(inputString: string): number {
151
- /* this function is idempotend, meaning it retrieves the same result for the same input
169
+ /* this function is idempotent, meaning it retrieves the same result for the same input
152
170
  no matter how many times it's called */
153
171
  // Convert the string to a hash code
154
172
  let hashCode = 0;
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import { Meta, StoryFn } from "@storybook/react";
3
+
4
+ import { getEnabledColorsProps } from "../../common/utils/colorHash";
5
+ import textFieldTest from "../TextField/stories/TextField.stories";
6
+
7
+ import { ColorField, ColorFieldProps } from "./ColorField";
8
+
9
+ export default {
10
+ title: "Forms/ColorField",
11
+ component: ColorField,
12
+ argTypes: {
13
+ ...textFieldTest.argTypes,
14
+ },
15
+ } as Meta<typeof ColorField>;
16
+
17
+ const Template: StoryFn<typeof ColorField> = (args) => <ColorField {...args}></ColorField>;
18
+
19
+ export const Default = Template.bind({});
20
+ Default.args = {
21
+ onChange: (e) => {
22
+ alert(e.target.value);
23
+ },
24
+ };
25
+
26
+ export const NoPalettePresets = Template.bind({});
27
+ NoPalettePresets.args = {
28
+ ...Default.args,
29
+ allowCustomColor: true,
30
+ colorPresets: [],
31
+ };
32
+
33
+ type TemplateColorHashProps = { stringForColorHashValue: string } & Pick<
34
+ ColorFieldProps,
35
+ "onChange" | "allowCustomColor"
36
+ > &
37
+ Pick<getEnabledColorsProps, "includeColorWeight" | "includePaletteGroup">;
38
+
39
+ const TemplateColorHash: StoryFn<TemplateColorHashProps> = (args: TemplateColorHashProps) => (
40
+ <ColorField
41
+ allowCustomColor={args.allowCustomColor}
42
+ colorPresets={ColorField.listColorPalettePresets({
43
+ includeColorWeight: args.includeColorWeight,
44
+ includePaletteGroup: args.includePaletteGroup,
45
+ })}
46
+ value={ColorField.calculateColorHashValue(args.stringForColorHashValue, {
47
+ allowCustomColor: args.allowCustomColor,
48
+ includeColorWeight: args.includeColorWeight,
49
+ includePaletteGroup: args.includePaletteGroup,
50
+ })}
51
+ />
52
+ );
53
+
54
+ /**
55
+ * Component provides a helper function to calculate a color hash from a text,
56
+ * that can be used as `value` or `defaultValue`.
57
+ *
58
+ * ```
59
+ * <ColorField value={ColorField.calculateColorHashValue("MyText")} />
60
+ * ```
61
+ *
62
+ * You can add `options` to set the config for the color palette filters.
63
+ * The same default values like on `ColorField` are used for them.
64
+ */
65
+ export const ColorHashValue = TemplateColorHash.bind({});
66
+ ColorHashValue.args = {
67
+ ...Default.args,
68
+ allowCustomColor: true,
69
+ includeColorWeight: [300, 500, 700],
70
+ includePaletteGroup: ["layout", "extra"],
71
+ stringForColorHashValue: "My text that will used to create a color hash as initial value.",
72
+ };
@@ -0,0 +1,101 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+
5
+ import "@testing-library/jest-dom";
6
+
7
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
8
+
9
+ import { ColorField } from "./ColorField";
10
+
11
+ describe("ColorField", () => {
12
+ describe("rendering", () => {
13
+ it("renders without crashing, and correct component CSS class is applied", () => {
14
+ const { container } = render(<ColorField />);
15
+ expect(container).not.toBeEmptyDOMElement();
16
+ expect(container.getElementsByClassName(`${eccgui}-colorfield`).length).toBe(1);
17
+ });
18
+
19
+ it("renders a color input by default (no palette presets)", () => {
20
+ const { container } = render(<ColorField colorPresets={[]} allowCustomColor={true} />);
21
+ expect(container.querySelector("input[type='color']")).toBeInTheDocument();
22
+ });
23
+
24
+ it("renders a readonly text input when palette colors are configured, and custom picker CSS class is applied", () => {
25
+ const { container } = render(
26
+ <ColorField
27
+ className="my-custom-class"
28
+ colorPresets={[
29
+ ["my-black", "#000000"],
30
+ ["my-white", "#ffffff"],
31
+ ]}
32
+ />
33
+ );
34
+ // With default palette settings, a text input with readOnly is shown
35
+ expect(container.querySelector("input[type='text']")).toBeInTheDocument();
36
+ expect(container.querySelector("input[readonly]")).toBeInTheDocument();
37
+ expect(container.querySelector(`.${eccgui}-colorfield--custom-picker`)).toBeInTheDocument();
38
+ });
39
+
40
+ it("applies additional className", () => {
41
+ render(<ColorField className="my-custom-class" colorPresets={[]} allowCustomColor={true} />);
42
+ expect(document.querySelector(".my-custom-class")).toBeInTheDocument();
43
+ });
44
+ });
45
+
46
+ describe("value handling", () => {
47
+ it("uses defaultValue as initial color", () => {
48
+ render(<ColorField defaultValue="#ff0000" colorPresets={[]} allowCustomColor={true} />);
49
+ const input = document.querySelector("input") as HTMLInputElement;
50
+ expect(input.value).toBe("#ff0000");
51
+ });
52
+
53
+ it("uses value prop as initial color", () => {
54
+ render(<ColorField value="#00ff00" colorPresets={[]} allowCustomColor={true} />);
55
+ const input = document.querySelector("input") as HTMLInputElement;
56
+ expect(input.value).toBe("#00ff00");
57
+ });
58
+
59
+ it("falls back to #000000 when no value or defaultValue is provided", () => {
60
+ render(<ColorField colorPresets={[]} allowCustomColor={true} />);
61
+ const input = document.querySelector("input") as HTMLInputElement;
62
+ expect(input.value).toBe("#000000");
63
+ });
64
+
65
+ it("updates displayed value when value prop changes", () => {
66
+ const { rerender } = render(<ColorField value="#ff0000" colorPresets={[]} allowCustomColor={true} />);
67
+ let input = document.querySelector("input") as HTMLInputElement;
68
+ expect(input.value).toBe("#ff0000");
69
+
70
+ rerender(<ColorField value="#0000ff" colorPresets={[]} allowCustomColor={true} />);
71
+ input = document.querySelector("input") as HTMLInputElement;
72
+ expect(input.value).toBe("#0000ff");
73
+ });
74
+ });
75
+
76
+ describe("disabled state", () => {
77
+ it("is disabled when disabled prop is true", () => {
78
+ render(<ColorField disabled colorPresets={[]} allowCustomColor={true} />);
79
+ const input = document.querySelector("input") as HTMLInputElement;
80
+ expect(input).toBeDisabled();
81
+ });
82
+
83
+ it("is disabled when no palette colors and allowCustomColor is false", () => {
84
+ render(<ColorField colorPresets={[]} allowCustomColor={false} />);
85
+ const input = document.querySelector("input") as HTMLInputElement;
86
+ expect(input).toBeDisabled();
87
+ });
88
+ });
89
+
90
+ describe("onChange callback", () => {
91
+ it("calls onChange when native color input changes", async () => {
92
+ const user = userEvent.setup();
93
+ const onChange = jest.fn();
94
+ render(<ColorField onChange={onChange} colorPresets={[]} allowCustomColor={true} />);
95
+ const input = document.querySelector("input[type='color']") as HTMLInputElement;
96
+ input.type = "text"; // for unknown reasons Jest seems not able to test it on color inputs
97
+ await user.type(input, "#123456");
98
+ expect(onChange).toHaveBeenCalled();
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,200 @@
1
+ import React, { CSSProperties } from "react";
2
+ import classNames from "classnames";
3
+ import Color from "color";
4
+
5
+ import { utils } from "../../common";
6
+ import { getEnabledColorsProps } from "../../common/utils/colorHash";
7
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
8
+ import { ContextOverlay } from "../ContextOverlay";
9
+ import { FieldSet } from "../Form";
10
+ import { RadioButton } from "../RadioButton/RadioButton";
11
+ import { Spacing } from "../Separation/Spacing";
12
+ import { Tag, TagList } from "../Tag";
13
+ import { TextField, TextFieldProps } from "../TextField";
14
+ import { Tooltip } from "../Tooltip/Tooltip";
15
+ import { WhiteSpaceContainer } from "../Typography";
16
+
17
+ type ColorPresets = [string, string][] | [string, Color][];
18
+ type ColorPresetConfiguration = Pick<getEnabledColorsProps, "includeColorWeight" | "includePaletteGroup">;
19
+
20
+ export interface ColorFieldProps extends Omit<TextFieldProps, "invisibleCharacterWarning"> {
21
+ /**
22
+ * Any color can be selected, not only from the color presets.
23
+ */
24
+ allowCustomColor?: boolean;
25
+ /**
26
+ * List of named colors that are used a selectable color options.
27
+ */
28
+ colorPresets?: ColorPresets;
29
+ }
30
+
31
+ /**
32
+ * Color input field that provides resets from the configured color palette.
33
+ * Use `includeColorWeight` and `includePaletteGroup` to filter them.
34
+ */
35
+ export const ColorField = ({
36
+ className = "",
37
+ allowCustomColor = false,
38
+ colorPresets = listColorPalettePresets(),
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
+ if (value && value !== colorValue) {
48
+ setColorValue(value);
49
+ }
50
+
51
+ const disableNativePicker = colorPresets.length > 0;
52
+ const disabled = (!disableNativePicker && !allowCustomColor) || otherTextFieldProps.disabled;
53
+
54
+ const forwardOnChange = (forwardedEvent: React.ChangeEvent<HTMLInputElement>) => {
55
+ setColorValue(forwardedEvent.target.value);
56
+ if (onChange) {
57
+ onChange(forwardedEvent);
58
+ }
59
+ };
60
+
61
+ const colorInput = (
62
+ <TextField
63
+ inputRef={ref}
64
+ className={classNames(`${eccgui}-colorfield`, className, {
65
+ [`${eccgui}-colorfield--custom-picker`]: disableNativePicker,
66
+ [`${eccgui}-colorfield--disabled`]: disabled,
67
+ })}
68
+ // we cannot use `color` type for the custom picker because we do not have control over it then
69
+ type={!disableNativePicker ? "color" : "text"}
70
+ readOnly={disableNativePicker}
71
+ disabled={disabled}
72
+ value={colorValue}
73
+ fullWidth={fullWidth}
74
+ {...otherTextFieldProps}
75
+ onChange={
76
+ !disableNativePicker
77
+ ? (e: React.ChangeEvent<HTMLInputElement>) => {
78
+ forwardOnChange(e);
79
+ }
80
+ : undefined
81
+ }
82
+ style={{ ...otherTextFieldProps.style, [`--eccgui-colorfield-background`]: colorValue } as CSSProperties}
83
+ />
84
+ );
85
+
86
+ return disableNativePicker && !disabled ? (
87
+ <ContextOverlay
88
+ fill={fullWidth}
89
+ content={
90
+ <WhiteSpaceContainer
91
+ paddingTop={"small"}
92
+ paddingRight={"small"}
93
+ paddingBottom={"small"}
94
+ paddingLeft={"small"}
95
+ className={`${eccgui}-colorfield__picker`}
96
+ >
97
+ {allowCustomColor && (
98
+ <>
99
+ <TextField
100
+ type={"color"}
101
+ value={colorValue}
102
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
103
+ forwardOnChange(e);
104
+ }}
105
+ />
106
+ <Spacing size={"small"} />
107
+ </>
108
+ )}
109
+ <FieldSet>
110
+ <TagList className={`${eccgui}-colorfield__palette`}>
111
+ {colorPresets!.map((color: [string, string | Color], idx: number) => [
112
+ <RadioButton
113
+ key={idx}
114
+ className={`${eccgui}-colorfield__palette__color`}
115
+ hideIndicator
116
+ value={typeof color[1] === "string" ? color[1] : color[1].toString()}
117
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
118
+ forwardOnChange(e);
119
+ }}
120
+ >
121
+ <Tooltip content={color[0]}>
122
+ <Tag
123
+ large
124
+ style={{ [`--eccgui-colorfield-palette-color`]: color[1] } as CSSProperties}
125
+ >
126
+ {color[1]}
127
+ </Tag>
128
+ </Tooltip>
129
+ </RadioButton>,
130
+ // Looks like we cannot force some type of line break in the tag list via CSS only
131
+ (idx + 1) % 8 === 0 && (
132
+ <>
133
+ <br className={`${eccgui}-colorfield__palette-linebreak`} />
134
+ </>
135
+ ),
136
+ ])}
137
+ </TagList>
138
+ </FieldSet>
139
+ </WhiteSpaceContainer>
140
+ }
141
+ >
142
+ {colorInput}
143
+ </ContextOverlay>
144
+ ) : (
145
+ colorInput
146
+ );
147
+ };
148
+
149
+ const defaultColorPaletteSet: ColorPresetConfiguration = {
150
+ // on default, we only include color weights that can have enough contrasts to black/white
151
+ includeColorWeight: [100, 300, 700, 900],
152
+ // on default, we only include layout colors
153
+ includePaletteGroup: ["layout"],
154
+ };
155
+
156
+ /**
157
+ * Simple helper function to get a list of colors defined in the color palette.
158
+ */
159
+ const listColorPalettePresets = (colorPaletteSet = defaultColorPaletteSet) => {
160
+ return utils
161
+ .getEnabledColorPropertiesFromPalette({
162
+ ...colorPaletteSet,
163
+ minimalColorDistance: 0, // we use all allowed colors, and do not check distances between them
164
+ })
165
+ .map((color: [string, string | Color]) => [
166
+ color[0].replace(`${eccgui}-color-palette-`, ""),
167
+ color[1],
168
+ ]) as ColorPresets;
169
+ };
170
+
171
+ ColorField.listColorPalettePresets = listColorPalettePresets;
172
+
173
+ type calculateColorHashValueProps = Pick<ColorFieldProps, "allowCustomColor"> & ColorPresetConfiguration;
174
+
175
+ /**
176
+ * Simple helper function that provide simple access to color hash calculation.
177
+ */
178
+ ColorField.calculateColorHashValue = (
179
+ text: string,
180
+ options: calculateColorHashValueProps = {
181
+ ...defaultColorPaletteSet,
182
+ allowCustomColor: false,
183
+ }
184
+ ) => {
185
+ const hash = utils.textToColorHash({
186
+ text,
187
+ options: {
188
+ returnValidColorsDirectly: options.allowCustomColor as boolean,
189
+ enabledColors: utils.getEnabledColorsFromPalette({
190
+ includePaletteGroup: options.includePaletteGroup,
191
+ includeColorWeight: options.includeColorWeight,
192
+ minimalColorDistance: 0,
193
+ }),
194
+ },
195
+ });
196
+
197
+ return hash ? hash : undefined;
198
+ };
199
+
200
+ export default ColorField;
@@ -0,0 +1,67 @@
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
+ &[class*="#{$ns}-intent-"] {
26
+ // we need to remove normal intent indicators like colored bg or blinking
27
+ .#{$ns}-input {
28
+ background-color: var(--#{$eccgui}-colorfield-background);
29
+ }
30
+ }
31
+
32
+ .#{$ns}-input-left-container {
33
+ top: 1px;
34
+ left: 1px !important;
35
+ height: calc(100% - 2px);
36
+ background-color: $eccgui-color-textfield-background;
37
+ }
38
+ .#{$ns}-input-action {
39
+ top: 1px;
40
+ right: 1px !important;
41
+ height: calc(100% - 2px);
42
+ background-color: $eccgui-color-textfield-background;
43
+ }
44
+ }
45
+
46
+ .#{$eccgui}-colorfield--disabled {
47
+ opacity: $eccgui-opacity-disabled;
48
+ }
49
+
50
+ .#{$eccgui}-colorfield__palette {
51
+ & > li:has(.#{$eccgui}-colorfield__palette-linebreak) {
52
+ display: block;
53
+ width: 100%;
54
+ height: 0;
55
+ margin: 0;
56
+ overflow: hidden;
57
+ }
58
+ }
59
+
60
+ .#{$eccgui}-colorfield__palette__color {
61
+ margin: 0;
62
+ .#{$eccgui}-tag__item {
63
+ width: 3rem;
64
+ color: var(--#{$eccgui}-colorfield-palette-color) !important;
65
+ background-color: var(--#{$eccgui}-colorfield-palette-color) !important;
66
+ }
67
+ }
@@ -1,13 +1,25 @@
1
1
  import React from "react";
2
2
  import { Radio as BlueprintRadioButton, RadioProps as BlueprintRadioProps } from "@blueprintjs/core";
3
+ import classNames from "classnames";
3
4
 
4
5
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
5
6
 
6
- export type RadioButtonProps = BlueprintRadioProps;
7
+ export interface RadioButtonProps extends BlueprintRadioProps {
8
+ /**
9
+ * Hide the indicator.
10
+ * The element cannot be identified as radio input then but a click on the children can be easily processed via `onChange` event.
11
+ */
12
+ hideIndicator?: boolean;
13
+ }
7
14
 
8
- export const RadioButton = ({ children, className = "", ...restProps }: RadioButtonProps) => {
15
+ export const RadioButton = ({ children, className = "", hideIndicator = false, ...restProps }: RadioButtonProps) => {
9
16
  return (
10
- <BlueprintRadioButton {...restProps} className={`${eccgui}-radiobutton ` + className}>
17
+ <BlueprintRadioButton
18
+ {...restProps}
19
+ className={classNames(`${eccgui}-radiobutton`, className, {
20
+ [`${eccgui}-radiobutton--hidden-indicator`]: hideIndicator,
21
+ })}
22
+ >
11
23
  {children}
12
24
  </BlueprintRadioButton>
13
25
  );
@@ -29,3 +29,16 @@
29
29
  }
30
30
  }
31
31
  }
32
+
33
+ .#{$eccgui}-radiobutton--hidden-indicator {
34
+ &:not(.#{$ns}-align-right) {
35
+ padding-inline-start: 0;
36
+ }
37
+ &:not(.#{$ns}-align-left) {
38
+ padding-inline-end: 0;
39
+ }
40
+
41
+ input ~ .#{$ns}-control-indicator {
42
+ visibility: hidden;
43
+ }
44
+ }
@@ -61,3 +61,26 @@ const invisibleCharacterWarningProps: TextFieldProps = {
61
61
  defaultValue: "Invisible character ->​<-",
62
62
  };
63
63
  InvisibleCharacterWarning.args = invisibleCharacterWarningProps;
64
+
65
+ /** Text field showing that emoji (✔️ variation-selector, 👨‍👩‍👧‍👦 ZWJ, #️⃣ keycap)
66
+ * are NOT reported as invisible characters, while a genuine ZWS still is. */
67
+ export const InvisibleCharacterWarningWithEmoji = Template.bind({});
68
+
69
+ const invisibleCharacterWarningWithEmojiProps: TextFieldProps = {
70
+ ...Default.args,
71
+ invisibleCharacterWarning: {
72
+ callback: (codePoints) => {
73
+ if (codePoints.size) {
74
+ const codePointsString = [...codePoints]
75
+ .map((n) => characters.invisibleZeroWidthCharacters.codePointMap.get(n)?.fullLabel)
76
+ .join(", ");
77
+ alert("Invisible character detected in input string. Code points: " + codePointsString);
78
+ }
79
+ },
80
+ callbackDelay: 500,
81
+ },
82
+ onChange: () => {},
83
+ // ZWS should be flagged; ✔️ 👨‍👩‍👧‍👦 #️⃣ should NOT be flagged
84
+ defaultValue: "Check\u200B ✔️ 👨‍👩‍👧‍👦 #️⃣",
85
+ };
86
+ InvisibleCharacterWarningWithEmoji.args = invisibleCharacterWarningWithEmojiProps;