@ledgerhq/lumen-ui-rnative 0.1.24 → 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/DotIcon/DotIcon.js +134 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.mdx +56 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.stories.js +217 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.stories.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.test.js +238 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.test.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/index.js +5 -0
- package/dist/module/lib/Components/DotIcon/index.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/types.js +4 -0
- package/dist/module/lib/Components/DotIcon/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/DotSymbol/DotSymbol.js +29 -22
- package/dist/module/lib/Components/DotSymbol/DotSymbol.js.map +1 -1
- package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js +31 -9
- package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js.map +1 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.js +2 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.stories.js +4 -0
- package/dist/module/lib/Components/MediaImage/MediaImage.stories.js.map +1 -1
- package/dist/module/lib/Components/Spinner/Spinner.js +6 -1
- package/dist/module/lib/Components/Spinner/Spinner.js.map +1 -1
- 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/Tag/Tag.js +2 -0
- package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
- package/dist/module/lib/Components/Tag/Tag.stories.js +8 -1
- package/dist/module/lib/Components/Tag/Tag.stories.js.map +1 -1
- package/dist/module/lib/Components/index.js +3 -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/DotIcon/DotIcon.d.ts +30 -0
- package/dist/typescript/src/lib/Components/DotIcon/DotIcon.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIcon/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/DotIcon/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIcon/types.d.ts +40 -0
- package/dist/typescript/src/lib/Components/DotIcon/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/DotSymbol/DotSymbol.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/MediaImage/types.d.ts +1 -1
- package/dist/typescript/src/lib/Components/MediaImage/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Spinner/Spinner.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Spinner/Spinner.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/TabBar/TabBar.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Tag/Tag.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Tag/types.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Tag/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/index.d.ts +3 -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/DotIcon/DotIcon.mdx +56 -0
- package/src/lib/Components/DotIcon/DotIcon.stories.tsx +154 -0
- package/src/lib/Components/DotIcon/DotIcon.test.tsx +224 -0
- package/src/lib/Components/DotIcon/DotIcon.tsx +146 -0
- package/src/lib/Components/DotIcon/index.ts +6 -0
- package/src/lib/Components/DotIcon/types.ts +44 -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/DotSymbol/DotSymbol.stories.tsx +26 -7
- package/src/lib/Components/DotSymbol/DotSymbol.tsx +22 -23
- package/src/lib/Components/MediaImage/MediaImage.stories.tsx +1 -0
- package/src/lib/Components/MediaImage/MediaImage.tsx +3 -1
- package/src/lib/Components/MediaImage/types.ts +1 -1
- package/src/lib/Components/Spinner/Spinner.tsx +6 -2
- package/src/lib/Components/TabBar/TabBar.tsx +17 -16
- package/src/lib/Components/Tag/Tag.stories.tsx +11 -1
- package/src/lib/Components/Tag/Tag.tsx +2 -0
- package/src/lib/Components/Tag/types.ts +8 -1
- package/src/lib/Components/index.ts +3 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { useStyleSheet } from '../../../styles';
|
|
3
|
+
import type { IconSize } from '../Icon';
|
|
4
|
+
import { Box } from '../Utility';
|
|
5
|
+
import type {
|
|
6
|
+
DotIconAppearance,
|
|
7
|
+
DotIconPin,
|
|
8
|
+
DotIconProps,
|
|
9
|
+
DotIconSize,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
const dotIconSizeMap: Record<DotIconSize, IconSize> = {
|
|
13
|
+
16: 12,
|
|
14
|
+
20: 12,
|
|
15
|
+
24: 16,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const dotSquareRadiusMap: Record<DotIconSize, number> = {
|
|
19
|
+
16: 5,
|
|
20
|
+
20: 6,
|
|
21
|
+
24: 8,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const mediaImageDotIconSizeMap = {
|
|
25
|
+
40: 16,
|
|
26
|
+
48: 20,
|
|
27
|
+
56: 24,
|
|
28
|
+
64: 24,
|
|
29
|
+
} as const satisfies Record<number, DotIconSize>;
|
|
30
|
+
|
|
31
|
+
export const spotDotIconSizeMap = {
|
|
32
|
+
40: 16,
|
|
33
|
+
48: 20,
|
|
34
|
+
56: 24,
|
|
35
|
+
72: 24,
|
|
36
|
+
} as const satisfies Record<number, DotIconSize>;
|
|
37
|
+
|
|
38
|
+
const pinAxisMap: Record<DotIconPin, [vertical: string, horizontal: string]> = {
|
|
39
|
+
'top-start': ['top', 'left'],
|
|
40
|
+
'top-end': ['top', 'right'],
|
|
41
|
+
'bottom-start': ['bottom', 'left'],
|
|
42
|
+
'bottom-end': ['bottom', 'right'],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const DOT_OFFSET = -3;
|
|
46
|
+
|
|
47
|
+
const getPinOffset = (pin: DotIconPin): Record<string, number> => {
|
|
48
|
+
const [v, h] = pinAxisMap[pin];
|
|
49
|
+
return { [v]: DOT_OFFSET, [h]: DOT_OFFSET };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const appearanceBgMap: Record<
|
|
53
|
+
DotIconAppearance,
|
|
54
|
+
'successStrong' | 'mutedStrong' | 'errorStrong'
|
|
55
|
+
> = {
|
|
56
|
+
success: 'successStrong',
|
|
57
|
+
muted: 'mutedStrong',
|
|
58
|
+
error: 'errorStrong',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const useStyles = ({
|
|
62
|
+
size,
|
|
63
|
+
shape,
|
|
64
|
+
pin,
|
|
65
|
+
appearance,
|
|
66
|
+
}: {
|
|
67
|
+
size: DotIconSize;
|
|
68
|
+
shape: 'square' | 'circle';
|
|
69
|
+
pin: DotIconPin;
|
|
70
|
+
appearance: DotIconAppearance;
|
|
71
|
+
}) => {
|
|
72
|
+
return useStyleSheet(
|
|
73
|
+
(t) => {
|
|
74
|
+
const sizeValue = t.sizes[`s${size}` as keyof typeof t.sizes] as number;
|
|
75
|
+
const radius =
|
|
76
|
+
shape === 'circle' ? t.borderRadius.full : dotSquareRadiusMap[size];
|
|
77
|
+
const pinOffset = getPinOffset(pin);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
dot: {
|
|
81
|
+
position: 'absolute',
|
|
82
|
+
zIndex: 10,
|
|
83
|
+
width: sizeValue,
|
|
84
|
+
height: sizeValue,
|
|
85
|
+
borderRadius: radius,
|
|
86
|
+
borderWidth: 1,
|
|
87
|
+
backgroundColor: t.colors.bg[appearanceBgMap[appearance]],
|
|
88
|
+
borderColor: t.colors.border.baseInverted,
|
|
89
|
+
overflow: 'hidden',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
...pinOffset,
|
|
93
|
+
},
|
|
94
|
+
icon: {
|
|
95
|
+
color: t.colors.text.onInteractive,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
[size, shape, pin, appearance],
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A wrapper component that positions a small icon indicator at a configurable
|
|
105
|
+
* corner of a child element like MediaImage or Spot. The dot background uses a
|
|
106
|
+
* semantic color (`success`, `muted`, or `error`).
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* import { DotIcon } from '@ledgerhq/lumen-ui-rnative';
|
|
110
|
+
*
|
|
111
|
+
* <DotIcon appearance="success" icon={ArrowDown} pin="bottom-end">
|
|
112
|
+
* <MediaImage src="https://example.com/usdc.png" alt="USDC" size={48} />
|
|
113
|
+
* </DotIcon>
|
|
114
|
+
*/
|
|
115
|
+
export const DotIcon = ({
|
|
116
|
+
children,
|
|
117
|
+
icon: Icon,
|
|
118
|
+
appearance,
|
|
119
|
+
pin = 'bottom-end',
|
|
120
|
+
size = 20,
|
|
121
|
+
shape = 'circle',
|
|
122
|
+
lx = {},
|
|
123
|
+
style,
|
|
124
|
+
ref,
|
|
125
|
+
...rest
|
|
126
|
+
}: DotIconProps) => {
|
|
127
|
+
const styles = useStyles({ size, shape, pin, appearance });
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Box
|
|
131
|
+
ref={ref}
|
|
132
|
+
lx={lx}
|
|
133
|
+
style={StyleSheet.flatten([{ position: 'relative' }, style])}
|
|
134
|
+
{...rest}
|
|
135
|
+
>
|
|
136
|
+
<Box style={{ alignSelf: 'flex-start', position: 'relative' }}>
|
|
137
|
+
{children}
|
|
138
|
+
<Box testID='dot-icon-dot' style={styles.dot}>
|
|
139
|
+
<Icon size={dotIconSizeMap[size]} style={styles.icon} />
|
|
140
|
+
</Box>
|
|
141
|
+
</Box>
|
|
142
|
+
</Box>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
DotIcon.displayName = 'DotIcon';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, TextStyle } from 'react-native';
|
|
3
|
+
import type { StyledViewProps } from '../../../styles';
|
|
4
|
+
import type { IconSize } from '../Icon';
|
|
5
|
+
|
|
6
|
+
export type DotIconSize = 16 | 20 | 24;
|
|
7
|
+
|
|
8
|
+
export type DotIconPin =
|
|
9
|
+
| 'top-start'
|
|
10
|
+
| 'top-end'
|
|
11
|
+
| 'bottom-start'
|
|
12
|
+
| 'bottom-end';
|
|
13
|
+
|
|
14
|
+
export type DotIconAppearance = 'success' | 'muted' | 'error';
|
|
15
|
+
|
|
16
|
+
export type DotIconProps = {
|
|
17
|
+
/**
|
|
18
|
+
* Icon component to render inside the dot.
|
|
19
|
+
*/
|
|
20
|
+
icon: ComponentType<{ size?: IconSize; style?: StyleProp<TextStyle> }>;
|
|
21
|
+
/**
|
|
22
|
+
* Semantic color of the dot background.
|
|
23
|
+
*/
|
|
24
|
+
appearance: DotIconAppearance;
|
|
25
|
+
/**
|
|
26
|
+
* Corner placement of the dot indicator.
|
|
27
|
+
* @default 'bottom-end'
|
|
28
|
+
*/
|
|
29
|
+
pin?: DotIconPin;
|
|
30
|
+
/**
|
|
31
|
+
* The size of the dot indicator in pixels.
|
|
32
|
+
* @default 20
|
|
33
|
+
*/
|
|
34
|
+
size?: DotIconSize;
|
|
35
|
+
/**
|
|
36
|
+
* The shape of the dot indicator.
|
|
37
|
+
* @default 'circle'
|
|
38
|
+
*/
|
|
39
|
+
shape?: 'square' | 'circle';
|
|
40
|
+
/**
|
|
41
|
+
* The wrapped component (e.g. MediaImage or Spot).
|
|
42
|
+
*/
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
} & 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'>;
|
|
@@ -32,8 +32,12 @@ export const Base: Story = {
|
|
|
32
32
|
pin: 'bottom-end',
|
|
33
33
|
size: 20,
|
|
34
34
|
shape: 'circle',
|
|
35
|
-
children: <MediaImage src={parentSrc} alt='Cardano' shape='circle' />,
|
|
36
35
|
},
|
|
36
|
+
render: (args) => (
|
|
37
|
+
<DotSymbol {...args}>
|
|
38
|
+
<MediaImage src={parentSrc} alt='Cardano' shape={args.shape} />
|
|
39
|
+
</DotSymbol>
|
|
40
|
+
),
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
export const PinShowcase: Story = {
|
|
@@ -135,6 +139,13 @@ export const SizeShowcase: Story = {
|
|
|
135
139
|
>
|
|
136
140
|
<MediaImage src={parentSrc} size={56} shape='circle' />
|
|
137
141
|
</DotSymbol>
|
|
142
|
+
<DotSymbol
|
|
143
|
+
src={dotSrc}
|
|
144
|
+
size={mediaImageDotSizeMap[64]}
|
|
145
|
+
pin='bottom-end'
|
|
146
|
+
>
|
|
147
|
+
<MediaImage src={parentSrc} size={64} shape='circle' />
|
|
148
|
+
</DotSymbol>
|
|
138
149
|
</Box>
|
|
139
150
|
<Box lx={{ flexDirection: 'row', alignItems: 'flex-end', gap: 's24' }}>
|
|
140
151
|
<DotSymbol
|
|
@@ -143,7 +154,7 @@ export const SizeShowcase: Story = {
|
|
|
143
154
|
size={mediaImageDotSizeMap[20]}
|
|
144
155
|
pin='bottom-end'
|
|
145
156
|
>
|
|
146
|
-
<MediaImage src={parentSrc} size={20} shape='
|
|
157
|
+
<MediaImage src={parentSrc} size={20} shape='square' />
|
|
147
158
|
</DotSymbol>
|
|
148
159
|
<DotSymbol
|
|
149
160
|
shape='square'
|
|
@@ -151,7 +162,7 @@ export const SizeShowcase: Story = {
|
|
|
151
162
|
size={mediaImageDotSizeMap[24]}
|
|
152
163
|
pin='bottom-end'
|
|
153
164
|
>
|
|
154
|
-
<MediaImage src={parentSrc} size={24} shape='
|
|
165
|
+
<MediaImage src={parentSrc} size={24} shape='square' />
|
|
155
166
|
</DotSymbol>
|
|
156
167
|
<DotSymbol
|
|
157
168
|
shape='square'
|
|
@@ -159,7 +170,7 @@ export const SizeShowcase: Story = {
|
|
|
159
170
|
size={mediaImageDotSizeMap[32]}
|
|
160
171
|
pin='bottom-end'
|
|
161
172
|
>
|
|
162
|
-
<MediaImage src={parentSrc} size={32} shape='
|
|
173
|
+
<MediaImage src={parentSrc} size={32} shape='square' />
|
|
163
174
|
</DotSymbol>
|
|
164
175
|
<DotSymbol
|
|
165
176
|
shape='square'
|
|
@@ -167,7 +178,7 @@ export const SizeShowcase: Story = {
|
|
|
167
178
|
size={mediaImageDotSizeMap[40]}
|
|
168
179
|
pin='bottom-end'
|
|
169
180
|
>
|
|
170
|
-
<MediaImage src={parentSrc} size={40} shape='
|
|
181
|
+
<MediaImage src={parentSrc} size={40} shape='square' />
|
|
171
182
|
</DotSymbol>
|
|
172
183
|
<DotSymbol
|
|
173
184
|
shape='square'
|
|
@@ -175,7 +186,7 @@ export const SizeShowcase: Story = {
|
|
|
175
186
|
size={mediaImageDotSizeMap[48]}
|
|
176
187
|
pin='bottom-end'
|
|
177
188
|
>
|
|
178
|
-
<MediaImage src={parentSrc} size={48} shape='
|
|
189
|
+
<MediaImage src={parentSrc} size={48} shape='square' />
|
|
179
190
|
</DotSymbol>
|
|
180
191
|
<DotSymbol
|
|
181
192
|
shape='square'
|
|
@@ -183,7 +194,15 @@ export const SizeShowcase: Story = {
|
|
|
183
194
|
size={mediaImageDotSizeMap[56]}
|
|
184
195
|
pin='bottom-end'
|
|
185
196
|
>
|
|
186
|
-
<MediaImage src={parentSrc} size={56} shape='
|
|
197
|
+
<MediaImage src={parentSrc} size={56} shape='square' />
|
|
198
|
+
</DotSymbol>
|
|
199
|
+
<DotSymbol
|
|
200
|
+
shape='square'
|
|
201
|
+
src={dotSrc}
|
|
202
|
+
size={mediaImageDotSizeMap[64]}
|
|
203
|
+
pin='bottom-end'
|
|
204
|
+
>
|
|
205
|
+
<MediaImage src={parentSrc} size={64} shape='square' />
|
|
187
206
|
</DotSymbol>
|
|
188
207
|
</Box>
|
|
189
208
|
</Box>
|