@rific/auto-paper 0.9.1 → 0.9.3
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 +57 -3
- package/dist/index.d.mts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.js +74 -73
- package/dist/index.mjs +68 -68
- package/package.json +1 -1
- package/src/ThemeProvider.tsx +10 -2
- package/src/components/Dialog.tsx +35 -7
- package/src/components/HarmonyPicker.tsx +44 -34
- package/src/components/PalettePicker.tsx +14 -1
- package/src/index.ts +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npm install expo-blur # frosted-glass BlurView
|
|
|
33
33
|
npm install expo-navigation-bar # (>= 56.0.0, SDK 54+) auto-syncs Android nav bar icon style to the theme when BottomNavigation is mounted
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
These integrations are opt-in: pass them to `Provider` (`expoBlur`, `navigationBar`, and `reanimated` — see the prop table below), and the relevant wrappers pick them up automatically. Omit them and you still get working fallbacks: `BlurView` renders its solid (non-blur) look, `BottomNavigation` simply skips nav-bar syncing, and `Dialog` treats `animatedStyle` as a no-op unless a reanimated module is injected.
|
|
37
37
|
|
|
38
38
|
The exported Redux slice has no dependency on `@reduxjs/toolkit`: it works with RTK stores, vanilla Redux, or no Redux at all.
|
|
39
39
|
|
|
@@ -238,6 +238,7 @@ export default function App() {
|
|
|
238
238
|
| `navigationBar` | `ExpoNavigationBarModule` | Injects `expo-navigation-bar` (`import * as ExpoNavigationBar from 'expo-navigation-bar'`) so the Android nav bar icon style auto-syncs with the theme while a `BottomNavigation` is mounted. Omit (and don't pass `onNavBarChange`) to skip nav bar syncing entirely. |
|
|
239
239
|
| `onNavBarChange` | `(color: string, dark: boolean) => void` | Overrides the built-in nav bar sync: called on Android when the theme changes while a `BottomNavigation` is mounted, instead of the automatic `navigationBar`-driven icon-style sync |
|
|
240
240
|
| `onReady` | `() => void` | Called once when the theme first resolves |
|
|
241
|
+
| `reanimated` | `ReanimatedModule` | Injects `react-native-reanimated` (for example `import Reanimated from 'react-native-reanimated'`) so `<Dialog animatedStyle={...}>` can animate its card on the UI thread. Omit to keep `animatedStyle` as a no-op. |
|
|
241
242
|
| `statusBarProps` | `StatusBarProps` | Spread over the auto-derived `StatusBar` defaults |
|
|
242
243
|
| `style` | `StyleProp<ViewStyle>` | Applied to the wrapper `View` |
|
|
243
244
|
|
|
@@ -255,6 +256,8 @@ export default function App() {
|
|
|
255
256
|
|
|
256
257
|
Returns `{ settings: ThemeSettings, set: (patch: Partial<ThemeSettings>) => void }`. Use this hook from any component inside `Provider` to read or update the current theme settings.
|
|
257
258
|
|
|
259
|
+
`useReanimatedModule()` is also available inside the provider tree when you need to read the injected reanimated module directly; it returns `undefined` unless `Provider` was given a `reanimated` prop.
|
|
260
|
+
|
|
258
261
|
```tsx
|
|
259
262
|
import { useThemeSettings } from '@rific/auto-paper'
|
|
260
263
|
|
|
@@ -324,6 +327,27 @@ function SettingsScreen() {
|
|
|
324
327
|
| `showLabels` | `boolean` | Set `false` for icon-only segments, useful in tight layouts where "System" would truncate. `accessibilityLabel` is always set regardless, so screen readers still announce the full word. Defaults to `true` |
|
|
325
328
|
| `icons` | `AppearanceIcons` | Per-value icon overrides: pass just the ones you want to change (e.g. `{ system: 'theme-light-dark' }`), merged over the defaults (`monitor` / `white-balance-sunny` / `weather-night`) |
|
|
326
329
|
|
|
330
|
+
### `HarmonyPicker`
|
|
331
|
+
|
|
332
|
+
Switches between the 6 `ColorHarmony` modes (`triadic`, `split-complementary`, `analogous`, `square`, `complementary`, `double-split`) via `react-native-paper`'s `SegmentedButtons`. `PalettePicker` can embed this same picker in its own dialog instead of standing alone — see `onHarmonyChange` below.
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
import { HarmonyPicker, useThemeSettings } from '@rific/auto-paper'
|
|
336
|
+
|
|
337
|
+
function SettingsScreen() {
|
|
338
|
+
const { settings: { harmony }, set } = useThemeSettings()
|
|
339
|
+
return <HarmonyPicker value={harmony} onChange={(h) => set({ harmony: h })} showLabels={false} />
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
| Prop | Type | Description |
|
|
344
|
+
|---|---|---|
|
|
345
|
+
| `value` | `ColorHarmony` | Currently selected harmony |
|
|
346
|
+
| `onChange` | `(harmony: ColorHarmony) => void` | Called when a segment is tapped |
|
|
347
|
+
| `showLabels` | `boolean` | Set `false` for icon-only segments. Also lowers Paper's default 76px-per-segment minimum width down to 0, so all 6 segments can always shrink to fit — however narrow the container, they resize down together instead of overflowing it. `accessibilityLabel` is always set regardless, so screen readers still announce the full harmony name either way. Defaults to `true` |
|
|
348
|
+
| `checkedColor` / `uncheckedColor` | `string` | Icon color for the checked and unchecked segments, matching Paper's own segmented-button vocabulary |
|
|
349
|
+
| `checkedContainerColor` / `uncheckedContainerColor` | `string` | Fill color for the checked and unchecked segments. Omit any of these four color props to fall back to Paper's default segmented-button styling |
|
|
350
|
+
|
|
327
351
|
### `Appbar`
|
|
328
352
|
|
|
329
353
|
A thin wrapper around `react-native-paper`'s `Appbar` that automatically syncs `StatusBar` background color and bar style to the current theme surface color.
|
|
@@ -377,6 +401,35 @@ import { Chip } from '@rific/auto-paper'
|
|
|
377
401
|
|
|
378
402
|
`ChipProps` is exported from `@rific/auto-paper` and extends `react-native-paper`'s `ChipProps` with the `variant` field. Use `PaperChipProps` if you need the base paper type.
|
|
379
403
|
|
|
404
|
+
### `Dialog`
|
|
405
|
+
|
|
406
|
+
A thin wrapper around `react-native-paper`'s `Dialog` with built-in blur support and optional UI-thread animation hooks. `blur` overrides the ambient blur setting for that dialog, and `animatedStyle` can be used to animate the card wrapper when a reanimated module has been injected into `Provider`.
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
import { Dialog, Provider } from '@rific/auto-paper'
|
|
410
|
+
import Reanimated, { useAnimatedStyle } from 'react-native-reanimated'
|
|
411
|
+
|
|
412
|
+
function SettingsDialog({ visible }: { visible: boolean }) {
|
|
413
|
+
const animatedStyle = useAnimatedStyle(() => ({ opacity: 1 }))
|
|
414
|
+
|
|
415
|
+
return (
|
|
416
|
+
<Provider initialValue={{ appearance: 'system', color: '#6750a4' }} reanimated={Reanimated}>
|
|
417
|
+
<Dialog visible={visible} onDismiss={() => {}} animatedStyle={animatedStyle} blur>
|
|
418
|
+
{/* dialog contents */}
|
|
419
|
+
</Dialog>
|
|
420
|
+
</Provider>
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
| Prop | Type | Description |
|
|
426
|
+
|---|---|---|
|
|
427
|
+
| `blur` | `boolean` | Overrides the ambient blur setting for this component's dialog |
|
|
428
|
+
| `animatedStyle` | `unknown` | Applied to the card's outer wrapper when a reanimated module has been injected; otherwise ignored |
|
|
429
|
+
| ...all `DialogProps` | | All props from `react-native-paper`'s `Dialog` are supported |
|
|
430
|
+
|
|
431
|
+
`DialogProps` is exported from `@rific/auto-paper` and extends `react-native-paper`'s `DialogProps` with the `blur` and `animatedStyle` fields. Use `PaperDialogProps` if you need the base paper type.
|
|
432
|
+
|
|
380
433
|
### `IconButton`
|
|
381
434
|
|
|
382
435
|
A thin wrapper around `react-native-paper`'s `IconButton` that adds a `variant` prop for applying theme-derived container and icon colors.
|
|
@@ -400,14 +453,14 @@ import { IconButton } from '@rific/auto-paper'
|
|
|
400
453
|
|
|
401
454
|
### `PalettePicker`
|
|
402
455
|
|
|
403
|
-
Like `ColorPicker`, but each swatch (and the trigger itself) renders the seed's full triadic palette as a 3-wedge pie instead of a flat color, so you see the whole result before picking.
|
|
456
|
+
Like `ColorPicker`, but each swatch (and the trigger itself) renders the seed's full triadic palette as a 3-wedge pie instead of a flat color, so you see the whole result before picking. Pass `onHarmonyChange` to fold a compact, icon-only `HarmonyPicker` row into the same dialog, above the swatch grid — every swatch's own pie reshapes live as harmony changes, so there's one place to tune both instead of two separate controls. Omit it and this renders exactly as a plain color-only picker always has.
|
|
404
457
|
|
|
405
458
|
```tsx
|
|
406
459
|
import { PalettePicker, useThemeSettings } from '@rific/auto-paper'
|
|
407
460
|
|
|
408
461
|
function SettingsScreen() {
|
|
409
462
|
const { settings: { color, harmony }, set } = useThemeSettings()
|
|
410
|
-
return <PalettePicker value={color} harmony={harmony} onChange={(c) => set({ color: c })} />
|
|
463
|
+
return <PalettePicker value={color} harmony={harmony} onChange={(c) => set({ color: c })} onHarmonyChange={(h) => set({ harmony: h })} />
|
|
411
464
|
}
|
|
412
465
|
```
|
|
413
466
|
|
|
@@ -416,6 +469,7 @@ function SettingsScreen() {
|
|
|
416
469
|
| `value` | `string` | Currently selected seed color |
|
|
417
470
|
| `onChange` | `(color: string) => void` | Called with the seed color when a swatch is tapped |
|
|
418
471
|
| `harmony` | `ColorHarmony` | Harmony used to compute each swatch's preview palette. Defaults to `'split-complementary'` |
|
|
472
|
+
| `onHarmonyChange` | `(harmony: ColorHarmony) => void` | Opt-in: passing this renders the `HarmonyPicker` row inside the dialog. Gated on the callback itself rather than a separate boolean, so there's no way to pass one without the other |
|
|
419
473
|
| `weights` | `PaletteWeights` | Relative size of each wedge: `{ primary, secondary, tertiary }`. Defaults to `{ primary: 2, secondary: 1, tertiary: 1 }` (primary half the pie, secondary/tertiary a quarter each). Values are normalized, so any positive ratio works; keep each share at or under half the total so its wedge stays a single slice |
|
|
420
474
|
| `colors` | `SeedColor[]` | Seed color swatches to offer. Defaults to the same set as `ColorPicker` |
|
|
421
475
|
| `blur` | `boolean` | Overrides the ambient blur setting for this component's dialog |
|
package/dist/index.d.mts
CHANGED
|
@@ -127,11 +127,19 @@ type Props$2 = {
|
|
|
127
127
|
};
|
|
128
128
|
declare const ColorPicker: ({ value, onChange, colors, blur: blurProp }: Props$2) => react.JSX.Element;
|
|
129
129
|
|
|
130
|
+
interface ReanimatedModule {
|
|
131
|
+
View: ComponentType<{
|
|
132
|
+
children?: ReactNode;
|
|
133
|
+
style?: any;
|
|
134
|
+
testID?: string;
|
|
135
|
+
}>;
|
|
136
|
+
}
|
|
130
137
|
type DialogProps = Omit<DialogProps$1, 'style'> & {
|
|
131
138
|
blur?: boolean;
|
|
132
139
|
style?: StyleProp<ViewStyle>;
|
|
140
|
+
animatedStyle?: unknown;
|
|
133
141
|
};
|
|
134
|
-
declare function DialogComponent({ blur: blurProp, dismissable, dismissableBackButton, visible, onDismiss, children, style, testID }: DialogProps): react__default.JSX.Element | null;
|
|
142
|
+
declare function DialogComponent({ animatedStyle, blur: blurProp, dismissable, dismissableBackButton, visible, onDismiss, children, style, testID }: DialogProps): react__default.JSX.Element | null;
|
|
135
143
|
declare namespace DialogComponent {
|
|
136
144
|
var Title: react__default.FC<DialogTitleProps>;
|
|
137
145
|
var Content: react__default.FC<DialogContentProps>;
|
|
@@ -145,9 +153,13 @@ declare function FAB(props: FABProps): react.JSX.Element;
|
|
|
145
153
|
type Props$1 = {
|
|
146
154
|
value: ColorHarmony;
|
|
147
155
|
onChange: (harmony: ColorHarmony) => void;
|
|
148
|
-
|
|
156
|
+
showLabels?: boolean;
|
|
157
|
+
checkedColor?: string;
|
|
158
|
+
uncheckedColor?: string;
|
|
159
|
+
checkedContainerColor?: string;
|
|
160
|
+
uncheckedContainerColor?: string;
|
|
149
161
|
};
|
|
150
|
-
declare const HarmonyPicker: ({ value, onChange,
|
|
162
|
+
declare const HarmonyPicker: ({ value, onChange, showLabels, checkedColor, uncheckedColor, checkedContainerColor, uncheckedContainerColor }: Props$1) => react.JSX.Element;
|
|
151
163
|
|
|
152
164
|
type IconButtonProps = IconButtonProps$1 & {
|
|
153
165
|
variant?: 'primary' | 'secondary' | 'tertiary' | 'surface';
|
|
@@ -170,11 +182,12 @@ type Props = {
|
|
|
170
182
|
value: string;
|
|
171
183
|
onChange: (color: string) => void;
|
|
172
184
|
harmony?: ColorHarmony;
|
|
185
|
+
onHarmonyChange?: (harmony: ColorHarmony) => void;
|
|
173
186
|
weights?: PaletteWeights;
|
|
174
187
|
colors?: SeedColor[];
|
|
175
188
|
blur?: boolean;
|
|
176
189
|
};
|
|
177
|
-
declare const PalettePicker: ({ value, onChange, harmony, weights, colors, blur: blurProp }: Props) => react.JSX.Element;
|
|
190
|
+
declare const PalettePicker: ({ value, onChange, harmony, onHarmonyChange, weights, colors, blur: blurProp }: Props) => react.JSX.Element;
|
|
178
191
|
|
|
179
192
|
declare const TextInput: react.ForwardRefExoticComponent<Omit<TextInputProps, "ref"> & react.RefAttributes<TextInput$1>>;
|
|
180
193
|
|
|
@@ -273,6 +286,7 @@ type NavBarContextType = {
|
|
|
273
286
|
};
|
|
274
287
|
declare const useNavBarContext: () => NavBarContextType;
|
|
275
288
|
declare const useBlurModule: () => ExpoBlurModule | undefined;
|
|
289
|
+
declare const useReanimatedModule: () => ReanimatedModule | undefined;
|
|
276
290
|
type ProviderProps = {
|
|
277
291
|
children: ReactNode;
|
|
278
292
|
defaults?: PaperDefaults;
|
|
@@ -284,10 +298,12 @@ type ProviderProps = {
|
|
|
284
298
|
onChange?: (settings: ThemeSettings) => void;
|
|
285
299
|
onNavBarChange?: (color: string, dark: boolean) => void;
|
|
286
300
|
onReady?: () => void;
|
|
301
|
+
/** Injects react-native-reanimated so `<Dialog animatedStyle={...}>` (e.g. from `useAnimatedStyle()`) can animate its card on the UI thread. Pass `import Reanimated from 'react-native-reanimated'`; omit to keep Dialog's `animatedStyle` prop a no-op. */
|
|
302
|
+
reanimated?: ReanimatedModule;
|
|
287
303
|
statusBarProps?: StatusBarProps;
|
|
288
304
|
style?: StyleProp<ViewStyle>;
|
|
289
305
|
};
|
|
290
|
-
declare function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, statusBarProps, style }: ProviderProps): react.JSX.Element | null;
|
|
306
|
+
declare function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, reanimated, statusBarProps, style }: ProviderProps): react.JSX.Element | null;
|
|
291
307
|
|
|
292
308
|
declare const useAutoPaperTheme: () => AutoPaperTheme;
|
|
293
309
|
|
|
@@ -323,4 +339,4 @@ declare const getTonalColor: (color: string, lightness: number) => string;
|
|
|
323
339
|
|
|
324
340
|
declare const isDarkColor: (value: string) => boolean;
|
|
325
341
|
|
|
326
|
-
export { Appbar, type AppearanceIcons, AppearancePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, HarmonyPicker, IconButton, type IconButtonProps, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getHex, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useNavBarContext, usePaperDefaults, useThemeSettings };
|
|
342
|
+
export { Appbar, type AppearanceIcons, AppearancePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, HarmonyPicker, IconButton, type IconButtonProps, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, type ReanimatedModule, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getHex, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useNavBarContext, usePaperDefaults, useReanimatedModule, useThemeSettings };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,11 +127,19 @@ type Props$2 = {
|
|
|
127
127
|
};
|
|
128
128
|
declare const ColorPicker: ({ value, onChange, colors, blur: blurProp }: Props$2) => react.JSX.Element;
|
|
129
129
|
|
|
130
|
+
interface ReanimatedModule {
|
|
131
|
+
View: ComponentType<{
|
|
132
|
+
children?: ReactNode;
|
|
133
|
+
style?: any;
|
|
134
|
+
testID?: string;
|
|
135
|
+
}>;
|
|
136
|
+
}
|
|
130
137
|
type DialogProps = Omit<DialogProps$1, 'style'> & {
|
|
131
138
|
blur?: boolean;
|
|
132
139
|
style?: StyleProp<ViewStyle>;
|
|
140
|
+
animatedStyle?: unknown;
|
|
133
141
|
};
|
|
134
|
-
declare function DialogComponent({ blur: blurProp, dismissable, dismissableBackButton, visible, onDismiss, children, style, testID }: DialogProps): react__default.JSX.Element | null;
|
|
142
|
+
declare function DialogComponent({ animatedStyle, blur: blurProp, dismissable, dismissableBackButton, visible, onDismiss, children, style, testID }: DialogProps): react__default.JSX.Element | null;
|
|
135
143
|
declare namespace DialogComponent {
|
|
136
144
|
var Title: react__default.FC<DialogTitleProps>;
|
|
137
145
|
var Content: react__default.FC<DialogContentProps>;
|
|
@@ -145,9 +153,13 @@ declare function FAB(props: FABProps): react.JSX.Element;
|
|
|
145
153
|
type Props$1 = {
|
|
146
154
|
value: ColorHarmony;
|
|
147
155
|
onChange: (harmony: ColorHarmony) => void;
|
|
148
|
-
|
|
156
|
+
showLabels?: boolean;
|
|
157
|
+
checkedColor?: string;
|
|
158
|
+
uncheckedColor?: string;
|
|
159
|
+
checkedContainerColor?: string;
|
|
160
|
+
uncheckedContainerColor?: string;
|
|
149
161
|
};
|
|
150
|
-
declare const HarmonyPicker: ({ value, onChange,
|
|
162
|
+
declare const HarmonyPicker: ({ value, onChange, showLabels, checkedColor, uncheckedColor, checkedContainerColor, uncheckedContainerColor }: Props$1) => react.JSX.Element;
|
|
151
163
|
|
|
152
164
|
type IconButtonProps = IconButtonProps$1 & {
|
|
153
165
|
variant?: 'primary' | 'secondary' | 'tertiary' | 'surface';
|
|
@@ -170,11 +182,12 @@ type Props = {
|
|
|
170
182
|
value: string;
|
|
171
183
|
onChange: (color: string) => void;
|
|
172
184
|
harmony?: ColorHarmony;
|
|
185
|
+
onHarmonyChange?: (harmony: ColorHarmony) => void;
|
|
173
186
|
weights?: PaletteWeights;
|
|
174
187
|
colors?: SeedColor[];
|
|
175
188
|
blur?: boolean;
|
|
176
189
|
};
|
|
177
|
-
declare const PalettePicker: ({ value, onChange, harmony, weights, colors, blur: blurProp }: Props) => react.JSX.Element;
|
|
190
|
+
declare const PalettePicker: ({ value, onChange, harmony, onHarmonyChange, weights, colors, blur: blurProp }: Props) => react.JSX.Element;
|
|
178
191
|
|
|
179
192
|
declare const TextInput: react.ForwardRefExoticComponent<Omit<TextInputProps, "ref"> & react.RefAttributes<TextInput$1>>;
|
|
180
193
|
|
|
@@ -273,6 +286,7 @@ type NavBarContextType = {
|
|
|
273
286
|
};
|
|
274
287
|
declare const useNavBarContext: () => NavBarContextType;
|
|
275
288
|
declare const useBlurModule: () => ExpoBlurModule | undefined;
|
|
289
|
+
declare const useReanimatedModule: () => ReanimatedModule | undefined;
|
|
276
290
|
type ProviderProps = {
|
|
277
291
|
children: ReactNode;
|
|
278
292
|
defaults?: PaperDefaults;
|
|
@@ -284,10 +298,12 @@ type ProviderProps = {
|
|
|
284
298
|
onChange?: (settings: ThemeSettings) => void;
|
|
285
299
|
onNavBarChange?: (color: string, dark: boolean) => void;
|
|
286
300
|
onReady?: () => void;
|
|
301
|
+
/** Injects react-native-reanimated so `<Dialog animatedStyle={...}>` (e.g. from `useAnimatedStyle()`) can animate its card on the UI thread. Pass `import Reanimated from 'react-native-reanimated'`; omit to keep Dialog's `animatedStyle` prop a no-op. */
|
|
302
|
+
reanimated?: ReanimatedModule;
|
|
287
303
|
statusBarProps?: StatusBarProps;
|
|
288
304
|
style?: StyleProp<ViewStyle>;
|
|
289
305
|
};
|
|
290
|
-
declare function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, statusBarProps, style }: ProviderProps): react.JSX.Element | null;
|
|
306
|
+
declare function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, reanimated, statusBarProps, style }: ProviderProps): react.JSX.Element | null;
|
|
291
307
|
|
|
292
308
|
declare const useAutoPaperTheme: () => AutoPaperTheme;
|
|
293
309
|
|
|
@@ -323,4 +339,4 @@ declare const getTonalColor: (color: string, lightness: number) => string;
|
|
|
323
339
|
|
|
324
340
|
declare const isDarkColor: (value: string) => boolean;
|
|
325
341
|
|
|
326
|
-
export { Appbar, type AppearanceIcons, AppearancePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, HarmonyPicker, IconButton, type IconButtonProps, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getHex, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useNavBarContext, usePaperDefaults, useThemeSettings };
|
|
342
|
+
export { Appbar, type AppearanceIcons, AppearancePicker, type AutoPaperTheme, BlurView, type BlurViewProps, BottomNavigation, Button, Chip, type ChipProps, type ColorHarmony, ColorPicker, type ColorRoles, Dialog, type DialogProps, type ExpoBlurModule, type ExpoNavigationBarModule, FAB, HarmonyPicker, IconButton, type IconButtonProps, Menu, type MenuProps, PalettePicker, type PaletteWeights, type PaperDefaults, Provider, type ProviderProps, type RGB, type ReanimatedModule, SEMANTIC_BASE_COLORS, type SeedColor, type SemanticColorRoles, TextInput, type ThemeAppearance, type ThemeSettings, ThemeSettingsContext, type ThemeSettingsContextType, type ThemeState, type TriadicPalette, createThemeReducer, defaultColors, defaultThemeSettings, getBlendedColor, getColorRoles, getContrastColor, getHex, getRgb, getThirdColor, getTintTextColor, getTonalColor, getTriadicPalette, isDarkColor, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer, useAutoPaperTheme, useBlur, useBlurModule, useComputedTheme, useNavBarContext, usePaperDefaults, useReanimatedModule, useThemeSettings };
|
package/dist/index.js
CHANGED
|
@@ -72,6 +72,7 @@ __export(index_exports, {
|
|
|
72
72
|
useComputedTheme: () => useComputedTheme,
|
|
73
73
|
useNavBarContext: () => useNavBarContext,
|
|
74
74
|
usePaperDefaults: () => usePaperDefaults,
|
|
75
|
+
useReanimatedModule: () => useReanimatedModule,
|
|
75
76
|
useThemeSettings: () => useThemeSettings
|
|
76
77
|
});
|
|
77
78
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -634,7 +635,9 @@ var NavBarContext = (0, import_react5.createContext)({});
|
|
|
634
635
|
var useNavBarContext = () => (0, import_react5.useContext)(NavBarContext);
|
|
635
636
|
var BlurModuleContext = (0, import_react5.createContext)(void 0);
|
|
636
637
|
var useBlurModule = () => (0, import_react5.useContext)(BlurModuleContext);
|
|
637
|
-
|
|
638
|
+
var ReanimatedModuleContext = (0, import_react5.createContext)(void 0);
|
|
639
|
+
var useReanimatedModule = () => (0, import_react5.useContext)(ReanimatedModuleContext);
|
|
640
|
+
function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, reanimated, statusBarProps, style }) {
|
|
638
641
|
const [settings, setSettings] = (0, import_react5.useState)(() => ({ ...defaultThemeSettings, ...initialValue }));
|
|
639
642
|
const set = (0, import_react5.useCallback)((patch) => {
|
|
640
643
|
setSettings((prev) => ({ ...prev, ...patch }));
|
|
@@ -659,7 +662,7 @@ function Provider({ children, defaults, expoBlur, initialValue, navigationBar, o
|
|
|
659
662
|
if (!theme) return null;
|
|
660
663
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ThemeSettingsContext.Provider, { value: { settings, set }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_paper4.Provider, { theme, children: [
|
|
661
664
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.StatusBar, { backgroundColor: theme.colors.background, barStyle: theme.dark ? "light-content" : "dark-content", ...statusBarProps }),
|
|
662
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) })
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) })
|
|
663
666
|
] }) });
|
|
664
667
|
}
|
|
665
668
|
var styles = import_react_native3.StyleSheet.create({
|
|
@@ -778,8 +781,9 @@ var import_react_native_safe_area_context = require("react-native-safe-area-cont
|
|
|
778
781
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
779
782
|
var ANIMATION_DURATION = 220;
|
|
780
783
|
var DIALOG_MARGIN = 32;
|
|
781
|
-
function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
|
|
784
|
+
function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
|
|
782
785
|
const blur = useBlur(blurProp);
|
|
786
|
+
const reanimated = useReanimatedModule();
|
|
783
787
|
const theme = (0, import_react_native_paper9.useTheme)();
|
|
784
788
|
const { colors, isV3, roundness } = theme;
|
|
785
789
|
const { scale } = theme.animation;
|
|
@@ -820,7 +824,7 @@ function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackBu
|
|
|
820
824
|
const borderRadius = (isV3 ? 7 : 1) * roundness;
|
|
821
825
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper9.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native6.Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [import_react_native6.StyleSheet.absoluteFill, { opacity }], testID, children: [
|
|
822
826
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native6.Pressable, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [import_react_native6.StyleSheet.absoluteFill, { backgroundColor: colors.backdrop }], testID: `${testID}-backdrop` }),
|
|
823
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native6.View, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) })
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native6.View, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: reanimated && animatedStyle ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(reanimated.View, { style: animatedStyle, testID: `${testID}-animated-wrapper`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) })
|
|
824
828
|
] }) });
|
|
825
829
|
}
|
|
826
830
|
DialogComponent.Title = import_react_native_paper9.Dialog.Title;
|
|
@@ -895,26 +899,8 @@ function FAB(props) {
|
|
|
895
899
|
}
|
|
896
900
|
|
|
897
901
|
// src/components/HarmonyPicker.tsx
|
|
898
|
-
var import_react11 = require("react");
|
|
899
|
-
var import_react_native_paper13 = require("react-native-paper");
|
|
900
|
-
|
|
901
|
-
// src/components/Menu.tsx
|
|
902
|
-
var import_react_native8 = require("react-native");
|
|
903
902
|
var import_react_native_paper12 = require("react-native-paper");
|
|
904
903
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
905
|
-
var MenuComponent = ({ blur: blurProp, children, ...props }) => {
|
|
906
|
-
const blur = useBlur(blurProp);
|
|
907
|
-
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native_paper12.Menu, { ...props, contentStyle: [styles4.content, props.contentStyle], children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BlurView, { blur, children }) });
|
|
908
|
-
};
|
|
909
|
-
var Menu = Object.assign(MenuComponent, {
|
|
910
|
-
Item: import_react_native_paper12.Menu.Item
|
|
911
|
-
});
|
|
912
|
-
var styles4 = import_react_native8.StyleSheet.create({
|
|
913
|
-
content: { backgroundColor: "transparent", paddingVertical: 0 }
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
// src/components/HarmonyPicker.tsx
|
|
917
|
-
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
918
904
|
var HARMONIES = [
|
|
919
905
|
{ label: "Triadic", value: "triadic", icon: "triangle-outline" },
|
|
920
906
|
{ label: "Split", value: "split-complementary", icon: "arrow-split-vertical" },
|
|
@@ -923,46 +909,56 @@ var HARMONIES = [
|
|
|
923
909
|
{ label: "Complement", value: "complementary", icon: "circle-half-full" },
|
|
924
910
|
{ label: "Dbl Split", value: "double-split", icon: "arrow-expand-horizontal" }
|
|
925
911
|
];
|
|
926
|
-
var
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
{
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
h.value
|
|
948
|
-
))
|
|
949
|
-
}
|
|
950
|
-
);
|
|
951
|
-
};
|
|
912
|
+
var COMPACT_MIN_WIDTH = 0;
|
|
913
|
+
var HarmonyPicker = ({ value, onChange, showLabels = true, checkedColor, uncheckedColor, checkedContainerColor, uncheckedContainerColor }) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
914
|
+
import_react_native_paper12.SegmentedButtons,
|
|
915
|
+
{
|
|
916
|
+
value,
|
|
917
|
+
onValueChange: (v) => onChange(v),
|
|
918
|
+
buttons: HARMONIES.map((h) => {
|
|
919
|
+
const checked = h.value === value;
|
|
920
|
+
const containerColor = checked ? checkedContainerColor : uncheckedContainerColor;
|
|
921
|
+
return {
|
|
922
|
+
value: h.value,
|
|
923
|
+
icon: h.icon,
|
|
924
|
+
label: showLabels ? h.label : void 0,
|
|
925
|
+
accessibilityLabel: h.label,
|
|
926
|
+
checkedColor,
|
|
927
|
+
uncheckedColor,
|
|
928
|
+
style: [showLabels ? void 0 : { minWidth: COMPACT_MIN_WIDTH }, containerColor ? { backgroundColor: containerColor } : void 0]
|
|
929
|
+
};
|
|
930
|
+
})
|
|
931
|
+
}
|
|
932
|
+
);
|
|
952
933
|
|
|
953
934
|
// src/components/IconButton.tsx
|
|
954
|
-
var
|
|
955
|
-
var
|
|
935
|
+
var import_react_native_paper13 = require("react-native-paper");
|
|
936
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
956
937
|
function IconButton({ variant, iconColor, containerColor, ...props }) {
|
|
957
938
|
const defaults = usePaperDefaults();
|
|
958
|
-
const { colors } = (0,
|
|
939
|
+
const { colors } = (0, import_react_native_paper13.useTheme)();
|
|
959
940
|
const variantContainerColor = variant === "primary" ? colors.primaryContainer : variant === "secondary" ? colors.secondaryContainer : variant === "tertiary" ? colors.tertiaryContainer : variant === "surface" ? colors.surface : void 0;
|
|
960
941
|
const variantIconColor = variant === "primary" ? colors.onPrimaryContainer : variant === "secondary" ? colors.onSecondaryContainer : variant === "tertiary" ? colors.onTertiaryContainer : variant === "surface" ? colors.onSurface : void 0;
|
|
961
|
-
return /* @__PURE__ */ (0,
|
|
942
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper13.IconButton, { ...defaults.IconButton, containerColor: containerColor ?? variantContainerColor, iconColor: iconColor ?? variantIconColor, ...props });
|
|
962
943
|
}
|
|
963
944
|
|
|
945
|
+
// src/components/Menu.tsx
|
|
946
|
+
var import_react_native8 = require("react-native");
|
|
947
|
+
var import_react_native_paper14 = require("react-native-paper");
|
|
948
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
949
|
+
var MenuComponent = ({ blur: blurProp, children, ...props }) => {
|
|
950
|
+
const blur = useBlur(blurProp);
|
|
951
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_paper14.Menu, { ...props, contentStyle: [styles4.content, props.contentStyle], children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(BlurView, { blur, children }) });
|
|
952
|
+
};
|
|
953
|
+
var Menu = Object.assign(MenuComponent, {
|
|
954
|
+
Item: import_react_native_paper14.Menu.Item
|
|
955
|
+
});
|
|
956
|
+
var styles4 = import_react_native8.StyleSheet.create({
|
|
957
|
+
content: { backgroundColor: "transparent", paddingVertical: 0 }
|
|
958
|
+
});
|
|
959
|
+
|
|
964
960
|
// src/components/PalettePicker.tsx
|
|
965
|
-
var
|
|
961
|
+
var import_react11 = require("react");
|
|
966
962
|
var import_react_native9 = require("react-native");
|
|
967
963
|
var import_react_native_paper15 = require("react-native-paper");
|
|
968
964
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
@@ -980,35 +976,39 @@ var buildWedges = (weights) => {
|
|
|
980
976
|
];
|
|
981
977
|
};
|
|
982
978
|
var Pie = ({ palette, weights = defaultWeights, size = SIZE2 }) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: [styles5.pie, { borderRadius: size / 2, height: size, width: size }], children: buildWedges(weights).map(({ key, rotate, share }) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: [styles5.rotator, { transform: [{ rotate: `${rotate}deg` }] }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: styles5.halfMask, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: [styles5.innerRotator, { transform: [{ rotate: `${share - 180}deg` }] }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: [styles5.wedgeFill, { backgroundColor: palette[key] }] }) }) }) }, key)) });
|
|
983
|
-
var PalettePicker = ({ value, onChange, harmony = "split-complementary", weights, colors = defaultColors, blur: blurProp }) => {
|
|
979
|
+
var PalettePicker = ({ value, onChange, harmony = "split-complementary", onHarmonyChange, weights, colors = defaultColors, blur: blurProp }) => {
|
|
984
980
|
const blur = useBlur(blurProp);
|
|
985
981
|
const { colors: themeColors } = (0, import_react_native_paper15.useTheme)();
|
|
986
|
-
const [open, setOpen] = (0,
|
|
982
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
987
983
|
const { onLayout, size } = useSwatchSize(SIZE2);
|
|
988
984
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
989
985
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper15.TouchableRipple, { onPress: () => setOpen(true), style: styles5.trigger, borderless: true, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native9.View, { style: styles5.triggerContent, children: [
|
|
990
986
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pie, { palette: getTriadicPalette(value, harmony), weights }),
|
|
991
987
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { pointerEvents: "none", style: [styles5.triggerIcon, { borderRadius: SIZE2 / 2 }, getSwatchRing(value, false, themeColors.outlineVariant)], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper15.Icon, { source: "palette", size: 20, color: getContrastColor(value) }) })
|
|
992
988
|
] }) }),
|
|
993
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */ (0, import_jsx_runtime14.
|
|
994
|
-
import_react_native9.
|
|
995
|
-
{
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Dialog.Content, { children: [
|
|
990
|
+
onHarmonyChange && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: styles5.harmonyRow, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HarmonyPicker, { value: harmony, onChange: onHarmonyChange, showLabels: false }) }),
|
|
991
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { style: styles5.swatches, onLayout, children: colors.map(({ value: hex }) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
992
|
+
import_react_native9.TouchableOpacity,
|
|
993
|
+
{
|
|
994
|
+
onPress: () => {
|
|
995
|
+
onChange(hex);
|
|
996
|
+
setOpen(false);
|
|
997
|
+
},
|
|
998
|
+
style: [styles5.swatch, { height: size, width: size }],
|
|
999
|
+
children: [
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pie, { palette: getTriadicPalette(hex, harmony), weights, size }),
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { pointerEvents: "none", style: [styles5.ring, { borderRadius: size / 2 }, getSwatchRing(hex, value === hex, themeColors.outlineVariant)], children: value === hex && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper15.Icon, { source: "check", size: 20, color: getContrastColor(hex) }) })
|
|
1002
|
+
]
|
|
999
1003
|
},
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native9.View, { pointerEvents: "none", style: [styles5.ring, { borderRadius: size / 2 }, getSwatchRing(hex, value === hex, themeColors.outlineVariant)], children: value === hex && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper15.Icon, { source: "check", size: 20, color: getContrastColor(hex) }) })
|
|
1004
|
-
]
|
|
1005
|
-
},
|
|
1006
|
-
hex
|
|
1007
|
-
)) }) }) })
|
|
1004
|
+
hex
|
|
1005
|
+
)) })
|
|
1006
|
+
] }) })
|
|
1008
1007
|
] });
|
|
1009
1008
|
};
|
|
1010
1009
|
var styles5 = import_react_native9.StyleSheet.create({
|
|
1011
1010
|
halfMask: { height: "100%", left: "50%", overflow: "hidden", position: "absolute", top: 0, width: "50%" },
|
|
1011
|
+
harmonyRow: { marginBottom: 4 },
|
|
1012
1012
|
innerRotator: { height: "100%", left: "-100%", overflow: "hidden", position: "absolute", top: 0, width: "200%" },
|
|
1013
1013
|
pie: { overflow: "hidden" },
|
|
1014
1014
|
rotator: { height: "100%", left: 0, overflow: "hidden", position: "absolute", top: 0, width: "100%" },
|
|
@@ -1022,10 +1022,10 @@ var styles5 = import_react_native9.StyleSheet.create({
|
|
|
1022
1022
|
});
|
|
1023
1023
|
|
|
1024
1024
|
// src/components/TextInput.tsx
|
|
1025
|
-
var
|
|
1025
|
+
var import_react12 = require("react");
|
|
1026
1026
|
var import_react_native_paper16 = require("react-native-paper");
|
|
1027
1027
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1028
|
-
var TextInput = (0,
|
|
1028
|
+
var TextInput = (0, import_react12.forwardRef)(function TextInput2(props, ref) {
|
|
1029
1029
|
const defaults = usePaperDefaults();
|
|
1030
1030
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper16.TextInput, { ref, ...defaults.TextInput, ...props });
|
|
1031
1031
|
});
|
|
@@ -1072,8 +1072,8 @@ var import_react_native_paper17 = require("react-native-paper");
|
|
|
1072
1072
|
var useAutoPaperTheme = () => (0, import_react_native_paper17.useTheme)();
|
|
1073
1073
|
|
|
1074
1074
|
// src/useThemeSettings.ts
|
|
1075
|
-
var
|
|
1076
|
-
var useThemeSettings = () => (0,
|
|
1075
|
+
var import_react13 = require("react");
|
|
1076
|
+
var useThemeSettings = () => (0, import_react13.useContext)(ThemeSettingsContext);
|
|
1077
1077
|
|
|
1078
1078
|
// src/utils/getThirdColor.ts
|
|
1079
1079
|
function rgbToHsl(r, g, b) {
|
|
@@ -1200,5 +1200,6 @@ var getTintTextColor = (tintColor, backdropColor, tintOpacity) => {
|
|
|
1200
1200
|
useComputedTheme,
|
|
1201
1201
|
useNavBarContext,
|
|
1202
1202
|
usePaperDefaults,
|
|
1203
|
+
useReanimatedModule,
|
|
1203
1204
|
useThemeSettings
|
|
1204
1205
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -556,7 +556,9 @@ var NavBarContext = createContext3({});
|
|
|
556
556
|
var useNavBarContext = () => useContext3(NavBarContext);
|
|
557
557
|
var BlurModuleContext = createContext3(void 0);
|
|
558
558
|
var useBlurModule = () => useContext3(BlurModuleContext);
|
|
559
|
-
|
|
559
|
+
var ReanimatedModuleContext = createContext3(void 0);
|
|
560
|
+
var useReanimatedModule = () => useContext3(ReanimatedModuleContext);
|
|
561
|
+
function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, reanimated, statusBarProps, style }) {
|
|
560
562
|
const [settings, setSettings] = useState2(() => ({ ...defaultThemeSettings, ...initialValue }));
|
|
561
563
|
const set = useCallback2((patch) => {
|
|
562
564
|
setSettings((prev) => ({ ...prev, ...patch }));
|
|
@@ -581,7 +583,7 @@ function Provider({ children, defaults, expoBlur, initialValue, navigationBar, o
|
|
|
581
583
|
if (!theme) return null;
|
|
582
584
|
return /* @__PURE__ */ jsx3(ThemeSettingsContext.Provider, { value: { settings, set }, children: /* @__PURE__ */ jsxs2(PaperProvider, { theme, children: [
|
|
583
585
|
/* @__PURE__ */ jsx3(StatusBar2, { backgroundColor: theme.colors.background, barStyle: theme.dark ? "light-content" : "dark-content", ...statusBarProps }),
|
|
584
|
-
/* @__PURE__ */ jsx3(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ jsx3(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ jsx3(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ jsx3(View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) })
|
|
586
|
+
/* @__PURE__ */ jsx3(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ jsx3(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ jsx3(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ jsx3(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ jsx3(View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) })
|
|
585
587
|
] }) });
|
|
586
588
|
}
|
|
587
589
|
var styles = StyleSheet.create({
|
|
@@ -700,8 +702,9 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
700
702
|
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
701
703
|
var ANIMATION_DURATION = 220;
|
|
702
704
|
var DIALOG_MARGIN = 32;
|
|
703
|
-
function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
|
|
705
|
+
function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
|
|
704
706
|
const blur = useBlur(blurProp);
|
|
707
|
+
const reanimated = useReanimatedModule();
|
|
705
708
|
const theme = useTheme5();
|
|
706
709
|
const { colors, isV3, roundness } = theme;
|
|
707
710
|
const { scale } = theme.animation;
|
|
@@ -742,7 +745,7 @@ function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackBu
|
|
|
742
745
|
const borderRadius = (isV3 ? 7 : 1) * roundness;
|
|
743
746
|
return /* @__PURE__ */ jsx8(Portal, { children: /* @__PURE__ */ jsxs4(Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [StyleSheet3.absoluteFill, { opacity }], testID, children: [
|
|
744
747
|
/* @__PURE__ */ jsx8(Pressable, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [StyleSheet3.absoluteFill, { backgroundColor: colors.backdrop }], testID: `${testID}-backdrop` }),
|
|
745
|
-
/* @__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 }) })
|
|
748
|
+
/* @__PURE__ */ jsx8(View3, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: reanimated && animatedStyle ? /* @__PURE__ */ jsx8(reanimated.View, { style: animatedStyle, testID: `${testID}-animated-wrapper`, children: /* @__PURE__ */ jsx8(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) }) : /* @__PURE__ */ jsx8(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) })
|
|
746
749
|
] }) });
|
|
747
750
|
}
|
|
748
751
|
DialogComponent.Title = PaperDialog.Title;
|
|
@@ -817,26 +820,8 @@ function FAB(props) {
|
|
|
817
820
|
}
|
|
818
821
|
|
|
819
822
|
// src/components/HarmonyPicker.tsx
|
|
820
|
-
import {
|
|
821
|
-
import { Button as Button2 } from "react-native-paper";
|
|
822
|
-
|
|
823
|
-
// src/components/Menu.tsx
|
|
824
|
-
import { StyleSheet as StyleSheet5 } from "react-native";
|
|
825
|
-
import { Menu as PaperMenu } from "react-native-paper";
|
|
823
|
+
import { SegmentedButtons as SegmentedButtons2 } from "react-native-paper";
|
|
826
824
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
827
|
-
var MenuComponent = ({ blur: blurProp, children, ...props }) => {
|
|
828
|
-
const blur = useBlur(blurProp);
|
|
829
|
-
return /* @__PURE__ */ jsx11(PaperMenu, { ...props, contentStyle: [styles4.content, props.contentStyle], children: /* @__PURE__ */ jsx11(BlurView, { blur, children }) });
|
|
830
|
-
};
|
|
831
|
-
var Menu = Object.assign(MenuComponent, {
|
|
832
|
-
Item: PaperMenu.Item
|
|
833
|
-
});
|
|
834
|
-
var styles4 = StyleSheet5.create({
|
|
835
|
-
content: { backgroundColor: "transparent", paddingVertical: 0 }
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
// src/components/HarmonyPicker.tsx
|
|
839
|
-
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
840
825
|
var HARMONIES = [
|
|
841
826
|
{ label: "Triadic", value: "triadic", icon: "triangle-outline" },
|
|
842
827
|
{ label: "Split", value: "split-complementary", icon: "arrow-split-vertical" },
|
|
@@ -845,46 +830,56 @@ var HARMONIES = [
|
|
|
845
830
|
{ label: "Complement", value: "complementary", icon: "circle-half-full" },
|
|
846
831
|
{ label: "Dbl Split", value: "double-split", icon: "arrow-expand-horizontal" }
|
|
847
832
|
];
|
|
848
|
-
var
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
{
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
h.value
|
|
870
|
-
))
|
|
871
|
-
}
|
|
872
|
-
);
|
|
873
|
-
};
|
|
833
|
+
var COMPACT_MIN_WIDTH = 0;
|
|
834
|
+
var HarmonyPicker = ({ value, onChange, showLabels = true, checkedColor, uncheckedColor, checkedContainerColor, uncheckedContainerColor }) => /* @__PURE__ */ jsx11(
|
|
835
|
+
SegmentedButtons2,
|
|
836
|
+
{
|
|
837
|
+
value,
|
|
838
|
+
onValueChange: (v) => onChange(v),
|
|
839
|
+
buttons: HARMONIES.map((h) => {
|
|
840
|
+
const checked = h.value === value;
|
|
841
|
+
const containerColor = checked ? checkedContainerColor : uncheckedContainerColor;
|
|
842
|
+
return {
|
|
843
|
+
value: h.value,
|
|
844
|
+
icon: h.icon,
|
|
845
|
+
label: showLabels ? h.label : void 0,
|
|
846
|
+
accessibilityLabel: h.label,
|
|
847
|
+
checkedColor,
|
|
848
|
+
uncheckedColor,
|
|
849
|
+
style: [showLabels ? void 0 : { minWidth: COMPACT_MIN_WIDTH }, containerColor ? { backgroundColor: containerColor } : void 0]
|
|
850
|
+
};
|
|
851
|
+
})
|
|
852
|
+
}
|
|
853
|
+
);
|
|
874
854
|
|
|
875
855
|
// src/components/IconButton.tsx
|
|
876
856
|
import { IconButton as PaperIconButton, useTheme as useTheme7 } from "react-native-paper";
|
|
877
|
-
import { jsx as
|
|
857
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
878
858
|
function IconButton({ variant, iconColor, containerColor, ...props }) {
|
|
879
859
|
const defaults = usePaperDefaults();
|
|
880
860
|
const { colors } = useTheme7();
|
|
881
861
|
const variantContainerColor = variant === "primary" ? colors.primaryContainer : variant === "secondary" ? colors.secondaryContainer : variant === "tertiary" ? colors.tertiaryContainer : variant === "surface" ? colors.surface : void 0;
|
|
882
862
|
const variantIconColor = variant === "primary" ? colors.onPrimaryContainer : variant === "secondary" ? colors.onSecondaryContainer : variant === "tertiary" ? colors.onTertiaryContainer : variant === "surface" ? colors.onSurface : void 0;
|
|
883
|
-
return /* @__PURE__ */
|
|
863
|
+
return /* @__PURE__ */ jsx12(PaperIconButton, { ...defaults.IconButton, containerColor: containerColor ?? variantContainerColor, iconColor: iconColor ?? variantIconColor, ...props });
|
|
884
864
|
}
|
|
885
865
|
|
|
866
|
+
// src/components/Menu.tsx
|
|
867
|
+
import { StyleSheet as StyleSheet5 } from "react-native";
|
|
868
|
+
import { Menu as PaperMenu } from "react-native-paper";
|
|
869
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
870
|
+
var MenuComponent = ({ blur: blurProp, children, ...props }) => {
|
|
871
|
+
const blur = useBlur(blurProp);
|
|
872
|
+
return /* @__PURE__ */ jsx13(PaperMenu, { ...props, contentStyle: [styles4.content, props.contentStyle], children: /* @__PURE__ */ jsx13(BlurView, { blur, children }) });
|
|
873
|
+
};
|
|
874
|
+
var Menu = Object.assign(MenuComponent, {
|
|
875
|
+
Item: PaperMenu.Item
|
|
876
|
+
});
|
|
877
|
+
var styles4 = StyleSheet5.create({
|
|
878
|
+
content: { backgroundColor: "transparent", paddingVertical: 0 }
|
|
879
|
+
});
|
|
880
|
+
|
|
886
881
|
// src/components/PalettePicker.tsx
|
|
887
|
-
import { useState as
|
|
882
|
+
import { useState as useState6 } from "react";
|
|
888
883
|
import { StyleSheet as StyleSheet6, TouchableOpacity as TouchableOpacity2, View as View5 } from "react-native";
|
|
889
884
|
import { Icon as Icon2, TouchableRipple as TouchableRipple2, useTheme as useTheme8 } from "react-native-paper";
|
|
890
885
|
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
@@ -902,35 +897,39 @@ var buildWedges = (weights) => {
|
|
|
902
897
|
];
|
|
903
898
|
};
|
|
904
899
|
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)) });
|
|
905
|
-
var PalettePicker = ({ value, onChange, harmony = "split-complementary", weights, colors = defaultColors, blur: blurProp }) => {
|
|
900
|
+
var PalettePicker = ({ value, onChange, harmony = "split-complementary", onHarmonyChange, weights, colors = defaultColors, blur: blurProp }) => {
|
|
906
901
|
const blur = useBlur(blurProp);
|
|
907
902
|
const { colors: themeColors } = useTheme8();
|
|
908
|
-
const [open, setOpen] =
|
|
903
|
+
const [open, setOpen] = useState6(false);
|
|
909
904
|
const { onLayout, size } = useSwatchSize(SIZE2);
|
|
910
905
|
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
911
906
|
/* @__PURE__ */ jsx14(TouchableRipple2, { onPress: () => setOpen(true), style: styles5.trigger, borderless: true, children: /* @__PURE__ */ jsxs6(View5, { style: styles5.triggerContent, children: [
|
|
912
907
|
/* @__PURE__ */ jsx14(Pie, { palette: getTriadicPalette(value, harmony), weights }),
|
|
913
908
|
/* @__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) }) })
|
|
914
909
|
] }) }),
|
|
915
|
-
/* @__PURE__ */ jsx14(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */
|
|
916
|
-
|
|
917
|
-
{
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
910
|
+
/* @__PURE__ */ jsx14(Dialog, { blur, visible: open, onDismiss: () => setOpen(false), children: /* @__PURE__ */ jsxs6(Dialog.Content, { children: [
|
|
911
|
+
onHarmonyChange && /* @__PURE__ */ jsx14(View5, { style: styles5.harmonyRow, children: /* @__PURE__ */ jsx14(HarmonyPicker, { value: harmony, onChange: onHarmonyChange, showLabels: false }) }),
|
|
912
|
+
/* @__PURE__ */ jsx14(View5, { style: styles5.swatches, onLayout, children: colors.map(({ value: hex }) => /* @__PURE__ */ jsxs6(
|
|
913
|
+
TouchableOpacity2,
|
|
914
|
+
{
|
|
915
|
+
onPress: () => {
|
|
916
|
+
onChange(hex);
|
|
917
|
+
setOpen(false);
|
|
918
|
+
},
|
|
919
|
+
style: [styles5.swatch, { height: size, width: size }],
|
|
920
|
+
children: [
|
|
921
|
+
/* @__PURE__ */ jsx14(Pie, { palette: getTriadicPalette(hex, harmony), weights, size }),
|
|
922
|
+
/* @__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) }) })
|
|
923
|
+
]
|
|
921
924
|
},
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
/* @__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) }) })
|
|
926
|
-
]
|
|
927
|
-
},
|
|
928
|
-
hex
|
|
929
|
-
)) }) }) })
|
|
925
|
+
hex
|
|
926
|
+
)) })
|
|
927
|
+
] }) })
|
|
930
928
|
] });
|
|
931
929
|
};
|
|
932
930
|
var styles5 = StyleSheet6.create({
|
|
933
931
|
halfMask: { height: "100%", left: "50%", overflow: "hidden", position: "absolute", top: 0, width: "50%" },
|
|
932
|
+
harmonyRow: { marginBottom: 4 },
|
|
934
933
|
innerRotator: { height: "100%", left: "-100%", overflow: "hidden", position: "absolute", top: 0, width: "200%" },
|
|
935
934
|
pie: { overflow: "hidden" },
|
|
936
935
|
rotator: { height: "100%", left: 0, overflow: "hidden", position: "absolute", top: 0, width: "100%" },
|
|
@@ -1121,5 +1120,6 @@ export {
|
|
|
1121
1120
|
useComputedTheme,
|
|
1122
1121
|
useNavBarContext,
|
|
1123
1122
|
usePaperDefaults,
|
|
1123
|
+
useReanimatedModule,
|
|
1124
1124
|
useThemeSettings
|
|
1125
1125
|
};
|
package/package.json
CHANGED
package/src/ThemeProvider.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { StatusBar, type StatusBarProps, type StyleProp, StyleSheet, View, type
|
|
|
3
3
|
import { Provider as PaperProvider } from 'react-native-paper'
|
|
4
4
|
|
|
5
5
|
import type { ExpoBlurModule } from './components/BlurView'
|
|
6
|
+
import type { ReanimatedModule } from './components/Dialog'
|
|
6
7
|
import type { ExpoNavigationBarModule } from './navigation-bar'
|
|
7
8
|
import { type PaperDefaults, PaperDefaultsContext } from './PaperDefaultsContext'
|
|
8
9
|
import { defaultThemeSettings, type ThemeSettings, ThemeSettingsContext } from './ThemeSettingsContext'
|
|
@@ -20,6 +21,9 @@ export const useNavBarContext = () => useContext(NavBarContext)
|
|
|
20
21
|
const BlurModuleContext = createContext<ExpoBlurModule | undefined>(undefined)
|
|
21
22
|
export const useBlurModule = () => useContext(BlurModuleContext)
|
|
22
23
|
|
|
24
|
+
const ReanimatedModuleContext = createContext<ReanimatedModule | undefined>(undefined)
|
|
25
|
+
export const useReanimatedModule = () => useContext(ReanimatedModuleContext)
|
|
26
|
+
|
|
23
27
|
export type ProviderProps = {
|
|
24
28
|
children: ReactNode
|
|
25
29
|
defaults?: PaperDefaults
|
|
@@ -31,11 +35,13 @@ export type ProviderProps = {
|
|
|
31
35
|
onChange?: (settings: ThemeSettings) => void
|
|
32
36
|
onNavBarChange?: (color: string, dark: boolean) => void
|
|
33
37
|
onReady?: () => void
|
|
38
|
+
/** Injects react-native-reanimated so `<Dialog animatedStyle={...}>` (e.g. from `useAnimatedStyle()`) can animate its card on the UI thread. Pass `import Reanimated from 'react-native-reanimated'`; omit to keep Dialog's `animatedStyle` prop a no-op. */
|
|
39
|
+
reanimated?: ReanimatedModule
|
|
34
40
|
statusBarProps?: StatusBarProps
|
|
35
41
|
style?: StyleProp<ViewStyle>
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
export function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, statusBarProps, style }: ProviderProps) {
|
|
44
|
+
export function Provider({ children, defaults, expoBlur, initialValue, navigationBar, onChange, onNavBarChange, onReady, reanimated, statusBarProps, style }: ProviderProps) {
|
|
39
45
|
const [settings, setSettings] = useState<ThemeSettings>(() => ({ ...defaultThemeSettings, ...initialValue }))
|
|
40
46
|
|
|
41
47
|
const set = useCallback((patch: Partial<ThemeSettings>) => {
|
|
@@ -71,7 +77,9 @@ export function Provider({ children, defaults, expoBlur, initialValue, navigatio
|
|
|
71
77
|
<PaperDefaultsContext.Provider value={defaults ?? {}}>
|
|
72
78
|
<NavBarContext.Provider value={{ navigationBar, onNavBarChange }}>
|
|
73
79
|
<BlurModuleContext.Provider value={expoBlur}>
|
|
74
|
-
<
|
|
80
|
+
<ReanimatedModuleContext.Provider value={reanimated}>
|
|
81
|
+
<View style={[styles.flex, { backgroundColor: theme.colors.background }, style]}>{children}</View>
|
|
82
|
+
</ReanimatedModuleContext.Provider>
|
|
75
83
|
</BlurModuleContext.Provider>
|
|
76
84
|
</NavBarContext.Provider>
|
|
77
85
|
</PaperDefaultsContext.Provider>
|
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react'
|
|
1
|
+
import React, { type ComponentType, type ReactNode, useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { Animated, BackHandler, Easing, Pressable, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
|
|
3
3
|
import { Dialog as PaperDialog, type DialogActionsProps, type DialogContentProps, type DialogProps as PaperDialogProps, type DialogScrollAreaProps, type DialogTitleProps, Portal, useTheme } from 'react-native-paper'
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
5
|
|
|
6
6
|
import { useBlur } from '../BlurContext'
|
|
7
|
+
import { useReanimatedModule } from '../ThemeProvider'
|
|
7
8
|
import { BlurView } from './BlurView'
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
10
|
+
// Minimal local mirror of react-native-reanimated's Animated.View — same reasoning as BlurView's
|
|
11
|
+
// ExpoBlurModule: Metro doesn't rewrite a require()-in-try/catch call into its module graph inside
|
|
12
|
+
// an ESM build, so the already-imported peer module is injected via <Provider reanimated={...}>
|
|
13
|
+
// (see ThemeProvider.tsx) instead of required directly. Only `View` is needed here. `style: any`,
|
|
14
|
+
// not a real style type — reanimated's actual AnimatedView only accepts its own branded
|
|
15
|
+
// AnimatedStyle shape, which this package never imports (no reanimated dependency at all), so `any`
|
|
16
|
+
// is the only type that both sides (this interface and the real component passed in) can agree on.
|
|
17
|
+
export interface ReanimatedModule {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
View: ComponentType<{ children?: ReactNode; style?: any; testID?: string }>
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
export type DialogProps = Omit<PaperDialogProps, 'style'> & {
|
|
12
23
|
blur?: boolean
|
|
13
24
|
style?: StyleProp<ViewStyle>
|
|
25
|
+
// Applied to the card's outer wrapper instead of the card itself, and only takes effect once a
|
|
26
|
+
// reanimated module has been injected via <Provider reanimated={...}> — otherwise ignored. `style`
|
|
27
|
+
// above is a plain object read once per render, so a fast-changing value (e.g. tracking the
|
|
28
|
+
// keyboard height every frame) driven through it needs a full JS re-render per update to paint at
|
|
29
|
+
// all; wiring the same value through `animatedStyle` (e.g. the result of Reanimated's
|
|
30
|
+
// useAnimatedStyle()) instead paints on the UI thread with no JS round-trip, which is the whole
|
|
31
|
+
// difference between a smooth follow and a laggy snap.
|
|
32
|
+
animatedStyle?: unknown
|
|
14
33
|
}
|
|
15
34
|
|
|
16
35
|
// Matches react-native-paper's Modal: DEFAULT_DURATION * theme.animation.scale, eased the same way,
|
|
@@ -20,8 +39,9 @@ const ANIMATION_DURATION = 220
|
|
|
20
39
|
// Minimum breathing room between the card and the screen edge, clamped up from safe-area insets.
|
|
21
40
|
const DIALOG_MARGIN = 32
|
|
22
41
|
|
|
23
|
-
function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = 'dialog' }: DialogProps) {
|
|
42
|
+
function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = 'dialog' }: DialogProps) {
|
|
24
43
|
const blur = useBlur(blurProp)
|
|
44
|
+
const reanimated = useReanimatedModule()
|
|
25
45
|
const theme = useTheme()
|
|
26
46
|
const { colors, isV3, roundness } = theme
|
|
27
47
|
const { scale } = theme.animation
|
|
@@ -75,9 +95,17 @@ function DialogComponent({ blur: blurProp, dismissable = true, dismissableBackBu
|
|
|
75
95
|
<Animated.View accessibilityLiveRegion='polite' accessibilityViewIsModal pointerEvents={visible ? 'box-none' : 'none'} style={[StyleSheet.absoluteFill, { opacity }]} testID={testID}>
|
|
76
96
|
<Pressable accessibilityLabel='Close modal' accessibilityRole='button' disabled={!dismissable} onPress={dismissable ? onDismiss : undefined} style={[StyleSheet.absoluteFill, { backgroundColor: colors.backdrop }]} testID={`${testID}-backdrop`} />
|
|
77
97
|
<View pointerEvents='box-none' style={[styles.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }]} testID={`${testID}-wrapper`}>
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
|
|
98
|
+
{reanimated && animatedStyle ? (
|
|
99
|
+
<reanimated.View style={animatedStyle} testID={`${testID}-animated-wrapper`}>
|
|
100
|
+
<BlurView blur={blur} style={[styles.card, { borderRadius }, style]} testID={`${testID}-surface`}>
|
|
101
|
+
{content}
|
|
102
|
+
</BlurView>
|
|
103
|
+
</reanimated.View>
|
|
104
|
+
) : (
|
|
105
|
+
<BlurView blur={blur} style={[styles.card, { borderRadius }, style]} testID={`${testID}-surface`}>
|
|
106
|
+
{content}
|
|
107
|
+
</BlurView>
|
|
108
|
+
)}
|
|
81
109
|
</View>
|
|
82
110
|
</Animated.View>
|
|
83
111
|
</Portal>
|
|
@@ -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
|
@@ -10,7 +10,7 @@ export type { ChipProps } from './components/Chip'
|
|
|
10
10
|
export { Chip } from './components/Chip'
|
|
11
11
|
export type { SeedColor } from './components/ColorPicker'
|
|
12
12
|
export { ColorPicker, defaultColors } from './components/ColorPicker'
|
|
13
|
-
export type { DialogProps } from './components/Dialog'
|
|
13
|
+
export type { DialogProps, ReanimatedModule } from './components/Dialog'
|
|
14
14
|
export { Dialog } from './components/Dialog'
|
|
15
15
|
export { FAB } from './components/FAB'
|
|
16
16
|
export { HarmonyPicker } from './components/HarmonyPicker'
|
|
@@ -27,7 +27,7 @@ export { usePaperDefaults } from './PaperDefaultsContext'
|
|
|
27
27
|
export type { ThemeState } from './redux/themeSlice'
|
|
28
28
|
export { createThemeReducer, selectThemeAppearance, selectThemeBlur, selectThemeColor, selectThemeHarmony, themeActions, themeReducer } from './redux/themeSlice'
|
|
29
29
|
export type { ProviderProps } from './ThemeProvider'
|
|
30
|
-
export { Provider, useBlurModule, useNavBarContext } from './ThemeProvider'
|
|
30
|
+
export { Provider, useBlurModule, useNavBarContext, useReanimatedModule } from './ThemeProvider'
|
|
31
31
|
export type { ThemeSettings, ThemeSettingsContextType } from './ThemeSettingsContext'
|
|
32
32
|
export { defaultThemeSettings, ThemeSettingsContext } from './ThemeSettingsContext'
|
|
33
33
|
export type { AutoPaperTheme, SemanticColorRoles, ThemeAppearance } from './useComputedTheme'
|