@rific/auto-paper 0.9.2 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rific/auto-paper",
3
- "version": "0.9.2",
3
+ "version": "0.9.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",
@@ -0,0 +1,17 @@
1
+ import { useThemeSettings } from '../useThemeSettings'
2
+ import { type AppearanceIcons, AppearancePicker } from './AppearancePicker'
3
+
4
+ type Props = {
5
+ showLabels?: boolean
6
+ icons?: AppearanceIcons
7
+ }
8
+
9
+ // AppearancePicker wired straight to the Provider's own ThemeSettings, for the common case where
10
+ // the appearance being edited IS the whole app's own theme. Takes neither `value` nor `onChange`
11
+ // — reads/writes useThemeSettings() itself, so there's nothing for a consumer to wire up. Reach
12
+ // for the plain AppearancePicker instead when it needs to be controlled by something other than
13
+ // the global theme (a local preview before committing, one-off UI unrelated to Provider).
14
+ export const AutoAppearancePicker = (props: Props) => {
15
+ const { settings, set } = useThemeSettings()
16
+ return <AppearancePicker {...props} value={settings.appearance} onChange={(appearance) => set({ appearance })} />
17
+ }
@@ -0,0 +1,21 @@
1
+ import { useThemeSettings } from '../useThemeSettings'
2
+ import { resolveSeedColor } from '../utils/resolveSeedColor'
3
+ import type { SeedColor } from './ColorPicker'
4
+ import { PalettePicker, type PaletteWeights } from './PalettePicker'
5
+
6
+ type Props = {
7
+ weights?: PaletteWeights
8
+ colors?: SeedColor[]
9
+ blur?: boolean
10
+ }
11
+
12
+ // PalettePicker wired straight to the Provider's own ThemeSettings — both color AND harmony —
13
+ // for the common case where the palette being edited IS the whole app's own theme. Takes none of
14
+ // `value`/`onChange`/`harmony`/`onHarmonyChange` — reads/writes useThemeSettings() itself
15
+ // (resolveSeedColor narrows ThemeSettings.color's `string | TriadicPalette` down to the plain
16
+ // string PalettePicker's own `value` wants). Reach for the plain PalettePicker instead when it
17
+ // needs to be controlled by something other than the global theme.
18
+ export const AutoPalettePicker = (props: Props) => {
19
+ const { settings, set } = useThemeSettings()
20
+ return <PalettePicker {...props} value={resolveSeedColor(settings.color)} onChange={(color) => set({ color })} harmony={settings.harmony} onHarmonyChange={(harmony) => set({ harmony })} />
21
+ }
@@ -1,9 +1,6 @@
1
- import { useState } from 'react'
2
- import { Button } from 'react-native-paper'
1
+ import { SegmentedButtons } from 'react-native-paper'
3
2
 
4
- import { useBlur } from '../BlurContext'
5
3
  import type { ColorHarmony } from '../utils/getTriadicPalette'
6
- import { Menu } from './Menu'
7
4
 
8
5
  const HARMONIES: { label: string; value: ColorHarmony; icon: string }[] = [
9
6
  { label: 'Triadic', value: 'triadic', icon: 'triangle-outline' },
@@ -14,39 +11,52 @@ const HARMONIES: { label: string; value: ColorHarmony; icon: string }[] = [
14
11
  { label: 'Dbl Split', value: 'double-split', icon: 'arrow-expand-horizontal' }
15
12
  ]
16
13
 
14
+ // Paper's own SegmentedButtonItem hardcodes `minWidth: 76` per segment (sized for a labeled text
15
+ // button), which 6 of never fit inside PalettePicker's own dialog card (capped at 400, and
16
+ // narrower still once its padding and safe-area margins are subtracted) — they just overflow past
17
+ // its `overflow: hidden` edge instead of shrinking. A fixed smaller guess (say 44) only moves the
18
+ // breakpoint rather than removing it — 6x44 still exceeds the card's content width on an ordinary
19
+ // ~360-375px phone. Zeroing the floor instead lets each segment's own `flex: 1` divide whatever
20
+ // width IS available with nothing left to enforce a minimum, so 6 of them mathematically can never
21
+ // exceed their container at any screen size — same "shrink to fit, never overflow" contract
22
+ // useSwatchGrid.ts's own SWATCH_GRID_COLUMNS already uses for the swatches below this row. Left
23
+ // untouched in labeled mode, where the wider floor is still doing its job of fitting the text.
24
+ const COMPACT_MIN_WIDTH = 0
25
+
17
26
  type Props = {
18
27
  value: ColorHarmony
19
28
  onChange: (harmony: ColorHarmony) => void
20
- blur?: boolean
29
+ // Hides text labels, leaving just the icon per segment — mirrors AppearancePicker's own
30
+ // showLabels, useful here since there are 6 segments to fit instead of 3.
31
+ // accessibilityLabel is always set regardless, so screen readers still announce the full
32
+ // harmony name either way.
33
+ showLabels?: boolean
34
+ // Per-segment fill, e.g. theme.colors.secondary/onSecondary for the checked segment and
35
+ // secondaryContainer/onSecondaryContainer for the rest — matches Paper's own checked/
36
+ // unchecked + container color vocabulary. Omit to fall back to Paper's default segmented-
37
+ // button styling.
38
+ checkedColor?: string
39
+ uncheckedColor?: string
40
+ checkedContainerColor?: string
41
+ uncheckedContainerColor?: string
21
42
  }
22
43
 
23
- export const HarmonyPicker = ({ value, onChange, blur: blurProp }: Props) => {
24
- const blur = useBlur(blurProp)
25
- const [open, setOpen] = useState(false)
26
- const selected = HARMONIES.find((h) => h.value === value)!
27
-
28
- return (
29
- <Menu
30
- blur={blur}
31
- visible={open}
32
- onDismiss={() => setOpen(false)}
33
- anchor={
34
- <Button mode='outlined' icon={selected.icon} onPress={() => setOpen(true)}>
35
- {selected.label}
36
- </Button>
44
+ export const HarmonyPicker = ({ value, onChange, showLabels = true, checkedColor, uncheckedColor, checkedContainerColor, uncheckedContainerColor }: Props) => (
45
+ <SegmentedButtons
46
+ value={value}
47
+ onValueChange={(v) => onChange(v as ColorHarmony)}
48
+ buttons={HARMONIES.map((h) => {
49
+ const checked = h.value === value
50
+ const containerColor = checked ? checkedContainerColor : uncheckedContainerColor
51
+ return {
52
+ value: h.value,
53
+ icon: h.icon,
54
+ label: showLabels ? h.label : undefined,
55
+ accessibilityLabel: h.label,
56
+ checkedColor,
57
+ uncheckedColor,
58
+ style: [showLabels ? undefined : { minWidth: COMPACT_MIN_WIDTH }, containerColor ? { backgroundColor: containerColor } : undefined]
37
59
  }
38
- >
39
- {HARMONIES.map((h) => (
40
- <Menu.Item
41
- key={h.value}
42
- leadingIcon={h.value === value ? 'check' : h.icon}
43
- title={h.label}
44
- onPress={() => {
45
- onChange(h.value)
46
- setOpen(false)
47
- }}
48
- />
49
- ))}
50
- </Menu>
51
- )
52
- }
60
+ })}
61
+ />
62
+ )
@@ -10,6 +10,7 @@ import { getTriadicPalette } from '../utils/getTriadicPalette'
10
10
  import type { SeedColor } from './ColorPicker'
11
11
  import { defaultColors } from './ColorPicker'
12
12
  import { Dialog } from './Dialog'
13
+ import { HarmonyPicker } from './HarmonyPicker'
13
14
 
14
15
  const SIZE = 48
15
16
 
@@ -55,12 +56,18 @@ type Props = {
55
56
  value: string
56
57
  onChange: (color: string) => void
57
58
  harmony?: ColorHarmony
59
+ // Opt-in: passing this turns on a compact, icon-only HarmonyPicker row inside the dialog, above
60
+ // the swatch grid, so every swatch's own Pie preview reshapes live as harmony changes. Omit it
61
+ // and this renders exactly as a plain PalettePicker always has — same trigger, same dialog,
62
+ // harmony fixed at whatever the `harmony` prop says. Gated on the callback itself rather than a
63
+ // separate boolean so there's no way to pass one without the other.
64
+ onHarmonyChange?: (harmony: ColorHarmony) => void
58
65
  weights?: PaletteWeights
59
66
  colors?: SeedColor[]
60
67
  blur?: boolean
61
68
  }
62
69
 
63
- export const PalettePicker = ({ value, onChange, harmony = 'split-complementary', weights, colors = defaultColors, blur: blurProp }: Props) => {
70
+ export const PalettePicker = ({ value, onChange, harmony = 'split-complementary', onHarmonyChange, weights, colors = defaultColors, blur: blurProp }: Props) => {
64
71
  const blur = useBlur(blurProp)
65
72
  const { colors: themeColors } = useTheme()
66
73
  const [open, setOpen] = useState(false)
@@ -79,6 +86,11 @@ export const PalettePicker = ({ value, onChange, harmony = 'split-complementary'
79
86
 
80
87
  <Dialog blur={blur} visible={open} onDismiss={() => setOpen(false)}>
81
88
  <Dialog.Content>
89
+ {onHarmonyChange && (
90
+ <View style={styles.harmonyRow}>
91
+ <HarmonyPicker value={harmony} onChange={onHarmonyChange} showLabels={false} />
92
+ </View>
93
+ )}
82
94
  <View style={styles.swatches} onLayout={onLayout}>
83
95
  {colors.map(({ value: hex }) => (
84
96
  <TouchableOpacity
@@ -104,6 +116,7 @@ export const PalettePicker = ({ value, onChange, harmony = 'split-complementary'
104
116
 
105
117
  const styles = StyleSheet.create({
106
118
  halfMask: { height: '100%', left: '50%', overflow: 'hidden', position: 'absolute', top: 0, width: '50%' },
119
+ harmonyRow: { marginBottom: 4 },
107
120
  innerRotator: { height: '100%', left: '-100%', overflow: 'hidden', position: 'absolute', top: 0, width: '200%' },
108
121
  pie: { overflow: 'hidden' },
109
122
  rotator: { height: '100%', left: 0, overflow: 'hidden', position: 'absolute', top: 0, width: '100%' },
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@ export { useBlur } from './BlurContext'
2
2
  export { Appbar } from './components/Appbar'
3
3
  export type { AppearanceIcons } from './components/AppearancePicker'
4
4
  export { AppearancePicker } from './components/AppearancePicker'
5
+ export { AutoAppearancePicker } from './components/AutoAppearancePicker'
6
+ export { AutoPalettePicker } from './components/AutoPalettePicker'
5
7
  export type { BlurViewProps, ExpoBlurModule } from './components/BlurView'
6
8
  export { BlurView } from './components/BlurView'
7
9
  export { BottomNavigation } from './components/BottomNavigation'
@@ -47,6 +49,7 @@ export { getTonalColor } from './utils/getTonalColor'
47
49
  export type { ColorHarmony, TriadicPalette } from './utils/getTriadicPalette'
48
50
  export { getTriadicPalette } from './utils/getTriadicPalette'
49
51
  export { isDarkColor } from './utils/isDarkColor'
52
+ export { resolveSeedColor } from './utils/resolveSeedColor'
50
53
  export type { AppbarHeaderProps } from 'react-native-paper'
51
54
  export type { BottomNavigationProps } from 'react-native-paper'
52
55
  export type { BottomNavigationRoute } from 'react-native-paper'
@@ -0,0 +1,8 @@
1
+ import type { TriadicPalette } from './getTriadicPalette'
2
+
3
+ // ThemeSettings.color is `string | TriadicPalette` — a plain seed hex normally, or an explicit
4
+ // triad when a consumer bypassed harmony math entirely (see the README's own "Explicit triad"
5
+ // section). Anything that needs a single representative color from it — a picker's own `value`,
6
+ // a card's accent — narrows via this: `.primary` is that triad's own seed, so it's the
7
+ // representative color on the rare path where this branch is actually hit.
8
+ export const resolveSeedColor = (color: string | TriadicPalette): string => (typeof color === 'string' ? color : color.primary)