@fibery/ui-kit 2.1.0 → 2.1.1
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.
- package/CHANGELOG.md +8 -0
- package/package.json +6 -3
- package/src/@types/react-color.d.ts +36 -0
- package/src/antd/{Tabs.js → Tabs.tsx} +5 -2
- package/src/color-filter.test.ts +24 -65
- package/src/color-filter.ts +62 -93
- package/src/color-picker/{ColorPickerOrLoader.js → ColorPickerOrLoader.tsx} +1 -1
- package/src/color-picker/{index.js → index.tsx} +32 -6
- package/src/color-utils.ts +3 -9
- package/src/design-system/colors-css.ts +16 -0
- package/src/design-system/colors.ts +23 -45
- package/src/design-system/def.ts +17 -0
- package/src/design-system/theme.ts +6 -2
- package/src/design-system/types.ts +102 -0
- package/src/design-system/vars.test.ts +9 -2
- package/src/design-system/vars.ts +4 -2
- package/src/design-system.ts +9 -1
- package/src/emoji-picker/emoji-picker-content-with-color.tsx +3 -3
- package/src/heat.ts +8 -0
- package/src/highlight-colors.test.ts +68 -0
- package/src/highlight-colors.ts +91 -0
- package/src/link-input/components/{AntTextAreaWithCustomReadState.js → AntTextAreaWithCustomReadState.tsx} +19 -8
- package/src/link-input/index.tsx +94 -0
- package/src/link-input/{utils.js → utils.ts} +1 -6
- package/src/number-input/decimal.ts +50 -0
- package/src/number-input/utils.ts +2 -2
- package/src/palette-generator.test.ts +2 -1
- package/src/palette-generator.ts +8 -26
- package/src/palettes/_.ts +2 -2
- package/src/palettes/common.ts +31 -0
- package/src/palettes/diff-colors.ts +29 -12
- package/src/palettes/inspect.defs.colors.neutral-arch.test.ts +72 -36
- package/src/palettes/inspect.defs.colors.neutral-user.test.ts +72 -36
- package/src/palettes/inspect.defs.colors.warm-arch.test.ts +226 -190
- package/src/palettes/inspect.defs.colors.warm-user.test.ts +226 -190
- package/src/palettes/neutral-arch.ts +3 -3
- package/src/palettes/neutral-user.ts +3 -3
- package/src/palettes/neutral.ts +4 -2
- package/src/palettes/warm-arch.ts +3 -3
- package/src/palettes/warm-user.ts +3 -3
- package/src/palettes/warm.ts +4 -3
- package/src/pretty-size.ts +5 -2
- package/src/root-theme-provider.test.tsx +1 -1
- package/src/scale-generator.ts +1 -32
- package/src/select/components/menu-list-virtua.tsx +127 -0
- package/src/select/index.tsx +1 -1
- package/src/select/reflection.ts +22 -0
- package/src/select/select.tsx +1 -0
- package/src/static-palettes.ts +2 -1
- package/src/thematic-color-picker.tsx +8 -1
- package/src/thematic-constants.tsx +2 -0
- package/src/thematic-controls.tsx +9 -1
- package/src/thematic-cvd.tsx +73 -0
- package/src/thematic-highlights.tsx +49 -0
- package/src/thematic-scales.tsx +8 -6
- package/src/thematic-state.ts +37 -16
- package/src/thematic.tsx +298 -305
- package/src/theme-provider.tsx +9 -3
- package/src/theme-styles.ts +2 -1
- package/src/link-input/index.js +0 -89
- package/src/number-input/decimal.js +0 -61
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
|
-
import {
|
|
3
|
-
import {darkenChroma, hslFrom, produceColor, serializeHexVariadic, setAlpha, setLuminanceChroma} from "../color-utils";
|
|
2
|
+
import {darkenChroma, hslFrom, produceColor, setAlpha, setLuminanceChroma} from "../color-utils";
|
|
4
3
|
import {ThemeMode} from "../theme-settings";
|
|
5
4
|
import {alpha, opacity, transparent} from "./alpha";
|
|
6
5
|
import {getDarkenColor, makeAdaptiveBgFg} from "./colors-js";
|
|
7
6
|
import {cssFromFilter, makeColorFilter} from "../color-filter";
|
|
7
|
+
import {highlightColors} from "../highlight-colors";
|
|
8
|
+
import type {Def, Defs, ThemePalette} from "./types";
|
|
9
|
+
import {mapDef} from "./def";
|
|
10
|
+
import mapValues from "lodash/mapValues";
|
|
8
11
|
|
|
9
12
|
export const cardTypeColors = [
|
|
10
13
|
"#4A4A4A",
|
|
@@ -57,9 +60,6 @@ const both =
|
|
|
57
60
|
(map: (_: string) => string) =>
|
|
58
61
|
([light, dark]: Def): Def => [map(light), map(dark)];
|
|
59
62
|
|
|
60
|
-
type Def = [light: string, dark: string];
|
|
61
|
-
type Defs = Record<string, Def>;
|
|
62
|
-
|
|
63
63
|
export function defsFromPalette({
|
|
64
64
|
key,
|
|
65
65
|
white,
|
|
@@ -76,20 +76,20 @@ export function defsFromPalette({
|
|
|
76
76
|
tealDark,
|
|
77
77
|
yellow,
|
|
78
78
|
yellowDark,
|
|
79
|
-
deneutralize,
|
|
79
|
+
deneutralize: deneutralizeFilter,
|
|
80
|
+
highlight,
|
|
80
81
|
}: ThemePalette) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
82
|
+
const [deneutralizeL, deneutralizeD] = mapDef(deneutralizeFilter, makeColorFilter);
|
|
83
|
+
const deneutralize = (def: Def) => mapDef(def, deneutralizeL, deneutralizeD);
|
|
83
84
|
|
|
84
85
|
// beg(TODO pull into ThemePalette)
|
|
85
86
|
const blackish = produceColor(black, (x) => {
|
|
86
87
|
const hsl = hslFrom(x);
|
|
87
88
|
return {...hsl, l: (hsl.l ?? 0) + 0.05};
|
|
88
89
|
});
|
|
89
|
-
|
|
90
|
-
const purpleDark9 =
|
|
91
|
-
const blue9 =
|
|
92
|
-
const blueDark9 = shift("rgba(151, 177, 237, 1)");
|
|
90
|
+
|
|
91
|
+
const [purple9, purpleDark9] = deneutralize(["rgb(157, 99, 212)", "rgb(131, 73, 182)"]);
|
|
92
|
+
const [blue9, blueDark9] = deneutralize(["rgba(56, 105, 204, 1)", "rgba(151, 177, 237, 1)"]);
|
|
93
93
|
// end(TODO pull into ThemePalette)
|
|
94
94
|
|
|
95
95
|
// whiteA = surface direction (white on light / black on dark) — blends INTO bg
|
|
@@ -413,9 +413,9 @@ export function defsFromPalette({
|
|
|
413
413
|
const bgActionsMenu = raised.level1;
|
|
414
414
|
const bgActionsMenuItemHover = surface.level4_5;
|
|
415
415
|
|
|
416
|
-
const iconFromFg = (fg: string) => ({
|
|
417
|
-
fg: same(fg),
|
|
418
|
-
bg: same(alpha(fg, 0.2)),
|
|
416
|
+
const iconFromFg = (fg: string): {fg: Def; bg: Def} => ({
|
|
417
|
+
fg: deneutralize(same(fg)),
|
|
418
|
+
bg: deneutralize(same(alpha(fg, 0.2))),
|
|
419
419
|
});
|
|
420
420
|
|
|
421
421
|
const swatches = {
|
|
@@ -426,9 +426,9 @@ export function defsFromPalette({
|
|
|
426
426
|
yellow: cardTypeColors[14],
|
|
427
427
|
};
|
|
428
428
|
|
|
429
|
-
const iconMySpace = iconFromFg(
|
|
430
|
-
const iconPrivate = iconFromFg(
|
|
431
|
-
const iconFavorites = iconFromFg(
|
|
429
|
+
const iconMySpace = iconFromFg(swatches.grayLight);
|
|
430
|
+
const iconPrivate = iconFromFg(swatches.green);
|
|
431
|
+
const iconFavorites = iconFromFg(swatches.yellow);
|
|
432
432
|
const defaultCardTypeColorUppercaseHex = same(
|
|
433
433
|
(key.includes("arch") ? swatches.yellow : key.includes("neutral") ? swatches.indigo : swatches.teal).toUpperCase()
|
|
434
434
|
);
|
|
@@ -436,8 +436,11 @@ export function defsFromPalette({
|
|
|
436
436
|
const iconBadge = iconFromFg(base[10]);
|
|
437
437
|
|
|
438
438
|
const colors = {
|
|
439
|
-
mode: ["light", "dark"],
|
|
440
439
|
...shadows,
|
|
440
|
+
...mapValues(highlightColors(highlight, surface.level0), deneutralize),
|
|
441
|
+
colorPickerCssFilter: mapDef(deneutralizeFilter, cssFromFilter),
|
|
442
|
+
|
|
443
|
+
mode: ["light", "dark"],
|
|
441
444
|
|
|
442
445
|
// Accent background tints (light → strong)
|
|
443
446
|
colorAccentBgSubtle: same(alpha(accent[9], 0.1)),
|
|
@@ -893,31 +896,6 @@ export function defsFromPalette({
|
|
|
893
896
|
colorMermaidPrimaryBorderColor: fg.muted,
|
|
894
897
|
colorMermaidLineColor: [base[10], baseDark[9]],
|
|
895
898
|
|
|
896
|
-
// Text highlight backgrounds (shifted for warm palette)
|
|
897
|
-
highlightBgYellow: [shift("rgba(250, 240, 185, 1)"), shift("rgba(88, 72, 18, 1)")],
|
|
898
|
-
highlightBgGreen: [shift("rgba(208, 242, 187, 1)"), shift("rgba(50, 88, 51, 1)")],
|
|
899
|
-
highlightBgAquamarine: [shift("rgba(195, 247, 193, 1)"), shift("rgba(15, 75, 52, 1)")],
|
|
900
|
-
highlightBgAqua: [shift("rgba(198, 242, 235, 1)"), shift("rgba(45, 87, 83, 1)")],
|
|
901
|
-
highlightBgBlue: [shift("rgba(208, 235, 250, 1)"), shift("rgba(45, 54, 90, 1)")],
|
|
902
|
-
highlightBgPurple: [shift("rgba(242, 228, 250, 1)"), shift("rgba(82, 50, 82, 1)")],
|
|
903
|
-
highlightBgPink: [shift("rgba(250, 228, 240, 1)"), shift("rgba(108, 50, 85, 1)")],
|
|
904
|
-
highlightBgRed: [shift("rgba(250, 224, 221, 1)"), shift("rgba(95, 44, 49, 1)")],
|
|
905
|
-
highlightBgGrey: [shift("rgba(222, 227, 233, 1)"), shift("rgba(62, 63, 78, 1)")],
|
|
906
|
-
|
|
907
|
-
// Text highlight backgrounds (shifted for warm palette)
|
|
908
|
-
highlightFgYellow: [shift("rgba(195, 155, 45, 1)"), shift("rgba(200, 168, 85, 1)")],
|
|
909
|
-
highlightFgGreen: [shift("rgba(80, 165, 65, 1)"), shift("rgba(88, 172, 78, 1)")],
|
|
910
|
-
highlightFgAquamarine: [shift("rgba(40, 165, 115, 1)"), shift("rgba(52, 172, 128, 1)")],
|
|
911
|
-
highlightFgAqua: [shift("rgba(35, 155, 150, 1)"), shift("rgba(52, 168, 158, 1)")],
|
|
912
|
-
highlightFgBlue: [shift("rgba(55, 125, 205, 1)"), shift("rgba(72, 135, 200, 1)")],
|
|
913
|
-
highlightFgPurple: [shift("rgba(150, 85, 205, 1)"), shift("rgba(155, 90, 200, 1)")],
|
|
914
|
-
highlightFgPink: [shift("rgba(205, 85, 148, 1)"), shift("rgba(200, 90, 152, 1)")],
|
|
915
|
-
highlightFgRed: [shift("rgba(205, 85, 80, 1)"), shift("rgba(200, 90, 85, 1)")],
|
|
916
|
-
highlightFgGrey: [shift("rgba(115, 125, 142, 1)"), shift("rgba(135, 140, 160, 1)")],
|
|
917
|
-
|
|
918
|
-
// Color picker CSS filter (applied as CSS filter property to warm the picker UI)
|
|
919
|
-
colorPickerCssFilter: same(colorPickerCssFilter),
|
|
920
|
-
|
|
921
899
|
//Whiteboard
|
|
922
900
|
colorBgWhiteboard: surface.level0,
|
|
923
901
|
colorGridDotWhiteboard: bgTabFocus,
|
|
@@ -1056,7 +1034,7 @@ export function defsFromPalette({
|
|
|
1056
1034
|
|
|
1057
1035
|
const adaptive = makeAdaptiveBgFg(white, blackish);
|
|
1058
1036
|
const fns = {
|
|
1059
|
-
deneutralize: {light:
|
|
1037
|
+
deneutralize: {light: deneutralizeL, dark: deneutralizeD},
|
|
1060
1038
|
getEnumBackground: {light: adaptive.bg[0], dark: adaptive.bg[1]},
|
|
1061
1039
|
getAppIconBackground: {light: adaptive.bg[0], dark: adaptive.bg[1], light2: adaptive.bg[1]},
|
|
1062
1040
|
getIconColor: {light: adaptive.fg[0], dark: adaptive.fg[1]},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {ModeDef} from "./types";
|
|
2
|
+
|
|
3
|
+
type Mapper<T, U> = (_: T) => U;
|
|
4
|
+
|
|
5
|
+
export function mapDef<T, U>(pair: ModeDef<T>, map0: Mapper<T, U>, map1?: Mapper<T, U>): ModeDef<U>;
|
|
6
|
+
export function mapDef<T, U>(
|
|
7
|
+
pair: ModeDef<T> | undefined,
|
|
8
|
+
map0: Mapper<T | undefined, U>,
|
|
9
|
+
map1?: Mapper<T | undefined, U>
|
|
10
|
+
): ModeDef<U>;
|
|
11
|
+
export function mapDef<T, U>(
|
|
12
|
+
pair: ModeDef<T> | undefined,
|
|
13
|
+
map0: Mapper<T | undefined, U>,
|
|
14
|
+
map1: Mapper<T | undefined, U> = map0
|
|
15
|
+
): ModeDef<U> {
|
|
16
|
+
return [map0(pair?.[0]), map1(pair?.[1])];
|
|
17
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {defsFromPalette, sideFromDefs, ThemeDefs} from "./colors";
|
|
2
2
|
import {ThemeMode} from "../theme-settings";
|
|
3
|
-
import {selectPalette
|
|
3
|
+
import {selectPalette} from "../palette-generator";
|
|
4
4
|
import memoize from "lodash/memoize";
|
|
5
5
|
import {varPrefix, varsFromTheme} from "./vars";
|
|
6
6
|
import {typedObjectEntries} from "@fibery/helpers/utils/object";
|
|
7
7
|
import {makeSelect} from "./colors-js";
|
|
8
|
+
import type {ThemePalette} from "./types";
|
|
9
|
+
import {LRUCache} from "lru-cache";
|
|
8
10
|
|
|
9
11
|
// intentionally non-memoized implementation detail for theme-styles use-case
|
|
10
12
|
export function themeFromDefs(defs: ThemeDefs, mode: ThemeMode) {
|
|
@@ -26,8 +28,9 @@ function themeFromPaletteImpl(palette: ThemePalette, mode: ThemeMode) {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export const themeFromPalette = memoize(themeFromPaletteImpl, (palette, mode) => `${palette.key}-${mode}`);
|
|
31
|
+
themeFromPalette.cache = new LRUCache({max: 4});
|
|
29
32
|
|
|
30
|
-
export const themeVars = varsFromTheme(
|
|
33
|
+
export const themeVars = varsFromTheme(themeFromPalette(selectPalette(), "light"));
|
|
31
34
|
|
|
32
35
|
export type Styles = Record<string, string>;
|
|
33
36
|
|
|
@@ -42,3 +45,4 @@ export const stylesFromTheme = memoize(
|
|
|
42
45
|
(theme: Pick<Theme, "key" | "colors">) => stylesFromThemeImpl(theme),
|
|
43
46
|
(theme) => theme.key
|
|
44
47
|
);
|
|
48
|
+
stylesFromTheme.cache = new LRUCache({max: 4});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type {ColorFilterSpec} from "../color-filter";
|
|
2
|
+
|
|
3
|
+
export type ModeDef<T> = [light: T, dark: T];
|
|
4
|
+
|
|
5
|
+
export type Def = ModeDef<string>; // ColorModeDef?
|
|
6
|
+
export type Defs = Record<string, Def>; // ColorModeDefs?
|
|
7
|
+
|
|
8
|
+
export type OneBasedScale<T> = readonly [
|
|
9
|
+
base00: null,
|
|
10
|
+
step01: T,
|
|
11
|
+
step02: T,
|
|
12
|
+
step03: T,
|
|
13
|
+
step04: T,
|
|
14
|
+
step05: T,
|
|
15
|
+
step06: T,
|
|
16
|
+
step07: T,
|
|
17
|
+
step08: T,
|
|
18
|
+
step09: T,
|
|
19
|
+
step10: T,
|
|
20
|
+
step11: T,
|
|
21
|
+
step12: T,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export type ZeroBasedScale<T> = readonly [
|
|
25
|
+
step01: T,
|
|
26
|
+
step02: T,
|
|
27
|
+
step03: T,
|
|
28
|
+
step04: T,
|
|
29
|
+
step05: T,
|
|
30
|
+
step06: T,
|
|
31
|
+
step07: T,
|
|
32
|
+
step08: T,
|
|
33
|
+
step09: T,
|
|
34
|
+
step10: T,
|
|
35
|
+
step11: T,
|
|
36
|
+
step12: T,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export type ColorScale = OneBasedScale<string>;
|
|
40
|
+
|
|
41
|
+
type BgDef = ModeDef<{Lc: number; c: number}>;
|
|
42
|
+
type FgDependantOnBgDef = ModeDef<{c: number; Lc: number}>;
|
|
43
|
+
|
|
44
|
+
// merge with / derive from text-editor implementation array
|
|
45
|
+
export type HighlightHueName = "yellow" | "green" | "aquamarine" | "aqua" | "blue" | "purple" | "pink" | "red" | "grey";
|
|
46
|
+
export type HighlightHueNameCap =
|
|
47
|
+
| "Yellow"
|
|
48
|
+
| "Green"
|
|
49
|
+
| "Aquamarine"
|
|
50
|
+
| "Aqua"
|
|
51
|
+
| "Blue"
|
|
52
|
+
| "Purple"
|
|
53
|
+
| "Pink"
|
|
54
|
+
| "Red"
|
|
55
|
+
| "Grey";
|
|
56
|
+
|
|
57
|
+
type HighlightIntensityDefs = {
|
|
58
|
+
bg: BgDef;
|
|
59
|
+
fg: FgDependantOnBgDef;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type HighlightIntensityOverrides = {
|
|
63
|
+
bg: ModeDef<Partial<{Lc: number; c: number}>>;
|
|
64
|
+
fg: ModeDef<Partial<{c: number; Lc: number}>>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type HighlightDefs = HighlightIntensityDefs & {
|
|
68
|
+
hues: Record<HighlightHueName, number>;
|
|
69
|
+
overrides?: Partial<Record<HighlightHueName, Partial<HighlightIntensityOverrides>>>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ThemePaletteBase = {
|
|
73
|
+
highlight: HighlightDefs;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type ThemePaletteCore = ThemePaletteBase & {
|
|
77
|
+
deneutralize?: ModeDef<ColorFilterSpec>;
|
|
78
|
+
|
|
79
|
+
white: string;
|
|
80
|
+
black: string;
|
|
81
|
+
whiteA: ColorScale;
|
|
82
|
+
blackA: ColorScale;
|
|
83
|
+
|
|
84
|
+
base: ColorScale;
|
|
85
|
+
baseDark: ColorScale;
|
|
86
|
+
|
|
87
|
+
red: ColorScale;
|
|
88
|
+
redDark: ColorScale;
|
|
89
|
+
|
|
90
|
+
teal: ColorScale;
|
|
91
|
+
tealDark: ColorScale;
|
|
92
|
+
|
|
93
|
+
yellow: ColorScale;
|
|
94
|
+
yellowDark: ColorScale;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type ThemePaletteKeyless = ThemePaletteCore & {
|
|
98
|
+
accent: ColorScale;
|
|
99
|
+
accentDark: ColorScale;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type ThemePalette = ThemePaletteKeyless & {key: string};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {themeFromPalette} from "./theme";
|
|
2
2
|
import {resolveColorArg, varsFromTheme} from "./vars";
|
|
3
3
|
import {selectPalette} from "../palette-generator";
|
|
4
|
-
import {cssFromUnsafe, getAlpha, getLuminance,
|
|
4
|
+
import {cssFromUnsafe, getAlpha, getLuminance, produceColor, serializeHexVariadic} from "../color-utils";
|
|
5
5
|
|
|
6
6
|
test("varsFromTheme", () => {
|
|
7
7
|
const palette = selectPalette();
|
|
@@ -13,6 +13,14 @@ test("varsFromTheme", () => {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
describe("resolveColorArg — CSS var guard", () => {
|
|
16
|
+
test("null from unsafe external world is identity", () => {
|
|
17
|
+
expect(resolveColorArg(null, "test")).toBe(null);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("undefined from unsafe external world is identity", () => {
|
|
21
|
+
expect(resolveColorArg(undefined, "test")).toBe(undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
16
24
|
it("passes through plain colors", () => {
|
|
17
25
|
expect(resolveColorArg("#ff0000", "test")).toBe("#ff0000");
|
|
18
26
|
expect(resolveColorArg("rgb(0,0,0)", "test")).toBe("rgb(0,0,0)");
|
|
@@ -54,7 +62,6 @@ describe("resolveColorArg — CSS var guard", () => {
|
|
|
54
62
|
const spy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
55
63
|
|
|
56
64
|
expect(cssFromUnsafe("var(--fibery-color-e2e)")).toMatchInlineSnapshot(`"rgb(255, 84, 0)"`);
|
|
57
|
-
expect(isValidColor("var(--fibery-color-e2e)")).toBe(true);
|
|
58
65
|
expect(getLuminance("var(--fibery-color-e2e)")).toBeGreaterThan(0);
|
|
59
66
|
expect(getAlpha("var(--fibery-color-e2e)")).toBe(1);
|
|
60
67
|
expect(serializeHexVariadic("var(--fibery-color-e2e)")).toMatchInlineSnapshot(`"#ff5400"`);
|
|
@@ -43,8 +43,10 @@ function tryResolveFromDOM(value: string): string | undefined {
|
|
|
43
43
|
return resolved || undefined;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export function resolveColorArg(value: string, fnName: string): string
|
|
47
|
-
|
|
46
|
+
export function resolveColorArg(value: string, fnName: string): string;
|
|
47
|
+
export function resolveColorArg(value: string | null | undefined, fnName: string): string | null | undefined;
|
|
48
|
+
export function resolveColorArg(value: string | null | undefined, fnName: string): string | null | undefined {
|
|
49
|
+
if (!value?.startsWith("var(")) {
|
|
48
50
|
return value;
|
|
49
51
|
}
|
|
50
52
|
|
package/src/design-system.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
export type * from "./design-system/types";
|
|
1
2
|
export {opacity, alpha} from "./design-system/alpha";
|
|
2
3
|
export {transition, durations, tooltipDelay, transitionDuration, transitions} from "./design-system/animation";
|
|
3
4
|
export {cardTypeColors, type ThemeDefs} from "./design-system/colors";
|
|
4
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
getLinearGradient,
|
|
7
|
+
getEnumTextColor,
|
|
8
|
+
getBgSwatchBorderColor,
|
|
9
|
+
getBgSwatchFillColor,
|
|
10
|
+
dropCursorColor,
|
|
11
|
+
shadows,
|
|
12
|
+
} from "./design-system/colors-css";
|
|
5
13
|
export {getDarkenColor, getTextColor, getObjectColorMemoized} from "./design-system/colors-js";
|
|
6
14
|
export {dateFormat, dateTimeFormat, timeFormat} from "./design-system/date";
|
|
7
15
|
export {
|
|
@@ -84,10 +84,10 @@ export const EmojiPickerContentWithColor: React.FC<EmojiPickerContentWithColorPr
|
|
|
84
84
|
<div className={colorPickerWrapperCss}>
|
|
85
85
|
<ColorPicker
|
|
86
86
|
colors={colors}
|
|
87
|
-
getSwatchHoverColor={getHoverColor}
|
|
87
|
+
getSwatchHoverColor={getHoverColor!}
|
|
88
88
|
getColorOverrideFn={getColorOverrideFn}
|
|
89
|
-
color={color}
|
|
90
|
-
onChangeComplete={onColorSelect}
|
|
89
|
+
color={color!}
|
|
90
|
+
onChangeComplete={onColorSelect!}
|
|
91
91
|
/>
|
|
92
92
|
</div>
|
|
93
93
|
</CollapsibleContent>
|
package/src/heat.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {interpolate, type Oklch} from "culori";
|
|
2
|
+
|
|
3
|
+
export function adjustHeat(color: Oklch, amount: number): Oklch {
|
|
4
|
+
const source = color;
|
|
5
|
+
const target = {...source, h: amount < 0 ? 264 : 70};
|
|
6
|
+
|
|
7
|
+
return interpolate([source, target], "oklch")(Math.abs(amount));
|
|
8
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {converter, parse} from "culori";
|
|
2
|
+
import {calcContrast} from "apcach";
|
|
3
|
+
import {bgKeyFrom, fgOnBgKeyFrom, highlightColors, highlightHueNames} from "./highlight-colors";
|
|
4
|
+
import {common} from "./palettes/common";
|
|
5
|
+
import {locateThemeBundle} from "./theme-styles";
|
|
6
|
+
import {oklchFrom} from "./color-utils";
|
|
7
|
+
|
|
8
|
+
const highlight = common.highlight;
|
|
9
|
+
const surface = locateThemeBundle(false).defs.colors.panelBg;
|
|
10
|
+
const generated = highlightColors(highlight, surface);
|
|
11
|
+
|
|
12
|
+
function calcLc(fg: string, bg: string) {
|
|
13
|
+
return Math.abs(calcContrast(fg, bg, "apca"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function withBgLc(Lc: number) {
|
|
17
|
+
return highlightColors({...highlight, bg: highlight.bg.map((x) => ({...x, Lc})) as typeof highlight.bg}, surface);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("highlight-colors", () => {
|
|
21
|
+
it("all values are parseable sRGB colors", () => {
|
|
22
|
+
for (const [, [light, dark]] of Object.entries(generated)) {
|
|
23
|
+
expect(parse(light)).toBeDefined();
|
|
24
|
+
expect(parse(dark)).toBeDefined();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const tolerance = 1.5;
|
|
29
|
+
it("fg Lc contrast from bg within ±" + tolerance, () => {
|
|
30
|
+
for (const name of highlightHueNames) {
|
|
31
|
+
const bg = generated[bgKeyFrom(name)];
|
|
32
|
+
const fg = generated[fgOnBgKeyFrom(name)];
|
|
33
|
+
const targetL = highlight.overrides?.[name]?.fg?.[0]?.Lc ?? highlight.fg[0].Lc;
|
|
34
|
+
const targetD = highlight.overrides?.[name]?.fg?.[1]?.Lc ?? highlight.fg[1].Lc;
|
|
35
|
+
expect(calcLc(fg[0], bg[0])).toBeGreaterThan(targetL - tolerance);
|
|
36
|
+
expect(calcLc(fg[0], bg[0])).toBeLessThan(targetL + tolerance);
|
|
37
|
+
expect(calcLc(fg[1], bg[1])).toBeGreaterThan(targetD - tolerance);
|
|
38
|
+
expect(calcLc(fg[1], bg[1])).toBeLessThan(targetD + tolerance);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("light bg colors are distinct per hue (not all white)", () => {
|
|
43
|
+
const lightBgs = highlightHueNames.map((n) => generated[bgKeyFrom(n)][0]);
|
|
44
|
+
const unique = new Set(lightBgs);
|
|
45
|
+
expect(unique.size).toBeGreaterThan(1);
|
|
46
|
+
for (const bg of lightBgs) {
|
|
47
|
+
expect(bg).not.toBe("#ffffff");
|
|
48
|
+
expect(bg).not.toBe("rgb(255, 255, 255)");
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("bridge is continuous at Lc=8 boundary", () => {
|
|
53
|
+
const below = withBgLc(7.9);
|
|
54
|
+
const above = withBgLc(8.1);
|
|
55
|
+
for (const name of highlightHueNames) {
|
|
56
|
+
const bgBelow = oklchFrom(parse(below[bgKeyFrom(name)][0])!);
|
|
57
|
+
const bgAbove = oklchFrom(parse(above[bgKeyFrom(name)][0])!);
|
|
58
|
+
expect(Math.abs((bgBelow?.l ?? 0) - (bgAbove?.l ?? 0))).toBeLessThan(0.01);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("grey bg is near-neutral (c < 0.025)", () => {
|
|
63
|
+
const lightBg = converter("oklch")(parse(generated.highlightBgGrey[0])!);
|
|
64
|
+
const darkBg = converter("oklch")(parse(generated.highlightBgGrey[1])!);
|
|
65
|
+
expect(lightBg?.c).toBeLessThan(0.025);
|
|
66
|
+
expect(darkBg?.c).toBeLessThan(0.025);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {apcach, crToBg} from "apcach";
|
|
2
|
+
import {clampChroma, formatRgb} from "culori";
|
|
3
|
+
import {oklchFrom} from "./color-utils";
|
|
4
|
+
import type {Def, HighlightDefs, HighlightHueName, HighlightHueNameCap, ModeDef} from "./design-system/types";
|
|
5
|
+
|
|
6
|
+
function pairFrom<T>(def: ModeDef<T>, mapper: (_: T, idx: number) => string) {
|
|
7
|
+
return def.map(mapper) as Def;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function merge<T>(base: ModeDef<T>, override?: ModeDef<Partial<T>>): ModeDef<T> {
|
|
11
|
+
return override ? (override.map((o, i) => ({...base[i], ...o})) as ModeDef<T>) : base;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LC_BRIDGE = 8;
|
|
15
|
+
|
|
16
|
+
function strictOklch(L: number, c: number, hue: number) {
|
|
17
|
+
return formatRgb(clampChroma({mode: "oklch", l: L, c, h: hue}, "oklch", "rgb"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function colorFromLc(surfaceColor: string, Lc: number, c: number, hue: number, referenceC?: number) {
|
|
21
|
+
const lcChroma = referenceC !== undefined && c < referenceC ? referenceC : c;
|
|
22
|
+
return strictOklch(colorLightnessFromLc(surfaceColor, Lc, lcChroma, hue), c, hue);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function colorLightnessFromLc(surfaceColor: string, Lc: number, c: number, hue: number) {
|
|
26
|
+
if (Lc >= LC_BRIDGE) {
|
|
27
|
+
return apcach(crToBg(surfaceColor, Lc), c, hue, 100, "srgb").lightness;
|
|
28
|
+
}
|
|
29
|
+
const surfaceL = oklchFrom(surfaceColor)?.l ?? 0;
|
|
30
|
+
if (Lc <= 0) {
|
|
31
|
+
return surfaceL;
|
|
32
|
+
}
|
|
33
|
+
const atBridge = apcach(crToBg(surfaceColor, LC_BRIDGE), c, hue, 100, "srgb").lightness;
|
|
34
|
+
return surfaceL + (atBridge - surfaceL) * (Lc / LC_BRIDGE);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const capitalize = (s: string) => s[0].toUpperCase() + s.slice(1);
|
|
38
|
+
|
|
39
|
+
type Colors = Record<`highlightFgOnBg${HighlightHueNameCap}`, Def> &
|
|
40
|
+
Record<`highlightBg${HighlightHueNameCap}`, Def> &
|
|
41
|
+
Record<`highlightFg${HighlightHueNameCap}`, Def>;
|
|
42
|
+
type Key = keyof Colors;
|
|
43
|
+
|
|
44
|
+
export function bgKeyFrom(hueName: HighlightHueName) {
|
|
45
|
+
return `highlightBg${capitalize(hueName)}` as Key;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function fgOnBgKeyFrom(hueName: HighlightHueName) {
|
|
49
|
+
return `highlightFgOnBg${capitalize(hueName)}` as Key;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function fgKeyFrom(hueName: HighlightHueName) {
|
|
53
|
+
return `highlightFg${capitalize(hueName)}` as Key;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const highlightHueNames: HighlightHueName[] = [
|
|
57
|
+
"yellow",
|
|
58
|
+
"green",
|
|
59
|
+
"aquamarine",
|
|
60
|
+
"aqua",
|
|
61
|
+
"blue",
|
|
62
|
+
"purple",
|
|
63
|
+
"pink",
|
|
64
|
+
"red",
|
|
65
|
+
"grey",
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export function highlightColors({bg, fg, hues, overrides}: HighlightDefs, surface: Def) {
|
|
69
|
+
const defs: Partial<Colors> = {};
|
|
70
|
+
|
|
71
|
+
for (const hueName of highlightHueNames) {
|
|
72
|
+
const hue = hues[hueName];
|
|
73
|
+
const ov = overrides?.[hueName];
|
|
74
|
+
const mergedBg = merge(bg, ov?.bg);
|
|
75
|
+
const bgs = pairFrom(mergedBg, (b, i) => {
|
|
76
|
+
return colorFromLc(surface[i], b.Lc, b.c, hue, bg[i].c);
|
|
77
|
+
});
|
|
78
|
+
defs[bgKeyFrom(hueName)] = bgs;
|
|
79
|
+
|
|
80
|
+
const mergedFg = merge(fg, ov?.fg);
|
|
81
|
+
defs[fgKeyFrom(hueName)] = pairFrom(mergedFg, (fg, i) => {
|
|
82
|
+
return colorFromLc(surface[i], fg.Lc, fg.c, hue);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
defs[fgOnBgKeyFrom(hueName)] = pairFrom(mergedFg, (fg, i) => {
|
|
86
|
+
return colorFromLc(bgs[i], fg.Lc, fg.c, hue);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return defs as Colors;
|
|
91
|
+
}
|
|
@@ -19,28 +19,39 @@ const inputStyle = css`
|
|
|
19
19
|
position: relative;
|
|
20
20
|
`;
|
|
21
21
|
|
|
22
|
-
export
|
|
22
|
+
export type AntTextAreaWithCustomReadStateProps = React.ComponentPropsWithoutRef<typeof AntTextArea> & {
|
|
23
|
+
renderReadState: () => React.ReactNode;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const AntTextAreaWithCustomReadState = ({
|
|
27
|
+
value,
|
|
28
|
+
onBlur,
|
|
29
|
+
onFocus,
|
|
30
|
+
style = {},
|
|
31
|
+
renderReadState,
|
|
32
|
+
...rest
|
|
33
|
+
}: AntTextAreaWithCustomReadStateProps) => {
|
|
23
34
|
const [focus, setFocus] = useState(false);
|
|
24
|
-
const input = useRef(null);
|
|
35
|
+
const input = useRef<HTMLTextAreaElement>(null);
|
|
25
36
|
|
|
26
37
|
const handleFocus = useCallback(
|
|
27
|
-
(e) => {
|
|
38
|
+
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
|
28
39
|
setFocus(true);
|
|
29
|
-
onFocus
|
|
40
|
+
onFocus?.(e);
|
|
30
41
|
},
|
|
31
42
|
[onFocus]
|
|
32
43
|
);
|
|
33
44
|
|
|
34
45
|
const handleBlur = useCallback(
|
|
35
|
-
(e) => {
|
|
46
|
+
(e: React.FocusEvent<HTMLTextAreaElement>) => {
|
|
36
47
|
setFocus(false);
|
|
37
|
-
onBlur
|
|
48
|
+
onBlur?.(e);
|
|
38
49
|
},
|
|
39
50
|
[onBlur]
|
|
40
51
|
);
|
|
41
52
|
|
|
42
53
|
const handleWrapperClick = useCallback(() => {
|
|
43
|
-
input.current
|
|
54
|
+
input.current?.focus();
|
|
44
55
|
}, [input]);
|
|
45
56
|
|
|
46
57
|
return (
|
|
@@ -52,7 +63,7 @@ export const AntTextAreaWithCustomReadState = ({value, onBlur, onFocus, style =
|
|
|
52
63
|
onFocus={handleFocus}
|
|
53
64
|
onBlur={handleBlur}
|
|
54
65
|
value={value}
|
|
55
|
-
style={{...style, color: !focus
|
|
66
|
+
style={{...style, color: !focus ? "transparent" : undefined}}
|
|
56
67
|
/>
|
|
57
68
|
{!focus && (
|
|
58
69
|
<div className={wrapperStyle} onClick={handleWrapperClick}>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {css} from "@linaria/core";
|
|
2
|
+
import {useCallback} from "react";
|
|
3
|
+
import {
|
|
4
|
+
AntTextAreaWithCustomReadState,
|
|
5
|
+
AntTextAreaWithCustomReadStateProps,
|
|
6
|
+
} from "./components/AntTextAreaWithCustomReadState";
|
|
7
|
+
import {getURL} from "./utils";
|
|
8
|
+
|
|
9
|
+
const urlLinkClassname = css`
|
|
10
|
+
word-break: break-word;
|
|
11
|
+
margin-right: 20px;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
type LinkProps = {value: string; children?: React.ReactNode} & React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
15
|
+
|
|
16
|
+
export const UrlInputLink = ({value, children, ...rest}: LinkProps) => {
|
|
17
|
+
const normalizedUrl = getURL(value);
|
|
18
|
+
if (!normalizedUrl) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<a
|
|
23
|
+
draggable={false}
|
|
24
|
+
target="_blank"
|
|
25
|
+
className={urlLinkClassname}
|
|
26
|
+
rel="noopener noreferrer nofollow"
|
|
27
|
+
href={normalizedUrl}
|
|
28
|
+
{...rest}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</a>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const UrlInput = (props: Omit<AntTextAreaWithCustomReadStateProps, "renderReadState">) => {
|
|
36
|
+
const handleClick = useCallback((e: React.MouseEvent) => e.stopPropagation(), []);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<AntTextAreaWithCustomReadState
|
|
40
|
+
{...props}
|
|
41
|
+
renderReadState={() => (
|
|
42
|
+
<UrlInputLink value={String(props.value ?? "")} onClick={handleClick}>
|
|
43
|
+
{String(props.value ?? "")}
|
|
44
|
+
</UrlInputLink>
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const PhoneInputLink = ({value, children, ...rest}: LinkProps) => {
|
|
51
|
+
return (
|
|
52
|
+
<a draggable={false} href={`tel:${value}`} {...rest}>
|
|
53
|
+
{children}
|
|
54
|
+
</a>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const PhoneInput = (props: Omit<AntTextAreaWithCustomReadStateProps, "renderReadState">) => {
|
|
59
|
+
const handleClick = useCallback((e: React.MouseEvent) => e.stopPropagation(), []);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<AntTextAreaWithCustomReadState
|
|
63
|
+
{...props}
|
|
64
|
+
renderReadState={() => (
|
|
65
|
+
<PhoneInputLink value={String(props.value ?? "")} onClick={handleClick}>
|
|
66
|
+
{String(props.value ?? "")}
|
|
67
|
+
</PhoneInputLink>
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const EmailInputLink = ({value, children, ...rest}: LinkProps) => {
|
|
74
|
+
return (
|
|
75
|
+
<a draggable={false} href={`mailto:${value}`} {...rest}>
|
|
76
|
+
{children}
|
|
77
|
+
</a>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const EmailInput = (props: Omit<AntTextAreaWithCustomReadStateProps, "renderReadState">) => {
|
|
82
|
+
const handleClick = useCallback((e: React.MouseEvent) => e.stopPropagation(), []);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<AntTextAreaWithCustomReadState
|
|
86
|
+
{...props}
|
|
87
|
+
renderReadState={() => (
|
|
88
|
+
<EmailInputLink value={String(props.value ?? "")} onClick={handleClick}>
|
|
89
|
+
{String(props.value ?? "")}
|
|
90
|
+
</EmailInputLink>
|
|
91
|
+
)}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
};
|