@ledgerhq/lumen-ui-rnative 0.1.18 → 0.1.20

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 (45) hide show
  1. package/dist/module/lib/Components/DotSymbol/DotSymbol.js +141 -0
  2. package/dist/module/lib/Components/DotSymbol/DotSymbol.js.map +1 -0
  3. package/dist/module/lib/Components/DotSymbol/DotSymbol.mdx +54 -0
  4. package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js +301 -0
  5. package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js.map +1 -0
  6. package/dist/module/lib/Components/DotSymbol/DotSymbol.test.js +134 -0
  7. package/dist/module/lib/Components/DotSymbol/DotSymbol.test.js.map +1 -0
  8. package/dist/module/lib/Components/DotSymbol/index.js +5 -0
  9. package/dist/module/lib/Components/DotSymbol/index.js.map +1 -0
  10. package/dist/module/lib/Components/DotSymbol/types.js +4 -0
  11. package/dist/module/lib/Components/DotSymbol/types.js.map +1 -0
  12. package/dist/module/lib/Components/MediaImage/MediaImage.js +1 -11
  13. package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
  14. package/dist/module/lib/Components/MediaImage/index.js +1 -1
  15. package/dist/module/lib/Components/MediaImage/index.js.map +1 -1
  16. package/dist/module/lib/Components/NavBar/NavBar.js +0 -2
  17. package/dist/module/lib/Components/NavBar/NavBar.js.map +1 -1
  18. package/dist/module/lib/Components/Select/Select.stories.js +1 -0
  19. package/dist/module/lib/Components/Select/Select.stories.js.map +1 -1
  20. package/dist/module/lib/Components/index.js +1 -0
  21. package/dist/module/lib/Components/index.js.map +1 -1
  22. package/dist/typescript/src/lib/Components/DotSymbol/DotSymbol.d.ts +21 -0
  23. package/dist/typescript/src/lib/Components/DotSymbol/DotSymbol.d.ts.map +1 -0
  24. package/dist/typescript/src/lib/Components/DotSymbol/index.d.ts +3 -0
  25. package/dist/typescript/src/lib/Components/DotSymbol/index.d.ts.map +1 -0
  26. package/dist/typescript/src/lib/Components/DotSymbol/types.d.ts +34 -0
  27. package/dist/typescript/src/lib/Components/DotSymbol/types.d.ts.map +1 -0
  28. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts +1 -2
  29. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
  30. package/dist/typescript/src/lib/Components/MediaImage/index.d.ts +1 -1
  31. package/dist/typescript/src/lib/Components/MediaImage/index.d.ts.map +1 -1
  32. package/dist/typescript/src/lib/Components/index.d.ts +1 -0
  33. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/lib/Components/DotSymbol/DotSymbol.mdx +54 -0
  36. package/src/lib/Components/DotSymbol/DotSymbol.stories.tsx +191 -0
  37. package/src/lib/Components/DotSymbol/DotSymbol.test.tsx +119 -0
  38. package/src/lib/Components/DotSymbol/DotSymbol.tsx +161 -0
  39. package/src/lib/Components/DotSymbol/index.ts +2 -0
  40. package/src/lib/Components/DotSymbol/types.ts +40 -0
  41. package/src/lib/Components/MediaImage/MediaImage.tsx +6 -17
  42. package/src/lib/Components/MediaImage/index.ts +1 -1
  43. package/src/lib/Components/NavBar/NavBar.tsx +0 -3
  44. package/src/lib/Components/Select/Select.stories.tsx +1 -0
  45. package/src/lib/Components/index.ts +1 -0
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { render, waitFor } from '@testing-library/react-native';
4
+ import { Text } from 'react-native';
5
+ import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
6
+ import { DotSymbol } from './DotSymbol';
7
+
8
+ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
9
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
10
+ {children}
11
+ </ThemeProvider>
12
+ );
13
+
14
+ describe('DotSymbol Component', () => {
15
+ const dotSrc = 'https://crypto-icons.ledger.com/BTC.png';
16
+
17
+ it('should render children and dot image', () => {
18
+ const { getByText, getByTestId } = render(
19
+ <TestWrapper>
20
+ <DotSymbol src={dotSrc} alt='Bitcoin'>
21
+ <Text>Child</Text>
22
+ </DotSymbol>
23
+ </TestWrapper>,
24
+ );
25
+
26
+ expect(getByText('Child')).toBeTruthy();
27
+
28
+ const img = getByTestId('dot-symbol-img');
29
+ expect(img.props.source).toEqual({ uri: dotSrc });
30
+ });
31
+
32
+ it('should render without children', () => {
33
+ const { getByTestId } = render(
34
+ <TestWrapper>
35
+ <DotSymbol src={dotSrc} />
36
+ </TestWrapper>,
37
+ );
38
+
39
+ expect(getByTestId('dot-symbol-img')).toBeTruthy();
40
+ });
41
+
42
+ it('should render fallback when image fails to load', async () => {
43
+ const { getByTestId, queryByTestId, rerender } = render(
44
+ <TestWrapper>
45
+ <DotSymbol src='https://broken-link.com/404.png' />
46
+ </TestWrapper>,
47
+ );
48
+
49
+ const img = getByTestId('dot-symbol-img');
50
+ img.props.onError();
51
+
52
+ rerender(
53
+ <TestWrapper>
54
+ <DotSymbol src='https://broken-link.com/404.png' />
55
+ </TestWrapper>,
56
+ );
57
+
58
+ await waitFor(() => {
59
+ expect(queryByTestId('dot-symbol-img')).toBeNull();
60
+ });
61
+ });
62
+
63
+ it('should reset error state when src changes', async () => {
64
+ const { getByTestId, rerender } = render(
65
+ <TestWrapper>
66
+ <DotSymbol src='https://broken-link.com/404.png' />
67
+ </TestWrapper>,
68
+ );
69
+
70
+ const img = getByTestId('dot-symbol-img');
71
+ img.props.onError();
72
+
73
+ rerender(
74
+ <TestWrapper>
75
+ <DotSymbol src={dotSrc} />
76
+ </TestWrapper>,
77
+ );
78
+
79
+ await waitFor(() => {
80
+ const newImg = getByTestId('dot-symbol-img');
81
+ expect(newImg.props.source).toEqual({ uri: dotSrc });
82
+ });
83
+ });
84
+
85
+ it('should set accessibility label from alt prop', () => {
86
+ const { getByLabelText } = render(
87
+ <TestWrapper>
88
+ <DotSymbol src={dotSrc} alt='Bitcoin network' />
89
+ </TestWrapper>,
90
+ );
91
+
92
+ expect(getByLabelText('Bitcoin network')).toBeTruthy();
93
+ });
94
+
95
+ it('should apply custom styles', () => {
96
+ const { getByTestId } = render(
97
+ <TestWrapper>
98
+ <DotSymbol testID='ds' src={dotSrc} style={{ marginTop: 10 }} />
99
+ </TestWrapper>,
100
+ );
101
+
102
+ const root = getByTestId('ds');
103
+ expect(root.props.style.marginTop).toBe(10);
104
+ });
105
+
106
+ it('should pass additional props', () => {
107
+ const { getByTestId } = render(
108
+ <TestWrapper>
109
+ <DotSymbol testID='custom-dot' src={dotSrc} />
110
+ </TestWrapper>,
111
+ );
112
+
113
+ expect(getByTestId('custom-dot')).toBeTruthy();
114
+ });
115
+
116
+ it('should have correct displayName', () => {
117
+ expect(DotSymbol.displayName).toBe('DotSymbol');
118
+ });
119
+ });
@@ -0,0 +1,161 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Image, StyleSheet } from 'react-native';
3
+ import { useStyleSheet } from '../../../styles';
4
+ import { MediaImageSize } from '../MediaImage';
5
+ import { SpotSize } from '../Spot';
6
+ import { Box } from '../Utility';
7
+ import { DotSymbolPin, DotSymbolProps, DotSymbolSize } from './types';
8
+
9
+ type BorderRadiusKey = 'xs' | 'sm' | 'md' | 'lg' | 'full';
10
+
11
+ const shapeRadiusMap: Record<DotSymbolSize, BorderRadiusKey> = {
12
+ 8: 'xs',
13
+ 10: 'xs',
14
+ 12: 'xs',
15
+ 16: 'sm',
16
+ 20: 'sm',
17
+ 24: 'md',
18
+ };
19
+
20
+ const offsetBySize: Record<DotSymbolSize, number> = {
21
+ 8: -2,
22
+ 10: -2,
23
+ 12: -2,
24
+ 16: -3,
25
+ 20: -3,
26
+ 24: -3,
27
+ };
28
+
29
+ export const mediaImageDotSizeMap: Record<MediaImageSize, DotSymbolSize> = {
30
+ 12: 8,
31
+ 16: 8,
32
+ 20: 8,
33
+ 24: 10,
34
+ 32: 12,
35
+ 40: 16,
36
+ 48: 20,
37
+ 56: 24,
38
+ };
39
+
40
+ export const spotDotSizeMap: Record<SpotSize, DotSymbolSize> = {
41
+ 32: 12,
42
+ 40: 16,
43
+ 48: 20,
44
+ 56: 24,
45
+ 72: 24,
46
+ };
47
+
48
+ const pinAxisMap: Record<DotSymbolPin, [vertical: string, horizontal: string]> =
49
+ {
50
+ 'top-start': ['top', 'left'],
51
+ 'top-end': ['top', 'right'],
52
+ 'bottom-start': ['bottom', 'left'],
53
+ 'bottom-end': ['bottom', 'right'],
54
+ };
55
+
56
+ const getPinOffset = (
57
+ pin: DotSymbolPin,
58
+ size: DotSymbolSize,
59
+ ): Record<string, number> => {
60
+ const [v, h] = pinAxisMap[pin];
61
+ const offset = offsetBySize[size];
62
+ return { [v]: offset, [h]: offset };
63
+ };
64
+
65
+ const useStyles = ({
66
+ size,
67
+ shape,
68
+ pin,
69
+ }: {
70
+ size: DotSymbolSize;
71
+ shape: 'square' | 'circle';
72
+ pin: DotSymbolPin;
73
+ }) => {
74
+ return useStyleSheet(
75
+ (t) => {
76
+ const sizeValue = t.sizes[`s${size}` as keyof typeof t.sizes] as number;
77
+ const radius =
78
+ shape === 'circle'
79
+ ? t.borderRadius.full
80
+ : t.borderRadius[shapeRadiusMap[size]];
81
+ const pinOffset = getPinOffset(pin, size);
82
+
83
+ return {
84
+ dot: {
85
+ position: 'absolute',
86
+ zIndex: 10,
87
+ width: sizeValue,
88
+ height: sizeValue,
89
+ borderRadius: radius,
90
+ borderWidth: 1,
91
+ backgroundColor: t.colors.bg.muted,
92
+ borderColor: t.colors.border.baseInverted,
93
+ overflow: 'hidden',
94
+ ...pinOffset,
95
+ },
96
+ image: {
97
+ width: '100%',
98
+ height: '100%',
99
+ },
100
+ };
101
+ },
102
+ [size, shape, pin],
103
+ );
104
+ };
105
+
106
+ /**
107
+ * A wrapper component that positions a small image indicator at a configurable
108
+ * corner of a child element like MediaImage or Spot.
109
+ *
110
+ * @example
111
+ * import { DotSymbol } from '@ledgerhq/lumen-ui-rnative';
112
+ *
113
+ * <DotSymbol src="https://example.com/eth.png" alt="Ethereum" pin="bottom-end">
114
+ * <MediaImage src="https://example.com/usdc.png" alt="USDC" size={48} />
115
+ * </DotSymbol>
116
+ */
117
+ export const DotSymbol = ({
118
+ children,
119
+ src,
120
+ alt,
121
+ pin = 'bottom-end',
122
+ size = 20,
123
+ shape = 'circle',
124
+ lx = {},
125
+ style,
126
+ ref,
127
+ ...rest
128
+ }: DotSymbolProps) => {
129
+ const styles = useStyles({ size, shape, pin });
130
+ const [error, setError] = useState(false);
131
+
132
+ useEffect(() => {
133
+ setError(false);
134
+ }, [src]);
135
+
136
+ return (
137
+ <Box
138
+ ref={ref}
139
+ lx={lx}
140
+ style={StyleSheet.flatten([{ position: 'relative' }, style])}
141
+ accessibilityRole='image'
142
+ accessibilityLabel={alt}
143
+ {...rest}
144
+ >
145
+ {children}
146
+ <Box style={styles.dot}>
147
+ {!error && (
148
+ <Image
149
+ source={{ uri: src }}
150
+ style={styles.image}
151
+ accessible={false}
152
+ onError={() => setError(true)}
153
+ testID='dot-symbol-img'
154
+ />
155
+ )}
156
+ </Box>
157
+ </Box>
158
+ );
159
+ };
160
+
161
+ DotSymbol.displayName = 'DotSymbol';
@@ -0,0 +1,2 @@
1
+ export { DotSymbol, mediaImageDotSizeMap, spotDotSizeMap } from './DotSymbol';
2
+ export * from './types';
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from 'react';
2
+ import { StyledViewProps } from '../../../styles';
3
+
4
+ export type DotSymbolSize = 8 | 10 | 12 | 16 | 20 | 24;
5
+
6
+ export type DotSymbolPin =
7
+ | 'top-start'
8
+ | 'top-end'
9
+ | 'bottom-start'
10
+ | 'bottom-end';
11
+
12
+ export type DotSymbolProps = {
13
+ /**
14
+ * Image source URL for the dot indicator.
15
+ */
16
+ src: string;
17
+ /**
18
+ * Alternative text for the dot image.
19
+ */
20
+ alt?: string;
21
+ /**
22
+ * Corner placement of the dot indicator.
23
+ * @default 'bottom-end'
24
+ */
25
+ pin?: DotSymbolPin;
26
+ /**
27
+ * The size of the dot indicator in pixels.
28
+ * @default 20
29
+ */
30
+ size?: DotSymbolSize;
31
+ /**
32
+ * The shape of the dot indicator.
33
+ * @default 'circle'
34
+ */
35
+ shape?: 'square' | 'circle';
36
+ /**
37
+ * The wrapped component (e.g. MediaImage or Spot).
38
+ */
39
+ children?: ReactNode;
40
+ } & Omit<StyledViewProps, 'children'>;
@@ -17,17 +17,6 @@ const borderRadiusMap: Record<MediaImageSize, BorderRadiusKey> = {
17
17
  56: 'lg',
18
18
  };
19
19
 
20
- export const mediaImageDotSizeMap: Record<MediaImageSize, number> = {
21
- 12: 8,
22
- 16: 8,
23
- 20: 8,
24
- 24: 10,
25
- 32: 12,
26
- 40: 16,
27
- 48: 20,
28
- 56: 24,
29
- } as const;
30
-
31
20
  const useStyles = ({
32
21
  size,
33
22
  shape,
@@ -48,14 +37,14 @@ const useStyles = ({
48
37
  width: sizeValue,
49
38
  height: sizeValue,
50
39
  borderRadius: radius,
51
- overflow: 'hidden' as const,
52
- alignItems: 'center' as const,
53
- justifyContent: 'center' as const,
54
- backgroundColor: t.colors.bg.mutedTransparent,
40
+ overflow: 'hidden',
41
+ alignItems: 'center',
42
+ justifyContent: 'center',
43
+ backgroundColor: t.colors.bg.muted,
55
44
  },
56
45
  image: {
57
- width: '100%' as const,
58
- height: '100%' as const,
46
+ width: '100%',
47
+ height: '100%',
59
48
  },
60
49
  };
61
50
  },
@@ -1,2 +1,2 @@
1
- export { MediaImage, mediaImageDotSizeMap } from './MediaImage';
1
+ export { MediaImage } from './MediaImage';
2
2
  export * from './types';
@@ -287,9 +287,6 @@ const useStyles = ({ appearance }: StyleParams) => {
287
287
  },
288
288
  ]),
289
289
  contentContainer: StyleSheet.flatten([
290
- {
291
- flex: 1,
292
- },
293
290
  {
294
291
  ...(appearance === 'compact' && {
295
292
  paddingHorizontal: t.spacings.s48,
@@ -16,6 +16,7 @@ import {
16
16
 
17
17
  const meta: Meta<typeof Select> = {
18
18
  component: Select,
19
+ tags: ['deprecated'],
19
20
  subcomponents: {
20
21
  SelectTrigger,
21
22
  SelectValue,
@@ -11,6 +11,7 @@ export * from './CardButton';
11
11
  export * from './ContentBanner';
12
12
  export * from './Checkbox';
13
13
  export * from './Divider';
14
+ export * from './DotSymbol';
14
15
  export * from './Icon';
15
16
  export * from './IconButton';
16
17
  export * from './InteractiveIcon';