@ornikar/bumper 2.13.0 → 2.14.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +90 -0
  3. package/dist/definitions/index.d.ts +2 -1
  4. package/dist/definitions/index.d.ts.map +1 -1
  5. package/dist/definitions/storybook.d.ts +2 -0
  6. package/dist/definitions/storybook.d.ts.map +1 -0
  7. package/dist/definitions/system/loading/loader/Loader.d.ts +19 -0
  8. package/dist/definitions/system/loading/loader/Loader.d.ts.map +1 -0
  9. package/dist/definitions/system/loading/loader/LoaderBackgroundCircle.d.ts +5 -0
  10. package/dist/definitions/system/loading/loader/LoaderBackgroundCircle.d.ts.map +1 -0
  11. package/dist/definitions/system/loading/loader/LoaderCircleWrapper.d.ts +6 -0
  12. package/dist/definitions/system/loading/loader/LoaderCircleWrapper.d.ts.map +1 -0
  13. package/dist/definitions/system/loading/loader/LoaderForegroundCircle.d.ts +11 -0
  14. package/dist/definitions/system/loading/loader/LoaderForegroundCircle.d.ts.map +1 -0
  15. package/dist/definitions/system/loading/loader/loaderConfig.d.ts +16 -0
  16. package/dist/definitions/system/loading/loader/loaderConfig.d.ts.map +1 -0
  17. package/dist/index-metro.es.android.js +162 -14
  18. package/dist/index-metro.es.android.js.map +1 -1
  19. package/dist/index-metro.es.ios.js +162 -14
  20. package/dist/index-metro.es.ios.js.map +1 -1
  21. package/dist/index-node-22.22.cjs.js +165 -12
  22. package/dist/index-node-22.22.cjs.js.map +1 -1
  23. package/dist/index-node-22.22.cjs.web.js +165 -12
  24. package/dist/index-node-22.22.cjs.web.js.map +1 -1
  25. package/dist/index-node-22.22.es.mjs +162 -14
  26. package/dist/index-node-22.22.es.mjs.map +1 -1
  27. package/dist/index-node-22.22.es.web.mjs +162 -14
  28. package/dist/index-node-22.22.es.web.mjs.map +1 -1
  29. package/dist/index.es.js +157 -14
  30. package/dist/index.es.js.map +1 -1
  31. package/dist/index.es.web.js +157 -14
  32. package/dist/index.es.web.js.map +1 -1
  33. package/dist/storybook-metro.es.android.js +402 -0
  34. package/dist/storybook-metro.es.android.js.map +1 -0
  35. package/dist/storybook-metro.es.ios.js +402 -0
  36. package/dist/storybook-metro.es.ios.js.map +1 -0
  37. package/dist/storybook-node-22.22.cjs.js +406 -0
  38. package/dist/storybook-node-22.22.cjs.js.map +1 -0
  39. package/dist/storybook-node-22.22.cjs.web.js +406 -0
  40. package/dist/storybook-node-22.22.cjs.web.js.map +1 -0
  41. package/dist/storybook-node-22.22.es.mjs +402 -0
  42. package/dist/storybook-node-22.22.es.mjs.map +1 -0
  43. package/dist/storybook-node-22.22.es.web.mjs +402 -0
  44. package/dist/storybook-node-22.22.es.web.mjs.map +1 -0
  45. package/dist/storybook.es.js +399 -0
  46. package/dist/storybook.es.js.map +1 -0
  47. package/dist/storybook.es.web.js +399 -0
  48. package/dist/storybook.es.web.js.map +1 -0
  49. package/dist/tsbuildinfo +1 -1
  50. package/package.json +42 -5
  51. package/src/index.ts +4 -1
  52. package/src/storybook.ts +1 -0
  53. package/src/system/loading/loader/Loader.features.stories.tsx +45 -0
  54. package/src/system/loading/loader/Loader.stories.tsx +28 -0
  55. package/src/system/loading/loader/Loader.tsx +65 -0
  56. package/src/system/loading/loader/LoaderBackgroundCircle.tsx +15 -0
  57. package/src/system/loading/loader/LoaderCircleWrapper.tsx +19 -0
  58. package/src/system/loading/loader/LoaderForegroundCircle.tsx +39 -0
  59. package/src/system/loading/loader/__snapshots__/Loader.features.stories.tsx.snap +703 -0
  60. package/src/system/loading/loader/__snapshots__/Loader.stories.tsx.snap +159 -0
  61. package/src/system/loading/loader/__snapshots_web__/Loader.features.stories.tsx.snap +225 -0
  62. package/src/system/loading/loader/__snapshots_web__/Loader.stories.tsx.snap +60 -0
  63. package/src/system/loading/loader/loaderConfig.ts +36 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornikar/bumper",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "directory": "@ornikar/bumper",
@@ -20,7 +20,8 @@
20
20
  },
21
21
  "ornikar": {
22
22
  "entries": [
23
- "index"
23
+ "index",
24
+ "storybook"
24
25
  ],
25
26
  "extraEntries": [
26
27
  "./src/tamagui.config.ts"
@@ -37,7 +38,9 @@
37
38
  "@storybook/preview-api": ">=8.6.15",
38
39
  "react": "^18.3.1",
39
40
  "react-dom": "^18.3.1",
40
- "react-native": ">=0.76.9"
41
+ "react-native": ">=0.76.9",
42
+ "react-native-reanimated": "^3.18.0",
43
+ "react-native-svg": "^15.8.0"
41
44
  },
42
45
  "peerDependenciesMeta": {
43
46
  "@storybook/preview-api": {
@@ -48,6 +51,12 @@
48
51
  },
49
52
  "react-native": {
50
53
  "optional": true
54
+ },
55
+ "react-native-reanimated": {
56
+ "optional": true
57
+ },
58
+ "react-native-svg": {
59
+ "optional": true
51
60
  }
52
61
  },
53
62
  "devDependencies": {
@@ -61,10 +70,18 @@
61
70
  "react": "18.3.1",
62
71
  "react-dom": "18.3.1",
63
72
  "react-native": "0.76.9",
73
+ "react-native-reanimated": "3.18.0",
74
+ "react-native-svg": "15.8.0",
64
75
  "react-test-renderer": "18.3.1",
65
76
  "storybook": "8.6.15"
66
77
  },
67
- "expo": {},
78
+ "expo": {
79
+ "install": {
80
+ "exclude": [
81
+ "react-native-reanimated"
82
+ ]
83
+ }
84
+ },
68
85
  "exports": {
69
86
  ".": {
70
87
  "types": "./dist/definitions/index.d.ts",
@@ -83,10 +100,30 @@
83
100
  "import": "./dist/index.es.web.js"
84
101
  }
85
102
  },
103
+ "./storybook": {
104
+ "types": "./dist/definitions/storybook.d.ts",
105
+ "node": {
106
+ "web": {
107
+ "require": "./dist/storybook-node-22.22.cjs.web.js",
108
+ "import": "./dist/storybook-node-22.22.es.web.mjs"
109
+ },
110
+ "import": "./dist/storybook-node-22.22.es.mjs",
111
+ "require": "./dist/storybook-node-22.22.cjs.js"
112
+ },
113
+ "react-native": {
114
+ "jest": "./dist/storybook-node-22.22.cjs.js"
115
+ },
116
+ "browser": {
117
+ "import": "./dist/storybook.es.web.js"
118
+ }
119
+ },
86
120
  "./package.json": "./package.json",
87
121
  "./src/tamagui.config.ts": "./src/tamagui.config.ts"
88
122
  },
89
- "browser": "./dist/index.es",
123
+ "browser": {
124
+ ".": "./dist/index.es",
125
+ "./storybook": "./dist/storybook.es"
126
+ },
90
127
  "main": "./dist/index-node-22.22.es.mjs",
91
128
  "react-native": "./dist/index-metro.es",
92
129
  "types": "./dist/definitions/index.d.ts"
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { BumperDecorator } from './shared/storybook/BumperDecorator';
2
1
  export { BumperProvider } from './system/core/provider/BumperProvider';
3
2
 
4
3
  // Primitives
@@ -33,3 +32,7 @@ export type { SwitchBreakpointsProps } from './system/core/breakpoints/SwitchBre
33
32
  export { SwitchBreakpoints } from './system/core/breakpoints/SwitchBreakpoins';
34
33
  export type { ValueForBreakpoint } from './system/core/breakpoints/utils/breakpointsUtils';
35
34
  export { getValueForBreakpoint } from './system/core/breakpoints/utils/breakpointsUtils';
35
+
36
+ // Loader
37
+ export type { LoaderProps } from './system/loading/loader/Loader';
38
+ export { Loader } from './system/loading/loader/Loader';
@@ -0,0 +1 @@
1
+ export { BumperDecorator } from './shared/storybook/BumperDecorator';
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Typography } from '../../content/typography';
3
+ import { HStack, VStack } from '../../core/primitives/Stack';
4
+ import { View } from '../../core/primitives/View';
5
+ import { Loader } from './Loader';
6
+
7
+ const meta: Meta<typeof Loader> = {
8
+ title: 'Bumper/Loading/Loader/Features',
9
+ component: Loader,
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Sizes: Story = {
16
+ render: () => (
17
+ <HStack gap="$space.16" alignItems="center">
18
+ <VStack alignItems="center" gap="$space.8">
19
+ <Loader size="icon" />
20
+ <Typography.Text variant="body-xs" color="$content.base.mid">
21
+ icon (20)
22
+ </Typography.Text>
23
+ </VStack>
24
+ <VStack alignItems="center" gap="$space.8">
25
+ <Loader size="page" />
26
+ <Typography.Text variant="body-xs" color="$content.base.mid">
27
+ page (48)
28
+ </Typography.Text>
29
+ </VStack>
30
+ </HStack>
31
+ ),
32
+ };
33
+
34
+ export const OnContrasted: Story = {
35
+ render: () => (
36
+ <HStack gap="$space.16" alignItems="center">
37
+ <View backgroundColor="$bg.base.hi.default" padding="$space.16" borderRadius="$radius.m">
38
+ <Loader isOnContrasted size="icon" />
39
+ </View>
40
+ <View backgroundColor="$bg.base.hi.default" padding="$space.16" borderRadius="$radius.m">
41
+ <Loader isOnContrasted size="page" />
42
+ </View>
43
+ </HStack>
44
+ ),
45
+ };
@@ -0,0 +1,28 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Loader } from './Loader';
3
+
4
+ const meta: Meta<typeof Loader> = {
5
+ title: 'Bumper/Loading/Loader',
6
+ component: Loader,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ size: {
10
+ control: 'select',
11
+ options: ['icon', 'page'],
12
+ description: 'Loader size (icon: 20px, page: 48px)',
13
+ },
14
+ isOnContrasted: {
15
+ control: 'boolean',
16
+ description: 'Use contrasted foreground color',
17
+ },
18
+ },
19
+ };
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ args: {
26
+ size: 'page',
27
+ },
28
+ };
@@ -0,0 +1,65 @@
1
+ import { styled, useStyle } from '@tamagui/core';
2
+ import type { ReactNode } from 'react';
3
+ import { useEffect } from 'react';
4
+ import { useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
5
+ import { View } from '../../core/primitives/View';
6
+ import type { TamaguiMediaProps } from '../../types';
7
+ import { LoaderBackgroundCircle } from './LoaderBackgroundCircle';
8
+ import { LoaderCircleWrapper } from './LoaderCircleWrapper';
9
+ import { LoaderForegroundCircle } from './LoaderForegroundCircle';
10
+ import type { LoaderSize } from './loaderConfig';
11
+ import { LOADER_ANIMATION, LOADER_SIZE_CONFIG } from './loaderConfig';
12
+
13
+ const { icon, page } = LOADER_SIZE_CONFIG;
14
+
15
+ const LoaderContainer = styled(View, {
16
+ name: 'Loader',
17
+ position: 'relative',
18
+ rotate: '-90deg',
19
+ variants: {
20
+ size: {
21
+ icon: { width: icon.size, height: icon.size },
22
+ page: { width: page.size, height: page.size },
23
+ },
24
+ } as const,
25
+ });
26
+
27
+ interface InternalLoaderProps {
28
+ /** The size of the loader. `'icon'` renders a small 20px loader, `'page'` renders a larger 48px loader.
29
+ * @default 'page'
30
+ * */
31
+ size?: LoaderSize;
32
+ /** Whether the loader is displayed on a contrasted (dark) background. When `true`, uses a high-contrast foreground color.
33
+ * @default false
34
+ * */
35
+ isOnContrasted?: boolean;
36
+ /** Test ID passed to the root container for testing purposes. */
37
+ testID?: string;
38
+ }
39
+
40
+ export type LoaderProps = TamaguiMediaProps<InternalLoaderProps>;
41
+
42
+ export function Loader({ size = 'page', isOnContrasted = false, testID }: LoaderProps): ReactNode {
43
+ const backgroundStyle = useStyle({ color: '$border.base.mid' });
44
+ const foregroundStyle = useStyle({
45
+ color: isOnContrasted ? '$border.base.onContrasted.hi' : '$content.accent',
46
+ });
47
+
48
+ const progress = useSharedValue(0);
49
+
50
+ useEffect(() => {
51
+ progress.value = withRepeat(withTiming(1, LOADER_ANIMATION), -1, false);
52
+ }, [progress]);
53
+
54
+ return (
55
+ <LoaderContainer size={size} testID={testID}>
56
+ <LoaderCircleWrapper size={size}>
57
+ <LoaderBackgroundCircle size={size} stroke={backgroundStyle.color} />
58
+ </LoaderCircleWrapper>
59
+
60
+ <LoaderCircleWrapper isForeground size={size}>
61
+ <LoaderForegroundCircle color={String(foregroundStyle.color)} size={size} progress={progress} />
62
+ </LoaderCircleWrapper>
63
+ </LoaderContainer>
64
+ );
65
+ }
@@ -0,0 +1,15 @@
1
+ import { styled } from '@tamagui/core';
2
+ import { Circle } from 'react-native-svg';
3
+ import { LOADER_SIZE_CONFIG } from './loaderConfig';
4
+
5
+ const { icon, page } = LOADER_SIZE_CONFIG;
6
+
7
+ export const LoaderBackgroundCircle = styled(Circle, {
8
+ fill: 'none',
9
+ variants: {
10
+ size: {
11
+ icon: { strokeWidth: icon.strokeWidth, cx: icon.center, cy: icon.center, r: icon.radius },
12
+ page: { strokeWidth: page.strokeWidth, cx: page.center, cy: page.center, r: page.radius },
13
+ },
14
+ } as const,
15
+ });
@@ -0,0 +1,19 @@
1
+ import { styled } from '@tamagui/core';
2
+ import Svg from 'react-native-svg';
3
+ import { LOADER_SIZE_CONFIG } from './loaderConfig';
4
+
5
+ const { icon, page } = LOADER_SIZE_CONFIG;
6
+
7
+ export const LoaderCircleWrapper = styled(Svg, {
8
+ variants: {
9
+ size: {
10
+ icon: { width: icon.size, height: icon.size, viewBox: icon.viewBox },
11
+ page: { width: page.size, height: page.size, viewBox: page.viewBox },
12
+ },
13
+ isForeground: {
14
+ true: {
15
+ position: 'absolute',
16
+ },
17
+ },
18
+ } as const,
19
+ });
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+ import Animated, { useAnimatedProps } from 'react-native-reanimated';
4
+ import { Circle } from 'react-native-svg';
5
+ import type { LoaderSize } from './loaderConfig';
6
+ import { LOADER_SIZE_CONFIG } from './loaderConfig';
7
+
8
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
9
+
10
+ interface LoaderForegroundCircleProps {
11
+ color: string;
12
+ size: LoaderSize;
13
+ progress: SharedValue<number>;
14
+ }
15
+
16
+ export function LoaderForegroundCircle({ color, size, progress }: LoaderForegroundCircleProps): ReactNode {
17
+ const { center, radius, circumference, strokeWidth } = LOADER_SIZE_CONFIG[size];
18
+
19
+ const animatedProps = useAnimatedProps(
20
+ () => ({
21
+ strokeDashoffset: circumference - 2 * circumference * progress.value,
22
+ }),
23
+ [circumference, progress],
24
+ );
25
+
26
+ return (
27
+ <AnimatedCircle
28
+ cx={center}
29
+ cy={center}
30
+ r={radius}
31
+ stroke={color}
32
+ strokeWidth={strokeWidth}
33
+ strokeLinecap="round"
34
+ fill="none"
35
+ strokeDasharray={circumference}
36
+ animatedProps={animatedProps}
37
+ />
38
+ );
39
+ }