@ledgerhq/lumen-ui-rnative 0.1.19 → 0.1.21

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 (120) hide show
  1. package/dist/module/lib/Components/AmountInput/AmountInput.js +2 -0
  2. package/dist/module/lib/Components/AmountInput/AmountInput.js.map +1 -1
  3. package/dist/module/lib/Components/Banner/Banner.js +13 -7
  4. package/dist/module/lib/Components/Banner/Banner.js.map +1 -1
  5. package/dist/module/lib/Components/BaseInput/BaseInput.js +7 -6
  6. package/dist/module/lib/Components/BaseInput/BaseInput.js.map +1 -1
  7. package/dist/module/lib/Components/BottomSheet/BottomSheet.mdx +4 -4
  8. package/dist/module/lib/Components/BottomSheet/BottomSheet.stories.js +10 -10
  9. package/dist/module/lib/Components/BottomSheet/BottomSheet.stories.js.map +1 -1
  10. package/dist/module/lib/Components/BottomSheet/BottomSheetHeader.js +12 -12
  11. package/dist/module/lib/Components/BottomSheet/BottomSheetHeader.js.map +1 -1
  12. package/dist/module/lib/Components/Button/BaseButton.js +3 -1
  13. package/dist/module/lib/Components/Button/BaseButton.js.map +1 -1
  14. package/dist/module/lib/Components/Card/Card.js +3 -1
  15. package/dist/module/lib/Components/Card/Card.js.map +1 -1
  16. package/dist/module/lib/Components/DotSymbol/DotSymbol.js +141 -0
  17. package/dist/module/lib/Components/DotSymbol/DotSymbol.js.map +1 -0
  18. package/dist/module/lib/Components/DotSymbol/DotSymbol.mdx +54 -0
  19. package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js +301 -0
  20. package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js.map +1 -0
  21. package/dist/module/lib/Components/DotSymbol/DotSymbol.test.js +134 -0
  22. package/dist/module/lib/Components/DotSymbol/DotSymbol.test.js.map +1 -0
  23. package/dist/module/lib/Components/DotSymbol/index.js +5 -0
  24. package/dist/module/lib/Components/DotSymbol/index.js.map +1 -0
  25. package/dist/module/lib/Components/DotSymbol/types.js +4 -0
  26. package/dist/module/lib/Components/DotSymbol/types.js.map +1 -0
  27. package/dist/module/lib/Components/ListItem/ListItem.js +2 -2
  28. package/dist/module/lib/Components/ListItem/ListItem.js.map +1 -1
  29. package/dist/module/lib/Components/MediaBanner/MediaBanner.js +1 -1
  30. package/dist/module/lib/Components/MediaBanner/MediaBanner.js.map +1 -1
  31. package/dist/module/lib/Components/MediaCard/MediaCard.js +1 -1
  32. package/dist/module/lib/Components/MediaCard/MediaCard.js.map +1 -1
  33. package/dist/module/lib/Components/MediaImage/MediaImage.js +1 -11
  34. package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
  35. package/dist/module/lib/Components/MediaImage/index.js +1 -1
  36. package/dist/module/lib/Components/MediaImage/index.js.map +1 -1
  37. package/dist/module/lib/Components/NavBar/NavBar.js +27 -25
  38. package/dist/module/lib/Components/NavBar/NavBar.js.map +1 -1
  39. package/dist/module/lib/Components/NavBar/NavBar.mdx +7 -7
  40. package/dist/module/lib/Components/NavBar/NavBar.stories.js +6 -6
  41. package/dist/module/lib/Components/NavBar/NavBar.stories.js.map +1 -1
  42. package/dist/module/lib/Components/NavBar/NavBar.test.js +24 -24
  43. package/dist/module/lib/Components/NavBar/NavBar.test.js.map +1 -1
  44. package/dist/module/lib/Components/Select/GlobalSelectBottomSheet.js +1 -1
  45. package/dist/module/lib/Components/Select/GlobalSelectBottomSheet.js.map +1 -1
  46. package/dist/module/lib/Components/Select/Select.stories.js +1 -0
  47. package/dist/module/lib/Components/Select/Select.stories.js.map +1 -1
  48. package/dist/module/lib/Components/Spot/Spot.js +1 -0
  49. package/dist/module/lib/Components/Spot/Spot.js.map +1 -1
  50. package/dist/module/lib/Components/Stepper/Stepper.js +3 -0
  51. package/dist/module/lib/Components/Stepper/Stepper.js.map +1 -1
  52. package/dist/module/lib/Components/TabBar/TabBar.js +2 -2
  53. package/dist/module/lib/Components/TabBar/TabBar.js.map +1 -1
  54. package/dist/module/lib/Components/Tag/Tag.js +0 -2
  55. package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
  56. package/dist/module/lib/Components/Tooltip/GlobalTooltipBottomSheet.js +1 -1
  57. package/dist/module/lib/Components/Tooltip/GlobalTooltipBottomSheet.js.map +1 -1
  58. package/dist/module/lib/Components/index.js +1 -0
  59. package/dist/module/lib/Components/index.js.map +1 -1
  60. package/dist/typescript/src/lib/Components/AmountInput/AmountInput.d.ts.map +1 -1
  61. package/dist/typescript/src/lib/Components/Banner/Banner.d.ts.map +1 -1
  62. package/dist/typescript/src/lib/Components/BottomSheet/BottomSheetHeader.d.ts +1 -1
  63. package/dist/typescript/src/lib/Components/BottomSheet/BottomSheetHeader.d.ts.map +1 -1
  64. package/dist/typescript/src/lib/Components/BottomSheet/types.d.ts +3 -2
  65. package/dist/typescript/src/lib/Components/BottomSheet/types.d.ts.map +1 -1
  66. package/dist/typescript/src/lib/Components/Button/BaseButton.d.ts.map +1 -1
  67. package/dist/typescript/src/lib/Components/Card/Card.d.ts.map +1 -1
  68. package/dist/typescript/src/lib/Components/DotSymbol/DotSymbol.d.ts +21 -0
  69. package/dist/typescript/src/lib/Components/DotSymbol/DotSymbol.d.ts.map +1 -0
  70. package/dist/typescript/src/lib/Components/DotSymbol/index.d.ts +3 -0
  71. package/dist/typescript/src/lib/Components/DotSymbol/index.d.ts.map +1 -0
  72. package/dist/typescript/src/lib/Components/DotSymbol/types.d.ts +34 -0
  73. package/dist/typescript/src/lib/Components/DotSymbol/types.d.ts.map +1 -0
  74. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts +1 -2
  75. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
  76. package/dist/typescript/src/lib/Components/MediaImage/index.d.ts +1 -1
  77. package/dist/typescript/src/lib/Components/MediaImage/index.d.ts.map +1 -1
  78. package/dist/typescript/src/lib/Components/NavBar/NavBar.d.ts +1 -1
  79. package/dist/typescript/src/lib/Components/NavBar/NavBar.d.ts.map +1 -1
  80. package/dist/typescript/src/lib/Components/NavBar/types.d.ts +3 -3
  81. package/dist/typescript/src/lib/Components/NavBar/types.d.ts.map +1 -1
  82. package/dist/typescript/src/lib/Components/Spot/Spot.d.ts.map +1 -1
  83. package/dist/typescript/src/lib/Components/Stepper/Stepper.d.ts.map +1 -1
  84. package/dist/typescript/src/lib/Components/Tag/Tag.d.ts.map +1 -1
  85. package/dist/typescript/src/lib/Components/index.d.ts +1 -0
  86. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  87. package/package.json +1 -1
  88. package/src/lib/Components/AmountInput/AmountInput.tsx +8 -2
  89. package/src/lib/Components/Banner/Banner.tsx +10 -4
  90. package/src/lib/Components/BaseInput/BaseInput.tsx +4 -3
  91. package/src/lib/Components/BottomSheet/BottomSheet.mdx +4 -4
  92. package/src/lib/Components/BottomSheet/BottomSheet.stories.tsx +10 -10
  93. package/src/lib/Components/BottomSheet/BottomSheetHeader.tsx +14 -14
  94. package/src/lib/Components/BottomSheet/types.ts +3 -2
  95. package/src/lib/Components/Button/BaseButton.tsx +4 -1
  96. package/src/lib/Components/Card/Card.tsx +2 -0
  97. package/src/lib/Components/DotSymbol/DotSymbol.mdx +54 -0
  98. package/src/lib/Components/DotSymbol/DotSymbol.stories.tsx +191 -0
  99. package/src/lib/Components/DotSymbol/DotSymbol.test.tsx +119 -0
  100. package/src/lib/Components/DotSymbol/DotSymbol.tsx +161 -0
  101. package/src/lib/Components/DotSymbol/index.ts +2 -0
  102. package/src/lib/Components/DotSymbol/types.ts +40 -0
  103. package/src/lib/Components/ListItem/ListItem.tsx +4 -4
  104. package/src/lib/Components/MediaBanner/MediaBanner.tsx +1 -1
  105. package/src/lib/Components/MediaCard/MediaCard.tsx +1 -1
  106. package/src/lib/Components/MediaImage/MediaImage.tsx +6 -17
  107. package/src/lib/Components/MediaImage/index.ts +1 -1
  108. package/src/lib/Components/NavBar/NavBar.mdx +7 -7
  109. package/src/lib/Components/NavBar/NavBar.stories.tsx +6 -6
  110. package/src/lib/Components/NavBar/NavBar.test.tsx +25 -25
  111. package/src/lib/Components/NavBar/NavBar.tsx +31 -29
  112. package/src/lib/Components/NavBar/types.ts +3 -4
  113. package/src/lib/Components/Select/GlobalSelectBottomSheet.tsx +1 -1
  114. package/src/lib/Components/Select/Select.stories.tsx +1 -0
  115. package/src/lib/Components/Spot/Spot.tsx +5 -1
  116. package/src/lib/Components/Stepper/Stepper.tsx +15 -3
  117. package/src/lib/Components/TabBar/TabBar.tsx +2 -2
  118. package/src/lib/Components/Tag/Tag.tsx +1 -3
  119. package/src/lib/Components/Tooltip/GlobalTooltipBottomSheet.tsx +1 -1
  120. package/src/lib/Components/index.ts +1 -0
@@ -129,7 +129,7 @@ export const Base: Story = {
129
129
  <BottomSheetView>
130
130
  <BottomSheetHeader
131
131
  title='Title'
132
- appearance='compact'
132
+ density='compact'
133
133
  description='Description'
134
134
  />
135
135
  <Box lx={{ flexDirection: 'column', gap: 's12' }}>
@@ -179,7 +179,7 @@ export const TitleExpanded: Story = {
179
179
  <BottomSheetView>
180
180
  <BottomSheetHeader
181
181
  title='Expanded title'
182
- appearance='expanded'
182
+ density='expanded'
183
183
  description='Expanded description.'
184
184
  />
185
185
  <Box lx={{ flexDirection: 'column', gap: 's12' }}>
@@ -224,7 +224,7 @@ export const DynamicSizingWithoutSnapPoints: Story = {
224
224
  <BottomSheetScrollView>
225
225
  <BottomSheetHeader
226
226
  title='Dynamic Sizing'
227
- appearance='compact'
227
+ density='compact'
228
228
  description='This bottom sheet adapts to its content height'
229
229
  />
230
230
  <Box lx={{ flexDirection: 'column', gap: 's12' }}>
@@ -266,7 +266,7 @@ export const DynamicSizingWithSnapPoints: Story = {
266
266
  <BottomSheetScrollView>
267
267
  <BottomSheetHeader
268
268
  title='Dynamic Sizing'
269
- appearance='compact'
269
+ density='compact'
270
270
  description='This bottom sheet adapts to its content height and has snap points'
271
271
  />
272
272
  <Box lx={{ flexDirection: 'column', gap: 's12' }}>
@@ -311,7 +311,7 @@ export const PreventClose: Story = {
311
311
  <BottomSheetView>
312
312
  <BottomSheetHeader
313
313
  title='Hidden Close Button'
314
- appearance='compact'
314
+ density='compact'
315
315
  description='This bottom sheet cannot be closed by dragging or button'
316
316
  />
317
317
  <Box lx={{ flexDirection: 'column', gap: 's12' }}>
@@ -355,7 +355,7 @@ export const ScrollView: Story = {
355
355
  <BottomSheetScrollView>
356
356
  <BottomSheetHeader
357
357
  title='Scrollable Content'
358
- appearance='compact'
358
+ density='compact'
359
359
  description='This bottom sheet contains a scrollable view'
360
360
  />
361
361
  <Box lx={{ flexDirection: 'column', gap: 's12' }}>
@@ -411,7 +411,7 @@ export const VirtualList: Story = {
411
411
  <BottomSheetHeader
412
412
  spacing
413
413
  title='Virtual List'
414
- appearance='compact'
414
+ density='compact'
415
415
  description='This bottom sheet contains a virtualized list'
416
416
  />
417
417
  <BottomSheetFlatList
@@ -491,7 +491,7 @@ export const StickyHeaderContent: Story = {
491
491
  <BottomSheetHeader
492
492
  spacing
493
493
  title='BottomSheetFlatList'
494
- appearance='compact'
494
+ density='compact'
495
495
  description='The search input is rendered as a sticky list header'
496
496
  />
497
497
  <BottomSheetFlatList
@@ -543,7 +543,7 @@ export const StickyHeaderContent: Story = {
543
543
  <BottomSheetHeader
544
544
  spacing
545
545
  title='BottomSheetScrollView'
546
- appearance='compact'
546
+ density='compact'
547
547
  description='The search input is rendered as a sticky list header'
548
548
  />
549
549
  {data.map((item) => (
@@ -613,7 +613,7 @@ export const VirtualizedList: Story = {
613
613
  <BottomSheetHeader
614
614
  spacing
615
615
  title='Virtualized List'
616
- appearance='compact'
616
+ density='compact'
617
617
  description='This bottom sheet uses a VirtualizedList with custom getItem/getItemCount'
618
618
  />
619
619
  <BottomSheetVirtualizedList
@@ -9,16 +9,16 @@ import { Box, Text } from '../Utility';
9
9
  import { useBottomSheetContext } from './BottomSheet';
10
10
  import { BottomSheetHeaderProps } from './types';
11
11
 
12
- type Appearance = NonNullable<BottomSheetHeaderProps['appearance']>;
12
+ type DensityValue = NonNullable<BottomSheetHeaderProps['density']>;
13
13
 
14
14
  const Z_INDEX_DIALOG_CONTENT = 1000;
15
15
 
16
16
  const useStyles = ({
17
- appearance,
17
+ density,
18
18
  spacing,
19
19
  hidden,
20
20
  }: {
21
- appearance: Appearance;
21
+ density: DensityValue;
22
22
  spacing: boolean;
23
23
  hidden: boolean;
24
24
  }) => {
@@ -42,7 +42,7 @@ const useStyles = ({
42
42
  justifyContent: 'space-between',
43
43
  gap: t.spacings.s16,
44
44
  },
45
- appearance === 'expanded' && {
45
+ density === 'expanded' && {
46
46
  marginBottom: t.spacings.s16,
47
47
  },
48
48
  hidden && {
@@ -53,7 +53,7 @@ const useStyles = ({
53
53
  {
54
54
  flex: 1,
55
55
  },
56
- appearance === 'expanded' && {
56
+ density === 'expanded' && {
57
57
  gap: t.spacings.s4,
58
58
  flex: 0,
59
59
  },
@@ -62,10 +62,10 @@ const useStyles = ({
62
62
  {
63
63
  color: t.colors.text.base,
64
64
  },
65
- appearance === 'expanded' && {
65
+ density === 'expanded' && {
66
66
  ...t.typographies.heading3SemiBold,
67
67
  },
68
- appearance === 'compact' && {
68
+ density === 'compact' && {
69
69
  textAlign: 'center',
70
70
  ...t.typographies.heading5SemiBold,
71
71
  },
@@ -74,7 +74,7 @@ const useStyles = ({
74
74
  t.typographies.body2,
75
75
  {
76
76
  color: t.colors.text.muted,
77
- textAlign: appearance === 'compact' ? 'center' : 'left',
77
+ textAlign: density === 'compact' ? 'center' : 'left',
78
78
  },
79
79
  ]),
80
80
  iconPlaceholder: {
@@ -82,7 +82,7 @@ const useStyles = ({
82
82
  height: t.sizes.s32,
83
83
  },
84
84
  }),
85
- [appearance, spacing, hidden],
85
+ [density, spacing, hidden],
86
86
  );
87
87
  };
88
88
 
@@ -91,7 +91,7 @@ export const BottomSheetHeader = ({
91
91
  style,
92
92
  title,
93
93
  description,
94
- appearance = 'compact',
94
+ density = 'compact',
95
95
  spacing = false,
96
96
  ...props
97
97
  }: BottomSheetHeaderProps) => {
@@ -110,9 +110,9 @@ export const BottomSheetHeader = ({
110
110
  const hasIcons = Boolean(onBack || !hideCloseButton);
111
111
 
112
112
  const styles = useStyles({
113
- appearance,
113
+ density,
114
114
  spacing,
115
- hidden: !hasIcons && appearance !== 'compact',
115
+ hidden: !hasIcons && density !== 'compact',
116
116
  });
117
117
 
118
118
  if (!title && !description && !onBack && hideCloseButton) {
@@ -154,7 +154,7 @@ export const BottomSheetHeader = ({
154
154
  />
155
155
  )}
156
156
  </Box>
157
- {appearance === 'compact' && titleComponent}
157
+ {density === 'compact' && titleComponent}
158
158
  <Box style={styles.iconPlaceholder}>
159
159
  {!hideCloseButton && (
160
160
  <IconButton
@@ -170,7 +170,7 @@ export const BottomSheetHeader = ({
170
170
  )}
171
171
  </Box>
172
172
  </Box>
173
- {appearance === 'expanded' && titleComponent}
173
+ {density === 'expanded' && titleComponent}
174
174
  </Box>
175
175
  );
176
176
  };
@@ -7,6 +7,7 @@ import {
7
7
  BottomSheetVirtualizedList as GorhomBottomSheetVirtualizedList,
8
8
  } from '@gorhom/bottom-sheet';
9
9
 
10
+ import { Density } from '@ledgerhq/lumen-utils-shared';
10
11
  import { PropsWithChildren, ReactNode, Ref } from 'react';
11
12
  import { StyledViewProps } from '../../../styles';
12
13
  export type BottomSheetProps = PropsWithChildren & {
@@ -134,10 +135,10 @@ export type BottomSheetProps = PropsWithChildren & {
134
135
 
135
136
  export type BottomSheetHeaderProps = {
136
137
  /**
137
- * The appearance of the header.
138
+ * The density of the header.
138
139
  * @default 'compact'
139
140
  */
140
- appearance?: 'compact' | 'expanded';
141
+ density?: Density;
141
142
  /**
142
143
  * The title of the header.
143
144
  */
@@ -35,6 +35,7 @@ const useStyles = ({
35
35
  size,
36
36
  disabled,
37
37
  pressed,
38
+ hasIcon,
38
39
  iconOnly,
39
40
  isFull,
40
41
  }: {
@@ -42,6 +43,7 @@ const useStyles = ({
42
43
  size: Size;
43
44
  disabled: boolean;
44
45
  pressed: boolean;
46
+ hasIcon: boolean;
45
47
  iconOnly: boolean;
46
48
  isFull: boolean;
47
49
  }) => {
@@ -121,7 +123,7 @@ const useStyles = ({
121
123
  typography,
122
124
  {
123
125
  color: disabled ? t.colors.text.disabled : textColors[appearance],
124
- textAlign: 'left',
126
+ textAlign: hasIcon ? 'left' : 'center',
125
127
  },
126
128
  ]),
127
129
  icon: {
@@ -212,6 +214,7 @@ const BaseButtonContent = ({
212
214
  size,
213
215
  disabled,
214
216
  pressed,
217
+ hasIcon: !!IconProp,
215
218
  iconOnly,
216
219
  isFull,
217
220
  });
@@ -472,6 +472,7 @@ export const CardContentTitle = ({
472
472
  asText: StyleSheet.flatten([
473
473
  t.typographies.body2SemiBold,
474
474
  {
475
+ flexShrink: 1,
475
476
  color: disabled ? t.colors.text.disabled : t.colors.text.base,
476
477
  textAlign: align === 'right' ? 'right' : 'left',
477
478
  },
@@ -538,6 +539,7 @@ export const CardContentDescription = ({
538
539
  asText: StyleSheet.flatten([
539
540
  t.typographies.body3,
540
541
  {
542
+ flexShrink: 1,
541
543
  color: disabled ? t.colors.text.disabled : t.colors.text.muted,
542
544
  textAlign: align === 'right' ? 'right' : 'left',
543
545
  },
@@ -0,0 +1,54 @@
1
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as DotSymbolStories from './DotSymbol.stories';
3
+ import { CustomTabs, Tab } from '../../../../.storybook/components';
4
+
5
+ <Meta title='Communication/DotSymbol' of={DotSymbolStories} />
6
+
7
+ <CustomTabs>
8
+ <Tab label="Overview">
9
+ # DotSymbol
10
+
11
+ ## Introduction
12
+
13
+ DotSymbol positions a small image indicator at a configurable corner of a child element such as MediaImage or Spot. It is commonly used to show a network badge or secondary icon overlapping a primary symbol.
14
+
15
+ ## Anatomy
16
+
17
+ <Canvas of={DotSymbolStories.Base} />
18
+
19
+ - **Wrapper**: Relative container that preserves the child's layout
20
+ - **Child**: The primary element (e.g. MediaImage, Spot)
21
+ - **Dot**: Absolutely-positioned image indicator with border and configurable shape
22
+
23
+ ## Properties
24
+
25
+ ### Overview
26
+
27
+ <Canvas of={DotSymbolStories.Base} />
28
+ <Controls of={DotSymbolStories.Base} />
29
+
30
+ ### Pin
31
+
32
+ Four corner placements are available: `bottom-end` (default), `top-end`, `bottom-start`, and `top-start`.
33
+
34
+ <Canvas of={DotSymbolStories.PinShowcase} />
35
+
36
+ ### Shapes
37
+
38
+ <Canvas of={DotSymbolStories.ShapeShowcase} />
39
+
40
+ - **circle** (default): Fully rounded dot
41
+ - **square**: Rounded corners that scale with size
42
+
43
+ ### Sizes
44
+
45
+ The dot size is driven by the parent MediaImage or Spot size via the mapping helpers `mediaImageDotSizeMap` and `spotDotSizeMap`.
46
+
47
+ <Canvas of={DotSymbolStories.SizeShowcase} />
48
+
49
+ ## Accessibility
50
+
51
+ - Always provide a meaningful `alt` prop on the DotSymbol so the indicator image is announced by screen readers.
52
+ - The child element should carry its own accessibility label independently.
53
+ </Tab>
54
+ </CustomTabs>
@@ -0,0 +1,191 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
+ import { CoinAlert } from '../../Symbols';
3
+ import { MediaImage } from '../MediaImage';
4
+ import { Spot } from '../Spot';
5
+ import { Box } from '../Utility';
6
+ import { DotSymbol, mediaImageDotSizeMap } from './DotSymbol';
7
+
8
+ const meta = {
9
+ component: DotSymbol,
10
+ title: 'Communication/DotSymbol',
11
+ parameters: {
12
+ docs: {
13
+ source: {
14
+ language: 'tsx',
15
+ format: true,
16
+ type: 'code',
17
+ },
18
+ },
19
+ },
20
+ } satisfies Meta<typeof DotSymbol>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ const parentSrc = 'https://crypto-icons.ledger.com/ADA.png';
26
+ const dotSrc = 'https://crypto-icons.ledger.com/BTC.png';
27
+
28
+ export const Base: Story = {
29
+ args: {
30
+ src: dotSrc,
31
+ alt: 'Ethereum network',
32
+ pin: 'bottom-end',
33
+ size: 20,
34
+ shape: 'circle',
35
+ children: <MediaImage src={parentSrc} alt='Cardano' shape='circle' />,
36
+ },
37
+ };
38
+
39
+ export const PinShowcase: Story = {
40
+ args: { src: dotSrc, alt: 'Pin showcase' },
41
+ render: () => (
42
+ <Box lx={{ flexDirection: 'column', alignItems: 'center', gap: 's24' }}>
43
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's32' }}>
44
+ <DotSymbol src={dotSrc} pin='bottom-end'>
45
+ <MediaImage src={parentSrc} shape='circle' />
46
+ </DotSymbol>
47
+ <DotSymbol src={dotSrc} pin='top-end'>
48
+ <MediaImage src={parentSrc} shape='circle' />
49
+ </DotSymbol>
50
+ <DotSymbol src={dotSrc} pin='bottom-start'>
51
+ <MediaImage src={parentSrc} shape='circle' />
52
+ </DotSymbol>
53
+ <DotSymbol src={dotSrc} pin='top-start'>
54
+ <MediaImage src={parentSrc} shape='circle' />
55
+ </DotSymbol>
56
+ </Box>
57
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's32' }}>
58
+ <DotSymbol src={dotSrc} pin='bottom-end'>
59
+ <Spot appearance='icon' icon={CoinAlert} />
60
+ </DotSymbol>
61
+ <DotSymbol src={dotSrc} pin='top-end'>
62
+ <Spot appearance='icon' icon={CoinAlert} />
63
+ </DotSymbol>
64
+ <DotSymbol src={dotSrc} pin='bottom-start'>
65
+ <Spot appearance='icon' icon={CoinAlert} />
66
+ </DotSymbol>
67
+ <DotSymbol src={dotSrc} pin='top-start'>
68
+ <Spot appearance='icon' icon={CoinAlert} />
69
+ </DotSymbol>
70
+ </Box>
71
+ </Box>
72
+ ),
73
+ };
74
+
75
+ export const ShapeShowcase: Story = {
76
+ args: { src: dotSrc, alt: 'Shape showcase' },
77
+ render: () => (
78
+ <Box lx={{ flexDirection: 'column', alignItems: 'center', gap: 's48' }}>
79
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's48' }}>
80
+ <DotSymbol shape='square' src={dotSrc} pin='bottom-end'>
81
+ <MediaImage src={parentSrc} shape='circle' />
82
+ </DotSymbol>
83
+ <DotSymbol shape='circle' src={dotSrc} pin='bottom-end'>
84
+ <MediaImage src={parentSrc} shape='circle' />
85
+ </DotSymbol>
86
+ </Box>
87
+ </Box>
88
+ ),
89
+ };
90
+
91
+ export const SizeShowcase: Story = {
92
+ args: { src: dotSrc, alt: 'Size showcase' },
93
+ render: () => (
94
+ <Box lx={{ flexDirection: 'column', gap: 's40' }}>
95
+ <Box lx={{ flexDirection: 'row', alignItems: 'flex-end', gap: 's24' }}>
96
+ <DotSymbol
97
+ src={dotSrc}
98
+ size={mediaImageDotSizeMap[20]}
99
+ pin='bottom-end'
100
+ >
101
+ <MediaImage src={parentSrc} size={20} shape='circle' />
102
+ </DotSymbol>
103
+ <DotSymbol
104
+ src={dotSrc}
105
+ size={mediaImageDotSizeMap[24]}
106
+ pin='bottom-end'
107
+ >
108
+ <MediaImage src={parentSrc} size={24} shape='circle' />
109
+ </DotSymbol>
110
+ <DotSymbol
111
+ src={dotSrc}
112
+ size={mediaImageDotSizeMap[32]}
113
+ pin='bottom-end'
114
+ >
115
+ <MediaImage src={parentSrc} size={32} shape='circle' />
116
+ </DotSymbol>
117
+ <DotSymbol
118
+ src={dotSrc}
119
+ size={mediaImageDotSizeMap[40]}
120
+ pin='bottom-end'
121
+ >
122
+ <MediaImage src={parentSrc} size={40} shape='circle' />
123
+ </DotSymbol>
124
+ <DotSymbol
125
+ src={dotSrc}
126
+ size={mediaImageDotSizeMap[48]}
127
+ pin='bottom-end'
128
+ >
129
+ <MediaImage src={parentSrc} size={48} shape='circle' />
130
+ </DotSymbol>
131
+ <DotSymbol
132
+ src={dotSrc}
133
+ size={mediaImageDotSizeMap[56]}
134
+ pin='bottom-end'
135
+ >
136
+ <MediaImage src={parentSrc} size={56} shape='circle' />
137
+ </DotSymbol>
138
+ </Box>
139
+ <Box lx={{ flexDirection: 'row', alignItems: 'flex-end', gap: 's24' }}>
140
+ <DotSymbol
141
+ shape='square'
142
+ src={dotSrc}
143
+ size={mediaImageDotSizeMap[20]}
144
+ pin='bottom-end'
145
+ >
146
+ <MediaImage src={parentSrc} size={20} shape='circle' />
147
+ </DotSymbol>
148
+ <DotSymbol
149
+ shape='square'
150
+ src={dotSrc}
151
+ size={mediaImageDotSizeMap[24]}
152
+ pin='bottom-end'
153
+ >
154
+ <MediaImage src={parentSrc} size={24} shape='circle' />
155
+ </DotSymbol>
156
+ <DotSymbol
157
+ shape='square'
158
+ src={dotSrc}
159
+ size={mediaImageDotSizeMap[32]}
160
+ pin='bottom-end'
161
+ >
162
+ <MediaImage src={parentSrc} size={32} shape='circle' />
163
+ </DotSymbol>
164
+ <DotSymbol
165
+ shape='square'
166
+ src={dotSrc}
167
+ size={mediaImageDotSizeMap[40]}
168
+ pin='bottom-end'
169
+ >
170
+ <MediaImage src={parentSrc} size={40} shape='circle' />
171
+ </DotSymbol>
172
+ <DotSymbol
173
+ shape='square'
174
+ src={dotSrc}
175
+ size={mediaImageDotSizeMap[48]}
176
+ pin='bottom-end'
177
+ >
178
+ <MediaImage src={parentSrc} size={48} shape='circle' />
179
+ </DotSymbol>
180
+ <DotSymbol
181
+ shape='square'
182
+ src={dotSrc}
183
+ size={mediaImageDotSizeMap[56]}
184
+ pin='bottom-end'
185
+ >
186
+ <MediaImage src={parentSrc} size={56} shape='circle' />
187
+ </DotSymbol>
188
+ </Box>
189
+ </Box>
190
+ ),
191
+ };
@@ -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
+ });