@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/README.md +53 -4
- package/dist/index.d.mts +54 -34
- package/dist/index.d.ts +54 -34
- package/dist/index.js +350 -329
- package/dist/index.mjs +326 -308
- package/package.json +1 -1
- package/src/components/AutoAppearancePicker.tsx +17 -0
- package/src/components/AutoPalettePicker.tsx +21 -0
- package/src/components/HarmonyPicker.tsx +44 -34
- package/src/components/PalettePicker.tsx +14 -1
- package/src/index.ts +3 -0
- package/src/utils/resolveSeedColor.ts +8 -0
package/package.json
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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)
|