react-native-terra-ui 0.7.2 → 0.8.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 +34 -0
- package/README.md +1 -1
- package/lib/module/components/avatar/Avatar.js +20 -20
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/button/Button.js +11 -11
- package/lib/module/components/button/Button.js.map +1 -1
- package/lib/module/components/chip/Chip.js +32 -32
- package/lib/module/components/chip/Chip.js.map +1 -1
- package/lib/module/components/empty-state/EmptyState.js +3 -3
- package/lib/module/components/empty-state/EmptyState.js.map +1 -1
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +30 -32
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/TitleHeader/index.js +26 -18
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/icon/Icon.js +3 -3
- package/lib/module/components/page-indicator/hooks/use-page-indicator-colors.js +2 -2
- package/lib/module/components/screen/Screen.js +63 -34
- package/lib/module/components/screen/Screen.js.map +1 -1
- package/lib/module/components/screen/ScreenContext.js +28 -9
- package/lib/module/components/screen/ScreenContext.js.map +1 -1
- package/lib/module/components/screen/ScreenFlatList.js +4 -162
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenList.js +81 -97
- package/lib/module/components/screen/ScreenList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +64 -115
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/components/screen/hooks/use-screen-ref.js +18 -0
- package/lib/module/components/screen/hooks/use-screen-ref.js.map +1 -0
- package/lib/module/components/screen/hooks/use-screen-scroll-handler.js +19 -0
- package/lib/module/components/screen/hooks/use-screen-scroll-handler.js.map +1 -0
- package/lib/module/components/screen/index.js.map +1 -1
- package/lib/module/components/screen/parts/ScreenFrame.js +47 -0
- package/lib/module/components/screen/parts/ScreenFrame.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenHeaderAppearance.js +43 -0
- package/lib/module/components/screen/parts/ScreenHeaderAppearance.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js +11 -13
- package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -1
- package/lib/module/components/screen/parts/ScreenSafeArea.android.js +27 -0
- package/lib/module/components/screen/parts/ScreenSafeArea.android.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenSafeArea.js +26 -0
- package/lib/module/components/screen/parts/ScreenSafeArea.js.map +1 -0
- package/lib/module/components/screen/utils.js +18 -9
- package/lib/module/components/screen/utils.js.map +1 -1
- package/lib/module/components/spinner/Spinner.js +2 -2
- package/lib/module/components/text/Text.js +1 -1
- package/lib/module/components/toast/Toast.js +13 -13
- package/lib/module/components/toast/Toast.js.map +1 -1
- package/lib/module/components/toolbar/Toolbar.js +2 -2
- package/lib/module/context/ThemeProvider.js +22 -39
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/context/index.js +1 -1
- package/lib/module/context/index.js.map +1 -1
- package/lib/module/theme/breakpoints.js +1 -1
- package/lib/module/theme/define-config.js +21 -0
- package/lib/module/theme/define-config.js.map +1 -0
- package/lib/module/theme/diagnostics.js +13 -0
- package/lib/module/theme/diagnostics.js.map +1 -0
- package/lib/module/theme/errors.js +15 -0
- package/lib/module/theme/errors.js.map +1 -0
- package/lib/module/theme/index.js +7 -1
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/legacy-tokens.js +119 -0
- package/lib/module/theme/legacy-tokens.js.map +1 -0
- package/lib/module/theme/registry.js +122 -130
- package/lib/module/theme/registry.js.map +1 -1
- package/lib/module/theme/resolve-config.js +346 -0
- package/lib/module/theme/resolve-config.js.map +1 -0
- package/lib/module/theme/runtime.js +57 -12
- package/lib/module/theme/runtime.js.map +1 -1
- package/lib/module/theme/screen-margin.js +6 -14
- package/lib/module/theme/screen-margin.js.map +1 -1
- package/lib/module/theme/screen-padding.js +17 -0
- package/lib/module/theme/screen-padding.js.map +1 -0
- package/lib/module/theme/selection-store.js +75 -0
- package/lib/module/theme/selection-store.js.map +1 -0
- package/lib/module/theme/theme.js +81 -36
- package/lib/module/theme/theme.js.map +1 -1
- package/lib/module/theme/tokens/canonical-color-keys.js +102 -0
- package/lib/module/theme/tokens/canonical-color-keys.js.map +1 -0
- package/lib/module/theme/tokens/dark.js +72 -88
- package/lib/module/theme/tokens/dark.js.map +1 -1
- package/lib/module/theme/tokens/light.js +69 -83
- package/lib/module/theme/tokens/light.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +14 -14
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/module/theme/tokens/validate.js +238 -0
- package/lib/module/theme/tokens/validate.js.map +1 -0
- package/lib/module/theme/unistyles-adapter.js +77 -0
- package/lib/module/theme/unistyles-adapter.js.map +1 -0
- package/lib/module/utils/accent-generator.js +476 -0
- package/lib/module/utils/accent-generator.js.map +1 -0
- package/lib/module/utils/accent-utils.js +52 -35
- package/lib/module/utils/accent-utils.js.map +1 -1
- package/lib/module/utils/color-utils.js +224 -7
- package/lib/module/utils/color-utils.js.map +1 -1
- package/lib/module/utils/deep-merge.js +1 -1
- package/lib/module/utils/deep-merge.js.map +1 -1
- package/lib/module/utils/resolve-theme-color.js +2 -2
- package/lib/typescript/src/components/chip/types.d.ts +1 -1
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts +1 -1
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +6 -5
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +11 -5
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/icon/Icon.d.ts +2 -2
- package/lib/typescript/src/components/screen/Screen.d.ts +45 -16
- package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +42 -26
- package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +5 -46
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenList.d.ts +62 -50
- package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +18 -58
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts +5 -0
- package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts +53 -0
- package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/index.d.ts +2 -2
- package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts +6 -0
- package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts +21 -0
- package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +6 -8
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts +7 -0
- package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts +6 -0
- package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/types.d.ts +27 -32
- package/lib/typescript/src/components/screen/types.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/utils.d.ts +9 -8
- package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/text/Text.d.ts +1 -1
- package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts +2 -2
- package/lib/typescript/src/context/ThemeProvider.d.ts +29 -12
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/context/index.d.ts +1 -1
- package/lib/typescript/src/context/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/breakpoints.d.ts +1 -1
- package/lib/typescript/src/theme/define-config.d.ts +23 -0
- package/lib/typescript/src/theme/define-config.d.ts.map +1 -0
- package/lib/typescript/src/theme/diagnostics.d.ts +4 -0
- package/lib/typescript/src/theme/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/theme/errors.d.ts +10 -0
- package/lib/typescript/src/theme/errors.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +10 -2
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/legacy-tokens.d.ts +187 -0
- package/lib/typescript/src/theme/legacy-tokens.d.ts.map +1 -0
- package/lib/typescript/src/theme/registry.d.ts +12 -17
- package/lib/typescript/src/theme/registry.d.ts.map +1 -1
- package/lib/typescript/src/theme/resolve-config.d.ts +99 -0
- package/lib/typescript/src/theme/resolve-config.d.ts.map +1 -0
- package/lib/typescript/src/theme/runtime.d.ts +25 -4
- package/lib/typescript/src/theme/runtime.d.ts.map +1 -1
- package/lib/typescript/src/theme/screen-margin.d.ts +6 -14
- package/lib/typescript/src/theme/screen-margin.d.ts.map +1 -1
- package/lib/typescript/src/theme/screen-padding.d.ts +15 -0
- package/lib/typescript/src/theme/screen-padding.d.ts.map +1 -0
- package/lib/typescript/src/theme/selection-store.d.ts +41 -0
- package/lib/typescript/src/theme/selection-store.d.ts.map +1 -0
- package/lib/typescript/src/theme/theme.d.ts +54 -1
- package/lib/typescript/src/theme/theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts +95 -0
- package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts.map +1 -0
- package/lib/typescript/src/theme/tokens/dark.d.ts +56 -63
- package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/light.d.ts +56 -71
- package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts +11 -10
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/validate.d.ts +34 -0
- package/lib/typescript/src/theme/tokens/validate.d.ts.map +1 -0
- package/lib/typescript/src/theme/types.d.ts +166 -86
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/lib/typescript/src/theme/unistyles-adapter.d.ts +33 -0
- package/lib/typescript/src/theme/unistyles-adapter.d.ts.map +1 -0
- package/lib/typescript/src/utils/accent-generator.d.ts +103 -0
- package/lib/typescript/src/utils/accent-generator.d.ts.map +1 -0
- package/lib/typescript/src/utils/accent-utils.d.ts +15 -2
- package/lib/typescript/src/utils/accent-utils.d.ts.map +1 -1
- package/lib/typescript/src/utils/color-utils.d.ts +59 -2
- package/lib/typescript/src/utils/color-utils.d.ts.map +1 -1
- package/lib/typescript/src/utils/deep-merge.d.ts +1 -0
- package/lib/typescript/src/utils/deep-merge.d.ts.map +1 -1
- package/lib/typescript/src/utils/resolve-theme-color.d.ts +2 -2
- package/package.json +1 -1
- package/src/components/avatar/Avatar.tsx +20 -20
- package/src/components/button/Button.tsx +11 -11
- package/src/components/chip/Chip.tsx +32 -32
- package/src/components/chip/types.ts +1 -1
- package/src/components/empty-state/EmptyState.tsx +4 -4
- package/src/components/header/variants/LargeTitleHeader/index.tsx +31 -30
- package/src/components/header/variants/TitleHeader/index.tsx +33 -22
- package/src/components/icon/Icon.tsx +4 -4
- package/src/components/page-indicator/hooks/use-page-indicator-colors.ts +2 -2
- package/src/components/screen/Screen.tsx +89 -43
- package/src/components/screen/ScreenContext.tsx +48 -28
- package/src/components/screen/ScreenFlatList.tsx +5 -191
- package/src/components/screen/ScreenList.tsx +197 -175
- package/src/components/screen/ScreenScrollView.tsx +92 -167
- package/src/components/screen/hooks/use-screen-ref.ts +24 -0
- package/src/components/screen/hooks/use-screen-scroll-handler.ts +31 -0
- package/src/components/screen/index.ts +8 -2
- package/src/components/screen/parts/ScreenFrame.tsx +49 -0
- package/src/components/screen/parts/ScreenHeaderAppearance.tsx +52 -0
- package/src/components/screen/parts/ScreenPlaceholder.tsx +16 -16
- package/src/components/screen/parts/ScreenSafeArea.android.tsx +22 -0
- package/src/components/screen/parts/ScreenSafeArea.tsx +20 -0
- package/src/components/screen/types.ts +29 -32
- package/src/components/screen/utils.ts +24 -11
- package/src/components/spinner/Spinner.tsx +2 -2
- package/src/components/text/Text.tsx +2 -2
- package/src/components/toast/Toast.tsx +13 -13
- package/src/components/toolbar/Toolbar.tsx +4 -4
- package/src/context/ThemeProvider.tsx +47 -51
- package/src/context/index.ts +1 -1
- package/src/theme/breakpoints.ts +1 -1
- package/src/theme/define-config.ts +27 -0
- package/src/theme/diagnostics.ts +11 -0
- package/src/theme/errors.ts +14 -0
- package/src/theme/index.ts +40 -1
- package/src/theme/legacy-tokens.ts +227 -0
- package/src/theme/registry.ts +119 -137
- package/src/theme/resolve-config.ts +406 -0
- package/src/theme/runtime.ts +52 -14
- package/src/theme/screen-margin.ts +6 -25
- package/src/theme/screen-padding.ts +26 -0
- package/src/theme/selection-store.ts +76 -0
- package/src/theme/theme.ts +104 -39
- package/src/theme/tokens/canonical-color-keys.ts +107 -0
- package/src/theme/tokens/dark.ts +91 -98
- package/src/theme/tokens/light.ts +88 -93
- package/src/theme/tokens/primitives.ts +14 -14
- package/src/theme/tokens/validate.ts +271 -0
- package/src/theme/types.ts +184 -95
- package/src/theme/unistyles-adapter.ts +75 -0
- package/src/utils/accent-generator.ts +530 -0
- package/src/utils/accent-utils.ts +66 -39
- package/src/utils/color-utils.ts +225 -8
- package/src/utils/deep-merge.ts +1 -1
- package/src/utils/resolve-theme-color.ts +2 -2
- package/lib/module/components/screen/parts/BottomSafeArea.js +0 -62
- package/lib/module/components/screen/parts/BottomSafeArea.js.map +0 -1
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +0 -30
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +0 -1
- package/src/components/screen/parts/BottomSafeArea.tsx +0 -59
package/src/utils/color-utils.ts
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Colour helpers shared by the accent generator, the contrast diagnostics and
|
|
3
|
+
* the public `shade` / `withAlpha` / `readableOn` utilities.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is pure sRGB maths with no runtime dependency:
|
|
6
|
+
* - parsing of `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()` and `rgba()`,
|
|
7
|
+
* - WCAG 2.x relative luminance / contrast with sRGB alpha compositing,
|
|
8
|
+
* - OKLab / OKLCH conversions (Björn Ottosson's published matrices) with
|
|
9
|
+
* sRGB gamut mapping by chroma reduction at constant lightness and hue.
|
|
10
|
+
*/
|
|
2
11
|
|
|
3
|
-
interface RGB {
|
|
12
|
+
export interface RGB {
|
|
4
13
|
r: number;
|
|
5
14
|
g: number;
|
|
6
15
|
b: number;
|
|
7
16
|
}
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
/** A parsed sRGB colour with 0-255 channels and a 0-1 alpha. */
|
|
19
|
+
export interface RGBA extends RGB {
|
|
20
|
+
a: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** OKLCH coordinates: L in [0, 1], C ≥ 0, h in degrees [0, 360). */
|
|
24
|
+
export interface OKLCH {
|
|
25
|
+
l: number;
|
|
26
|
+
c: number;
|
|
27
|
+
h: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const clamp255 = (n: number): number => Math.max(0, Math.min(255, Math.round(n)));
|
|
31
|
+
const clamp01 = (n: number): number => Math.max(0, Math.min(1, n));
|
|
10
32
|
|
|
11
33
|
function parseHex(hex: string): RGB | null {
|
|
12
34
|
const hex6 = /^#?([0-9a-f]{6})$/i.exec(hex.trim())?.[1];
|
|
@@ -16,7 +38,201 @@ function parseHex(hex: string): RGB | null {
|
|
|
16
38
|
}
|
|
17
39
|
|
|
18
40
|
const toHex = ({ r, g, b }: RGB): string =>
|
|
19
|
-
`#${[r, g, b].map((v) =>
|
|
41
|
+
`#${[r, g, b].map((v) => clamp255(v).toString(16).padStart(2, "0")).join("")}`;
|
|
42
|
+
|
|
43
|
+
/** Formats 0-255 channels as a lowercase `#rrggbb` string (channels are rounded and clamped). */
|
|
44
|
+
export function rgbToHex(rgb: RGB): string {
|
|
45
|
+
return toHex(rgb);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parses an sRGB colour string. Accepts `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`
|
|
50
|
+
* (case-insensitive) and `rgb(r, g, b)` / `rgba(r, g, b, a)` with integer or
|
|
51
|
+
* percentage channels. Returns `null` for anything else (named colours,
|
|
52
|
+
* `transparent`, hsl(), gradients, platform dynamic colours, ...).
|
|
53
|
+
*/
|
|
54
|
+
export function parseColor(value: unknown): RGBA | null {
|
|
55
|
+
if (typeof value !== "string") return null;
|
|
56
|
+
const input = value.trim();
|
|
57
|
+
|
|
58
|
+
const hex = /^#([0-9a-f]{3,8})$/i.exec(input)?.[1];
|
|
59
|
+
if (hex !== undefined) {
|
|
60
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
61
|
+
const [r, g, b, a] = [...hex].map((ch) => Number.parseInt(ch + ch, 16));
|
|
62
|
+
return { r: r ?? 0, g: g ?? 0, b: b ?? 0, a: hex.length === 4 ? (a ?? 255) / 255 : 1 };
|
|
63
|
+
}
|
|
64
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
65
|
+
const channels = [0, 2, 4, 6]
|
|
66
|
+
.filter((start) => start < hex.length)
|
|
67
|
+
.map((start) => Number.parseInt(hex.slice(start, start + 2), 16));
|
|
68
|
+
const [r, g, b, a] = channels;
|
|
69
|
+
return { r: r ?? 0, g: g ?? 0, b: b ?? 0, a: hex.length === 8 ? (a ?? 255) / 255 : 1 };
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fn = /^rgba?\(\s*([^)]*)\)$/i.exec(input)?.[1];
|
|
75
|
+
if (fn !== undefined) {
|
|
76
|
+
const parts = fn.split(/\s*[,/]\s*|\s+/).filter((part) => part.length > 0);
|
|
77
|
+
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
78
|
+
const channel = (part: string): number | null => {
|
|
79
|
+
const percent = part.endsWith("%");
|
|
80
|
+
const n = Number(percent ? part.slice(0, -1) : part);
|
|
81
|
+
if (!Number.isFinite(n)) return null;
|
|
82
|
+
return clamp255(percent ? (n / 100) * 255 : n);
|
|
83
|
+
};
|
|
84
|
+
const alpha = (part: string | undefined): number | null => {
|
|
85
|
+
if (part === undefined) return 1;
|
|
86
|
+
const percent = part.endsWith("%");
|
|
87
|
+
const n = Number(percent ? part.slice(0, -1) : part);
|
|
88
|
+
if (!Number.isFinite(n)) return null;
|
|
89
|
+
return clamp01(percent ? n / 100 : n);
|
|
90
|
+
};
|
|
91
|
+
const r = channel(parts[0] as string);
|
|
92
|
+
const g = channel(parts[1] as string);
|
|
93
|
+
const b = channel(parts[2] as string);
|
|
94
|
+
const a = alpha(parts[3]);
|
|
95
|
+
if (r === null || g === null || b === null || a === null) return null;
|
|
96
|
+
return { r, g, b, a };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Source-over compositing of `front` at `alpha` onto an opaque `back`, in gamma-encoded sRGB. */
|
|
103
|
+
export function compositeOver(front: RGB, back: RGB, alpha: number): RGB {
|
|
104
|
+
const a = clamp01(alpha);
|
|
105
|
+
return {
|
|
106
|
+
r: front.r * a + back.r * (1 - a),
|
|
107
|
+
g: front.g * a + back.g * (1 - a),
|
|
108
|
+
b: front.b * a + back.b * (1 - a),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const linearizeChannel = (channel: number): number => {
|
|
113
|
+
const srgb = channel / 255;
|
|
114
|
+
return srgb <= 0.04045 ? srgb / 12.92 : ((srgb + 0.055) / 1.055) ** 2.4;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const delinearizeChannel = (linear: number): number => {
|
|
118
|
+
const v = Math.max(0, Math.min(1, linear));
|
|
119
|
+
return (v <= 0.0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055) * 255;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** WCAG 2.x relative luminance of an opaque sRGB colour (0-255 channels). */
|
|
123
|
+
export function relativeLuminance(rgb: RGB): number {
|
|
124
|
+
return (
|
|
125
|
+
0.2126 * linearizeChannel(rgb.r) + 0.7152 * linearizeChannel(rgb.g) + 0.0722 * linearizeChannel(rgb.b)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** WCAG 2.x contrast ratio between two opaque sRGB colours (≥ 1). */
|
|
130
|
+
export function contrastRatio(a: RGB, b: RGB): number {
|
|
131
|
+
const la = relativeLuminance(a);
|
|
132
|
+
const lb = relativeLuminance(b);
|
|
133
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Contrast of a foreground on a background, both composited over an opaque
|
|
138
|
+
* `surface`, with `opacity` applied to the whole control afterwards (the way a
|
|
139
|
+
* pressed Button or Toast action fades as one unit). Matches the reference
|
|
140
|
+
* calculation in `theme/__tests__/color-contrast.test.ts`.
|
|
141
|
+
*/
|
|
142
|
+
export function compositedContrast(fg: RGBA, bg: RGBA, surface: RGB, opacity = 1): number {
|
|
143
|
+
const background = compositeOver(bg, surface, bg.a);
|
|
144
|
+
const foreground = compositeOver(fg, background, fg.a);
|
|
145
|
+
return contrastRatio(
|
|
146
|
+
compositeOver(foreground, surface, opacity),
|
|
147
|
+
compositeOver(background, surface, opacity)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── OKLab / OKLCH ────────────────────────────────────────────────────────────
|
|
152
|
+
// Matrices from https://bottosson.github.io/posts/oklab/ (sRGB D65).
|
|
153
|
+
|
|
154
|
+
const cbrt = (x: number): number => (x < 0 ? -((-x) ** (1 / 3)) : x ** (1 / 3));
|
|
155
|
+
|
|
156
|
+
function linearSrgbToOklab(r: number, g: number, b: number): [number, number, number] {
|
|
157
|
+
const l = cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
158
|
+
const m = cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
159
|
+
const s = cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
160
|
+
return [
|
|
161
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
162
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
163
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function oklabToLinearSrgb(L: number, a: number, b: number): [number, number, number] {
|
|
168
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
169
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
170
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
171
|
+
return [
|
|
172
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
173
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
174
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Converts 0-255 sRGB channels to OKLCH. Achromatic input yields `c ≈ 0` and `h = 0`. */
|
|
179
|
+
export function rgbToOklch(rgb: RGB): OKLCH {
|
|
180
|
+
const [L, a, b] = linearSrgbToOklab(
|
|
181
|
+
linearizeChannel(rgb.r),
|
|
182
|
+
linearizeChannel(rgb.g),
|
|
183
|
+
linearizeChannel(rgb.b)
|
|
184
|
+
);
|
|
185
|
+
const c = Math.hypot(a, b);
|
|
186
|
+
if (c < 1e-4) return { l: clamp01(L), c: 0, h: 0 };
|
|
187
|
+
const h = (Math.atan2(b, a) * 180) / Math.PI;
|
|
188
|
+
return { l: clamp01(L), c, h: h < 0 ? h + 360 : h };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function oklchToLinear({ l, c, h }: OKLCH): [number, number, number] {
|
|
192
|
+
const rad = (h * Math.PI) / 180;
|
|
193
|
+
return oklabToLinearSrgb(l, c * Math.cos(rad), c * Math.sin(rad));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const GAMUT_EPSILON = 1e-4;
|
|
197
|
+
|
|
198
|
+
const inGamut = (linear: readonly number[]): boolean =>
|
|
199
|
+
linear.every((v) => v >= -GAMUT_EPSILON && v <= 1 + GAMUT_EPSILON);
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Converts OKLCH to 0-255 sRGB channels. Colours outside the sRGB gamut are
|
|
203
|
+
* mapped by reducing chroma at constant lightness and hue (binary search)
|
|
204
|
+
* until they fit, so the requested lightness — what the contrast targets
|
|
205
|
+
* depend on — is preserved as closely as the gamut allows.
|
|
206
|
+
*/
|
|
207
|
+
export function oklchToRgb(color: OKLCH): RGB {
|
|
208
|
+
const l = clamp01(color.l);
|
|
209
|
+
const h = ((color.h % 360) + 360) % 360;
|
|
210
|
+
let c = Math.max(0, color.c);
|
|
211
|
+
let linear = oklchToLinear({ l, c, h });
|
|
212
|
+
if (!inGamut(linear)) {
|
|
213
|
+
let lo = 0;
|
|
214
|
+
let hi = c;
|
|
215
|
+
for (let i = 0; i < 24; i += 1) {
|
|
216
|
+
const mid = (lo + hi) / 2;
|
|
217
|
+
if (inGamut(oklchToLinear({ l, c: mid, h }))) lo = mid;
|
|
218
|
+
else hi = mid;
|
|
219
|
+
}
|
|
220
|
+
c = lo;
|
|
221
|
+
linear = oklchToLinear({ l, c, h });
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
r: delinearizeChannel(linear[0]),
|
|
225
|
+
g: delinearizeChannel(linear[1]),
|
|
226
|
+
b: delinearizeChannel(linear[2]),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** `oklchToRgb` followed by `rgbToHex`. */
|
|
231
|
+
export function oklchToHex(color: OKLCH): string {
|
|
232
|
+
return toHex(oklchToRgb(color));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ─── Public shade / alpha / readable helpers (unchanged behaviour) ────────────
|
|
20
236
|
|
|
21
237
|
/**
|
|
22
238
|
* Shifts a hex color toward black (negative `amount`) or white (positive),
|
|
@@ -41,11 +257,12 @@ export function withAlpha(hex: string, alpha: number): string {
|
|
|
41
257
|
return `rgba(${c.r}, ${c.g}, ${c.b}, ${alpha})`;
|
|
42
258
|
}
|
|
43
259
|
|
|
44
|
-
/** Picks
|
|
260
|
+
/** Picks the higher-contrast black/white foreground for an opaque 6-digit hex. */
|
|
45
261
|
export function readableOn(hex: string): string {
|
|
46
262
|
const c = parseHex(hex);
|
|
47
263
|
if (!c) return "#ffffff";
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
264
|
+
const luminance = relativeLuminance(c);
|
|
265
|
+
const blackContrast = (luminance + 0.05) / 0.05;
|
|
266
|
+
const whiteContrast = 1.05 / (luminance + 0.05);
|
|
267
|
+
return blackContrast >= whiteContrast ? "#000000" : "#ffffff";
|
|
51
268
|
}
|
package/src/utils/deep-merge.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type DeepPartial<T> = T extends (infer U)[]
|
|
|
5
5
|
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
6
6
|
: T;
|
|
7
7
|
|
|
8
|
-
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
8
|
+
export const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
9
9
|
typeof value === "object" &&
|
|
10
10
|
value !== null &&
|
|
11
11
|
!Array.isArray(value) &&
|
|
@@ -12,8 +12,8 @@ const isColorLiteral = (value: string): boolean =>
|
|
|
12
12
|
* Raw literals pass through unchanged.
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
|
-
* resolveThemeColor('text.
|
|
16
|
-
* resolveThemeColor('
|
|
15
|
+
* resolveThemeColor('text.primary', theme) // → '#020618'
|
|
16
|
+
* resolveThemeColor('accent.solid.bg', theme) // → '#009966'
|
|
17
17
|
* resolveThemeColor('#ff0000', theme) // → '#ff0000'
|
|
18
18
|
*/
|
|
19
19
|
export function resolveThemeColor(token: ColorToken | undefined, theme: TerraTheme): string | undefined {
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
-
/**
|
|
5
|
-
* `SafeAreaView` is not re-exported from the `react-native-screens` root, so it
|
|
6
|
-
* has to be reached by path — and it has to be the TypeScript source entry, the
|
|
7
|
-
* one the package points React Native at. The built `lib/module` copy of the
|
|
8
|
-
* native component never went through React Native's codegen, so loading it
|
|
9
|
-
* fails at bundle time with "Could not find component config for native
|
|
10
|
-
* component".
|
|
11
|
-
*
|
|
12
|
-
* `require` rather than `import` so the dependency's own `.tsx` is not pulled
|
|
13
|
-
* into this package's build, where it trips compiler options it was never
|
|
14
|
-
* written against.
|
|
15
|
-
*/
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
17
|
-
const {
|
|
18
|
-
SafeAreaView
|
|
19
|
-
} = require("react-native-screens/src/components/safe-area");
|
|
20
|
-
const BOTTOM_EDGE = {
|
|
21
|
-
bottom: true
|
|
22
|
-
};
|
|
23
|
-
const SPACER = {
|
|
24
|
-
flex: 0
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Bottom clearance that measures itself.
|
|
29
|
-
*
|
|
30
|
-
* The insets Unistyles reports are the window's, which cover the system bars and
|
|
31
|
-
* nothing else — 24dp for the gesture bar on a Pixel — while a native tab bar
|
|
32
|
-
* sits on top of them for another 80dp and overlaps the content beneath it.
|
|
33
|
-
* `react-native-screens` tracks that second one separately, as an *interface*
|
|
34
|
-
* inset, and reports it to its own `SafeAreaView`; asking a view is the only way
|
|
35
|
-
* to learn it. Neither `runtime.insets.bottom` nor `useSafeAreaInsets()` knows
|
|
36
|
-
* about it — both read 24 on a tab screen — and a constant would be wrong the
|
|
37
|
-
* moment the bar is absent, which is every pushed screen.
|
|
38
|
-
*
|
|
39
|
-
* `insetType="all"` covers system and interface both, so this view owns the
|
|
40
|
-
* whole bottom edge — which is why the scroll containers pad only their margin;
|
|
41
|
-
* adding the window inset there too would double-count the gesture bar.
|
|
42
|
-
* `"interface"` alone would be wrong: it measures the same on a tab screen (the
|
|
43
|
-
* bar's inset already spans the gesture bar beneath it) but reports nothing on a
|
|
44
|
-
* pushed screen, which would then lose its gesture-bar clearance.
|
|
45
|
-
*
|
|
46
|
-
* `insetType` is Android-only; on iOS the bottom safe area of a view inside a
|
|
47
|
-
* tab controller already includes the bar. Measured on a tab screen: 132dp on a
|
|
48
|
-
* Pixel, 95pt on an iPhone 17 — against the 24dp / 34pt the window insets
|
|
49
|
-
* report, which is the gap this closes.
|
|
50
|
-
*
|
|
51
|
-
* `flex: 0` cancels the `flex: 1` that `SafeAreaView` sets on itself, which
|
|
52
|
-
* would otherwise stretch it into the leftover space of a list shorter than the
|
|
53
|
-
* viewport. With no children, its height is exactly the padding it applies.
|
|
54
|
-
*/
|
|
55
|
-
export function BottomSafeArea() {
|
|
56
|
-
return /*#__PURE__*/_jsx(SafeAreaView, {
|
|
57
|
-
edges: BOTTOM_EDGE,
|
|
58
|
-
insetType: "all",
|
|
59
|
-
style: SPACER
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=BottomSafeArea.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["SafeAreaView","require","BOTTOM_EDGE","bottom","SPACER","flex","BottomSafeArea","_jsx","edges","insetType","style"],"sourceRoot":"../../../../../src","sources":["components/screen/parts/BottomSafeArea.tsx"],"mappings":";;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;EAAEA;AAAa,CAAC,GAAGC,OAAO,CAAC,+CAA+C,CAE/E;AAED,MAAMC,WAAW,GAAG;EAAEC,MAAM,EAAE;AAAK,CAAU;AAC7C,MAAMC,MAAM,GAAG;EAAEC,IAAI,EAAE;AAAE,CAAU;;AAEnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAAA,EAAG;EAC/B,oBAAOC,IAAA,CAACP,YAAY;IAACQ,KAAK,EAAEN,WAAY;IAACO,SAAS,EAAC,KAAK;IAACC,KAAK,EAAEN;EAAO,CAAE,CAAC;AAC5E","ignoreList":[]}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bottom clearance that measures itself.
|
|
3
|
-
*
|
|
4
|
-
* The insets Unistyles reports are the window's, which cover the system bars and
|
|
5
|
-
* nothing else — 24dp for the gesture bar on a Pixel — while a native tab bar
|
|
6
|
-
* sits on top of them for another 80dp and overlaps the content beneath it.
|
|
7
|
-
* `react-native-screens` tracks that second one separately, as an *interface*
|
|
8
|
-
* inset, and reports it to its own `SafeAreaView`; asking a view is the only way
|
|
9
|
-
* to learn it. Neither `runtime.insets.bottom` nor `useSafeAreaInsets()` knows
|
|
10
|
-
* about it — both read 24 on a tab screen — and a constant would be wrong the
|
|
11
|
-
* moment the bar is absent, which is every pushed screen.
|
|
12
|
-
*
|
|
13
|
-
* `insetType="all"` covers system and interface both, so this view owns the
|
|
14
|
-
* whole bottom edge — which is why the scroll containers pad only their margin;
|
|
15
|
-
* adding the window inset there too would double-count the gesture bar.
|
|
16
|
-
* `"interface"` alone would be wrong: it measures the same on a tab screen (the
|
|
17
|
-
* bar's inset already spans the gesture bar beneath it) but reports nothing on a
|
|
18
|
-
* pushed screen, which would then lose its gesture-bar clearance.
|
|
19
|
-
*
|
|
20
|
-
* `insetType` is Android-only; on iOS the bottom safe area of a view inside a
|
|
21
|
-
* tab controller already includes the bar. Measured on a tab screen: 132dp on a
|
|
22
|
-
* Pixel, 95pt on an iPhone 17 — against the 24dp / 34pt the window insets
|
|
23
|
-
* report, which is the gap this closes.
|
|
24
|
-
*
|
|
25
|
-
* `flex: 0` cancels the `flex: 1` that `SafeAreaView` sets on itself, which
|
|
26
|
-
* would otherwise stretch it into the leftover space of a list shorter than the
|
|
27
|
-
* viewport. With no children, its height is exactly the padding it applies.
|
|
28
|
-
*/
|
|
29
|
-
export declare function BottomSafeArea(): import("react").JSX.Element;
|
|
30
|
-
//# sourceMappingURL=BottomSafeArea.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSafeArea.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screen/parts/BottomSafeArea.tsx"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,gCAE7B"}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
2
|
-
|
|
3
|
-
type SafeAreaViewProps = {
|
|
4
|
-
edges: { readonly bottom: true };
|
|
5
|
-
insetType: "all";
|
|
6
|
-
style: { readonly flex: 0 };
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* `SafeAreaView` is not re-exported from the `react-native-screens` root, so it
|
|
11
|
-
* has to be reached by path — and it has to be the TypeScript source entry, the
|
|
12
|
-
* one the package points React Native at. The built `lib/module` copy of the
|
|
13
|
-
* native component never went through React Native's codegen, so loading it
|
|
14
|
-
* fails at bundle time with "Could not find component config for native
|
|
15
|
-
* component".
|
|
16
|
-
*
|
|
17
|
-
* `require` rather than `import` so the dependency's own `.tsx` is not pulled
|
|
18
|
-
* into this package's build, where it trips compiler options it was never
|
|
19
|
-
* written against.
|
|
20
|
-
*/
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
-
const { SafeAreaView } = require("react-native-screens/src/components/safe-area") as {
|
|
23
|
-
SafeAreaView: ComponentType<SafeAreaViewProps>;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const BOTTOM_EDGE = { bottom: true } as const;
|
|
27
|
-
const SPACER = { flex: 0 } as const;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Bottom clearance that measures itself.
|
|
31
|
-
*
|
|
32
|
-
* The insets Unistyles reports are the window's, which cover the system bars and
|
|
33
|
-
* nothing else — 24dp for the gesture bar on a Pixel — while a native tab bar
|
|
34
|
-
* sits on top of them for another 80dp and overlaps the content beneath it.
|
|
35
|
-
* `react-native-screens` tracks that second one separately, as an *interface*
|
|
36
|
-
* inset, and reports it to its own `SafeAreaView`; asking a view is the only way
|
|
37
|
-
* to learn it. Neither `runtime.insets.bottom` nor `useSafeAreaInsets()` knows
|
|
38
|
-
* about it — both read 24 on a tab screen — and a constant would be wrong the
|
|
39
|
-
* moment the bar is absent, which is every pushed screen.
|
|
40
|
-
*
|
|
41
|
-
* `insetType="all"` covers system and interface both, so this view owns the
|
|
42
|
-
* whole bottom edge — which is why the scroll containers pad only their margin;
|
|
43
|
-
* adding the window inset there too would double-count the gesture bar.
|
|
44
|
-
* `"interface"` alone would be wrong: it measures the same on a tab screen (the
|
|
45
|
-
* bar's inset already spans the gesture bar beneath it) but reports nothing on a
|
|
46
|
-
* pushed screen, which would then lose its gesture-bar clearance.
|
|
47
|
-
*
|
|
48
|
-
* `insetType` is Android-only; on iOS the bottom safe area of a view inside a
|
|
49
|
-
* tab controller already includes the bar. Measured on a tab screen: 132dp on a
|
|
50
|
-
* Pixel, 95pt on an iPhone 17 — against the 24dp / 34pt the window insets
|
|
51
|
-
* report, which is the gap this closes.
|
|
52
|
-
*
|
|
53
|
-
* `flex: 0` cancels the `flex: 1` that `SafeAreaView` sets on itself, which
|
|
54
|
-
* would otherwise stretch it into the leftover space of a list shorter than the
|
|
55
|
-
* viewport. With no children, its height is exactly the padding it applies.
|
|
56
|
-
*/
|
|
57
|
-
export function BottomSafeArea() {
|
|
58
|
-
return <SafeAreaView edges={BOTTOM_EDGE} insetType="all" style={SPACER} />;
|
|
59
|
-
}
|