@oxyhq/bloom 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/animated-check/AnimatedCheck.js +162 -0
- package/lib/commonjs/animated-check/AnimatedCheck.js.map +1 -0
- package/lib/commonjs/animated-check/index.js +13 -0
- package/lib/commonjs/animated-check/index.js.map +1 -0
- package/lib/commonjs/dialog/placement.js +9 -7
- package/lib/commonjs/dialog/placement.js.map +1 -1
- package/lib/commonjs/fill/index.js.map +1 -1
- package/lib/commonjs/hooks/index.js +20 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useGutters.js +106 -0
- package/lib/commonjs/hooks/useGutters.js.map +1 -0
- package/lib/commonjs/hooks/useHaptics.js +69 -0
- package/lib/commonjs/hooks/useHaptics.js.map +1 -0
- package/lib/commonjs/hooks/useInteractionState.js +8 -2
- package/lib/commonjs/hooks/useInteractionState.js.map +1 -1
- package/lib/commonjs/index.js +155 -63
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +155 -63
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/media-inset-border/MediaInsetBorder.js +50 -0
- package/lib/commonjs/media-inset-border/MediaInsetBorder.js.map +1 -0
- package/lib/commonjs/media-inset-border/index.js +13 -0
- package/lib/commonjs/media-inset-border/index.js.map +1 -0
- package/lib/commonjs/motion/ScreenTransition.js +46 -0
- package/lib/commonjs/motion/ScreenTransition.js.map +1 -0
- package/lib/commonjs/motion/index.js +32 -0
- package/lib/commonjs/motion/index.js.map +1 -0
- package/lib/commonjs/motion/motion.js +93 -0
- package/lib/commonjs/motion/motion.js.map +1 -0
- package/lib/commonjs/pressable-scale/PressableScale.js +75 -0
- package/lib/commonjs/pressable-scale/PressableScale.js.map +1 -0
- package/lib/commonjs/pressable-scale/index.js +13 -0
- package/lib/commonjs/pressable-scale/index.js.map +1 -0
- package/lib/commonjs/pressable-with-hover/PressableWithHover.js +36 -0
- package/lib/commonjs/pressable-with-hover/PressableWithHover.js.map +1 -0
- package/lib/commonjs/pressable-with-hover/index.js +13 -0
- package/lib/commonjs/pressable-with-hover/index.js.map +1 -0
- package/lib/commonjs/styles/breakpoints.js +24 -0
- package/lib/commonjs/styles/breakpoints.js.map +1 -0
- package/lib/commonjs/styles/index.js +7 -0
- package/lib/commonjs/styles/index.js.map +1 -1
- package/lib/commonjs/subtle-hover/SubtleHover.js +69 -0
- package/lib/commonjs/subtle-hover/SubtleHover.js.map +1 -0
- package/lib/commonjs/subtle-hover/index.js +13 -0
- package/lib/commonjs/subtle-hover/index.js.map +1 -0
- package/lib/commonjs/theme/build-theme.js +2 -0
- package/lib/commonjs/theme/build-theme.js.map +1 -1
- package/lib/commonjs/theme/gradients.js +39 -0
- package/lib/commonjs/theme/gradients.js.map +1 -0
- package/lib/commonjs/theme/index.js +7 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/module/animated-check/AnimatedCheck.js +157 -0
- package/lib/module/animated-check/AnimatedCheck.js.map +1 -0
- package/lib/module/animated-check/index.js +4 -0
- package/lib/module/animated-check/index.js.map +1 -0
- package/lib/module/dialog/placement.js +9 -7
- package/lib/module/dialog/placement.js.map +1 -1
- package/lib/module/fill/index.js.map +1 -1
- package/lib/module/hooks/index.js +2 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useGutters.js +103 -0
- package/lib/module/hooks/useGutters.js.map +1 -0
- package/lib/module/hooks/useHaptics.js +65 -0
- package/lib/module/hooks/useHaptics.js.map +1 -0
- package/lib/module/hooks/useInteractionState.js +10 -3
- package/lib/module/hooks/useInteractionState.js.map +1 -1
- package/lib/module/index.js +10 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +10 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/media-inset-border/MediaInsetBorder.js +47 -0
- package/lib/module/media-inset-border/MediaInsetBorder.js.map +1 -0
- package/lib/module/media-inset-border/index.js +4 -0
- package/lib/module/media-inset-border/index.js.map +1 -0
- package/lib/module/motion/ScreenTransition.js +40 -0
- package/lib/module/motion/ScreenTransition.js.map +1 -0
- package/lib/module/motion/index.js +5 -0
- package/lib/module/motion/index.js.map +1 -0
- package/lib/module/motion/motion.js +87 -0
- package/lib/module/motion/motion.js.map +1 -0
- package/lib/module/pressable-scale/PressableScale.js +72 -0
- package/lib/module/pressable-scale/PressableScale.js.map +1 -0
- package/lib/module/pressable-scale/index.js +4 -0
- package/lib/module/pressable-scale/index.js.map +1 -0
- package/lib/module/pressable-with-hover/PressableWithHover.js +31 -0
- package/lib/module/pressable-with-hover/PressableWithHover.js.map +1 -0
- package/lib/module/pressable-with-hover/index.js +4 -0
- package/lib/module/pressable-with-hover/index.js.map +1 -0
- package/lib/module/styles/breakpoints.js +20 -0
- package/lib/module/styles/breakpoints.js.map +1 -0
- package/lib/module/styles/index.js +1 -0
- package/lib/module/styles/index.js.map +1 -1
- package/lib/module/subtle-hover/SubtleHover.js +66 -0
- package/lib/module/subtle-hover/SubtleHover.js.map +1 -0
- package/lib/module/subtle-hover/index.js +4 -0
- package/lib/module/subtle-hover/index.js.map +1 -0
- package/lib/module/theme/build-theme.js +2 -0
- package/lib/module/theme/build-theme.js.map +1 -1
- package/lib/module/theme/gradients.js +35 -0
- package/lib/module/theme/gradients.js.map +1 -0
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/typescript/commonjs/animated-check/AnimatedCheck.d.ts +18 -0
- package/lib/typescript/commonjs/animated-check/AnimatedCheck.d.ts.map +1 -0
- package/lib/typescript/commonjs/animated-check/index.d.ts +3 -0
- package/lib/typescript/commonjs/animated-check/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts +8 -7
- package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/commonjs/fill/index.d.ts +2 -2
- package/lib/typescript/commonjs/fill/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/index.d.ts +4 -0
- package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/useGutters.d.ts +32 -0
- package/lib/typescript/commonjs/hooks/useGutters.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useHaptics.d.ts +28 -0
- package/lib/typescript/commonjs/hooks/useHaptics.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useInteractionState.d.ts +6 -0
- package/lib/typescript/commonjs/hooks/useInteractionState.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +16 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +16 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/media-inset-border/MediaInsetBorder.d.ts +26 -0
- package/lib/typescript/commonjs/media-inset-border/MediaInsetBorder.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-inset-border/index.d.ts +3 -0
- package/lib/typescript/commonjs/media-inset-border/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/ScreenTransition.d.ts +33 -0
- package/lib/typescript/commonjs/motion/ScreenTransition.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/index.d.ts +4 -0
- package/lib/typescript/commonjs/motion/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/motion.d.ts +24 -0
- package/lib/typescript/commonjs/motion/motion.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-scale/PressableScale.d.ts +21 -0
- package/lib/typescript/commonjs/pressable-scale/PressableScale.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-scale/index.d.ts +3 -0
- package/lib/typescript/commonjs/pressable-scale/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-with-hover/PressableWithHover.d.ts +18 -0
- package/lib/typescript/commonjs/pressable-with-hover/PressableWithHover.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-with-hover/index.d.ts +3 -0
- package/lib/typescript/commonjs/pressable-with-hover/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/styles/breakpoints.d.ts +18 -0
- package/lib/typescript/commonjs/styles/breakpoints.d.ts.map +1 -0
- package/lib/typescript/commonjs/styles/index.d.ts +2 -0
- package/lib/typescript/commonjs/styles/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/subtle-hover/SubtleHover.d.ts +32 -0
- package/lib/typescript/commonjs/subtle-hover/SubtleHover.d.ts.map +1 -0
- package/lib/typescript/commonjs/subtle-hover/index.d.ts +3 -0
- package/lib/typescript/commonjs/subtle-hover/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/gradients.d.ts +12 -0
- package/lib/typescript/commonjs/theme/gradients.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/index.d.ts +2 -1
- package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/types.d.ts +11 -0
- package/lib/typescript/commonjs/theme/types.d.ts.map +1 -1
- package/lib/typescript/module/animated-check/AnimatedCheck.d.ts +18 -0
- package/lib/typescript/module/animated-check/AnimatedCheck.d.ts.map +1 -0
- package/lib/typescript/module/animated-check/index.d.ts +3 -0
- package/lib/typescript/module/animated-check/index.d.ts.map +1 -0
- package/lib/typescript/module/dialog/placement.d.ts +8 -7
- package/lib/typescript/module/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/module/fill/index.d.ts +2 -2
- package/lib/typescript/module/fill/index.d.ts.map +1 -1
- package/lib/typescript/module/hooks/index.d.ts +4 -0
- package/lib/typescript/module/hooks/index.d.ts.map +1 -1
- package/lib/typescript/module/hooks/useGutters.d.ts +32 -0
- package/lib/typescript/module/hooks/useGutters.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useHaptics.d.ts +28 -0
- package/lib/typescript/module/hooks/useHaptics.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useInteractionState.d.ts +6 -0
- package/lib/typescript/module/hooks/useInteractionState.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +16 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +16 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/media-inset-border/MediaInsetBorder.d.ts +26 -0
- package/lib/typescript/module/media-inset-border/MediaInsetBorder.d.ts.map +1 -0
- package/lib/typescript/module/media-inset-border/index.d.ts +3 -0
- package/lib/typescript/module/media-inset-border/index.d.ts.map +1 -0
- package/lib/typescript/module/motion/ScreenTransition.d.ts +33 -0
- package/lib/typescript/module/motion/ScreenTransition.d.ts.map +1 -0
- package/lib/typescript/module/motion/index.d.ts +4 -0
- package/lib/typescript/module/motion/index.d.ts.map +1 -0
- package/lib/typescript/module/motion/motion.d.ts +24 -0
- package/lib/typescript/module/motion/motion.d.ts.map +1 -0
- package/lib/typescript/module/pressable-scale/PressableScale.d.ts +21 -0
- package/lib/typescript/module/pressable-scale/PressableScale.d.ts.map +1 -0
- package/lib/typescript/module/pressable-scale/index.d.ts +3 -0
- package/lib/typescript/module/pressable-scale/index.d.ts.map +1 -0
- package/lib/typescript/module/pressable-with-hover/PressableWithHover.d.ts +18 -0
- package/lib/typescript/module/pressable-with-hover/PressableWithHover.d.ts.map +1 -0
- package/lib/typescript/module/pressable-with-hover/index.d.ts +3 -0
- package/lib/typescript/module/pressable-with-hover/index.d.ts.map +1 -0
- package/lib/typescript/module/styles/breakpoints.d.ts +18 -0
- package/lib/typescript/module/styles/breakpoints.d.ts.map +1 -0
- package/lib/typescript/module/styles/index.d.ts +2 -0
- package/lib/typescript/module/styles/index.d.ts.map +1 -1
- package/lib/typescript/module/subtle-hover/SubtleHover.d.ts +32 -0
- package/lib/typescript/module/subtle-hover/SubtleHover.d.ts.map +1 -0
- package/lib/typescript/module/subtle-hover/index.d.ts +3 -0
- package/lib/typescript/module/subtle-hover/index.d.ts.map +1 -0
- package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/module/theme/gradients.d.ts +12 -0
- package/lib/typescript/module/theme/gradients.d.ts.map +1 -0
- package/lib/typescript/module/theme/index.d.ts +2 -1
- package/lib/typescript/module/theme/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/types.d.ts +11 -0
- package/lib/typescript/module/theme/types.d.ts.map +1 -1
- package/package.json +72 -1
- package/src/__tests__/AnimatedCheck.test.tsx +31 -0
- package/src/__tests__/MediaInsetBorder.test.tsx +36 -0
- package/src/__tests__/Motion.test.tsx +58 -0
- package/src/__tests__/PressableScale.test.tsx +29 -0
- package/src/__tests__/PressableWithHover.test.tsx +48 -0
- package/src/__tests__/SubtleHover.test.tsx +42 -0
- package/src/__tests__/theme-gradients.test.ts +31 -0
- package/src/__tests__/theme.test.ts +3 -0
- package/src/__tests__/useGutters.test.ts +58 -0
- package/src/__tests__/useHaptics.test.tsx +58 -0
- package/src/__tests__/useInteractionState.test.ts +27 -0
- package/src/animated-check/AnimatedCheck.stories.tsx +60 -0
- package/src/animated-check/AnimatedCheck.tsx +173 -0
- package/src/animated-check/index.ts +2 -0
- package/src/dialog/placement.ts +10 -7
- package/src/fill/index.tsx +2 -2
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useGutters.ts +99 -0
- package/src/hooks/useHaptics.tsx +69 -0
- package/src/hooks/useInteractionState.ts +8 -2
- package/src/index.ts +20 -0
- package/src/index.web.ts +20 -0
- package/src/media-inset-border/MediaInsetBorder.stories.tsx +60 -0
- package/src/media-inset-border/MediaInsetBorder.tsx +72 -0
- package/src/media-inset-border/index.ts +2 -0
- package/src/motion/Motion.stories.tsx +99 -0
- package/src/motion/ScreenTransition.tsx +71 -0
- package/src/motion/index.ts +3 -0
- package/src/motion/motion.ts +75 -0
- package/src/pressable-scale/PressableScale.stories.tsx +71 -0
- package/src/pressable-scale/PressableScale.tsx +103 -0
- package/src/pressable-scale/index.ts +2 -0
- package/src/pressable-with-hover/PressableWithHover.stories.tsx +43 -0
- package/src/pressable-with-hover/PressableWithHover.tsx +43 -0
- package/src/pressable-with-hover/index.ts +2 -0
- package/src/styles/breakpoints.ts +18 -0
- package/src/styles/index.ts +2 -0
- package/src/subtle-hover/SubtleHover.stories.tsx +72 -0
- package/src/subtle-hover/SubtleHover.tsx +90 -0
- package/src/subtle-hover/index.ts +2 -0
- package/src/theme/build-theme.ts +2 -0
- package/src/theme/gradients.ts +61 -0
- package/src/theme/index.ts +2 -1
- package/src/theme/types.ts +13 -0
|
@@ -34,9 +34,20 @@ export interface ThemeColors {
|
|
|
34
34
|
shadow: string;
|
|
35
35
|
overlay: string;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* A single named gradient: an ordered list of `[offset, color]` stops where
|
|
39
|
+
* `offset` runs 0–1 along the gradient axis and `color` is any RN/CSS color.
|
|
40
|
+
*/
|
|
41
|
+
export interface ThemeGradient {
|
|
42
|
+
values: Array<[number, string]>;
|
|
43
|
+
}
|
|
44
|
+
/** Map of named gradients exposed on the theme (`theme.gradients`). */
|
|
45
|
+
export type ThemeGradients = Record<string, ThemeGradient>;
|
|
37
46
|
export interface Theme {
|
|
38
47
|
mode: 'light' | 'dark';
|
|
39
48
|
colors: ThemeColors;
|
|
49
|
+
/** Brand-neutral named linear-gradient palettes. Theme-agnostic (same in light/dark). */
|
|
50
|
+
gradients: ThemeGradients;
|
|
40
51
|
isDark: boolean;
|
|
41
52
|
isLight: boolean;
|
|
42
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/theme/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAE3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IAErB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IAEpB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IAEpB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/theme/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAE3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IAErB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IAEpB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IAEpB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjC;AAED,uEAAuE;AACvE,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,yFAAyF;IACzF,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -371,6 +371,72 @@
|
|
|
371
371
|
"default": "./lib/commonjs/fill/index.js"
|
|
372
372
|
}
|
|
373
373
|
},
|
|
374
|
+
"./media-inset-border": {
|
|
375
|
+
"react-native": "./src/media-inset-border/index.ts",
|
|
376
|
+
"import": {
|
|
377
|
+
"types": "./lib/typescript/module/media-inset-border/index.d.ts",
|
|
378
|
+
"default": "./lib/module/media-inset-border/index.js"
|
|
379
|
+
},
|
|
380
|
+
"require": {
|
|
381
|
+
"types": "./lib/typescript/commonjs/media-inset-border/index.d.ts",
|
|
382
|
+
"default": "./lib/commonjs/media-inset-border/index.js"
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
"./pressable-scale": {
|
|
386
|
+
"react-native": "./src/pressable-scale/index.ts",
|
|
387
|
+
"import": {
|
|
388
|
+
"types": "./lib/typescript/module/pressable-scale/index.d.ts",
|
|
389
|
+
"default": "./lib/module/pressable-scale/index.js"
|
|
390
|
+
},
|
|
391
|
+
"require": {
|
|
392
|
+
"types": "./lib/typescript/commonjs/pressable-scale/index.d.ts",
|
|
393
|
+
"default": "./lib/commonjs/pressable-scale/index.js"
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
"./pressable-with-hover": {
|
|
397
|
+
"react-native": "./src/pressable-with-hover/index.ts",
|
|
398
|
+
"import": {
|
|
399
|
+
"types": "./lib/typescript/module/pressable-with-hover/index.d.ts",
|
|
400
|
+
"default": "./lib/module/pressable-with-hover/index.js"
|
|
401
|
+
},
|
|
402
|
+
"require": {
|
|
403
|
+
"types": "./lib/typescript/commonjs/pressable-with-hover/index.d.ts",
|
|
404
|
+
"default": "./lib/commonjs/pressable-with-hover/index.js"
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
"./subtle-hover": {
|
|
408
|
+
"react-native": "./src/subtle-hover/index.ts",
|
|
409
|
+
"import": {
|
|
410
|
+
"types": "./lib/typescript/module/subtle-hover/index.d.ts",
|
|
411
|
+
"default": "./lib/module/subtle-hover/index.js"
|
|
412
|
+
},
|
|
413
|
+
"require": {
|
|
414
|
+
"types": "./lib/typescript/commonjs/subtle-hover/index.d.ts",
|
|
415
|
+
"default": "./lib/commonjs/subtle-hover/index.js"
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
"./motion": {
|
|
419
|
+
"react-native": "./src/motion/index.ts",
|
|
420
|
+
"import": {
|
|
421
|
+
"types": "./lib/typescript/module/motion/index.d.ts",
|
|
422
|
+
"default": "./lib/module/motion/index.js"
|
|
423
|
+
},
|
|
424
|
+
"require": {
|
|
425
|
+
"types": "./lib/typescript/commonjs/motion/index.d.ts",
|
|
426
|
+
"default": "./lib/commonjs/motion/index.js"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"./animated-check": {
|
|
430
|
+
"react-native": "./src/animated-check/index.ts",
|
|
431
|
+
"import": {
|
|
432
|
+
"types": "./lib/typescript/module/animated-check/index.d.ts",
|
|
433
|
+
"default": "./lib/module/animated-check/index.js"
|
|
434
|
+
},
|
|
435
|
+
"require": {
|
|
436
|
+
"types": "./lib/typescript/commonjs/animated-check/index.d.ts",
|
|
437
|
+
"default": "./lib/commonjs/animated-check/index.js"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
374
440
|
"./icon-circle": {
|
|
375
441
|
"react-native": "./src/icon-circle/index.tsx",
|
|
376
442
|
"import": {
|
|
@@ -927,6 +993,7 @@
|
|
|
927
993
|
"@types/react-native": "*",
|
|
928
994
|
"@vitejs/plugin-react": "^6.0.2",
|
|
929
995
|
"expo-font": "^56.0.5",
|
|
996
|
+
"expo-haptics": "~56.0.3",
|
|
930
997
|
"expo-router": "^56.0.0",
|
|
931
998
|
"jest": "^30.3.0",
|
|
932
999
|
"jest-environment-jsdom": "^30.4.1",
|
|
@@ -951,6 +1018,7 @@
|
|
|
951
1018
|
"peerDependencies": {
|
|
952
1019
|
"expo": "*",
|
|
953
1020
|
"expo-font": "*",
|
|
1021
|
+
"expo-haptics": "*",
|
|
954
1022
|
"expo-router": ">=3.0.0",
|
|
955
1023
|
"react": ">=18.0.0",
|
|
956
1024
|
"react-dom": ">=18.0.0",
|
|
@@ -969,6 +1037,9 @@
|
|
|
969
1037
|
"expo-font": {
|
|
970
1038
|
"optional": true
|
|
971
1039
|
},
|
|
1040
|
+
"expo-haptics": {
|
|
1041
|
+
"optional": true
|
|
1042
|
+
},
|
|
972
1043
|
"expo-router": {
|
|
973
1044
|
"optional": true
|
|
974
1045
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
5
|
+
import { AnimatedCheck, type AnimatedCheckRef } from '../animated-check';
|
|
6
|
+
|
|
7
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
8
|
+
return render(
|
|
9
|
+
<BloomThemeProvider mode="light" colorPreset="teal">
|
|
10
|
+
{ui}
|
|
11
|
+
</BloomThemeProvider>,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('AnimatedCheck', () => {
|
|
16
|
+
it('carries a displayName', () => {
|
|
17
|
+
expect(AnimatedCheck.displayName).toBe('AnimatedCheck');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders the animated SVG when react-native-svg is available', () => {
|
|
21
|
+
const { toJSON } = renderWithTheme(<AnimatedCheck size={48} />);
|
|
22
|
+
expect(toJSON()).not.toBeNull();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('exposes an imperative play() that runs without throwing', () => {
|
|
26
|
+
const ref = React.createRef<AnimatedCheckRef>();
|
|
27
|
+
renderWithTheme(<AnimatedCheck ref={ref} size={48} color="#6366f1" />);
|
|
28
|
+
expect(ref.current).not.toBeNull();
|
|
29
|
+
expect(() => act(() => ref.current?.play())).not.toThrow();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
5
|
+
import { MediaInsetBorder } from '../media-inset-border';
|
|
6
|
+
|
|
7
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
8
|
+
return render(
|
|
9
|
+
<BloomThemeProvider mode="light" colorPreset="teal">
|
|
10
|
+
{ui}
|
|
11
|
+
</BloomThemeProvider>,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('MediaInsetBorder', () => {
|
|
16
|
+
it('carries a displayName', () => {
|
|
17
|
+
expect(MediaInsetBorder.displayName).toBe('MediaInsetBorder');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders the softened border by default', () => {
|
|
21
|
+
const { UNSAFE_root } = renderWithTheme(<MediaInsetBorder />);
|
|
22
|
+
expect(UNSAFE_root).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders the opaque variant', () => {
|
|
26
|
+
const { UNSAFE_root } = renderWithTheme(<MediaInsetBorder opaque />);
|
|
27
|
+
expect(UNSAFE_root).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('accepts a style override for borderRadius', () => {
|
|
31
|
+
const { UNSAFE_root } = renderWithTheme(
|
|
32
|
+
<MediaInsetBorder style={{ borderRadius: 999 }} />,
|
|
33
|
+
);
|
|
34
|
+
expect(UNSAFE_root).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { render } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ScaleAndFadeIn,
|
|
7
|
+
ScaleAndFadeOut,
|
|
8
|
+
ScreenTransition,
|
|
9
|
+
ShrinkAndPop,
|
|
10
|
+
} from '../motion';
|
|
11
|
+
|
|
12
|
+
// The reanimated mock resolves `withTiming(v)` -> v, `withDelay(d, v)` -> v and
|
|
13
|
+
// `withSequence(...v)` -> last, so the builder output is deterministic.
|
|
14
|
+
|
|
15
|
+
describe('motion presets', () => {
|
|
16
|
+
it('ScaleAndFadeIn enters from 70% scale and transparent', () => {
|
|
17
|
+
const { initialValues, animations } = ScaleAndFadeIn();
|
|
18
|
+
expect(initialValues).toEqual({ opacity: 0, transform: [{ scale: 0.7 }] });
|
|
19
|
+
expect(animations).toEqual({ opacity: 1, transform: [{ scale: 1 }] });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('ScaleAndFadeOut exits to 70% scale and transparent', () => {
|
|
23
|
+
const { initialValues, animations } = ScaleAndFadeOut();
|
|
24
|
+
expect(initialValues).toEqual({ opacity: 1, transform: [{ scale: 1 }] });
|
|
25
|
+
expect(animations).toEqual({ opacity: 0, transform: [{ scale: 0.7 }] });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('ShrinkAndPop overshoots to 110% before leaving', () => {
|
|
29
|
+
const { initialValues, animations } = ShrinkAndPop();
|
|
30
|
+
expect(initialValues).toEqual({ opacity: 1, transform: [{ scale: 1 }] });
|
|
31
|
+
// withSequence resolves to its final keyframe (the 1.1 overshoot) in the mock.
|
|
32
|
+
expect(animations.transform).toEqual([{ scale: 1.1 }]);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('ScreenTransition', () => {
|
|
37
|
+
it('carries a displayName', () => {
|
|
38
|
+
expect(ScreenTransition.displayName).toBe('ScreenTransition');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renders its children for a forward transition', () => {
|
|
42
|
+
const { getByText } = render(
|
|
43
|
+
<ScreenTransition direction="forward">
|
|
44
|
+
<Text>Screen body</Text>
|
|
45
|
+
</ScreenTransition>,
|
|
46
|
+
);
|
|
47
|
+
expect(getByText('Screen body')).toBeTruthy();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders its children for a backward transition', () => {
|
|
51
|
+
const { getByText } = render(
|
|
52
|
+
<ScreenTransition direction="backward" enabledWeb>
|
|
53
|
+
<Text>Back</Text>
|
|
54
|
+
</ScreenTransition>,
|
|
55
|
+
);
|
|
56
|
+
expect(getByText('Back')).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { render } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import { PressableScale } from '../pressable-scale';
|
|
6
|
+
|
|
7
|
+
describe('PressableScale', () => {
|
|
8
|
+
it('carries a displayName', () => {
|
|
9
|
+
expect(PressableScale.displayName).toBe('PressableScale');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders its children', () => {
|
|
13
|
+
const { getByText } = render(
|
|
14
|
+
<PressableScale accessibilityLabel="Press me">
|
|
15
|
+
<Text>Tap target</Text>
|
|
16
|
+
</PressableScale>,
|
|
17
|
+
);
|
|
18
|
+
expect(getByText('Tap target')).toBeTruthy();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('renders with a custom target scale', () => {
|
|
22
|
+
const { getByText } = render(
|
|
23
|
+
<PressableScale targetScale={0.9} accessibilityLabel="Press me">
|
|
24
|
+
<Text>Scaled</Text>
|
|
25
|
+
</PressableScale>,
|
|
26
|
+
);
|
|
27
|
+
expect(getByText('Scaled')).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { fireEvent, render } from '@testing-library/react-native';
|
|
4
|
+
|
|
5
|
+
import { PressableWithHover } from '../pressable-with-hover';
|
|
6
|
+
|
|
7
|
+
describe('PressableWithHover', () => {
|
|
8
|
+
it('carries a displayName', () => {
|
|
9
|
+
expect(PressableWithHover.displayName).toBe('PressableWithHover');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders its children', () => {
|
|
13
|
+
const { getByText } = render(
|
|
14
|
+
<PressableWithHover
|
|
15
|
+
accessibilityLabel="Row"
|
|
16
|
+
style={{ backgroundColor: 'white' }}
|
|
17
|
+
hoverStyle={{ backgroundColor: 'gray' }}
|
|
18
|
+
>
|
|
19
|
+
<Text>Row</Text>
|
|
20
|
+
</PressableWithHover>,
|
|
21
|
+
);
|
|
22
|
+
expect(getByText('Row')).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('merges hoverStyle only while hovered', () => {
|
|
26
|
+
const { getByTestId } = render(
|
|
27
|
+
<PressableWithHover
|
|
28
|
+
testID="row"
|
|
29
|
+
style={{ backgroundColor: 'white' }}
|
|
30
|
+
hoverStyle={{ backgroundColor: 'gray' }}
|
|
31
|
+
>
|
|
32
|
+
<Text>Row</Text>
|
|
33
|
+
</PressableWithHover>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const row = getByTestId('row');
|
|
37
|
+
expect(row.props.style).toEqual({ backgroundColor: 'white' });
|
|
38
|
+
|
|
39
|
+
fireEvent(row, 'hoverIn');
|
|
40
|
+
expect(getByTestId('row').props.style).toEqual([
|
|
41
|
+
{ backgroundColor: 'white' },
|
|
42
|
+
{ backgroundColor: 'gray' },
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
fireEvent(row, 'hoverOut');
|
|
46
|
+
expect(getByTestId('row').props.style).toEqual({ backgroundColor: 'white' });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { BloomThemeProvider } from '../theme/BloomThemeProvider';
|
|
5
|
+
import { SubtleHover } from '../subtle-hover';
|
|
6
|
+
|
|
7
|
+
// Default test Platform.OS is 'ios' (see __mocks__/react-native.ts), so the
|
|
8
|
+
// web-only overlay is suppressed unless `native` is opted in.
|
|
9
|
+
function renderWithTheme(ui: React.ReactElement) {
|
|
10
|
+
return render(
|
|
11
|
+
<BloomThemeProvider mode="light" colorPreset="teal">
|
|
12
|
+
{ui}
|
|
13
|
+
</BloomThemeProvider>,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('SubtleHover', () => {
|
|
18
|
+
it('carries a displayName', () => {
|
|
19
|
+
expect(SubtleHover.displayName).toBe('SubtleHover');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// `renderWithTheme` wraps the subject in the provider's flex View, so the
|
|
23
|
+
// overlay's presence is read from that wrapper's `children` rather than the
|
|
24
|
+
// root being null.
|
|
25
|
+
it('renders nothing on native by default', () => {
|
|
26
|
+
const tree = renderWithTheme(<SubtleHover hover />).toJSON();
|
|
27
|
+
expect(tree).not.toBeNull();
|
|
28
|
+
expect(tree?.children).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders the overlay on native when opted in', () => {
|
|
32
|
+
const tree = renderWithTheme(<SubtleHover hover native />).toJSON();
|
|
33
|
+
expect(tree?.children).not.toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('accepts an opacity override and a style', () => {
|
|
37
|
+
const tree = renderWithTheme(
|
|
38
|
+
<SubtleHover hover native opacity={0.9} style={{ borderRadius: 12 }} />,
|
|
39
|
+
).toJSON();
|
|
40
|
+
expect(tree?.children).not.toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { buildTheme } from '../theme/build-theme';
|
|
2
|
+
import { THEME_GRADIENTS } from '../theme/gradients';
|
|
3
|
+
|
|
4
|
+
describe('theme gradients', () => {
|
|
5
|
+
it('are exposed on the built theme', () => {
|
|
6
|
+
const theme = buildTheme('teal', 'light');
|
|
7
|
+
expect(theme.gradients).toBe(THEME_GRADIENTS);
|
|
8
|
+
expect(Object.keys(theme.gradients).length).toBeGreaterThan(0);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('every gradient has ordered [offset, color] stops', () => {
|
|
12
|
+
for (const [name, gradient] of Object.entries(THEME_GRADIENTS)) {
|
|
13
|
+
expect(name).toBeTruthy();
|
|
14
|
+
expect(Array.isArray(gradient.values)).toBe(true);
|
|
15
|
+
expect(gradient.values.length).toBeGreaterThanOrEqual(2);
|
|
16
|
+
|
|
17
|
+
for (const [offset, color] of gradient.values) {
|
|
18
|
+
expect(typeof offset).toBe('number');
|
|
19
|
+
expect(offset).toBeGreaterThanOrEqual(0);
|
|
20
|
+
expect(offset).toBeLessThanOrEqual(1);
|
|
21
|
+
expect(color).toMatch(/^#[0-9a-f]{6}$/i);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('are identical across light and dark mode (theme-agnostic)', () => {
|
|
27
|
+
expect(buildTheme('teal', 'dark').gradients).toBe(
|
|
28
|
+
buildTheme('teal', 'light').gradients,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
hexToAppColorName,
|
|
6
6
|
} from '../theme/color-presets';
|
|
7
7
|
import { buildTheme } from '../theme/build-theme';
|
|
8
|
+
import { THEME_GRADIENTS } from '../theme/gradients';
|
|
8
9
|
import type { Theme, ThemeColors, ThemeMode } from '../theme/types';
|
|
9
10
|
|
|
10
11
|
describe('Theme system', () => {
|
|
@@ -111,6 +112,7 @@ describe('Theme system', () => {
|
|
|
111
112
|
isDark: false,
|
|
112
113
|
isLight: true,
|
|
113
114
|
colors: createMockColors(),
|
|
115
|
+
gradients: THEME_GRADIENTS,
|
|
114
116
|
};
|
|
115
117
|
expect(lightTheme.isDark).toBe(false);
|
|
116
118
|
expect(lightTheme.isLight).toBe(true);
|
|
@@ -120,6 +122,7 @@ describe('Theme system', () => {
|
|
|
120
122
|
isDark: true,
|
|
121
123
|
isLight: false,
|
|
122
124
|
colors: createMockColors(),
|
|
125
|
+
gradients: THEME_GRADIENTS,
|
|
123
126
|
};
|
|
124
127
|
expect(darkTheme.isDark).toBe(true);
|
|
125
128
|
expect(darkTheme.isLight).toBe(false);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-native';
|
|
2
|
+
|
|
3
|
+
import { useGutters } from '../hooks/useGutters';
|
|
4
|
+
import { space } from '../styles/tokens';
|
|
5
|
+
|
|
6
|
+
// The RN mock reports a 375px-wide window (below the `sm` 640 breakpoint), so
|
|
7
|
+
// every case resolves to the `base` tier.
|
|
8
|
+
|
|
9
|
+
describe('useGutters', () => {
|
|
10
|
+
it('expands a single size to all four edges', () => {
|
|
11
|
+
const { result } = renderHook(() => useGutters(['base']));
|
|
12
|
+
expect(result.current).toEqual({
|
|
13
|
+
paddingTop: space.lg,
|
|
14
|
+
paddingRight: space.lg,
|
|
15
|
+
paddingBottom: space.lg,
|
|
16
|
+
paddingLeft: space.lg,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('treats a two-value list as [vertical, horizontal]', () => {
|
|
21
|
+
const { result } = renderHook(() => useGutters([0, 'wide']));
|
|
22
|
+
expect(result.current).toEqual({
|
|
23
|
+
paddingTop: 0,
|
|
24
|
+
paddingRight: space.xl,
|
|
25
|
+
paddingBottom: 0,
|
|
26
|
+
paddingLeft: space.xl,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('maps a four-value list to [top, right, bottom, left]', () => {
|
|
31
|
+
const { result } = renderHook(() =>
|
|
32
|
+
useGutters(['compact', 'base', 0, 'wide']),
|
|
33
|
+
);
|
|
34
|
+
expect(result.current).toEqual({
|
|
35
|
+
paddingTop: space.sm,
|
|
36
|
+
paddingRight: space.lg,
|
|
37
|
+
paddingBottom: 0,
|
|
38
|
+
paddingLeft: space.xl,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns zero padding for a `0` gutter', () => {
|
|
43
|
+
const { result } = renderHook(() => useGutters([0]));
|
|
44
|
+
expect(result.current).toEqual({
|
|
45
|
+
paddingTop: 0,
|
|
46
|
+
paddingRight: 0,
|
|
47
|
+
paddingBottom: 0,
|
|
48
|
+
paddingLeft: 0,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('keeps a stable object identity across re-renders at the same width', () => {
|
|
53
|
+
const { result, rerender } = renderHook(() => useGutters(['base']));
|
|
54
|
+
const first = result.current;
|
|
55
|
+
rerender({});
|
|
56
|
+
expect(result.current).toBe(first);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import { act, renderHook } from '@testing-library/react-native';
|
|
4
|
+
import { ImpactFeedbackStyle, impactAsync } from 'expo-haptics';
|
|
5
|
+
|
|
6
|
+
import { BloomHapticsProvider, useHaptics } from '../hooks/useHaptics';
|
|
7
|
+
|
|
8
|
+
const originalOS = Platform.OS;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
Platform.OS = originalOS;
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('useHaptics', () => {
|
|
16
|
+
it('returns a callback', () => {
|
|
17
|
+
const { result } = renderHook(() => useHaptics());
|
|
18
|
+
expect(typeof result.current).toBe('function');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('fires the mapped impact style on iOS', () => {
|
|
22
|
+
Platform.OS = 'ios';
|
|
23
|
+
const { result } = renderHook(() => useHaptics());
|
|
24
|
+
act(() => result.current('heavy'));
|
|
25
|
+
expect(impactAsync).toHaveBeenCalledWith(ImpactFeedbackStyle.Heavy);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('defaults to the light impact', () => {
|
|
29
|
+
Platform.OS = 'ios';
|
|
30
|
+
const { result } = renderHook(() => useHaptics());
|
|
31
|
+
act(() => result.current());
|
|
32
|
+
expect(impactAsync).toHaveBeenCalledWith(ImpactFeedbackStyle.Light);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('clamps to the light impact on Android regardless of strength', () => {
|
|
36
|
+
Platform.OS = 'android';
|
|
37
|
+
const { result } = renderHook(() => useHaptics());
|
|
38
|
+
act(() => result.current('heavy'));
|
|
39
|
+
expect(impactAsync).toHaveBeenCalledWith(ImpactFeedbackStyle.Light);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('no-ops on web', () => {
|
|
43
|
+
Platform.OS = 'web';
|
|
44
|
+
const { result } = renderHook(() => useHaptics());
|
|
45
|
+
act(() => result.current('medium'));
|
|
46
|
+
expect(impactAsync).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('no-ops when disabled via BloomHapticsProvider', () => {
|
|
50
|
+
Platform.OS = 'ios';
|
|
51
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
52
|
+
<BloomHapticsProvider enabled={false}>{children}</BloomHapticsProvider>
|
|
53
|
+
);
|
|
54
|
+
const { result } = renderHook(() => useHaptics(), { wrapper });
|
|
55
|
+
act(() => result.current('medium'));
|
|
56
|
+
expect(impactAsync).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react-native';
|
|
2
|
+
|
|
3
|
+
import { useInteractionState } from '../hooks/useInteractionState';
|
|
4
|
+
|
|
5
|
+
describe('useInteractionState', () => {
|
|
6
|
+
it('starts inactive and toggles on onIn/onOut', () => {
|
|
7
|
+
const { result } = renderHook(() => useInteractionState());
|
|
8
|
+
expect(result.current.state).toBe(false);
|
|
9
|
+
|
|
10
|
+
act(() => result.current.onIn());
|
|
11
|
+
expect(result.current.state).toBe(true);
|
|
12
|
+
|
|
13
|
+
act(() => result.current.onOut());
|
|
14
|
+
expect(result.current.state).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('keeps handler identities stable across renders', () => {
|
|
18
|
+
const { result, rerender } = renderHook(() => useInteractionState());
|
|
19
|
+
const firstOnIn = result.current.onIn;
|
|
20
|
+
const firstOnOut = result.current.onOut;
|
|
21
|
+
|
|
22
|
+
rerender({});
|
|
23
|
+
|
|
24
|
+
expect(result.current.onIn).toBe(firstOnIn);
|
|
25
|
+
expect(result.current.onOut).toBe(firstOnOut);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { Pressable, Text, View } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { AnimatedCheck, type AnimatedCheckRef } from './index';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof AnimatedCheck> = {
|
|
8
|
+
title: 'Components/AnimatedCheck',
|
|
9
|
+
component: AnimatedCheck,
|
|
10
|
+
argTypes: {
|
|
11
|
+
size: { control: { type: 'range', min: 16, max: 128, step: 4 } },
|
|
12
|
+
color: { control: 'color' },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj<typeof AnimatedCheck>;
|
|
19
|
+
|
|
20
|
+
export const Playable: Story = {
|
|
21
|
+
args: { size: 64 },
|
|
22
|
+
render: (args) => {
|
|
23
|
+
function Demo() {
|
|
24
|
+
const ref = useRef<AnimatedCheckRef>(null);
|
|
25
|
+
return (
|
|
26
|
+
<View style={{ gap: 16, alignItems: 'center' }}>
|
|
27
|
+
<AnimatedCheck ref={ref} {...args} />
|
|
28
|
+
<Pressable
|
|
29
|
+
accessibilityLabel="Play"
|
|
30
|
+
onPress={() => ref.current?.play()}
|
|
31
|
+
style={{
|
|
32
|
+
paddingVertical: 8,
|
|
33
|
+
paddingHorizontal: 16,
|
|
34
|
+
borderRadius: 8,
|
|
35
|
+
backgroundColor: '#111827',
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
<Text style={{ color: 'white', fontWeight: '600' }}>Play</Text>
|
|
39
|
+
</Pressable>
|
|
40
|
+
</View>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return <Demo />;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const CustomColor: Story = {
|
|
48
|
+
args: { size: 80, color: '#6366f1' },
|
|
49
|
+
render: (args) => {
|
|
50
|
+
function Demo() {
|
|
51
|
+
const ref = useRef<AnimatedCheckRef>(null);
|
|
52
|
+
return (
|
|
53
|
+
<Pressable accessibilityLabel="Replay" onPress={() => ref.current?.play()}>
|
|
54
|
+
<AnimatedCheck ref={ref} {...args} />
|
|
55
|
+
</Pressable>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return <Demo />;
|
|
59
|
+
},
|
|
60
|
+
};
|