@rific/auto-paper 0.13.2 → 0.13.4
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/README.md +5 -3
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +24 -3
- package/dist/index.mjs +22 -3
- package/package.json +7 -7
- package/src/ThemeProvider.tsx +1 -1
- package/src/components/Appbar.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/utils/getColorRoles.ts +13 -2
- package/src/utils/getContrastRatio.ts +20 -0
package/README.md
CHANGED
|
@@ -413,7 +413,7 @@ function SettingsScreen() {
|
|
|
413
413
|
|
|
414
414
|
### `Appbar`
|
|
415
415
|
|
|
416
|
-
A thin wrapper around `react-native-paper`'s `Appbar` that automatically syncs `StatusBar`
|
|
416
|
+
A thin wrapper around `react-native-paper`'s `Appbar` that automatically syncs the `StatusBar` bar style to the current theme (React Native 0.88 removed `StatusBar`'s Android-only `backgroundColor`, which edge-to-edge already ignored, so 0.13.3 stopped passing it) (no `StatusBar` under `<Provider statusBar={false}>`).
|
|
417
417
|
|
|
418
418
|
```tsx
|
|
419
419
|
import { Appbar } from '@rific/auto-paper'
|
|
@@ -648,7 +648,7 @@ const appearance = useSelector((state: RootState) => selectThemeAppearance(state
|
|
|
648
648
|
### Color utilities
|
|
649
649
|
|
|
650
650
|
```ts
|
|
651
|
-
import { getTriadicPalette, getThirdColor, getBlendedColor, getColorRoles, getContrastColor, isDarkColor, getRgb, getHex, getTonalColor, getTintTextColor, resolveSeedColor } from '@rific/auto-paper'
|
|
651
|
+
import { getTriadicPalette, getThirdColor, getBlendedColor, getColorRoles, getContrastColor, getContrastRatio, getLuminance, isDarkColor, getRgb, getHex, getTonalColor, getTintTextColor, resolveSeedColor } from '@rific/auto-paper'
|
|
652
652
|
import type { ColorHarmony, TriadicPalette } from '@rific/auto-paper'
|
|
653
653
|
|
|
654
654
|
getTriadicPalette('#6750a4') // split-complementary (default)
|
|
@@ -661,7 +661,9 @@ getTriadicPalette('#6750a4', 'double-split')
|
|
|
661
661
|
getThirdColor('#e53935', '#1e88e5') // → '#59e52a' (hue maximally distant from both inputs; s/l averaged from both)
|
|
662
662
|
|
|
663
663
|
getBlendedColor('#ff0000', '#0000ff', 0.5) // → '#800080'
|
|
664
|
-
getColorRoles('#6750a4', '#fffbfe') // → { color, onColor, container, onContainer }: the MD3 color/onColor/container/onContainer math, generalized (this is what useComputedTheme uses internally for every role)
|
|
664
|
+
getColorRoles('#6750a4', '#fffbfe') // → { color, onColor, container, onContainer }: the MD3 color/onColor/container/onContainer math, generalized (this is what useComputedTheme uses internally for every role). onColor is light while light text reads on the color at 2.5:1, dark below that
|
|
665
|
+
getContrastRatio('#767676', '#ffffff') // → 4.54 (WCAG 2 contrast ratio, 1 to 21, either order)
|
|
666
|
+
getLuminance('#ffffff') // → 1 (WCAG 2 relative luminance, 0 for black)
|
|
665
667
|
getContrastColor('#6750a4') // → '#ffffff' (black/white pick for content sitting on a fully saturated fill)
|
|
666
668
|
isDarkColor('#6750a4') // → true
|
|
667
669
|
getRgb('coral') // → { r: 255, g: 127, b: 80 }
|
package/dist/index.d.mts
CHANGED
|
@@ -391,6 +391,9 @@ type ColorRoles = {
|
|
|
391
391
|
};
|
|
392
392
|
declare const getColorRoles: (base: string, surface: string, containerAlpha?: number) => ColorRoles;
|
|
393
393
|
|
|
394
|
+
declare const getLuminance: (value: string) => number;
|
|
395
|
+
declare const getContrastRatio: (a: string, b: string) => number;
|
|
396
|
+
|
|
394
397
|
declare const getHex: (value: string) => string | null;
|
|
395
398
|
|
|
396
399
|
type RGB = {
|
|
@@ -413,4 +416,4 @@ declare const isDarkColor: (value: string) => boolean;
|
|
|
413
416
|
|
|
414
417
|
declare const resolveSeedColor: (color: string | TriadicPalette) => string;
|
|
415
418
|
|
|
416
|
-
export { Appbar, type AppearanceIcons, AppearancePicker, AutoAppearancePicker, AutoPalettePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, FadingScrollView, type FadingScrollViewProps, HarmonyPicker, IconButton, type IconButtonProps, type MaskedViewModule, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, type ReanimatedModule, SCROLL_FADE_HEIGHT, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeBridgeProps, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getHex, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, resolveSeedColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useMaskedViewModule, useNavBarContext, usePaperDefaults, useReanimatedModule, useThemeBridgeProps, useThemeSettings };
|
|
419
|
+
export { Appbar, type AppearanceIcons, AppearancePicker, AutoAppearancePicker, AutoPalettePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, FadingScrollView, type FadingScrollViewProps, HarmonyPicker, IconButton, type IconButtonProps, type MaskedViewModule, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, type ReanimatedModule, SCROLL_FADE_HEIGHT, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeBridgeProps, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getContrastRatio, getHex, getLuminance, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, resolveSeedColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useMaskedViewModule, useNavBarContext, usePaperDefaults, useReanimatedModule, useThemeBridgeProps, useThemeSettings };
|
package/dist/index.d.ts
CHANGED
|
@@ -391,6 +391,9 @@ type ColorRoles = {
|
|
|
391
391
|
};
|
|
392
392
|
declare const getColorRoles: (base: string, surface: string, containerAlpha?: number) => ColorRoles;
|
|
393
393
|
|
|
394
|
+
declare const getLuminance: (value: string) => number;
|
|
395
|
+
declare const getContrastRatio: (a: string, b: string) => number;
|
|
396
|
+
|
|
394
397
|
declare const getHex: (value: string) => string | null;
|
|
395
398
|
|
|
396
399
|
type RGB = {
|
|
@@ -413,4 +416,4 @@ declare const isDarkColor: (value: string) => boolean;
|
|
|
413
416
|
|
|
414
417
|
declare const resolveSeedColor: (color: string | TriadicPalette) => string;
|
|
415
418
|
|
|
416
|
-
export { Appbar, type AppearanceIcons, AppearancePicker, AutoAppearancePicker, AutoPalettePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, FadingScrollView, type FadingScrollViewProps, HarmonyPicker, IconButton, type IconButtonProps, type MaskedViewModule, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, type ReanimatedModule, SCROLL_FADE_HEIGHT, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeBridgeProps, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getHex, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, resolveSeedColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useMaskedViewModule, useNavBarContext, usePaperDefaults, useReanimatedModule, useThemeBridgeProps, useThemeSettings };
|
|
419
|
+
export { Appbar, type AppearanceIcons, AppearancePicker, AutoAppearancePicker, AutoPalettePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, FadingScrollView, type FadingScrollViewProps, HarmonyPicker, IconButton, type IconButtonProps, type MaskedViewModule, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, type ReanimatedModule, SCROLL_FADE_HEIGHT, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeBridgeProps, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getContrastRatio, getHex, getLuminance, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, resolveSeedColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useMaskedViewModule, useNavBarContext, usePaperDefaults, useReanimatedModule, useThemeBridgeProps, useThemeSettings };
|
package/dist/index.js
CHANGED
|
@@ -57,7 +57,9 @@ __export(index_exports, {
|
|
|
57
57
|
getBlendedColor: () => getBlendedColor,
|
|
58
58
|
getColorRoles: () => getColorRoles,
|
|
59
59
|
getContrastColor: () => getContrastColor,
|
|
60
|
+
getContrastRatio: () => getContrastRatio,
|
|
60
61
|
getHex: () => getHex,
|
|
62
|
+
getLuminance: () => getLuminance,
|
|
61
63
|
getRgb: () => getRgb,
|
|
62
64
|
getThirdColor: () => getThirdColor,
|
|
63
65
|
getTintTextColor: () => getTintTextColor,
|
|
@@ -353,6 +355,21 @@ var getBlendedColor = (foreground, background, alpha) => {
|
|
|
353
355
|
return getHex(`rgb(${r}, ${g}, ${b})`) ?? background;
|
|
354
356
|
};
|
|
355
357
|
|
|
358
|
+
// src/utils/getContrastRatio.ts
|
|
359
|
+
var getLuminance = (value) => {
|
|
360
|
+
const rgb = getRgb(value);
|
|
361
|
+
if (!rgb) return 0;
|
|
362
|
+
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((channel) => {
|
|
363
|
+
const c = channel / 255;
|
|
364
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
365
|
+
});
|
|
366
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
367
|
+
};
|
|
368
|
+
var getContrastRatio = (a, b) => {
|
|
369
|
+
const [lighter, darker] = [getLuminance(a), getLuminance(b)].sort((x, y) => y - x);
|
|
370
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
371
|
+
};
|
|
372
|
+
|
|
356
373
|
// src/utils/getTonalColor.ts
|
|
357
374
|
var getTonalColor = (color, lightness) => {
|
|
358
375
|
const rgb = getRgb(color);
|
|
@@ -417,11 +434,13 @@ var isDarkColor = (value) => {
|
|
|
417
434
|
};
|
|
418
435
|
|
|
419
436
|
// src/utils/getColorRoles.ts
|
|
437
|
+
var MIN_LIGHT_ON_COLOR_CONTRAST = 2.5;
|
|
420
438
|
var getColorRoles = (base, surface, containerAlpha = 0.15) => {
|
|
421
439
|
const container = getBlendedColor(base, surface, containerAlpha);
|
|
440
|
+
const lightOnColor = getTonalColor(base, 0.95);
|
|
422
441
|
return {
|
|
423
442
|
color: base,
|
|
424
|
-
onColor:
|
|
443
|
+
onColor: getContrastRatio(lightOnColor, base) >= MIN_LIGHT_ON_COLOR_CONTRAST ? lightOnColor : getTonalColor(base, 0.08),
|
|
425
444
|
container,
|
|
426
445
|
onContainer: getTonalColor(base, isDarkColor(container) ? 0.92 : 0.12)
|
|
427
446
|
};
|
|
@@ -628,7 +647,7 @@ function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, maskedVie
|
|
|
628
647
|
}, [theme, onReady]);
|
|
629
648
|
if (!theme) return null;
|
|
630
649
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_paper2.Provider, { theme, children: [
|
|
631
|
-
statusBar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.StatusBar, {
|
|
650
|
+
statusBar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.StatusBar, { barStyle: theme.dark ? "light-content" : "dark-content", ...statusBarProps }),
|
|
632
651
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBarEnabledContext.Provider, { value: statusBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MaskedViewModuleContext.Provider, { value: maskedView, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper2.Portal.Host, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) }) }) }) })
|
|
633
652
|
] });
|
|
634
653
|
}
|
|
@@ -643,7 +662,7 @@ function Header({ style, ...props }) {
|
|
|
643
662
|
const theme = (0, import_react_native_paper3.useTheme)();
|
|
644
663
|
const statusBar = useStatusBarEnabled();
|
|
645
664
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
646
|
-
statusBar && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.StatusBar, {
|
|
665
|
+
statusBar && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.StatusBar, { barStyle: theme.dark ? "light-content" : "dark-content" }),
|
|
647
666
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper3.Appbar.Header, { ...defaults.AppbarHeader, ...props, style })
|
|
648
667
|
] });
|
|
649
668
|
}
|
|
@@ -1325,7 +1344,9 @@ var getTintTextColor = (tintColor, backdropColor, tintOpacity) => {
|
|
|
1325
1344
|
getBlendedColor,
|
|
1326
1345
|
getColorRoles,
|
|
1327
1346
|
getContrastColor,
|
|
1347
|
+
getContrastRatio,
|
|
1328
1348
|
getHex,
|
|
1349
|
+
getLuminance,
|
|
1329
1350
|
getRgb,
|
|
1330
1351
|
getThirdColor,
|
|
1331
1352
|
getTintTextColor,
|
package/dist/index.mjs
CHANGED
|
@@ -267,6 +267,21 @@ var getBlendedColor = (foreground, background, alpha) => {
|
|
|
267
267
|
return getHex(`rgb(${r}, ${g}, ${b})`) ?? background;
|
|
268
268
|
};
|
|
269
269
|
|
|
270
|
+
// src/utils/getContrastRatio.ts
|
|
271
|
+
var getLuminance = (value) => {
|
|
272
|
+
const rgb = getRgb(value);
|
|
273
|
+
if (!rgb) return 0;
|
|
274
|
+
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((channel) => {
|
|
275
|
+
const c = channel / 255;
|
|
276
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
277
|
+
});
|
|
278
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
279
|
+
};
|
|
280
|
+
var getContrastRatio = (a, b) => {
|
|
281
|
+
const [lighter, darker] = [getLuminance(a), getLuminance(b)].sort((x, y) => y - x);
|
|
282
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
283
|
+
};
|
|
284
|
+
|
|
270
285
|
// src/utils/getTonalColor.ts
|
|
271
286
|
var getTonalColor = (color, lightness) => {
|
|
272
287
|
const rgb = getRgb(color);
|
|
@@ -331,11 +346,13 @@ var isDarkColor = (value) => {
|
|
|
331
346
|
};
|
|
332
347
|
|
|
333
348
|
// src/utils/getColorRoles.ts
|
|
349
|
+
var MIN_LIGHT_ON_COLOR_CONTRAST = 2.5;
|
|
334
350
|
var getColorRoles = (base, surface, containerAlpha = 0.15) => {
|
|
335
351
|
const container = getBlendedColor(base, surface, containerAlpha);
|
|
352
|
+
const lightOnColor = getTonalColor(base, 0.95);
|
|
336
353
|
return {
|
|
337
354
|
color: base,
|
|
338
|
-
onColor:
|
|
355
|
+
onColor: getContrastRatio(lightOnColor, base) >= MIN_LIGHT_ON_COLOR_CONTRAST ? lightOnColor : getTonalColor(base, 0.08),
|
|
339
356
|
container,
|
|
340
357
|
onContainer: getTonalColor(base, isDarkColor(container) ? 0.92 : 0.12)
|
|
341
358
|
};
|
|
@@ -542,7 +559,7 @@ function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, maskedVie
|
|
|
542
559
|
}, [theme, onReady]);
|
|
543
560
|
if (!theme) return null;
|
|
544
561
|
return /* @__PURE__ */ jsxs(PaperProvider, { theme, children: [
|
|
545
|
-
statusBar && /* @__PURE__ */ jsx(StatusBar, {
|
|
562
|
+
statusBar && /* @__PURE__ */ jsx(StatusBar, { barStyle: theme.dark ? "light-content" : "dark-content", ...statusBarProps }),
|
|
546
563
|
/* @__PURE__ */ jsx(StatusBarEnabledContext.Provider, { value: statusBar, children: /* @__PURE__ */ jsx(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ jsx(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ jsx(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ jsx(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ jsx(MaskedViewModuleContext.Provider, { value: maskedView, children: /* @__PURE__ */ jsx(Portal.Host, { children: /* @__PURE__ */ jsx(View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) }) }) }) })
|
|
547
564
|
] });
|
|
548
565
|
}
|
|
@@ -557,7 +574,7 @@ function Header({ style, ...props }) {
|
|
|
557
574
|
const theme = useTheme();
|
|
558
575
|
const statusBar = useStatusBarEnabled();
|
|
559
576
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
560
|
-
statusBar && /* @__PURE__ */ jsx2(StatusBar2, {
|
|
577
|
+
statusBar && /* @__PURE__ */ jsx2(StatusBar2, { barStyle: theme.dark ? "light-content" : "dark-content" }),
|
|
561
578
|
/* @__PURE__ */ jsx2(PaperAppbar.Header, { ...defaults.AppbarHeader, ...props, style })
|
|
562
579
|
] });
|
|
563
580
|
}
|
|
@@ -1238,7 +1255,9 @@ export {
|
|
|
1238
1255
|
getBlendedColor,
|
|
1239
1256
|
getColorRoles,
|
|
1240
1257
|
getContrastColor,
|
|
1258
|
+
getContrastRatio,
|
|
1241
1259
|
getHex,
|
|
1260
|
+
getLuminance,
|
|
1242
1261
|
getRgb,
|
|
1243
1262
|
getThirdColor,
|
|
1244
1263
|
getTintTextColor,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rific/auto-paper",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "Adaptive react-native-paper theming: triadic palette from a seed color + system/light/dark appearance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@infinitetoken/eslint-config": "0.2.0",
|
|
72
|
-
"@infinitetoken/jest-config": "^0.
|
|
73
|
-
"@infinitetoken/tsconfig": "^0.
|
|
72
|
+
"@infinitetoken/jest-config": "^0.5.1",
|
|
73
|
+
"@infinitetoken/tsconfig": "^0.6.0",
|
|
74
74
|
"@react-native-masked-view/masked-view": "^0.3.2",
|
|
75
75
|
"@reduxjs/toolkit": "^2.0.0",
|
|
76
76
|
"@rific/core": "^0.3.0",
|
|
@@ -81,16 +81,16 @@
|
|
|
81
81
|
"eslint": "^9.39.4",
|
|
82
82
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
83
83
|
"eslint-plugin-react-native": "^5.0.0",
|
|
84
|
-
"expo-blur": "^
|
|
85
|
-
"expo-navigation-bar": "^
|
|
84
|
+
"expo-blur": "^58.0.3",
|
|
85
|
+
"expo-navigation-bar": "^58.0.4",
|
|
86
86
|
"jest": "^30.0.0",
|
|
87
87
|
"jest-environment-jsdom": "^30.0.0",
|
|
88
88
|
"prettier": "^3.8.3",
|
|
89
89
|
"react": "^19.0.0",
|
|
90
90
|
"react-dom": "^19.0.0",
|
|
91
|
-
"react-native": "^0.
|
|
91
|
+
"react-native": "^0.88.0",
|
|
92
92
|
"react-native-paper": "^5.12.0",
|
|
93
|
-
"react-native-safe-area-context": "^5.
|
|
93
|
+
"react-native-safe-area-context": "^5.10.1",
|
|
94
94
|
"tsup": "^8.0.0",
|
|
95
95
|
"typescript": "^6.0.3"
|
|
96
96
|
},
|
package/src/ThemeProvider.tsx
CHANGED
|
@@ -109,7 +109,7 @@ function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, maskedVie
|
|
|
109
109
|
|
|
110
110
|
return (
|
|
111
111
|
<PaperProvider theme={theme}>
|
|
112
|
-
{statusBar && <StatusBar
|
|
112
|
+
{statusBar && <StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} {...statusBarProps} />}
|
|
113
113
|
<StatusBarEnabledContext.Provider value={statusBar}>
|
|
114
114
|
<PaperDefaultsContext.Provider value={defaults ?? {}}>
|
|
115
115
|
<NavBarContext.Provider value={{ navigationBar, onNavBarChange }}>
|
|
@@ -11,7 +11,7 @@ function Header({ style, ...props }: AppbarHeaderProps) {
|
|
|
11
11
|
const statusBar = useStatusBarEnabled()
|
|
12
12
|
return (
|
|
13
13
|
<>
|
|
14
|
-
{statusBar && <StatusBar
|
|
14
|
+
{statusBar && <StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />}
|
|
15
15
|
<PaperAppbar.Header {...defaults.AppbarHeader} {...props} style={style} />
|
|
16
16
|
</>
|
|
17
17
|
)
|
package/src/index.ts
CHANGED
|
@@ -43,6 +43,7 @@ export { useThemeSettings } from './useThemeSettings'
|
|
|
43
43
|
export { getBlendedColor } from './utils/getBlendedColor'
|
|
44
44
|
export type { ColorRoles } from './utils/getColorRoles'
|
|
45
45
|
export { getColorRoles } from './utils/getColorRoles'
|
|
46
|
+
export { getContrastRatio, getLuminance } from './utils/getContrastRatio'
|
|
46
47
|
export { getHex } from './utils/getHex'
|
|
47
48
|
export type { RGB } from './utils/getRgb'
|
|
48
49
|
export { getRgb } from './utils/getRgb'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getBlendedColor } from './getBlendedColor'
|
|
2
|
+
import { getContrastRatio } from './getContrastRatio'
|
|
2
3
|
import { getTonalColor } from './getTonalColor'
|
|
3
4
|
import { isDarkColor } from './isDarkColor'
|
|
4
5
|
|
|
@@ -9,18 +10,28 @@ export type ColorRoles = {
|
|
|
9
10
|
onContainer: string
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
// The lowest contrast light text may have on a color before onColor turns dark instead. The fleet's
|
|
14
|
+
// colored buttons have always had light text, and keep it wherever it reads about as well as it does on
|
|
15
|
+
// their reds, blues and greens (2.8 to 3.1:1 for the picker's Red, Blue, Emerald and Coral); only
|
|
16
|
+
// colors light enough that it fades take dark text: the picker's Lime, Amber, Cyan and Sky (Yellow
|
|
17
|
+
// already did). Judging by the color's own darkness alone gave those near-white text that all but
|
|
18
|
+
// vanished (Jay, 2026-10-19: 1.96:1 on a light green Ready button).
|
|
19
|
+
const MIN_LIGHT_ON_COLOR_CONTRAST = 2.5
|
|
20
|
+
|
|
12
21
|
// The MD3 "color / onColor / container / onContainer" quadruple, generalized from the math
|
|
13
22
|
// useComputedTheme already uses for primary/secondary/tertiary: onColor is tuned to sit on the
|
|
14
|
-
// vivid color itself
|
|
23
|
+
// vivid color itself (light while it reads, MIN_LIGHT_ON_COLOR_CONTRAST, else dark), container is
|
|
24
|
+
// base blended toward the theme's real surface (so it inherits
|
|
15
25
|
// that surface's actual lightness rather than a fixed light/dark guess), and onContainer is tuned
|
|
16
26
|
// against the *computed* container rather than a light/dark assumption, so it stays readable
|
|
17
27
|
// whatever surface color it's handed. onContainer also reads safely directly on `surface` itself
|
|
18
28
|
// (not just on `container`), since container is only lightly blended toward it.
|
|
19
29
|
export const getColorRoles = (base: string, surface: string, containerAlpha = 0.15): ColorRoles => {
|
|
20
30
|
const container = getBlendedColor(base, surface, containerAlpha)
|
|
31
|
+
const lightOnColor = getTonalColor(base, 0.95)
|
|
21
32
|
return {
|
|
22
33
|
color: base,
|
|
23
|
-
onColor:
|
|
34
|
+
onColor: getContrastRatio(lightOnColor, base) >= MIN_LIGHT_ON_COLOR_CONTRAST ? lightOnColor : getTonalColor(base, 0.08),
|
|
24
35
|
container,
|
|
25
36
|
onContainer: getTonalColor(base, isDarkColor(container) ? 0.92 : 0.12)
|
|
26
37
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRgb } from './getRgb'
|
|
2
|
+
|
|
3
|
+
// WCAG 2's relative luminance (0 for black, 1 for white): the same formula isDarkColor applies.
|
|
4
|
+
export const getLuminance = (value: string): number => {
|
|
5
|
+
const rgb = getRgb(value)
|
|
6
|
+
if (!rgb) return 0
|
|
7
|
+
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((channel) => {
|
|
8
|
+
const c = channel / 255
|
|
9
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
|
10
|
+
})
|
|
11
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// WCAG 2's contrast ratio between two colors, 1 (identical) to 21 (black on white). Order doesn't matter.
|
|
15
|
+
export const getContrastRatio = (a: string, b: string): number => {
|
|
16
|
+
const [lighter, darker] = [getLuminance(a), getLuminance(b)].sort((x, y) => y - x)
|
|
17
|
+
return (lighter + 0.05) / (darker + 0.05)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default getContrastRatio
|