@ledgerhq/lumen-ui-rnative 0.1.25 → 0.1.26
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/dist/module/index.js +1 -0
- package/dist/module/index.js.map +1 -1
- package/dist/module/lib/Components/Avatar/Avatar.js +22 -23
- package/dist/module/lib/Components/Avatar/Avatar.js.map +1 -1
- package/dist/module/lib/Components/Avatar/Avatar.test.js +17 -23
- package/dist/module/lib/Components/Avatar/Avatar.test.js.map +1 -1
- package/dist/module/lib/Components/DotCount/DotCount.js +128 -0
- package/dist/module/lib/Components/DotCount/DotCount.js.map +1 -0
- package/dist/module/lib/Components/DotCount/DotCount.mdx +86 -0
- package/dist/module/lib/Components/DotCount/DotCount.stories.js +172 -0
- package/dist/module/lib/Components/DotCount/DotCount.stories.js.map +1 -0
- package/dist/module/lib/Components/DotCount/DotCount.test.js +174 -0
- package/dist/module/lib/Components/DotCount/DotCount.test.js.map +1 -0
- package/dist/module/lib/Components/DotCount/index.js +5 -0
- package/dist/module/lib/Components/DotCount/index.js.map +1 -0
- package/dist/module/lib/Components/DotCount/types.js +4 -0
- package/dist/module/lib/Components/DotCount/types.js.map +1 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.js +89 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.js.map +1 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.mdx +82 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js +84 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js.map +1 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.test.js +136 -0
- package/dist/module/lib/Components/DotIndicator/DotIndicator.test.js.map +1 -0
- package/dist/module/lib/Components/DotIndicator/index.js +5 -0
- package/dist/module/lib/Components/DotIndicator/index.js.map +1 -0
- package/dist/module/lib/Components/DotIndicator/types.js +4 -0
- package/dist/module/lib/Components/DotIndicator/types.js.map +1 -0
- package/dist/module/lib/Components/TabBar/TabBar.js +16 -13
- package/dist/module/lib/Components/TabBar/TabBar.js.map +1 -1
- package/dist/module/lib/Components/index.js +2 -0
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/typescript/src/index.d.ts +1 -0
- package/dist/typescript/src/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Avatar/Avatar.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Avatar/Avatar.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/DotCount/DotCount.d.ts +3 -0
- package/dist/typescript/src/lib/Components/DotCount/DotCount.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotCount/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/DotCount/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotCount/types.d.ts +40 -0
- package/dist/typescript/src/lib/Components/DotCount/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIndicator/DotIndicator.d.ts +3 -0
- package/dist/typescript/src/lib/Components/DotIndicator/DotIndicator.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIndicator/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/DotIndicator/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIndicator/types.d.ts +25 -0
- package/dist/typescript/src/lib/Components/DotIndicator/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/TabBar/TabBar.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/index.d.ts +2 -0
- package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/lib/Components/Avatar/Avatar.test.tsx +17 -27
- package/src/lib/Components/Avatar/Avatar.tsx +24 -21
- package/src/lib/Components/DotCount/DotCount.mdx +86 -0
- package/src/lib/Components/DotCount/DotCount.stories.tsx +124 -0
- package/src/lib/Components/DotCount/DotCount.test.tsx +150 -0
- package/src/lib/Components/DotCount/DotCount.tsx +130 -0
- package/src/lib/Components/DotCount/index.ts +2 -0
- package/src/lib/Components/DotCount/types.ts +40 -0
- package/src/lib/Components/DotIndicator/DotIndicator.mdx +82 -0
- package/src/lib/Components/DotIndicator/DotIndicator.stories.tsx +67 -0
- package/src/lib/Components/DotIndicator/DotIndicator.test.tsx +132 -0
- package/src/lib/Components/DotIndicator/DotIndicator.tsx +97 -0
- package/src/lib/Components/DotIndicator/index.ts +2 -0
- package/src/lib/Components/DotIndicator/types.ts +25 -0
- package/src/lib/Components/TabBar/TabBar.tsx +17 -16
- package/src/lib/Components/index.ts +2 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyledViewProps } from '../../../styles';
|
|
3
|
+
|
|
4
|
+
export type DotCountProps = {
|
|
5
|
+
/**
|
|
6
|
+
* The size of the dot count.
|
|
7
|
+
* @default md
|
|
8
|
+
*/
|
|
9
|
+
size?: 'lg' | 'md';
|
|
10
|
+
/**
|
|
11
|
+
* The amount to be displayed.
|
|
12
|
+
*
|
|
13
|
+
* If higher than `max`, the displayed value will be "[max]+".
|
|
14
|
+
*/
|
|
15
|
+
value: number;
|
|
16
|
+
/**
|
|
17
|
+
* The max value shown.
|
|
18
|
+
*
|
|
19
|
+
* If `value` is higher than `max`, the displayed value will be "[max]+".
|
|
20
|
+
*
|
|
21
|
+
* By design, it will ignore values higher than 99.
|
|
22
|
+
* @default 99
|
|
23
|
+
*/
|
|
24
|
+
max?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The appearance of the dot count.
|
|
27
|
+
* @default base
|
|
28
|
+
*/
|
|
29
|
+
appearance?: 'base' | 'red';
|
|
30
|
+
/**
|
|
31
|
+
* Whether the dot count should show a disabled appearance.
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Can be used as a wrapper to any component in case you wish to overlay a dot count on top of it.
|
|
37
|
+
* If provided, it'll pin the dot count to the top-right of the child component passed.
|
|
38
|
+
*/
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
} & Omit<StyledViewProps, 'children'>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as DotIndicatorStories from './DotIndicator.stories';
|
|
3
|
+
import { CustomTabs, Tab } from '../../../../.storybook/components';
|
|
4
|
+
|
|
5
|
+
<Meta title='Communication/DotIndicator' of={DotIndicatorStories} />
|
|
6
|
+
|
|
7
|
+
# DotIndicator
|
|
8
|
+
|
|
9
|
+
<CustomTabs>
|
|
10
|
+
<Tab label="Overview">
|
|
11
|
+
|
|
12
|
+
## Introduction
|
|
13
|
+
|
|
14
|
+
DotIndicator is a small colored dot used to signal status or draw attention without displaying a number. It supports two appearances (`base` and `red`) and can be pinned to the top-right of a child element.
|
|
15
|
+
|
|
16
|
+
> View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=16470-25597).
|
|
17
|
+
|
|
18
|
+
## Properties
|
|
19
|
+
|
|
20
|
+
<Canvas of={DotIndicatorStories.Base} />
|
|
21
|
+
<Controls of={DotIndicatorStories.Base} />
|
|
22
|
+
|
|
23
|
+
## Sizes
|
|
24
|
+
|
|
25
|
+
DotIndicator comes in three sizes:
|
|
26
|
+
|
|
27
|
+
- **xs** - compact dot for tight layouts.
|
|
28
|
+
- **sm** (default) - standard dot for most use cases.
|
|
29
|
+
- **md** - larger dot for prominent indicators.
|
|
30
|
+
|
|
31
|
+
<Canvas of={DotIndicatorStories.SizeShowcase} />
|
|
32
|
+
|
|
33
|
+
## Appearances
|
|
34
|
+
|
|
35
|
+
Two color schemes are available:
|
|
36
|
+
|
|
37
|
+
- **base** (default) - neutral dot for general-purpose indicators.
|
|
38
|
+
- **red** - high-visibility dot, typically used for unread or alert states.
|
|
39
|
+
|
|
40
|
+
<Canvas of={DotIndicatorStories.AppearanceShowcase} />
|
|
41
|
+
|
|
42
|
+
## With Children
|
|
43
|
+
|
|
44
|
+
When wrapping a child element, the dot pins itself to the top-right corner.
|
|
45
|
+
|
|
46
|
+
<Canvas of={DotIndicatorStories.WithChildren} />
|
|
47
|
+
|
|
48
|
+
</Tab>
|
|
49
|
+
<Tab label="Implementation">
|
|
50
|
+
|
|
51
|
+
## Setup
|
|
52
|
+
|
|
53
|
+
Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
|
|
54
|
+
|
|
55
|
+
## Basic Usage
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { DotIndicator } from '@ledgerhq/lumen-ui-rnative';
|
|
59
|
+
|
|
60
|
+
function MyComponent() {
|
|
61
|
+
return <DotIndicator appearance="red" />;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Pinned to a child
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<DotIndicator appearance="red">
|
|
69
|
+
<Button size="sm">Submit</Button>
|
|
70
|
+
</DotIndicator>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Accessibility
|
|
74
|
+
|
|
75
|
+
Pass `accessibilityLabel` to provide screen reader context:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<DotIndicator accessibilityLabel="New notifications" />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
</Tab>
|
|
82
|
+
</CustomTabs>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
|
+
import { Button } from '../Button/Button';
|
|
3
|
+
import { Box } from '../Utility/Box';
|
|
4
|
+
import { DotIndicator } from './DotIndicator';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
component: DotIndicator,
|
|
8
|
+
title: 'Communication/DotIndicator',
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
source: {
|
|
12
|
+
language: 'tsx',
|
|
13
|
+
format: true,
|
|
14
|
+
type: 'code',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
size: {
|
|
20
|
+
control: 'radio',
|
|
21
|
+
options: ['xs', 'sm', 'md'],
|
|
22
|
+
},
|
|
23
|
+
appearance: {
|
|
24
|
+
control: 'radio',
|
|
25
|
+
options: ['base', 'red'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
} satisfies Meta<typeof DotIndicator>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Base: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
appearance: 'base',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const SizeShowcase: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's12' }}>
|
|
42
|
+
<DotIndicator size='xs' />
|
|
43
|
+
<DotIndicator size='sm' />
|
|
44
|
+
<DotIndicator size='md' />
|
|
45
|
+
</Box>
|
|
46
|
+
),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const AppearanceShowcase: Story = {
|
|
50
|
+
render: () => (
|
|
51
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's12' }}>
|
|
52
|
+
<DotIndicator appearance='base' />
|
|
53
|
+
<DotIndicator appearance='red' />
|
|
54
|
+
<DotIndicator disabled />
|
|
55
|
+
</Box>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const WithChildren: Story = {
|
|
60
|
+
render: () => (
|
|
61
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's12' }}>
|
|
62
|
+
<DotIndicator appearance='red'>
|
|
63
|
+
<Button size='sm'>Submit</Button>
|
|
64
|
+
</DotIndicator>
|
|
65
|
+
</Box>
|
|
66
|
+
),
|
|
67
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import { render } from '@testing-library/react-native';
|
|
4
|
+
import { createRef } from 'react';
|
|
5
|
+
import { Text, type View } from 'react-native';
|
|
6
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
7
|
+
import { DotIndicator } from './DotIndicator';
|
|
8
|
+
|
|
9
|
+
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
|
10
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
11
|
+
{children}
|
|
12
|
+
</ThemeProvider>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
describe('DotIndicator', () => {
|
|
16
|
+
it('should render without crashing', () => {
|
|
17
|
+
const { toJSON } = render(
|
|
18
|
+
<TestWrapper>
|
|
19
|
+
<DotIndicator />
|
|
20
|
+
</TestWrapper>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(toJSON()).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should render children', () => {
|
|
27
|
+
const { getByText } = render(
|
|
28
|
+
<TestWrapper>
|
|
29
|
+
<DotIndicator>
|
|
30
|
+
<Text>Child</Text>
|
|
31
|
+
</DotIndicator>
|
|
32
|
+
</TestWrapper>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(getByText('Child')).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should apply accessibility label', () => {
|
|
39
|
+
const { getByLabelText } = render(
|
|
40
|
+
<TestWrapper>
|
|
41
|
+
<DotIndicator accessibilityLabel='New notifications' />
|
|
42
|
+
</TestWrapper>,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(getByLabelText('New notifications')).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should forward testID to the outer wrapper', () => {
|
|
49
|
+
const { getByTestId } = render(
|
|
50
|
+
<TestWrapper>
|
|
51
|
+
<DotIndicator testID='dot-indicator' />
|
|
52
|
+
</TestWrapper>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(getByTestId('dot-indicator')).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should forward pointerEvents to the outer wrapper', () => {
|
|
59
|
+
const { getByTestId } = render(
|
|
60
|
+
<TestWrapper>
|
|
61
|
+
<DotIndicator testID='dot-indicator' pointerEvents='none' />
|
|
62
|
+
</TestWrapper>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(getByTestId('dot-indicator').props.pointerEvents).toBe('none');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should forward ref to the outer wrapper', () => {
|
|
69
|
+
const ref = createRef<View>();
|
|
70
|
+
|
|
71
|
+
render(
|
|
72
|
+
<TestWrapper>
|
|
73
|
+
<DotIndicator ref={ref} />
|
|
74
|
+
</TestWrapper>,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(ref.current).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should render in xs size', () => {
|
|
81
|
+
const { toJSON } = render(
|
|
82
|
+
<TestWrapper>
|
|
83
|
+
<DotIndicator size='xs' />
|
|
84
|
+
</TestWrapper>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(toJSON()).toBeTruthy();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should render in md size', () => {
|
|
91
|
+
const { toJSON } = render(
|
|
92
|
+
<TestWrapper>
|
|
93
|
+
<DotIndicator size='md' />
|
|
94
|
+
</TestWrapper>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(toJSON()).toBeTruthy();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should render with red appearance', () => {
|
|
101
|
+
const { toJSON } = render(
|
|
102
|
+
<TestWrapper>
|
|
103
|
+
<DotIndicator appearance='red' />
|
|
104
|
+
</TestWrapper>,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(toJSON()).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should render with disabled state', () => {
|
|
111
|
+
const { toJSON } = render(
|
|
112
|
+
<TestWrapper>
|
|
113
|
+
<DotIndicator disabled />
|
|
114
|
+
</TestWrapper>,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(toJSON()).toBeTruthy();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should render pinned overlay when children provided', () => {
|
|
121
|
+
const { getByTestId, getByText } = render(
|
|
122
|
+
<TestWrapper>
|
|
123
|
+
<DotIndicator testID='dot-indicator'>
|
|
124
|
+
<Text>Content</Text>
|
|
125
|
+
</DotIndicator>
|
|
126
|
+
</TestWrapper>,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(getByTestId('dot-indicator')).toBeTruthy();
|
|
130
|
+
expect(getByText('Content')).toBeTruthy();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import { useStyleSheet } from '../../../styles';
|
|
4
|
+
import { Box } from '../Utility';
|
|
5
|
+
import type { DotIndicatorProps } from './types';
|
|
6
|
+
|
|
7
|
+
export function DotIndicator({
|
|
8
|
+
size = 'sm',
|
|
9
|
+
appearance = 'base',
|
|
10
|
+
disabled: disabledProp = false,
|
|
11
|
+
lx = {},
|
|
12
|
+
style,
|
|
13
|
+
children,
|
|
14
|
+
accessibilityLabel,
|
|
15
|
+
ref,
|
|
16
|
+
...props
|
|
17
|
+
}: DotIndicatorProps) {
|
|
18
|
+
const disabled = useDisabledContext({
|
|
19
|
+
consumerName: 'DotIndicator',
|
|
20
|
+
mergeWith: { disabled: disabledProp },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const styles = useStyles({
|
|
24
|
+
size,
|
|
25
|
+
appearance,
|
|
26
|
+
disabled,
|
|
27
|
+
pinned: !!children,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
ref={ref}
|
|
33
|
+
lx={lx}
|
|
34
|
+
style={StyleSheet.flatten([
|
|
35
|
+
children ? { position: 'relative' } : undefined,
|
|
36
|
+
style,
|
|
37
|
+
])}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
<Box
|
|
41
|
+
style={styles.container}
|
|
42
|
+
accessibilityRole='image'
|
|
43
|
+
accessibilityLabel={accessibilityLabel}
|
|
44
|
+
accessible={!!accessibilityLabel}
|
|
45
|
+
pointerEvents='none'
|
|
46
|
+
/>
|
|
47
|
+
{children}
|
|
48
|
+
</Box>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const useStyles = ({
|
|
53
|
+
size,
|
|
54
|
+
appearance,
|
|
55
|
+
disabled,
|
|
56
|
+
pinned,
|
|
57
|
+
}: {
|
|
58
|
+
size: NonNullable<DotIndicatorProps['size']>;
|
|
59
|
+
appearance: NonNullable<DotIndicatorProps['appearance']>;
|
|
60
|
+
disabled: boolean;
|
|
61
|
+
pinned: boolean;
|
|
62
|
+
}) => {
|
|
63
|
+
return useStyleSheet(
|
|
64
|
+
(t) => {
|
|
65
|
+
const sizeMap = {
|
|
66
|
+
xs: t.sizes.s10,
|
|
67
|
+
sm: t.sizes.s12,
|
|
68
|
+
md: t.sizes.s16,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const bgColorMap = {
|
|
72
|
+
base: { backgroundColor: t.colors.bg.interactive },
|
|
73
|
+
red: { backgroundColor: t.colors.bg.errorStrong },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
container: {
|
|
78
|
+
height: sizeMap[size],
|
|
79
|
+
aspectRatio: 1,
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
borderRadius: t.borderRadius.full,
|
|
83
|
+
...(pinned && {
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
top: t.spacings.s0,
|
|
86
|
+
right: t.spacings.s0,
|
|
87
|
+
zIndex: 1,
|
|
88
|
+
}),
|
|
89
|
+
...(disabled
|
|
90
|
+
? { backgroundColor: t.colors.bg.disabled }
|
|
91
|
+
: { ...bgColorMap[appearance] }),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
[size, appearance, disabled, pinned],
|
|
96
|
+
);
|
|
97
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyledViewProps } from '../../../styles';
|
|
3
|
+
|
|
4
|
+
export type DotIndicatorProps = {
|
|
5
|
+
/**
|
|
6
|
+
* The size of the dot indicator.
|
|
7
|
+
* @default sm
|
|
8
|
+
*/
|
|
9
|
+
size?: 'xs' | 'sm' | 'md';
|
|
10
|
+
/**
|
|
11
|
+
* The appearance of the dot indicator.
|
|
12
|
+
* @default base
|
|
13
|
+
*/
|
|
14
|
+
appearance?: 'base' | 'red';
|
|
15
|
+
/**
|
|
16
|
+
* Whether the dot indicator should show a disabled appearance.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Can be used as a wrapper to any component in case you wish to overlay a dot indicator on top of it.
|
|
22
|
+
* If provided, it'll pin the dot indicator to the top-right of the child component passed.
|
|
23
|
+
*/
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
} & Omit<StyledViewProps, 'children'>;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { BlurView } from '@sbaiahmed1/react-native-blur';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
Children,
|
|
5
|
-
isValidElement,
|
|
6
|
-
useCallback,
|
|
7
|
-
useEffect,
|
|
8
|
-
useRef,
|
|
9
|
-
} from 'react';
|
|
3
|
+
import { Children, isValidElement, useEffect, useMemo, useRef } from 'react';
|
|
10
4
|
import type { LayoutChangeEvent } from 'react-native';
|
|
11
5
|
import { Platform, StyleSheet, Text, View } from 'react-native';
|
|
12
6
|
import Animated, {
|
|
@@ -19,6 +13,7 @@ import Animated, {
|
|
|
19
13
|
} from 'react-native-reanimated';
|
|
20
14
|
import { useStyleSheet, useTheme } from '../../../styles';
|
|
21
15
|
import { useTimingConfig } from '../../Animations/useTimingConfig';
|
|
16
|
+
import { triggerHapticFeedback } from '../../Haptics';
|
|
22
17
|
import { Placeholder } from '../../Symbols';
|
|
23
18
|
import { Box, Pressable } from '../Utility';
|
|
24
19
|
import { TabBarContextProvider, useTabBarContext } from './TabBarContext';
|
|
@@ -51,14 +46,16 @@ const useTabBarItemAnimations = ({ isActive }: { isActive: boolean }) => {
|
|
|
51
46
|
50,
|
|
52
47
|
withTiming(isActive ? 1 : 0, activeTimingConfig),
|
|
53
48
|
);
|
|
54
|
-
return () => cancelAnimation(activeProgress);
|
|
55
49
|
}, [isActive, activeProgress, activeTimingConfig]);
|
|
56
50
|
|
|
51
|
+
useEffect(() => () => cancelAnimation(activeProgress), [activeProgress]);
|
|
52
|
+
|
|
57
53
|
const onPressIn = () => {
|
|
58
54
|
pressProgress.value = withTiming(0.9, pressInTimingConfig);
|
|
59
55
|
};
|
|
60
56
|
|
|
61
57
|
const onPressOut = () => {
|
|
58
|
+
triggerHapticFeedback('light');
|
|
62
59
|
pressProgress.value = withSequence(
|
|
63
60
|
withTiming(0.95, { duration: 0 }),
|
|
64
61
|
withTiming(1, pressOutTimingConfig),
|
|
@@ -112,7 +109,7 @@ const useTabBarPillLayout = ({
|
|
|
112
109
|
easing: 'easeInOut',
|
|
113
110
|
});
|
|
114
111
|
|
|
115
|
-
const
|
|
112
|
+
const activeIndex = useMemo(() => {
|
|
116
113
|
return Children.toArray(children).findIndex((child) => {
|
|
117
114
|
if (isValidElement<TabBarItemProps>(child)) {
|
|
118
115
|
return child.props.value === active;
|
|
@@ -121,6 +118,9 @@ const useTabBarPillLayout = ({
|
|
|
121
118
|
});
|
|
122
119
|
}, [active, children]);
|
|
123
120
|
|
|
121
|
+
const activeIndexRef = useRef(activeIndex);
|
|
122
|
+
activeIndexRef.current = activeIndex;
|
|
123
|
+
|
|
124
124
|
const onLayout = (e: LayoutChangeEvent): void => {
|
|
125
125
|
const { width, height } = e.nativeEvent.layout;
|
|
126
126
|
const count = Children.count(children);
|
|
@@ -131,7 +131,7 @@ const useTabBarPillLayout = ({
|
|
|
131
131
|
|
|
132
132
|
if (!hasLayoutRef.current) {
|
|
133
133
|
hasLayoutRef.current = true;
|
|
134
|
-
const index =
|
|
134
|
+
const index = activeIndexRef.current;
|
|
135
135
|
if (index >= 0) {
|
|
136
136
|
pillProgress.value = index * slotWidth;
|
|
137
137
|
}
|
|
@@ -140,14 +140,15 @@ const useTabBarPillLayout = ({
|
|
|
140
140
|
|
|
141
141
|
useEffect(() => {
|
|
142
142
|
if (!hasLayoutRef.current) return;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
if (activeIndex >= 0 && itemWidth.value > 0) {
|
|
144
|
+
pillProgress.value = withTiming(
|
|
145
|
+
activeIndex * itemWidth.value,
|
|
146
|
+
timingConfig,
|
|
147
|
+
);
|
|
147
148
|
}
|
|
149
|
+
}, [activeIndex, itemWidth, pillProgress, timingConfig]);
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
}, [itemWidth, pillProgress, getActiveIndex, timingConfig]);
|
|
151
|
+
useEffect(() => () => cancelAnimation(pillProgress), [pillProgress]);
|
|
151
152
|
|
|
152
153
|
const animatedPillStyle = useAnimatedStyle(
|
|
153
154
|
() => ({
|
|
@@ -4,6 +4,8 @@ export * from './AmountInput';
|
|
|
4
4
|
export * from './Avatar';
|
|
5
5
|
export * from './Banner';
|
|
6
6
|
export * from './BottomSheet';
|
|
7
|
+
export * from './DotCount';
|
|
8
|
+
export * from './DotIndicator';
|
|
7
9
|
export * from './Button';
|
|
8
10
|
export * from './Card';
|
|
9
11
|
export * from './CardButton';
|