@rific/auto-paper 0.6.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -5
- package/dist/index.d.mts +63 -10
- package/dist/index.d.ts +63 -10
- package/dist/index.js +238 -63
- package/dist/index.mjs +222 -62
- package/package.json +4 -2
- package/src/components/AppearancePicker.tsx +25 -5
- package/src/components/BlurView.tsx +1 -1
- package/src/components/ColorPicker.tsx +17 -10
- package/src/components/Dialog.tsx +57 -22
- package/src/components/PalettePicker.tsx +117 -0
- package/src/index.ts +9 -2
- package/src/useComputedTheme.ts +87 -21
- package/src/useSwatchGrid.ts +18 -0
- package/src/useTheme.ts +11 -0
- package/src/utils/getColorRoles.ts +29 -0
- package/src/utils/getSwatchContrast.ts +26 -0
package/dist/index.mjs
CHANGED
|
@@ -61,11 +61,19 @@ var Appbar = {
|
|
|
61
61
|
import { SegmentedButtons } from "react-native-paper";
|
|
62
62
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
63
63
|
var APPEARANCES = [
|
|
64
|
-
{ label: "System", value: "system"
|
|
65
|
-
{ label: "Light", value: "light"
|
|
66
|
-
{ label: "Dark", value: "dark"
|
|
64
|
+
{ label: "System", value: "system" },
|
|
65
|
+
{ label: "Light", value: "light" },
|
|
66
|
+
{ label: "Dark", value: "dark" }
|
|
67
67
|
];
|
|
68
|
-
var
|
|
68
|
+
var DEFAULT_ICONS = {
|
|
69
|
+
system: "monitor",
|
|
70
|
+
light: "white-balance-sunny",
|
|
71
|
+
dark: "weather-night"
|
|
72
|
+
};
|
|
73
|
+
var AppearancePicker = ({ value, onChange, showLabels = true, icons }) => {
|
|
74
|
+
const resolvedIcons = { ...DEFAULT_ICONS, ...icons };
|
|
75
|
+
return /* @__PURE__ */ jsx2(SegmentedButtons, { value, onValueChange: (v) => onChange(v), buttons: APPEARANCES.map((a) => ({ value: a.value, label: showLabels ? a.label : void 0, icon: resolvedIcons[a.value], accessibilityLabel: a.label })) });
|
|
76
|
+
};
|
|
69
77
|
|
|
70
78
|
// src/components/BlurView.tsx
|
|
71
79
|
import { useContext as useContext3 } from "react";
|
|
@@ -95,7 +103,7 @@ var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacit
|
|
|
95
103
|
tint,
|
|
96
104
|
children
|
|
97
105
|
] });
|
|
98
|
-
return /* @__PURE__ */ jsxs2(View, { style: [{ backgroundColor: surfaceColor }, style], children: [
|
|
106
|
+
return /* @__PURE__ */ jsxs2(View, { ...props, style: [{ backgroundColor: surfaceColor }, style], children: [
|
|
99
107
|
tint,
|
|
100
108
|
children
|
|
101
109
|
] });
|
|
@@ -407,6 +415,27 @@ var getTonalColor = (color, lightness) => {
|
|
|
407
415
|
return getHex(`rgb(${toInt(r2)}, ${toInt(g2)}, ${toInt(b2)})`) ?? color;
|
|
408
416
|
};
|
|
409
417
|
|
|
418
|
+
// src/utils/isDarkColor.ts
|
|
419
|
+
var isDarkColor = (value) => {
|
|
420
|
+
const rgb = getRgb(value);
|
|
421
|
+
if (!rgb) return false;
|
|
422
|
+
const [r, g, b] = [rgb.r / 255, rgb.g / 255, rgb.b / 255];
|
|
423
|
+
const [rs, gs, bs] = [r, g, b].map((c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
424
|
+
const luminance = 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
425
|
+
return luminance < 0.5;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// src/utils/getColorRoles.ts
|
|
429
|
+
var getColorRoles = (base, surface, containerAlpha = 0.15) => {
|
|
430
|
+
const container = getBlendedColor(base, surface, containerAlpha);
|
|
431
|
+
return {
|
|
432
|
+
color: base,
|
|
433
|
+
onColor: getTonalColor(base, isDarkColor(base) ? 0.95 : 0.08),
|
|
434
|
+
container,
|
|
435
|
+
onContainer: getTonalColor(base, isDarkColor(container) ? 0.92 : 0.12)
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
|
|
410
439
|
// src/utils/getTriadicPalette.ts
|
|
411
440
|
var HARMONY_OFFSETS = {
|
|
412
441
|
analogous: [1 / 12, 2 / 12],
|
|
@@ -488,17 +517,12 @@ function hslToHex(h, s, l) {
|
|
|
488
517
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
489
518
|
}
|
|
490
519
|
|
|
491
|
-
// src/utils/isDarkColor.ts
|
|
492
|
-
var isDarkColor = (value) => {
|
|
493
|
-
const rgb = getRgb(value);
|
|
494
|
-
if (!rgb) return false;
|
|
495
|
-
const [r, g, b] = [rgb.r / 255, rgb.g / 255, rgb.b / 255];
|
|
496
|
-
const [rs, gs, bs] = [r, g, b].map((c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
497
|
-
const luminance = 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
498
|
-
return luminance < 0.5;
|
|
499
|
-
};
|
|
500
|
-
|
|
501
520
|
// src/useComputedTheme.ts
|
|
521
|
+
var SEMANTIC_BASE_COLORS = {
|
|
522
|
+
success: "#2E7D32",
|
|
523
|
+
warning: "#A15C00",
|
|
524
|
+
danger: "#B00020"
|
|
525
|
+
};
|
|
502
526
|
function useComputedTheme(appearance, color, harmony = "split-complementary") {
|
|
503
527
|
const [theme, setTheme] = useState(null);
|
|
504
528
|
const computeTheme = useCallback(() => {
|
|
@@ -506,27 +530,45 @@ function useComputedTheme(appearance, color, harmony = "split-complementary") {
|
|
|
506
530
|
if (resolved === "system") resolved = Appearance.getColorScheme();
|
|
507
531
|
if (resolved === "no-preference" || !resolved) resolved = "light";
|
|
508
532
|
const base = resolved === "dark" ? MD3DarkTheme : MD3LightTheme;
|
|
533
|
+
const surface = base.colors.surface;
|
|
534
|
+
const successRoles = getColorRoles(SEMANTIC_BASE_COLORS.success, surface);
|
|
535
|
+
const warningRoles = getColorRoles(SEMANTIC_BASE_COLORS.warning, surface);
|
|
536
|
+
const dangerRoles = getColorRoles(SEMANTIC_BASE_COLORS.danger, surface);
|
|
509
537
|
const _theme = {
|
|
510
538
|
...base,
|
|
511
539
|
colors: {
|
|
512
540
|
...base.colors,
|
|
513
|
-
elevation: { ...base.colors.elevation }
|
|
541
|
+
elevation: { ...base.colors.elevation },
|
|
542
|
+
success: successRoles.color,
|
|
543
|
+
onSuccess: successRoles.onColor,
|
|
544
|
+
successContainer: successRoles.container,
|
|
545
|
+
onSuccessContainer: successRoles.onContainer,
|
|
546
|
+
warning: warningRoles.color,
|
|
547
|
+
onWarning: warningRoles.onColor,
|
|
548
|
+
warningContainer: warningRoles.container,
|
|
549
|
+
onWarningContainer: warningRoles.onContainer,
|
|
550
|
+
danger: dangerRoles.color,
|
|
551
|
+
onDanger: dangerRoles.onColor,
|
|
552
|
+
dangerContainer: dangerRoles.container,
|
|
553
|
+
onDangerContainer: dangerRoles.onContainer
|
|
514
554
|
}
|
|
515
555
|
};
|
|
516
556
|
const palette = getTriadicPalette(color, harmony);
|
|
517
|
-
const
|
|
518
|
-
_theme.colors.primary =
|
|
519
|
-
_theme.colors.onPrimary =
|
|
520
|
-
_theme.colors.primaryContainer =
|
|
521
|
-
_theme.colors.onPrimaryContainer =
|
|
522
|
-
|
|
523
|
-
_theme.colors.
|
|
524
|
-
_theme.colors.
|
|
525
|
-
_theme.colors.
|
|
526
|
-
_theme.colors.
|
|
527
|
-
|
|
528
|
-
_theme.colors.
|
|
529
|
-
_theme.colors.
|
|
557
|
+
const primaryRoles = getColorRoles(palette.primary, surface);
|
|
558
|
+
_theme.colors.primary = primaryRoles.color;
|
|
559
|
+
_theme.colors.onPrimary = primaryRoles.onColor;
|
|
560
|
+
_theme.colors.primaryContainer = primaryRoles.container;
|
|
561
|
+
_theme.colors.onPrimaryContainer = primaryRoles.onContainer;
|
|
562
|
+
const secondaryRoles = getColorRoles(palette.secondary, surface);
|
|
563
|
+
_theme.colors.secondary = secondaryRoles.color;
|
|
564
|
+
_theme.colors.onSecondary = secondaryRoles.onColor;
|
|
565
|
+
_theme.colors.secondaryContainer = secondaryRoles.container;
|
|
566
|
+
_theme.colors.onSecondaryContainer = secondaryRoles.onContainer;
|
|
567
|
+
const tertiaryRoles = getColorRoles(palette.tertiary, surface);
|
|
568
|
+
_theme.colors.tertiary = tertiaryRoles.color;
|
|
569
|
+
_theme.colors.onTertiary = tertiaryRoles.onColor;
|
|
570
|
+
_theme.colors.tertiaryContainer = tertiaryRoles.container;
|
|
571
|
+
_theme.colors.onTertiaryContainer = tertiaryRoles.onContainer;
|
|
530
572
|
const blended = getBlendedColor(color, palette.secondary, 0.5);
|
|
531
573
|
const tintSurface = (alpha) => getBlendedColor(blended, surface, alpha);
|
|
532
574
|
_theme.colors.background = base.dark ? "#0F0F0F" : "#F0F0F0";
|
|
@@ -635,35 +677,81 @@ function Chip({ variant, style, selectedColor, ...props }) {
|
|
|
635
677
|
}
|
|
636
678
|
|
|
637
679
|
// src/components/ColorPicker.tsx
|
|
638
|
-
import { useState as
|
|
680
|
+
import { useState as useState5 } from "react";
|
|
639
681
|
import { StyleSheet as StyleSheet4, TouchableOpacity, View as View4 } from "react-native";
|
|
640
|
-
import { Icon, TouchableRipple } from "react-native-paper";
|
|
682
|
+
import { Icon, TouchableRipple, useTheme as useTheme6 } from "react-native-paper";
|
|
683
|
+
|
|
684
|
+
// src/useSwatchGrid.ts
|
|
685
|
+
import { useState as useState3 } from "react";
|
|
686
|
+
var SWATCH_GRID_COLUMNS = 5;
|
|
687
|
+
var SWATCH_GRID_GAP = 12;
|
|
688
|
+
function useSwatchSize(fallback) {
|
|
689
|
+
const [width, setWidth] = useState3(0);
|
|
690
|
+
const onLayout = ({ nativeEvent }) => setWidth(nativeEvent.layout.width);
|
|
691
|
+
const size = width > 0 ? (width - SWATCH_GRID_GAP * (SWATCH_GRID_COLUMNS - 1)) / SWATCH_GRID_COLUMNS : fallback;
|
|
692
|
+
return { onLayout, size };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/utils/getSwatchContrast.ts
|
|
696
|
+
var RING_WIDTH = 1;
|
|
697
|
+
var SELECTED_RING_WIDTH = 3;
|
|
698
|
+
var getContrastColor = (color) => isDarkColor(color) ? "#ffffff" : "#000000";
|
|
699
|
+
var getSwatchRing = (color, selected, outlineColor) => ({
|
|
700
|
+
borderColor: selected ? getContrastColor(color) : outlineColor,
|
|
701
|
+
borderWidth: selected ? SELECTED_RING_WIDTH : RING_WIDTH
|
|
702
|
+
});
|
|
641
703
|
|
|
642
704
|
// src/components/Dialog.tsx
|
|
643
|
-
import { useEffect as useEffect4, useRef as useRef2, useState as
|
|
644
|
-
import { Animated, StyleSheet as StyleSheet3,
|
|
705
|
+
import React2, { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
|
|
706
|
+
import { Animated, BackHandler, Easing, Pressable, StyleSheet as StyleSheet3, View as View3 } from "react-native";
|
|
645
707
|
import { Dialog as PaperDialog, Portal, useTheme as useTheme5 } from "react-native-paper";
|
|
708
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
646
709
|
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
647
|
-
|
|
710
|
+
var ANIMATION_DURATION = 220;
|
|
711
|
+
var DIALOG_MARGIN = 32;
|
|
712
|
+
function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
|
|
648
713
|
const blur = useBlur(blurProp);
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
const
|
|
714
|
+
const theme = useTheme5();
|
|
715
|
+
const { colors, isV3, roundness } = theme;
|
|
716
|
+
const { scale } = theme.animation;
|
|
717
|
+
const { bottom, left, right, top } = useSafeAreaInsets();
|
|
718
|
+
const opacity = useRef2(new Animated.Value(visible ? 1 : 0)).current;
|
|
719
|
+
const [mounted, setMounted] = useState4(visible);
|
|
652
720
|
useEffect4(() => {
|
|
653
721
|
if (visible) {
|
|
654
722
|
setMounted(true);
|
|
655
|
-
Animated.timing(opacity, {
|
|
723
|
+
Animated.timing(opacity, { duration: scale * ANIMATION_DURATION, easing: Easing.out(Easing.cubic), toValue: 1, useNativeDriver: true }).start();
|
|
656
724
|
} else {
|
|
657
|
-
Animated.timing(opacity, {
|
|
725
|
+
Animated.timing(opacity, { duration: scale * ANIMATION_DURATION, easing: Easing.out(Easing.cubic), toValue: 0, useNativeDriver: true }).start(({ finished }) => {
|
|
726
|
+
if (finished) setMounted(false);
|
|
727
|
+
});
|
|
658
728
|
}
|
|
659
729
|
}, [visible]);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
730
|
+
useEffect4(() => {
|
|
731
|
+
if (!visible) return void 0;
|
|
732
|
+
const onHardwareBackPress = () => {
|
|
733
|
+
if (dismissable || dismissableBackButton) onDismiss?.();
|
|
734
|
+
return true;
|
|
735
|
+
};
|
|
736
|
+
const subscription = BackHandler.addEventListener("hardwareBackPress", onHardwareBackPress);
|
|
737
|
+
return () => subscription.remove();
|
|
738
|
+
}, [dismissable, dismissableBackButton, onDismiss, visible]);
|
|
663
739
|
if (!mounted) return null;
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
740
|
+
const content = React2.Children.toArray(children).filter((child) => child != null && typeof child !== "boolean").map((child, i) => {
|
|
741
|
+
if (isV3) {
|
|
742
|
+
if (i === 0 && React2.isValidElement(child)) {
|
|
743
|
+
return React2.cloneElement(child, { style: [{ marginTop: 24 }, child.props.style] });
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (i === 0 && React2.isValidElement(child) && child.type === PaperDialog.Content) {
|
|
747
|
+
return React2.cloneElement(child, { style: [{ paddingTop: 24 }, child.props.style] });
|
|
748
|
+
}
|
|
749
|
+
return child;
|
|
750
|
+
});
|
|
751
|
+
const borderRadius = (isV3 ? 7 : 1) * roundness;
|
|
752
|
+
return /* @__PURE__ */ jsx8(Portal, { children: /* @__PURE__ */ jsxs4(Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [StyleSheet3.absoluteFill, { opacity }], testID, children: [
|
|
753
|
+
/* @__PURE__ */ jsx8(Pressable, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [StyleSheet3.absoluteFill, { backgroundColor: colors.backdrop }], testID: `${testID}-backdrop` }),
|
|
754
|
+
/* @__PURE__ */ jsx8(View3, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: /* @__PURE__ */ jsx8(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) })
|
|
667
755
|
] }) });
|
|
668
756
|
}
|
|
669
757
|
DialogComponent.Title = PaperDialog.Title;
|
|
@@ -674,12 +762,12 @@ var Dialog = DialogComponent;
|
|
|
674
762
|
var styles2 = StyleSheet3.create({
|
|
675
763
|
// Width cap keeps dialogs card-sized on wide viewports (web/tablet); no effect on phones.
|
|
676
764
|
card: { alignSelf: "center", maxWidth: 400, overflow: "hidden", width: "100%" },
|
|
677
|
-
|
|
678
|
-
wrapper: { flex: 1, justifyContent: "center", paddingHorizontal: 26 }
|
|
765
|
+
wrapper: { flex: 1, justifyContent: "center", paddingVertical: DIALOG_MARGIN }
|
|
679
766
|
});
|
|
680
767
|
|
|
681
768
|
// src/components/ColorPicker.tsx
|
|
682
769
|
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
770
|
+
var SIZE = 48;
|
|
683
771
|
var defaultColors = [
|
|
684
772
|
{ label: "Red", value: "#f44336" },
|
|
685
773
|
{ label: "Coral", value: "#ff5722" },
|
|
@@ -690,6 +778,7 @@ var defaultColors = [
|
|
|
690
778
|
{ label: "Emerald", value: "#4caf50" },
|
|
691
779
|
{ label: "Teal", value: "#009688" },
|
|
692
780
|
{ label: "Cyan", value: "#00bcd4" },
|
|
781
|
+
{ label: "Sky", value: "#03a9f4" },
|
|
693
782
|
{ label: "Blue", value: "#2196f3" },
|
|
694
783
|
{ label: "Indigo", value: "#3f51b5" },
|
|
695
784
|
{ label: "Deep Purple", value: "#673ab7" },
|
|
@@ -698,32 +787,34 @@ var defaultColors = [
|
|
|
698
787
|
{ label: "Pink", value: "#ec407a" },
|
|
699
788
|
{ label: "Rose", value: "#e91e63" },
|
|
700
789
|
{ label: "Brown", value: "#795548" },
|
|
790
|
+
{ label: "Taupe", value: "#8d7b68" },
|
|
701
791
|
{ label: "Blue Grey", value: "#607d8b" }
|
|
702
792
|
];
|
|
703
793
|
var ColorPicker = ({ value, onChange, colors = defaultColors, blur: blurProp }) => {
|
|
704
794
|
const blur = useBlur(blurProp);
|
|
705
|
-
const
|
|
795
|
+
const { colors: themeColors } = useTheme6();
|
|
796
|
+
const [open, setOpen] = useState5(false);
|
|
797
|
+
const { onLayout, size } = useSwatchSize(SIZE);
|
|
706
798
|
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
707
|
-
/* @__PURE__ */ jsx9(TouchableRipple, { onPress: () => setOpen(true), style: [styles3.trigger, { backgroundColor: value }], borderless: true, children: /* @__PURE__ */ jsx9(Icon, { source: "palette", size: 20, color:
|
|
708
|
-
/* @__PURE__ */ jsx9(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */ jsx9(Dialog.Content, { children: /* @__PURE__ */ jsx9(View4, { style: styles3.swatches, children: colors.map(({ value: hex }) => /* @__PURE__ */ jsx9(
|
|
799
|
+
/* @__PURE__ */ jsx9(TouchableRipple, { onPress: () => setOpen(true), style: [styles3.trigger, { backgroundColor: value }, getSwatchRing(value, false, themeColors.outlineVariant)], borderless: true, children: /* @__PURE__ */ jsx9(Icon, { source: "palette", size: 20, color: getContrastColor(value) }) }),
|
|
800
|
+
/* @__PURE__ */ jsx9(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */ jsx9(Dialog.Content, { children: /* @__PURE__ */ jsx9(View4, { style: styles3.swatches, onLayout, children: colors.map(({ value: hex }) => /* @__PURE__ */ jsx9(
|
|
709
801
|
TouchableOpacity,
|
|
710
802
|
{
|
|
711
803
|
onPress: () => {
|
|
712
804
|
onChange(hex);
|
|
713
805
|
setOpen(false);
|
|
714
806
|
},
|
|
715
|
-
style: [styles3.swatch, { backgroundColor: hex }, value === hex
|
|
716
|
-
children: value === hex && /* @__PURE__ */ jsx9(Icon, { source: "check", size: 20, color:
|
|
807
|
+
style: [styles3.swatch, { backgroundColor: hex, borderRadius: size / 2, height: size, width: size }, getSwatchRing(hex, value === hex, themeColors.outlineVariant)],
|
|
808
|
+
children: value === hex && /* @__PURE__ */ jsx9(Icon, { source: "check", size: 20, color: getContrastColor(hex) })
|
|
717
809
|
},
|
|
718
810
|
hex
|
|
719
811
|
)) }) }) })
|
|
720
812
|
] });
|
|
721
813
|
};
|
|
722
814
|
var styles3 = StyleSheet4.create({
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
trigger: { alignItems: "center", alignSelf: "flex-start", borderRadius: 24, height: 48, justifyContent: "center", width: 48 }
|
|
815
|
+
swatch: { alignItems: "center", justifyContent: "center" },
|
|
816
|
+
swatches: { flexDirection: "row", flexWrap: "wrap", gap: SWATCH_GRID_GAP, paddingTop: 12 },
|
|
817
|
+
trigger: { alignItems: "center", alignSelf: "flex-start", borderRadius: SIZE / 2, height: SIZE, justifyContent: "center", width: SIZE }
|
|
727
818
|
});
|
|
728
819
|
|
|
729
820
|
// src/components/FAB.tsx
|
|
@@ -735,7 +826,7 @@ function FAB(props) {
|
|
|
735
826
|
}
|
|
736
827
|
|
|
737
828
|
// src/components/HarmonyPicker.tsx
|
|
738
|
-
import { useState as
|
|
829
|
+
import { useState as useState6 } from "react";
|
|
739
830
|
import { Button as Button2 } from "react-native-paper";
|
|
740
831
|
|
|
741
832
|
// src/components/Menu.tsx
|
|
@@ -765,7 +856,7 @@ var HARMONIES = [
|
|
|
765
856
|
];
|
|
766
857
|
var HarmonyPicker = ({ value, onChange, blur: blurProp }) => {
|
|
767
858
|
const blur = useBlur(blurProp);
|
|
768
|
-
const [open, setOpen] =
|
|
859
|
+
const [open, setOpen] = useState6(false);
|
|
769
860
|
const selected = HARMONIES.find((h) => h.value === value);
|
|
770
861
|
return /* @__PURE__ */ jsx12(
|
|
771
862
|
Menu,
|
|
@@ -791,23 +882,83 @@ var HarmonyPicker = ({ value, onChange, blur: blurProp }) => {
|
|
|
791
882
|
};
|
|
792
883
|
|
|
793
884
|
// src/components/IconButton.tsx
|
|
794
|
-
import { IconButton as PaperIconButton, useTheme as
|
|
885
|
+
import { IconButton as PaperIconButton, useTheme as useTheme7 } from "react-native-paper";
|
|
795
886
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
796
887
|
function IconButton({ variant, iconColor, containerColor, ...props }) {
|
|
797
888
|
const defaults = usePaperDefaults();
|
|
798
|
-
const { colors } =
|
|
889
|
+
const { colors } = useTheme7();
|
|
799
890
|
const variantContainerColor = variant === "primary" ? colors.primaryContainer : variant === "secondary" ? colors.secondaryContainer : variant === "tertiary" ? colors.tertiaryContainer : variant === "surface" ? colors.surface : void 0;
|
|
800
891
|
const variantIconColor = variant === "primary" ? colors.onPrimaryContainer : variant === "secondary" ? colors.onSecondaryContainer : variant === "tertiary" ? colors.onTertiaryContainer : variant === "surface" ? colors.onSurface : void 0;
|
|
801
892
|
return /* @__PURE__ */ jsx13(PaperIconButton, { ...defaults.IconButton, containerColor: containerColor ?? variantContainerColor, iconColor: iconColor ?? variantIconColor, ...props });
|
|
802
893
|
}
|
|
803
894
|
|
|
895
|
+
// src/components/PalettePicker.tsx
|
|
896
|
+
import { useState as useState7 } from "react";
|
|
897
|
+
import { StyleSheet as StyleSheet6, TouchableOpacity as TouchableOpacity2, View as View5 } from "react-native";
|
|
898
|
+
import { Icon as Icon2, TouchableRipple as TouchableRipple2, useTheme as useTheme8 } from "react-native-paper";
|
|
899
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
900
|
+
var SIZE2 = 48;
|
|
901
|
+
var defaultWeights = { primary: 2, secondary: 1, tertiary: 1 };
|
|
902
|
+
var buildWedges = (weights) => {
|
|
903
|
+
const total = weights.primary + weights.secondary + weights.tertiary;
|
|
904
|
+
const primary = weights.primary / total * 360;
|
|
905
|
+
const secondary = weights.secondary / total * 360;
|
|
906
|
+
const tertiary = weights.tertiary / total * 360;
|
|
907
|
+
return [
|
|
908
|
+
{ key: "primary", rotate: -primary / 2, share: primary },
|
|
909
|
+
{ key: "tertiary", rotate: primary / 2, share: tertiary },
|
|
910
|
+
{ key: "secondary", rotate: primary / 2 + tertiary, share: secondary }
|
|
911
|
+
];
|
|
912
|
+
};
|
|
913
|
+
var Pie = ({ palette, weights = defaultWeights, size = SIZE2 }) => /* @__PURE__ */ jsx14(View5, { style: [styles5.pie, { borderRadius: size / 2, height: size, width: size }], children: buildWedges(weights).map(({ key, rotate, share }) => /* @__PURE__ */ jsx14(View5, { style: [styles5.rotator, { transform: [{ rotate: `${rotate}deg` }] }], children: /* @__PURE__ */ jsx14(View5, { style: styles5.halfMask, children: /* @__PURE__ */ jsx14(View5, { style: [styles5.innerRotator, { transform: [{ rotate: `${share - 180}deg` }] }], children: /* @__PURE__ */ jsx14(View5, { style: [styles5.wedgeFill, { backgroundColor: palette[key] }] }) }) }) }, key)) });
|
|
914
|
+
var PalettePicker = ({ value, onChange, harmony = "split-complementary", weights, colors = defaultColors, blur: blurProp }) => {
|
|
915
|
+
const blur = useBlur(blurProp);
|
|
916
|
+
const { colors: themeColors } = useTheme8();
|
|
917
|
+
const [open, setOpen] = useState7(false);
|
|
918
|
+
const { onLayout, size } = useSwatchSize(SIZE2);
|
|
919
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
920
|
+
/* @__PURE__ */ jsx14(TouchableRipple2, { onPress: () => setOpen(true), style: styles5.trigger, borderless: true, children: /* @__PURE__ */ jsxs6(View5, { style: styles5.triggerContent, children: [
|
|
921
|
+
/* @__PURE__ */ jsx14(Pie, { palette: getTriadicPalette(value, harmony), weights }),
|
|
922
|
+
/* @__PURE__ */ jsx14(View5, { pointerEvents: "none", style: [styles5.triggerIcon, { borderRadius: SIZE2 / 2 }, getSwatchRing(value, false, themeColors.outlineVariant)], children: /* @__PURE__ */ jsx14(Icon2, { source: "palette", size: 20, color: getContrastColor(value) }) })
|
|
923
|
+
] }) }),
|
|
924
|
+
/* @__PURE__ */ jsx14(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */ jsx14(Dialog.Content, { children: /* @__PURE__ */ jsx14(View5, { style: styles5.swatches, onLayout, children: colors.map(({ value: hex }) => /* @__PURE__ */ jsxs6(
|
|
925
|
+
TouchableOpacity2,
|
|
926
|
+
{
|
|
927
|
+
onPress: () => {
|
|
928
|
+
onChange(hex);
|
|
929
|
+
setOpen(false);
|
|
930
|
+
},
|
|
931
|
+
style: [styles5.swatch, { height: size, width: size }],
|
|
932
|
+
children: [
|
|
933
|
+
/* @__PURE__ */ jsx14(Pie, { palette: getTriadicPalette(hex, harmony), weights, size }),
|
|
934
|
+
/* @__PURE__ */ jsx14(View5, { pointerEvents: "none", style: [styles5.ring, { borderRadius: size / 2 }, getSwatchRing(hex, value === hex, themeColors.outlineVariant)], children: value === hex && /* @__PURE__ */ jsx14(Icon2, { source: "check", size: 20, color: getContrastColor(hex) }) })
|
|
935
|
+
]
|
|
936
|
+
},
|
|
937
|
+
hex
|
|
938
|
+
)) }) }) })
|
|
939
|
+
] });
|
|
940
|
+
};
|
|
941
|
+
var styles5 = StyleSheet6.create({
|
|
942
|
+
halfMask: { height: "100%", left: "50%", overflow: "hidden", position: "absolute", top: 0, width: "50%" },
|
|
943
|
+
innerRotator: { height: "100%", left: "-100%", overflow: "hidden", position: "absolute", top: 0, width: "200%" },
|
|
944
|
+
pie: { overflow: "hidden" },
|
|
945
|
+
rotator: { height: "100%", left: 0, overflow: "hidden", position: "absolute", top: 0, width: "100%" },
|
|
946
|
+
ring: { alignItems: "center", bottom: 0, justifyContent: "center", left: 0, position: "absolute", right: 0, top: 0 },
|
|
947
|
+
swatch: { alignItems: "center", justifyContent: "center" },
|
|
948
|
+
swatches: { flexDirection: "row", flexWrap: "wrap", gap: SWATCH_GRID_GAP, paddingTop: 12 },
|
|
949
|
+
trigger: { alignItems: "center", alignSelf: "flex-start", borderRadius: SIZE2 / 2, height: SIZE2, justifyContent: "center", width: SIZE2 },
|
|
950
|
+
triggerContent: { height: SIZE2, width: SIZE2 },
|
|
951
|
+
triggerIcon: { alignItems: "center", bottom: 0, justifyContent: "center", left: 0, position: "absolute", right: 0, top: 0 },
|
|
952
|
+
wedgeFill: { height: "100%", left: "50%", position: "absolute", top: 0, width: "50%" }
|
|
953
|
+
});
|
|
954
|
+
|
|
804
955
|
// src/components/TextInput.tsx
|
|
805
956
|
import { forwardRef } from "react";
|
|
806
957
|
import { TextInput as PaperTextInput } from "react-native-paper";
|
|
807
|
-
import { jsx as
|
|
958
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
808
959
|
var TextInput = forwardRef(function TextInput2(props, ref) {
|
|
809
960
|
const defaults = usePaperDefaults();
|
|
810
|
-
return /* @__PURE__ */
|
|
961
|
+
return /* @__PURE__ */ jsx15(PaperTextInput, { ref, ...defaults.TextInput, ...props });
|
|
811
962
|
});
|
|
812
963
|
|
|
813
964
|
// src/redux/themeSlice.ts
|
|
@@ -847,6 +998,10 @@ var selectThemeBlur = (state) => state.blur;
|
|
|
847
998
|
var selectThemeColor = (state) => state.color;
|
|
848
999
|
var selectThemeHarmony = (state) => state.harmony;
|
|
849
1000
|
|
|
1001
|
+
// src/useTheme.ts
|
|
1002
|
+
import { useTheme as usePaperTheme } from "react-native-paper";
|
|
1003
|
+
var useAutoPaperTheme = () => usePaperTheme();
|
|
1004
|
+
|
|
850
1005
|
// src/useThemeSettings.ts
|
|
851
1006
|
import { useContext as useContext5 } from "react";
|
|
852
1007
|
var useThemeSettings = () => useContext5(ThemeSettingsContext);
|
|
@@ -869,13 +1024,17 @@ export {
|
|
|
869
1024
|
HarmonyPicker,
|
|
870
1025
|
IconButton,
|
|
871
1026
|
Menu,
|
|
1027
|
+
PalettePicker,
|
|
872
1028
|
Provider,
|
|
1029
|
+
SEMANTIC_BASE_COLORS,
|
|
873
1030
|
TextInput,
|
|
874
1031
|
ThemeSettingsContext,
|
|
875
1032
|
createThemeReducer,
|
|
876
1033
|
defaultColors,
|
|
877
1034
|
defaultThemeSettings,
|
|
878
1035
|
getBlendedColor,
|
|
1036
|
+
getColorRoles,
|
|
1037
|
+
getContrastColor,
|
|
879
1038
|
getHex,
|
|
880
1039
|
getRgb,
|
|
881
1040
|
getTintTextColor,
|
|
@@ -888,6 +1047,7 @@ export {
|
|
|
888
1047
|
selectThemeHarmony,
|
|
889
1048
|
themeActions,
|
|
890
1049
|
themeReducer,
|
|
1050
|
+
useAutoPaperTheme,
|
|
891
1051
|
useBlur,
|
|
892
1052
|
useComputedTheme,
|
|
893
1053
|
usePaperDefaults,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rific/auto-paper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"react-dom": "^19.0.0",
|
|
87
87
|
"react-native": "^0.76.0",
|
|
88
88
|
"react-native-paper": "^5.12.0",
|
|
89
|
+
"react-native-safe-area-context": "^5.5.2",
|
|
89
90
|
"ts-jest": "^29.4.9",
|
|
90
91
|
"ts-node": "^10.9.2",
|
|
91
92
|
"tsup": "^8.0.0",
|
|
@@ -97,7 +98,8 @@
|
|
|
97
98
|
"expo-navigation-bar": ">=56.0.0",
|
|
98
99
|
"react": ">=18.0.0",
|
|
99
100
|
"react-native": ">=0.76.0",
|
|
100
|
-
"react-native-paper": ">=5.0.0"
|
|
101
|
+
"react-native-paper": ">=5.0.0",
|
|
102
|
+
"react-native-safe-area-context": "*"
|
|
101
103
|
},
|
|
102
104
|
"peerDependenciesMeta": {
|
|
103
105
|
"expo-blur": {
|
|
@@ -2,15 +2,35 @@ import { SegmentedButtons } from 'react-native-paper'
|
|
|
2
2
|
|
|
3
3
|
import type { ThemeAppearance } from '../useComputedTheme'
|
|
4
4
|
|
|
5
|
-
const APPEARANCES: { label: string; value: ThemeAppearance
|
|
6
|
-
{ label: 'System', value: 'system'
|
|
7
|
-
{ label: 'Light', value: 'light'
|
|
8
|
-
{ label: 'Dark', value: 'dark'
|
|
5
|
+
const APPEARANCES: { label: string; value: ThemeAppearance }[] = [
|
|
6
|
+
{ label: 'System', value: 'system' },
|
|
7
|
+
{ label: 'Light', value: 'light' },
|
|
8
|
+
{ label: 'Dark', value: 'dark' }
|
|
9
9
|
]
|
|
10
10
|
|
|
11
|
+
export type AppearanceIcons = Partial<Record<ThemeAppearance, string>>
|
|
12
|
+
|
|
13
|
+
// 'monitor' (a plain device/screen glyph) reads more clearly as "match my device" than
|
|
14
|
+
// 'brightness-auto' (an exposure/brightness symbol) — swap via the `icons` prop for a different
|
|
15
|
+
// convention, e.g. `theme-light-dark`'s half-sun/half-moon.
|
|
16
|
+
const DEFAULT_ICONS: Record<ThemeAppearance, string> = {
|
|
17
|
+
system: 'monitor',
|
|
18
|
+
light: 'white-balance-sunny',
|
|
19
|
+
dark: 'weather-night'
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
type Props = {
|
|
12
23
|
value: ThemeAppearance
|
|
13
24
|
onChange: (appearance: ThemeAppearance) => void
|
|
25
|
+
// Hides the text label, leaving just the icon per segment — useful in tight layouts where
|
|
26
|
+
// "System" truncates. accessibilityLabel is always set regardless, so screen readers still
|
|
27
|
+
// announce the full word either way.
|
|
28
|
+
showLabels?: boolean
|
|
29
|
+
// Per-value icon overrides, merged over DEFAULT_ICONS — pass just the ones you want to change.
|
|
30
|
+
icons?: AppearanceIcons
|
|
14
31
|
}
|
|
15
32
|
|
|
16
|
-
export const AppearancePicker = ({ value, onChange
|
|
33
|
+
export const AppearancePicker = ({ value, onChange, showLabels = true, icons }: Props) => {
|
|
34
|
+
const resolvedIcons = { ...DEFAULT_ICONS, ...icons }
|
|
35
|
+
return <SegmentedButtons value={value} onValueChange={(v) => onChange(v as ThemeAppearance)} buttons={APPEARANCES.map((a) => ({ value: a.value, label: showLabels ? a.label : undefined, icon: resolvedIcons[a.value], accessibilityLabel: a.label }))} />
|
|
36
|
+
}
|
|
@@ -67,7 +67,7 @@ export const BlurView = ({ blur = true, children, elevation, style, tintColor, t
|
|
|
67
67
|
</ExpoBlurView>
|
|
68
68
|
)
|
|
69
69
|
return (
|
|
70
|
-
<View style={[{ backgroundColor: surfaceColor }, style]}>
|
|
70
|
+
<View {...props} style={[{ backgroundColor: surfaceColor }, style]}>
|
|
71
71
|
{tint}
|
|
72
72
|
{children}
|
|
73
73
|
</View>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { StyleSheet, TouchableOpacity, View } from 'react-native'
|
|
3
|
-
import { Icon, TouchableRipple } from 'react-native-paper'
|
|
3
|
+
import { Icon, TouchableRipple, useTheme } from 'react-native-paper'
|
|
4
4
|
|
|
5
5
|
import { useBlur } from '../BlurContext'
|
|
6
|
+
import { SWATCH_GRID_GAP, useSwatchSize } from '../useSwatchGrid'
|
|
7
|
+
import { getContrastColor, getSwatchRing } from '../utils/getSwatchContrast'
|
|
6
8
|
import { Dialog } from './Dialog'
|
|
7
9
|
|
|
10
|
+
const SIZE = 48
|
|
11
|
+
|
|
8
12
|
export type SeedColor = { label: string; value: string }
|
|
9
13
|
|
|
10
14
|
export const defaultColors: SeedColor[] = [
|
|
@@ -17,6 +21,7 @@ export const defaultColors: SeedColor[] = [
|
|
|
17
21
|
{ label: 'Emerald', value: '#4caf50' },
|
|
18
22
|
{ label: 'Teal', value: '#009688' },
|
|
19
23
|
{ label: 'Cyan', value: '#00bcd4' },
|
|
24
|
+
{ label: 'Sky', value: '#03a9f4' },
|
|
20
25
|
{ label: 'Blue', value: '#2196f3' },
|
|
21
26
|
{ label: 'Indigo', value: '#3f51b5' },
|
|
22
27
|
{ label: 'Deep Purple', value: '#673ab7' },
|
|
@@ -25,6 +30,7 @@ export const defaultColors: SeedColor[] = [
|
|
|
25
30
|
{ label: 'Pink', value: '#ec407a' },
|
|
26
31
|
{ label: 'Rose', value: '#e91e63' },
|
|
27
32
|
{ label: 'Brown', value: '#795548' },
|
|
33
|
+
{ label: 'Taupe', value: '#8d7b68' },
|
|
28
34
|
{ label: 'Blue Grey', value: '#607d8b' }
|
|
29
35
|
]
|
|
30
36
|
|
|
@@ -37,17 +43,19 @@ type Props = {
|
|
|
37
43
|
|
|
38
44
|
export const ColorPicker = ({ value, onChange, colors = defaultColors, blur: blurProp }: Props) => {
|
|
39
45
|
const blur = useBlur(blurProp)
|
|
46
|
+
const { colors: themeColors } = useTheme()
|
|
40
47
|
const [open, setOpen] = useState(false)
|
|
48
|
+
const { onLayout, size } = useSwatchSize(SIZE)
|
|
41
49
|
|
|
42
50
|
return (
|
|
43
51
|
<>
|
|
44
|
-
<TouchableRipple onPress={() => setOpen(true)} style={[styles.trigger, { backgroundColor: value }]} borderless>
|
|
45
|
-
<Icon source='palette' size={20} color=
|
|
52
|
+
<TouchableRipple onPress={() => setOpen(true)} style={[styles.trigger, { backgroundColor: value }, getSwatchRing(value, false, themeColors.outlineVariant)]} borderless>
|
|
53
|
+
<Icon source='palette' size={20} color={getContrastColor(value)} />
|
|
46
54
|
</TouchableRipple>
|
|
47
55
|
|
|
48
56
|
<Dialog blur={blur} visible={open} onDismiss={() => setOpen(false)}>
|
|
49
57
|
<Dialog.Content>
|
|
50
|
-
<View style={styles.swatches}>
|
|
58
|
+
<View style={styles.swatches} onLayout={onLayout}>
|
|
51
59
|
{colors.map(({ value: hex }) => (
|
|
52
60
|
<TouchableOpacity
|
|
53
61
|
key={hex}
|
|
@@ -55,9 +63,9 @@ export const ColorPicker = ({ value, onChange, colors = defaultColors, blur: blu
|
|
|
55
63
|
onChange(hex)
|
|
56
64
|
setOpen(false)
|
|
57
65
|
}}
|
|
58
|
-
style={[styles.swatch, { backgroundColor: hex }, value === hex
|
|
66
|
+
style={[styles.swatch, { backgroundColor: hex, borderRadius: size / 2, height: size, width: size }, getSwatchRing(hex, value === hex, themeColors.outlineVariant)]}
|
|
59
67
|
>
|
|
60
|
-
{value === hex && <Icon source='check' size={20} color=
|
|
68
|
+
{value === hex && <Icon source='check' size={20} color={getContrastColor(hex)} />}
|
|
61
69
|
</TouchableOpacity>
|
|
62
70
|
))}
|
|
63
71
|
</View>
|
|
@@ -68,8 +76,7 @@ export const ColorPicker = ({ value, onChange, colors = defaultColors, blur: blu
|
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
const styles = StyleSheet.create({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
trigger: { alignItems: 'center', alignSelf: 'flex-start', borderRadius: 24, height: 48, justifyContent: 'center', width: 48 }
|
|
79
|
+
swatch: { alignItems: 'center', justifyContent: 'center' },
|
|
80
|
+
swatches: { flexDirection: 'row', flexWrap: 'wrap', gap: SWATCH_GRID_GAP, paddingTop: 12 },
|
|
81
|
+
trigger: { alignItems: 'center', alignSelf: 'flex-start', borderRadius: SIZE / 2, height: SIZE, justifyContent: 'center', width: SIZE }
|
|
75
82
|
})
|