@fibery/ui-kit 1.40.4 → 1.42.0

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 (143) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/eslint.config.mjs +16 -0
  3. package/package.json +10 -9
  4. package/src/a11y-color.test.ts +5 -24
  5. package/src/actions-menu/actions-menu-item.tsx +6 -9
  6. package/src/actions-menu/context-actions-menu.tsx +8 -3
  7. package/src/ai/model.tsx +0 -1
  8. package/src/ai/temperature.tsx +0 -1
  9. package/src/animated-height-container.tsx +3 -3
  10. package/src/antd/index.tsx +3 -0
  11. package/src/antd/input.tsx +0 -1
  12. package/src/antd/styles.ts +9 -9
  13. package/src/app-icon-with-fallback.tsx +5 -5
  14. package/src/app-icon-wrapper.tsx +2 -3
  15. package/src/app-icon.tsx +3 -3
  16. package/src/avatar.tsx +3 -3
  17. package/src/button/base-button.tsx +0 -1
  18. package/src/button/make-button-colors.ts +2 -2
  19. package/src/checkbox.tsx +1 -1
  20. package/src/collapsible-section.tsx +11 -1
  21. package/src/color-utils.test.ts +15 -25
  22. package/src/color-utils.ts +43 -8
  23. package/src/command-menu/index.tsx +1 -0
  24. package/src/context-menu/index.tsx +5 -5
  25. package/src/date-picker/contexts.ts +2 -2
  26. package/src/date-picker/date-range-picker-popup-content.tsx +1 -2
  27. package/src/date-picker/date-range-picker.tsx +29 -31
  28. package/src/date-picker/single-date-picker-popup-content.tsx +1 -2
  29. package/src/date-picker/single-date-picker.tsx +149 -124
  30. package/src/date-picker/types.ts +4 -4
  31. package/src/day-select/iso-week-day-select.tsx +2 -2
  32. package/src/day-select/week-day-select.tsx +2 -2
  33. package/src/delayed.tsx +0 -1
  34. package/src/design-system/alpha.test.ts +59 -0
  35. package/src/design-system/alpha.ts +41 -0
  36. package/src/design-system/animation.ts +10 -0
  37. package/src/design-system/colors-css.test.ts +39 -0
  38. package/src/design-system/colors-css.ts +25 -0
  39. package/src/design-system/colors-js.test.ts +232 -0
  40. package/src/design-system/colors-js.ts +107 -0
  41. package/src/design-system/colors.test.ts +74 -0
  42. package/src/{design-system.colors.ts → design-system/colors.ts} +133 -134
  43. package/src/design-system/date.ts +3 -0
  44. package/src/design-system/layout.ts +106 -0
  45. package/src/design-system/theme.test.ts +94 -0
  46. package/src/design-system/theme.ts +93 -0
  47. package/src/design-system/typography.ts +179 -0
  48. package/src/design-system/vars.test.ts +1679 -0
  49. package/src/design-system/vars.ts +52 -0
  50. package/src/design-system.test.ts +46 -303
  51. package/src/design-system.ts +25 -545
  52. package/src/dropdown-menu/index.tsx +5 -5
  53. package/src/emoji-picker/icon-emoji-picker.tsx +1 -1
  54. package/src/fibermoji-placeholder.tsx +3 -3
  55. package/src/field-container.tsx +11 -2
  56. package/src/file-item/file-icon.tsx +3 -3
  57. package/src/file-item-2.tsx +4 -10
  58. package/src/file-item.tsx +0 -1
  59. package/src/icons/ast/ChatBubble.ts +8 -0
  60. package/src/icons/ast/ChatFloat.ts +8 -0
  61. package/src/icons/ast/ChatSidebar.ts +8 -0
  62. package/src/icons/ast/FileOther.ts +8 -0
  63. package/src/icons/ast/NoBorder.ts +8 -0
  64. package/src/icons/ast/NoFill.ts +8 -0
  65. package/src/icons/ast/index.tsx +6 -0
  66. package/src/icons/react/ChatBubble.tsx +13 -0
  67. package/src/icons/react/ChatFloat.tsx +13 -0
  68. package/src/icons/react/ChatSidebar.tsx +13 -0
  69. package/src/icons/react/FileOther.tsx +13 -0
  70. package/src/icons/react/NoBorder.tsx +13 -0
  71. package/src/icons/react/NoFill.tsx +13 -0
  72. package/src/icons/react/index.tsx +6 -0
  73. package/src/icons/svg/chat-bubble.svg +4 -0
  74. package/src/icons/svg/chat-float.svg +4 -0
  75. package/src/icons/svg/chat-sidebar.svg +4 -0
  76. package/src/icons/svg/file-other.svg +3 -0
  77. package/src/icons/svg/no-border.svg +17 -0
  78. package/src/icons/svg/no-fill.svg +4 -0
  79. package/src/images-gallery/images-gallery.tsx +8 -6
  80. package/src/images-gallery/slide-buttons.tsx +2 -2
  81. package/src/is-iOS.ts +0 -1
  82. package/src/is-in-popup.ts +2 -1
  83. package/src/lists/actions-menu-row-surface.tsx +4 -4
  84. package/src/loading-sausage.tsx +2 -2
  85. package/src/media-query-utils.ts +1 -2
  86. package/src/mobile-keyboard-aware-popup.tsx +2 -2
  87. package/src/modal-menu/modal-menu-content.tsx +2 -2
  88. package/src/number-input/decimal.js +9 -7
  89. package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
  90. package/src/number-input/number-input-inline.tsx +118 -0
  91. package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
  92. package/src/number-input/number-input.tsx +63 -121
  93. package/src/number-input/types.ts +19 -0
  94. package/src/number-input/utils.ts +61 -0
  95. package/src/online-users.tsx +2 -2
  96. package/src/palette-generator.test.ts +24 -281
  97. package/src/palette-generator.ts +39 -45
  98. package/src/palettes/_.ts +72 -0
  99. package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
  100. package/src/palettes/slate-arch.ts +188 -0
  101. package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
  102. package/src/palettes/slate-user.ts +187 -0
  103. package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
  104. package/src/palettes/warm-arch.ts +222 -0
  105. package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
  106. package/src/palettes/warm-user.ts +222 -0
  107. package/src/popover/get-element-ref.ts +28 -0
  108. package/src/popover/index.tsx +232 -333
  109. package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
  110. package/src/popover/mobile-popover.tsx +169 -0
  111. package/src/popover/modifiers.tsx +2 -2
  112. package/src/popover/popup-stack-context.tsx +8 -9
  113. package/src/root-theme-provider.test.tsx +114 -19
  114. package/src/scale-generator.ts +31 -22
  115. package/src/select/components/menu-list-virtualized.tsx +5 -3
  116. package/src/select/index.tsx +6 -5
  117. package/src/select/select.tsx +36 -38
  118. package/src/select/util.ts +1 -1
  119. package/src/static-palettes.ts +221 -11
  120. package/src/thematic-controls.tsx +6 -6
  121. package/src/thematic-scales.tsx +15 -15
  122. package/src/thematic-state.ts +57 -20
  123. package/src/thematic.tsx +36 -32
  124. package/src/theme-provider.test.tsx +31 -19
  125. package/src/theme-provider.tsx +41 -38
  126. package/src/theme-settings.ts +66 -11
  127. package/src/theme-styles.ts +53 -8
  128. package/src/toast/toast.tsx +1 -2
  129. package/src/toggle.tsx +2 -2
  130. package/src/tooltip.tsx +4 -3
  131. package/src/type-badge.tsx +4 -11
  132. package/src/unit/styles.ts +0 -23
  133. package/src/use-is-phone.tsx +7 -2
  134. package/src/use-on-screen-keyboard-data.tsx +2 -2
  135. package/src/with-data.tsx +4 -3
  136. package/src/workflow-progress-icon.tsx +2 -2
  137. package/.eslintignore +0 -3
  138. package/.eslintrc +0 -14
  139. package/src/__mocks__/createInlineTheme.js +0 -3
  140. package/src/__snapshots__/design-system.test.ts.snap +0 -7265
  141. package/src/create-inline-theme.ts +0 -67
  142. package/src/number-input/index.js +0 -191
  143. package/src/palette.ts +0 -253
@@ -0,0 +1,59 @@
1
+ import {getOpacities, transparent} from "./alpha";
2
+
3
+ describe("getOpacities", () => {
4
+ test("color maps to alphas", () => {
5
+ expect(getOpacities("#f00")).toMatchInlineSnapshot(`
6
+ {
7
+ "opacity0": "rgba(255,0,0,0)",
8
+ "opacity10": "rgba(255,0,0,0.1)",
9
+ "opacity100": "rgb(255,0,0)",
10
+ "opacity15": "rgba(255,0,0,0.15)",
11
+ "opacity20": "rgba(255,0,0,0.2)",
12
+ "opacity25": "rgba(255,0,0,0.25)",
13
+ "opacity30": "rgba(255,0,0,0.3)",
14
+ "opacity35": "rgba(255,0,0,0.35)",
15
+ "opacity40": "rgba(255,0,0,0.4)",
16
+ "opacity45": "rgba(255,0,0,0.45)",
17
+ "opacity5": "rgba(255,0,0,0.05)",
18
+ "opacity50": "rgba(255,0,0,0.5)",
19
+ "opacity55": "rgba(255,0,0,0.55)",
20
+ "opacity60": "rgba(255,0,0,0.6)",
21
+ "opacity65": "rgba(255,0,0,0.65)",
22
+ "opacity70": "rgba(255,0,0,0.7)",
23
+ "opacity75": "rgba(255,0,0,0.75)",
24
+ "opacity80": "rgba(255,0,0,0.8)",
25
+ "opacity85": "rgba(255,0,0,0.85)",
26
+ "opacity90": "rgba(255,0,0,0.9)",
27
+ "opacity95": "rgba(255,0,0,0.95)",
28
+ }
29
+ `);
30
+ });
31
+
32
+ test("transparent is identity map", () => {
33
+ expect(getOpacities(transparent)).toMatchInlineSnapshot(`
34
+ {
35
+ "opacity0": "rgba(255, 255, 255, 0)",
36
+ "opacity10": "rgba(255, 255, 255, 0)",
37
+ "opacity100": "rgba(255, 255, 255, 0)",
38
+ "opacity15": "rgba(255, 255, 255, 0)",
39
+ "opacity20": "rgba(255, 255, 255, 0)",
40
+ "opacity25": "rgba(255, 255, 255, 0)",
41
+ "opacity30": "rgba(255, 255, 255, 0)",
42
+ "opacity35": "rgba(255, 255, 255, 0)",
43
+ "opacity40": "rgba(255, 255, 255, 0)",
44
+ "opacity45": "rgba(255, 255, 255, 0)",
45
+ "opacity5": "rgba(255, 255, 255, 0)",
46
+ "opacity50": "rgba(255, 255, 255, 0)",
47
+ "opacity55": "rgba(255, 255, 255, 0)",
48
+ "opacity60": "rgba(255, 255, 255, 0)",
49
+ "opacity65": "rgba(255, 255, 255, 0)",
50
+ "opacity70": "rgba(255, 255, 255, 0)",
51
+ "opacity75": "rgba(255, 255, 255, 0)",
52
+ "opacity80": "rgba(255, 255, 255, 0)",
53
+ "opacity85": "rgba(255, 255, 255, 0)",
54
+ "opacity90": "rgba(255, 255, 255, 0)",
55
+ "opacity95": "rgba(255, 255, 255, 0)",
56
+ }
57
+ `);
58
+ });
59
+ });
@@ -0,0 +1,41 @@
1
+ import {produceColor, setAlpha} from "../color-utils";
2
+ import mapValues from "lodash/mapValues";
3
+ import memoize from "lodash/memoize";
4
+ export const transparent = "rgba(255, 255, 255, 0)";
5
+
6
+ export const opacity = {
7
+ opacity100: 1,
8
+ opacity95: 0.95,
9
+ opacity90: 0.9,
10
+ opacity85: 0.85,
11
+ opacity80: 0.8,
12
+ opacity75: 0.75,
13
+ opacity70: 0.7,
14
+ opacity65: 0.65,
15
+ opacity60: 0.6,
16
+ opacity55: 0.55,
17
+ opacity50: 0.5,
18
+ opacity45: 0.45,
19
+ opacity40: 0.4,
20
+ opacity35: 0.35,
21
+ opacity30: 0.3,
22
+ opacity25: 0.25,
23
+ opacity20: 0.2,
24
+ opacity15: 0.15,
25
+ opacity10: 0.1,
26
+ opacity5: 0.05,
27
+ opacity0: 0,
28
+ } as const;
29
+
30
+ /**
31
+ * @deprecated I would think thrice on semantic value of this wrapper before one more usage
32
+ * maybe you will come up with something better
33
+ */
34
+ export const getOpacities = memoize((color: string) =>
35
+ mapValues(opacity, (opacityValue) => {
36
+ if (color === transparent) {
37
+ return transparent;
38
+ }
39
+ return produceColor(color, (c) => setAlpha(c, opacityValue));
40
+ })
41
+ );
@@ -0,0 +1,10 @@
1
+ export const duration = 120;
2
+
3
+ export const easing = "ease-in-out";
4
+
5
+ export const transition = `${duration}ms ${easing}`;
6
+
7
+ export const tooltipDelay = {
8
+ enter: 500,
9
+ leave: 0,
10
+ } as const;
@@ -0,0 +1,39 @@
1
+ import {getEnumTextColor, getLinearGradient} from "./colors-css";
2
+
3
+ describe("getLinearGradient", () => {
4
+ test("single color", () => {
5
+ expect(getLinearGradient(["#FF5400"])).toEqual("linear-gradient(to bottom, #FF5400 0%, #FF5400 100%)");
6
+ });
7
+
8
+ test("two colors", () => {
9
+ expect(getLinearGradient(["#FF5400", "#4978D4"])).toEqual(
10
+ "linear-gradient(to bottom, #FF5400 0%, #FF5400 50%, #4978D4 50%, #4978D4 100%)"
11
+ );
12
+ });
13
+
14
+ test("three colors", () => {
15
+ expect(getLinearGradient(["#FF5400", "#4978D4", "#08BD9F"])).toEqual(
16
+ "linear-gradient(to bottom, #FF5400 0%, #FF5400 33.33333333333333%, #4978D4 33.33333333333333%, #4978D4 66.66666666666666%, #08BD9F 66.66666666666666%, #08BD9F 100%)"
17
+ );
18
+ });
19
+
20
+ test("four colors", () => {
21
+ expect(getLinearGradient(["#FF5400", "#4978D4", "#08BD9F", "#FFFFFF"])).toEqual(
22
+ "linear-gradient(to bottom, #FF5400 0%, #FF5400 25%, #4978D4 25%, #4978D4 50%, #08BD9F 50%, #08BD9F 75%, #FFFFFF 75%, #FFFFFF 100%)"
23
+ );
24
+ });
25
+
26
+ test("card type colors subset", () => {
27
+ expect(getLinearGradient(["#4A4A4A", "#6A849B", "#99A2AB", "#D40915", "#E72065"])).toEqual(
28
+ "linear-gradient(to bottom, #4A4A4A 0%, #4A4A4A 20%, #6A849B 20%, #6A849B 40%, #99A2AB 40%, #99A2AB 60%, #D40915 60%, #D40915 80%, #E72065 80%, #E72065 100%)"
29
+ );
30
+ });
31
+ });
32
+
33
+ describe("getEnumTextColor", () => {
34
+ test("snapshot", () => {
35
+ expect(getEnumTextColor("#4978D4")).toMatchInlineSnapshot(
36
+ `"color-mix(in srgb, #4978D4 25%, var(--fibery-color-textColor) 75%)"`
37
+ );
38
+ });
39
+ });
@@ -0,0 +1,25 @@
1
+ import {themeVars} from "./theme";
2
+
3
+ export function getLinearGradient(backgroundColors: Array<string>): string {
4
+ const parts = backgroundColors.flatMap((color, index, arr) => [
5
+ `${color} ${(index / arr.length) * 100}%`,
6
+ `${color} ${((index + 1) / arr.length) * 100}%`,
7
+ ]);
8
+ return `linear-gradient(to bottom, ${parts.join(", ")})`;
9
+ }
10
+
11
+ export function getEnumTextColor(color: string) {
12
+ return `color-mix(in srgb, ${color} 25%, ${themeVars.textColor} 75%)`;
13
+ }
14
+
15
+ function themeVarWithAlpha(themeVar: string, alpha: number) {
16
+ return `color-mix(in srgb, ${themeVar} ${Math.round(alpha * 100)}%, transparent)`;
17
+ }
18
+
19
+ export const dropCursorColor = themeVarWithAlpha(themeVars.primary, 0.7);
20
+
21
+ export const shadows = {
22
+ border: `0 0 0 1px ${themeVars.shades.opacity10}`,
23
+ inversedBorder: `0 0 0 1px ${themeVars.lights.opacity10}`,
24
+ videoPreview: "0 4px 30px 4px rgba(0, 0, 0, .15)",
25
+ } as const;
@@ -0,0 +1,232 @@
1
+ import {getDarkenColor, getObjectColorMemoized as getObjectColor, getTextColor} from "./colors-js";
2
+ import {getThemeColors} from "./theme";
3
+
4
+ const testColors = [
5
+ "#FF5400", // red
6
+ "#4978D4", // blue
7
+ "#08BD9F", // green
8
+ "#FBA32F", // yellow/orange
9
+ "#673DB6", // purple
10
+ "#000000", // black
11
+ "#FFFFFF", // white
12
+ ];
13
+
14
+ describe("getDarkenColor", () => {
15
+ test("snapshot", () => {
16
+ expect(testColors.map((color) => getDarkenColor(color))).toEqual([
17
+ "rgb(212,74,29)",
18
+ "rgb(70,98,162)",
19
+ "rgb(82,159,138)",
20
+ "rgb(214,143,56)",
21
+ "rgb(78,46,138)",
22
+ "rgb(0,0,0)",
23
+ "rgb(229,229,229)",
24
+ ]);
25
+ });
26
+ });
27
+
28
+ describe("getTextColor", () => {
29
+ test("snapshot", () => {
30
+ expect(testColors.map((color) => getTextColor(color))).toEqual([
31
+ "rgb(255,84,0)",
32
+ "rgb(73,120,212)",
33
+ "rgb(8,189,159)",
34
+ "rgb(251,163,47)",
35
+ "rgb(103,61,182)",
36
+ "rgb(0,0,0)",
37
+ "rgb(255,255,255)",
38
+ ]);
39
+ });
40
+ });
41
+
42
+ const themes = {
43
+ light: getThemeColors(null, "light"),
44
+ dark: getThemeColors(null, "dark"),
45
+ light2: getThemeColors(null, "light2"),
46
+ };
47
+
48
+ const colors = {
49
+ red: "#FF5400",
50
+ blue: "#4978D4",
51
+ green: "#08BD9F",
52
+ yellow: "#FBA32F",
53
+ purple: "#673DB6",
54
+ black: "#000000",
55
+ white: "#FFFFFF",
56
+ } as const;
57
+
58
+ function mapColors(fn: (color: string) => string) {
59
+ return Object.fromEntries(Object.entries(colors).map(([k, hex]) => [k, fn(hex)]));
60
+ }
61
+
62
+ describe("getEnumBackground", () => {
63
+ test("light", () => {
64
+ expect(mapColors(themes.light.fns.getEnumBackground)).toEqual({
65
+ black: "rgba(0,0,0,0.15)",
66
+ blue: "rgba(73,120,212,0.15)",
67
+ green: "rgba(8,189,159,0.15)",
68
+ purple: "rgba(103,61,182,0.15)",
69
+ red: "rgba(255,84,0,0.15)",
70
+ white: "rgba(255,255,255,0.15)",
71
+ yellow: "rgba(251,163,47,0.15)",
72
+ });
73
+ });
74
+ test("dark", () => {
75
+ expect(mapColors(themes.dark.fns.getEnumBackground)).toEqual({
76
+ black: "rgb(39,39,39)",
77
+ blue: "rgba(73,120,212,0.3)",
78
+ green: "rgba(8,189,159,0.3)",
79
+ purple: "rgba(103,61,182,0.3)",
80
+ red: "rgba(255,84,0,0.3)",
81
+ white: "rgba(255,255,255,0.3)",
82
+ yellow: "rgba(251,163,47,0.3)",
83
+ });
84
+ });
85
+ test("light2 defaults to light", () => {
86
+ expect(mapColors(themes.light2.fns.getEnumBackground)).toEqual(mapColors(themes.light.fns.getEnumBackground));
87
+ });
88
+ });
89
+
90
+ describe("getIconColor", () => {
91
+ test("light", () => {
92
+ expect(mapColors(themes.light.fns.getIconColor)).toEqual({
93
+ black: "rgba(0,0,0,0.95)",
94
+ blue: "rgba(73,120,212,0.95)",
95
+ green: "rgb(7,168,141)",
96
+ purple: "rgba(103,61,182,0.95)",
97
+ red: "rgba(255,84,0,0.95)",
98
+ white: "rgb(148,148,148)",
99
+ yellow: "rgb(219,126,4)",
100
+ });
101
+ });
102
+ test("dark", () => {
103
+ expect(mapColors(themes.dark.fns.getIconColor)).toEqual({
104
+ black: "rgb(130,130,130)",
105
+ blue: "rgb(84,128,215)",
106
+ green: "rgba(8,189,159,0.95)",
107
+ purple: "rgb(145,112,207)",
108
+ red: "rgba(255,84,0,0.95)",
109
+ white: "rgba(255,255,255,0.95)",
110
+ yellow: "rgba(251,163,47,0.95)",
111
+ });
112
+ });
113
+ });
114
+
115
+ describe("getAppIconBackground", () => {
116
+ test("light", () => {
117
+ expect(mapColors(themes.light.fns.getAppIconBackground)).toEqual({
118
+ black: "rgba(0,0,0,0.15)",
119
+ blue: "rgba(73,120,212,0.15)",
120
+ green: "rgba(8,189,159,0.15)",
121
+ purple: "rgba(103,61,182,0.15)",
122
+ red: "rgba(255,84,0,0.15)",
123
+ white: "rgba(255,255,255,0.15)",
124
+ yellow: "rgba(251,163,47,0.15)",
125
+ });
126
+ });
127
+ test("dark", () => {
128
+ expect(mapColors(themes.dark.fns.getAppIconBackground)).toEqual({
129
+ black: "rgb(39,39,39)",
130
+ blue: "rgba(73,120,212,0.3)",
131
+ green: "rgba(8,189,159,0.3)",
132
+ purple: "rgba(103,61,182,0.3)",
133
+ red: "rgba(255,84,0,0.3)",
134
+ white: "rgba(255,255,255,0.3)",
135
+ yellow: "rgba(251,163,47,0.3)",
136
+ });
137
+ });
138
+ test("light2 uses dark fn", () => {
139
+ expect(mapColors(themes.light2.fns.getAppIconBackground)).toEqual(mapColors(themes.dark.fns.getAppIconBackground));
140
+ });
141
+ });
142
+
143
+ describe("getLinkedHighlightBackground", () => {
144
+ test("light", () => {
145
+ expect(mapColors(themes.light.fns.getLinkedHighlightBackground)).toEqual({
146
+ black: "rgba(203,203,203,0.3)",
147
+ blue: "rgba(187,204,239,0.3)",
148
+ green: "rgba(125,220,204,0.3)",
149
+ purple: "rgba(210,198,234,0.3)",
150
+ red: "rgba(255,189,156,0.3)",
151
+ white: "rgba(203,203,203,0.3)",
152
+ yellow: "rgba(252,193,114,0.3)",
153
+ });
154
+ });
155
+ test("dark", () => {
156
+ expect(mapColors(themes.dark.fns.getLinkedHighlightBackground)).toEqual({
157
+ black: "rgba(188,188,188,0.4)",
158
+ blue: "rgba(165,188,234,0.4)",
159
+ green: "rgba(77,208,186,0.4)",
160
+ purple: "rgba(196,180,227,0.4)",
161
+ red: "rgba(255,166,122,0.4)",
162
+ white: "rgba(188,188,188,0.4)",
163
+ yellow: "rgba(251,171,65,0.4)",
164
+ });
165
+ });
166
+ });
167
+
168
+ describe("getLinkedHighlightDecorationBackground", () => {
169
+ test("light", () => {
170
+ expect(mapColors(themes.light.fns.getLinkedHighlightDecorationBackground)).toEqual({
171
+ black: "rgba(188,188,188,0.9)",
172
+ blue: "rgba(165,188,234,0.9)",
173
+ green: "rgba(77,208,186,0.9)",
174
+ purple: "rgba(196,180,227,0.9)",
175
+ red: "rgba(255,166,122,0.9)",
176
+ white: "rgba(188,188,188,0.9)",
177
+ yellow: "rgba(251,171,65,0.9)",
178
+ });
179
+ });
180
+ test("dark", () => {
181
+ expect(mapColors(themes.dark.fns.getLinkedHighlightDecorationBackground)).toEqual({
182
+ black: "rgba(178,178,178,0.9)",
183
+ blue: "rgba(155,178,223,0.9)",
184
+ green: "rgba(65,197,176,0.9)",
185
+ purple: "rgba(186,170,217,0.9)",
186
+ red: "rgba(244,156,113,0.9)",
187
+ white: "rgba(178,178,178,0.9)",
188
+ yellow: "rgba(240,161,55,0.9)",
189
+ });
190
+ });
191
+ });
192
+
193
+ describe("getObjectColor", () => {
194
+ test("empty name returns default", () => {
195
+ expect(getObjectColor("")).toEqual("#999999");
196
+ });
197
+
198
+ test("null/undefined returns default", () => {
199
+ expect(getObjectColor(null)).toEqual("#999999");
200
+ expect(getObjectColor(undefined)).toEqual("#999999");
201
+ });
202
+
203
+ test("typical entity names", () => {
204
+ expect(getObjectColor("Task")).toEqual("#d6895c");
205
+ expect(getObjectColor("Feature")).toEqual("#d47de8");
206
+ expect(getObjectColor("Bug")).toEqual("#cc33c2");
207
+ expect(getObjectColor("User Story")).toEqual("#85d3e0");
208
+ expect(getObjectColor("Epic")).toEqual("#e085cd");
209
+ });
210
+
211
+ test("database names", () => {
212
+ expect(getObjectColor("Product Management")).toEqual("#66b8ff");
213
+ expect(getObjectColor("Engineering")).toEqual("#ff002f");
214
+ expect(getObjectColor("Design")).toEqual("#0d18f2");
215
+ });
216
+
217
+ test("special characters", () => {
218
+ expect(getObjectColor("Task #1")).toEqual("#e87d89");
219
+ expect(getObjectColor("Feature (v2)")).toEqual("#f09e75");
220
+ expect(getObjectColor("Bug-123")).toEqual("#66c7ff");
221
+ });
222
+
223
+ test("long names", () => {
224
+ expect(getObjectColor("This is a very long entity name that might be used in some cases")).toEqual("#ff6694");
225
+ });
226
+
227
+ test("unicode names", () => {
228
+ expect(getObjectColor("Задача")).toEqual("#cc7a33");
229
+ expect(getObjectColor("機能")).toEqual("#72e619");
230
+ expect(getObjectColor("🚀 Feature")).toEqual("#dfe619");
231
+ });
232
+ });
@@ -0,0 +1,107 @@
1
+ import ColorHash from "color-hash";
2
+ import {ThemeMode} from "../theme-settings";
3
+ import {
4
+ clampRgb,
5
+ darkenChroma,
6
+ desaturateChroma,
7
+ produceColor,
8
+ set,
9
+ setAlpha,
10
+ setLuminanceChroma,
11
+ toCss,
12
+ } from "../color-utils";
13
+ import memoize from "lodash/memoize";
14
+ import {getOpacities} from "./alpha";
15
+
16
+ export const getDarkenColor = memoize((color: string): string => {
17
+ return produceColor(color, [(c) => darkenChroma(c, 0.5), clampRgb, (c) => desaturateChroma(c, 0.8)]);
18
+ });
19
+
20
+ export const getTextColor = memoize((color: string): string => {
21
+ return toCss(color);
22
+ });
23
+
24
+ export const getObjectColorMemoized = memoize((name) => {
25
+ if (!name) {
26
+ return "#999999";
27
+ }
28
+ const colorHash = new ColorHash({
29
+ lightness: [0.5, 0.6, 0.7],
30
+ saturation: [0.6, 0.7, 0.8, 0.9, 1],
31
+ });
32
+ return colorHash.hex(name);
33
+ });
34
+
35
+ export type TypeColors = ReturnType<typeof getTypeColors>;
36
+
37
+ export function getTypeColors(color: string, theme: ThemeMode) {
38
+ const makeBrightenPrimary = () => {
39
+ return produceColor(color, [
40
+ (c) => setLuminanceChroma(c, 0.92),
41
+ (c) => desaturateChroma(c, 0.1),
42
+ (c) => setAlpha(c, 0.98),
43
+ ]);
44
+ };
45
+
46
+ const makeHeaderBg = () => {
47
+ return produceColor(color, [
48
+ (c) => setLuminanceChroma(c, 0.7),
49
+ (c) => desaturateChroma(c, 0.5),
50
+ (c) => set(c, "hsl.h", (h) => h + 2),
51
+ ]);
52
+ };
53
+
54
+ const makeLightDetailsBg = () => {
55
+ return produceColor(color, [(c) => setAlpha(c, 0.5), (c) => setLuminanceChroma(c, 0.85)]);
56
+ };
57
+
58
+ const makeDarkDetailsBg = () => {
59
+ return produceColor(color, [
60
+ (c) => setAlpha(c, 0.03),
61
+ (c) => setLuminanceChroma(c, 0.8),
62
+ (c) => desaturateChroma(c, 0.8),
63
+ (c) => darkenChroma(c, 0.1),
64
+ ]);
65
+ };
66
+
67
+ const makeFocus = () => {
68
+ return produceColor(color, (c) => setAlpha(c, 0.25));
69
+ };
70
+
71
+ const makeSeparator = () => {
72
+ return produceColor(color, (c) => setAlpha(c, 0.3));
73
+ };
74
+
75
+ const lightTypeColors = {
76
+ primary: color,
77
+ opacity: getOpacities(color),
78
+ darkenPrimary: getDarkenColor(color),
79
+ brightenPrimary: makeBrightenPrimary(),
80
+ headerBg: makeHeaderBg(),
81
+ detailsBg: makeLightDetailsBg(),
82
+ focus: makeFocus(),
83
+ separator: makeSeparator(),
84
+ };
85
+
86
+ const darkTypeColors = {
87
+ primary: color,
88
+ opacity: getOpacities(color),
89
+ darkenPrimary: getDarkenColor(color),
90
+ brightenPrimary: makeBrightenPrimary(),
91
+ headerBg: makeHeaderBg(),
92
+ detailsBg: makeDarkDetailsBg(),
93
+ focus: makeFocus(),
94
+ separator: makeSeparator(),
95
+ };
96
+
97
+ switch (theme) {
98
+ case "light":
99
+ return lightTypeColors;
100
+ case "dark":
101
+ return darkTypeColors;
102
+ case "light2":
103
+ return lightTypeColors;
104
+ default:
105
+ return lightTypeColors;
106
+ }
107
+ }
@@ -0,0 +1,74 @@
1
+ import {pickColors, type ThemeDefs} from "./colors";
2
+
3
+ describe("pickColors", () => {
4
+ const themeDefs = {
5
+ colors: {
6
+ mode: ["light", "dark"],
7
+ colorAI: ["hsla(271, 57%, 61%, 1)", "hsla(272, 43%, 50%, 1)"],
8
+ transparent: ["rgba(255, 255, 255, 0)", "rgba(0, 0, 0, 0)"],
9
+ lights: [{opacity5: "light5"}, {opacity5: "dark-light5"}],
10
+ shades: [{opacity5: "shade5"}, {opacity5: "dark-shade5"}],
11
+ separators: [{opacity5: "separator5"}, {opacity5: "dark-separator5"}],
12
+ } as unknown as ThemeDefs["colors"],
13
+ fns: {
14
+ getIconColor: {light: (_: string) => "IconColor", dark: (_: string) => "DarkIconColor"},
15
+ } as unknown as ThemeDefs["fns"],
16
+ } satisfies Partial<ThemeDefs> as ThemeDefs;
17
+
18
+ test("light", () => {
19
+ expect(pickColors(themeDefs, "light")).toMatchInlineSnapshot(`
20
+ {
21
+ "colorAI": "hsla(271, 57%, 61%, 1)",
22
+ "lights": {
23
+ "opacity5": "light5",
24
+ },
25
+ "mode": "light",
26
+ "separators": {
27
+ "opacity5": "separator5",
28
+ },
29
+ "shades": {
30
+ "opacity5": "shade5",
31
+ },
32
+ "transparent": "rgba(255, 255, 255, 0)",
33
+ }
34
+ `);
35
+ });
36
+
37
+ test("dark", () => {
38
+ expect(pickColors(themeDefs, "dark")).toMatchInlineSnapshot(`
39
+ {
40
+ "colorAI": "hsla(272, 43%, 50%, 1)",
41
+ "lights": {
42
+ "opacity5": "dark-light5",
43
+ },
44
+ "mode": "dark",
45
+ "separators": {
46
+ "opacity5": "dark-separator5",
47
+ },
48
+ "shades": {
49
+ "opacity5": "dark-shade5",
50
+ },
51
+ "transparent": "rgba(0, 0, 0, 0)",
52
+ }
53
+ `);
54
+ });
55
+
56
+ test("light2 overrides mode", () => {
57
+ expect(pickColors(themeDefs, "light2")).toMatchInlineSnapshot(`
58
+ {
59
+ "colorAI": "hsla(271, 57%, 61%, 1)",
60
+ "lights": {
61
+ "opacity5": "light5",
62
+ },
63
+ "mode": "light2",
64
+ "separators": {
65
+ "opacity5": "separator5",
66
+ },
67
+ "shades": {
68
+ "opacity5": "shade5",
69
+ },
70
+ "transparent": "rgba(255, 255, 255, 0)",
71
+ }
72
+ `);
73
+ });
74
+ });