@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.
Files changed (124) hide show
  1. package/dist/module/index.js +1 -0
  2. package/dist/module/index.js.map +1 -1
  3. package/dist/module/lib/Components/Avatar/Avatar.js +22 -23
  4. package/dist/module/lib/Components/Avatar/Avatar.js.map +1 -1
  5. package/dist/module/lib/Components/Avatar/Avatar.test.js +17 -23
  6. package/dist/module/lib/Components/Avatar/Avatar.test.js.map +1 -1
  7. package/dist/module/lib/Components/DotCount/DotCount.js +128 -0
  8. package/dist/module/lib/Components/DotCount/DotCount.js.map +1 -0
  9. package/dist/module/lib/Components/DotCount/DotCount.mdx +86 -0
  10. package/dist/module/lib/Components/DotCount/DotCount.stories.js +172 -0
  11. package/dist/module/lib/Components/DotCount/DotCount.stories.js.map +1 -0
  12. package/dist/module/lib/Components/DotCount/DotCount.test.js +174 -0
  13. package/dist/module/lib/Components/DotCount/DotCount.test.js.map +1 -0
  14. package/dist/module/lib/Components/DotCount/index.js +5 -0
  15. package/dist/module/lib/Components/DotCount/index.js.map +1 -0
  16. package/dist/module/lib/Components/DotCount/types.js +4 -0
  17. package/dist/module/lib/Components/DotCount/types.js.map +1 -0
  18. package/dist/module/lib/Components/DotIcon/DotIcon.js +134 -0
  19. package/dist/module/lib/Components/DotIcon/DotIcon.js.map +1 -0
  20. package/dist/module/lib/Components/DotIcon/DotIcon.mdx +56 -0
  21. package/dist/module/lib/Components/DotIcon/DotIcon.stories.js +217 -0
  22. package/dist/module/lib/Components/DotIcon/DotIcon.stories.js.map +1 -0
  23. package/dist/module/lib/Components/DotIcon/DotIcon.test.js +238 -0
  24. package/dist/module/lib/Components/DotIcon/DotIcon.test.js.map +1 -0
  25. package/dist/module/lib/Components/DotIcon/index.js +5 -0
  26. package/dist/module/lib/Components/DotIcon/index.js.map +1 -0
  27. package/dist/module/lib/Components/DotIcon/types.js +4 -0
  28. package/dist/module/lib/Components/DotIcon/types.js.map +1 -0
  29. package/dist/module/lib/Components/DotIndicator/DotIndicator.js +89 -0
  30. package/dist/module/lib/Components/DotIndicator/DotIndicator.js.map +1 -0
  31. package/dist/module/lib/Components/DotIndicator/DotIndicator.mdx +82 -0
  32. package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js +84 -0
  33. package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js.map +1 -0
  34. package/dist/module/lib/Components/DotIndicator/DotIndicator.test.js +136 -0
  35. package/dist/module/lib/Components/DotIndicator/DotIndicator.test.js.map +1 -0
  36. package/dist/module/lib/Components/DotIndicator/index.js +5 -0
  37. package/dist/module/lib/Components/DotIndicator/index.js.map +1 -0
  38. package/dist/module/lib/Components/DotIndicator/types.js +4 -0
  39. package/dist/module/lib/Components/DotIndicator/types.js.map +1 -0
  40. package/dist/module/lib/Components/DotSymbol/DotSymbol.js +29 -22
  41. package/dist/module/lib/Components/DotSymbol/DotSymbol.js.map +1 -1
  42. package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js +31 -9
  43. package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js.map +1 -1
  44. package/dist/module/lib/Components/MediaImage/MediaImage.js +2 -1
  45. package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
  46. package/dist/module/lib/Components/MediaImage/MediaImage.stories.js +4 -0
  47. package/dist/module/lib/Components/MediaImage/MediaImage.stories.js.map +1 -1
  48. package/dist/module/lib/Components/Spinner/Spinner.js +6 -1
  49. package/dist/module/lib/Components/Spinner/Spinner.js.map +1 -1
  50. package/dist/module/lib/Components/TabBar/TabBar.js +16 -13
  51. package/dist/module/lib/Components/TabBar/TabBar.js.map +1 -1
  52. package/dist/module/lib/Components/Tag/Tag.js +2 -0
  53. package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
  54. package/dist/module/lib/Components/Tag/Tag.stories.js +8 -1
  55. package/dist/module/lib/Components/Tag/Tag.stories.js.map +1 -1
  56. package/dist/module/lib/Components/index.js +3 -0
  57. package/dist/module/lib/Components/index.js.map +1 -1
  58. package/dist/typescript/src/index.d.ts +1 -0
  59. package/dist/typescript/src/index.d.ts.map +1 -1
  60. package/dist/typescript/src/lib/Components/Avatar/Avatar.d.ts +1 -1
  61. package/dist/typescript/src/lib/Components/Avatar/Avatar.d.ts.map +1 -1
  62. package/dist/typescript/src/lib/Components/DotCount/DotCount.d.ts +3 -0
  63. package/dist/typescript/src/lib/Components/DotCount/DotCount.d.ts.map +1 -0
  64. package/dist/typescript/src/lib/Components/DotCount/index.d.ts +3 -0
  65. package/dist/typescript/src/lib/Components/DotCount/index.d.ts.map +1 -0
  66. package/dist/typescript/src/lib/Components/DotCount/types.d.ts +40 -0
  67. package/dist/typescript/src/lib/Components/DotCount/types.d.ts.map +1 -0
  68. package/dist/typescript/src/lib/Components/DotIcon/DotIcon.d.ts +30 -0
  69. package/dist/typescript/src/lib/Components/DotIcon/DotIcon.d.ts.map +1 -0
  70. package/dist/typescript/src/lib/Components/DotIcon/index.d.ts +3 -0
  71. package/dist/typescript/src/lib/Components/DotIcon/index.d.ts.map +1 -0
  72. package/dist/typescript/src/lib/Components/DotIcon/types.d.ts +40 -0
  73. package/dist/typescript/src/lib/Components/DotIcon/types.d.ts.map +1 -0
  74. package/dist/typescript/src/lib/Components/DotIndicator/DotIndicator.d.ts +3 -0
  75. package/dist/typescript/src/lib/Components/DotIndicator/DotIndicator.d.ts.map +1 -0
  76. package/dist/typescript/src/lib/Components/DotIndicator/index.d.ts +3 -0
  77. package/dist/typescript/src/lib/Components/DotIndicator/index.d.ts.map +1 -0
  78. package/dist/typescript/src/lib/Components/DotIndicator/types.d.ts +25 -0
  79. package/dist/typescript/src/lib/Components/DotIndicator/types.d.ts.map +1 -0
  80. package/dist/typescript/src/lib/Components/DotSymbol/DotSymbol.d.ts.map +1 -1
  81. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
  82. package/dist/typescript/src/lib/Components/MediaImage/types.d.ts +1 -1
  83. package/dist/typescript/src/lib/Components/MediaImage/types.d.ts.map +1 -1
  84. package/dist/typescript/src/lib/Components/Spinner/Spinner.d.ts +1 -1
  85. package/dist/typescript/src/lib/Components/Spinner/Spinner.d.ts.map +1 -1
  86. package/dist/typescript/src/lib/Components/TabBar/TabBar.d.ts.map +1 -1
  87. package/dist/typescript/src/lib/Components/Tag/Tag.d.ts.map +1 -1
  88. package/dist/typescript/src/lib/Components/Tag/types.d.ts +1 -1
  89. package/dist/typescript/src/lib/Components/Tag/types.d.ts.map +1 -1
  90. package/dist/typescript/src/lib/Components/index.d.ts +3 -0
  91. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  92. package/package.json +1 -1
  93. package/src/index.ts +1 -0
  94. package/src/lib/Components/Avatar/Avatar.test.tsx +17 -27
  95. package/src/lib/Components/Avatar/Avatar.tsx +24 -21
  96. package/src/lib/Components/DotCount/DotCount.mdx +86 -0
  97. package/src/lib/Components/DotCount/DotCount.stories.tsx +124 -0
  98. package/src/lib/Components/DotCount/DotCount.test.tsx +150 -0
  99. package/src/lib/Components/DotCount/DotCount.tsx +130 -0
  100. package/src/lib/Components/DotCount/index.ts +2 -0
  101. package/src/lib/Components/DotCount/types.ts +40 -0
  102. package/src/lib/Components/DotIcon/DotIcon.mdx +56 -0
  103. package/src/lib/Components/DotIcon/DotIcon.stories.tsx +154 -0
  104. package/src/lib/Components/DotIcon/DotIcon.test.tsx +224 -0
  105. package/src/lib/Components/DotIcon/DotIcon.tsx +146 -0
  106. package/src/lib/Components/DotIcon/index.ts +6 -0
  107. package/src/lib/Components/DotIcon/types.ts +44 -0
  108. package/src/lib/Components/DotIndicator/DotIndicator.mdx +82 -0
  109. package/src/lib/Components/DotIndicator/DotIndicator.stories.tsx +67 -0
  110. package/src/lib/Components/DotIndicator/DotIndicator.test.tsx +132 -0
  111. package/src/lib/Components/DotIndicator/DotIndicator.tsx +97 -0
  112. package/src/lib/Components/DotIndicator/index.ts +2 -0
  113. package/src/lib/Components/DotIndicator/types.ts +25 -0
  114. package/src/lib/Components/DotSymbol/DotSymbol.stories.tsx +26 -7
  115. package/src/lib/Components/DotSymbol/DotSymbol.tsx +22 -23
  116. package/src/lib/Components/MediaImage/MediaImage.stories.tsx +1 -0
  117. package/src/lib/Components/MediaImage/MediaImage.tsx +3 -1
  118. package/src/lib/Components/MediaImage/types.ts +1 -1
  119. package/src/lib/Components/Spinner/Spinner.tsx +6 -2
  120. package/src/lib/Components/TabBar/TabBar.tsx +17 -16
  121. package/src/lib/Components/Tag/Tag.stories.tsx +11 -1
  122. package/src/lib/Components/Tag/Tag.tsx +2 -0
  123. package/src/lib/Components/Tag/types.ts +8 -1
  124. package/src/lib/Components/index.ts +3 -0
@@ -0,0 +1,130 @@
1
+ import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
2
+ import { StyleSheet } from 'react-native';
3
+ import { useStyleSheet } from '../../../styles';
4
+ import { Box, Text } from '../Utility';
5
+ import type { DotCountProps } from './types';
6
+
7
+ export function DotCount({
8
+ value,
9
+ size = 'md',
10
+ max = 99,
11
+ appearance = 'base',
12
+ disabled: disabledProp = false,
13
+ lx = {},
14
+ style,
15
+ children,
16
+ accessibilityLabel,
17
+ ref,
18
+ ...props
19
+ }: DotCountProps) {
20
+ const disabled = useDisabledContext({
21
+ consumerName: 'DotCount',
22
+ mergeWith: { disabled: disabledProp },
23
+ });
24
+
25
+ const styles = useStyles({
26
+ size,
27
+ appearance,
28
+ disabled,
29
+ pinned: !!children,
30
+ });
31
+
32
+ const cappedMax = Math.max(1, Math.min(max, 99));
33
+
34
+ return (
35
+ <Box
36
+ ref={ref}
37
+ lx={lx}
38
+ style={StyleSheet.flatten([
39
+ children ? { position: 'relative' } : undefined,
40
+ style,
41
+ ])}
42
+ {...props}
43
+ >
44
+ <Box
45
+ style={styles.container}
46
+ accessibilityRole='image'
47
+ accessibilityLabel={accessibilityLabel}
48
+ accessible={!!accessibilityLabel}
49
+ pointerEvents='none'
50
+ >
51
+ {value > 0 && (
52
+ <Text style={styles.text} allowFontScaling={false}>
53
+ {value <= cappedMax ? value : `${cappedMax}+`}
54
+ </Text>
55
+ )}
56
+ </Box>
57
+ {children}
58
+ </Box>
59
+ );
60
+ }
61
+
62
+ const useStyles = ({
63
+ size,
64
+ appearance,
65
+ disabled,
66
+ pinned,
67
+ }: {
68
+ size: NonNullable<DotCountProps['size']>;
69
+ appearance: NonNullable<DotCountProps['appearance']>;
70
+ disabled: boolean;
71
+ pinned: boolean;
72
+ }) => {
73
+ return useStyleSheet(
74
+ (t) => {
75
+ const sizeMap = {
76
+ lg: {
77
+ minWidth: t.sizes.s24,
78
+ minHeight: t.sizes.s24,
79
+ paddingHorizontal: t.spacings.s8,
80
+ paddingVertical: t.spacings.s2,
81
+ },
82
+ md: {
83
+ minHeight: t.sizes.s16,
84
+ minWidth: t.sizes.s16,
85
+ paddingHorizontal: t.spacings.s4,
86
+ },
87
+ };
88
+
89
+ const bgColorMap = {
90
+ base: { backgroundColor: t.colors.bg.interactive },
91
+ red: { backgroundColor: t.colors.bg.errorStrong },
92
+ };
93
+
94
+ const textMap = {
95
+ lg: { ...t.typographies.body2SemiBold },
96
+ md: { ...t.typographies.body4SemiBold },
97
+ };
98
+
99
+ const textColorMap = {
100
+ base: { color: t.colors.text.onInteractive },
101
+ red: { color: t.colors.text.onErrorStrong },
102
+ };
103
+
104
+ return {
105
+ container: {
106
+ alignItems: 'center',
107
+ justifyContent: 'center',
108
+ borderRadius: t.borderRadius.full,
109
+ ...(pinned && {
110
+ position: 'absolute',
111
+ top: t.spacings.s0,
112
+ right: t.spacings.s0,
113
+ zIndex: 1,
114
+ }),
115
+ ...sizeMap[size],
116
+ ...(disabled
117
+ ? { backgroundColor: t.colors.bg.disabled }
118
+ : { ...bgColorMap[appearance] }),
119
+ },
120
+ text: {
121
+ ...textMap[size],
122
+ ...(disabled
123
+ ? { color: t.colors.text.disabled }
124
+ : { ...textColorMap[appearance] }),
125
+ },
126
+ };
127
+ },
128
+ [size, appearance, disabled, pinned],
129
+ );
130
+ };
@@ -0,0 +1,2 @@
1
+ export * from './DotCount';
2
+ export * from './types';
@@ -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,56 @@
1
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as DotIconStories from './DotIcon.stories';
3
+
4
+ <Meta title='Communication/DotIcon' of={DotIconStories} />
5
+
6
+ # DotIcon
7
+
8
+ ## Introduction
9
+
10
+ DotIcon positions a small icon indicator at a configurable corner of a child element such as MediaImage or Spot. The dot background uses a semantic color (`success`, `muted`, or `error`) to convey meaning at a glance.
11
+ > View in [Figma](https://www.figma.com/design/zSkvGGiqcnhywp2l3HTHxA/1.-Symbol-Library?node-id=6159-1866).
12
+
13
+ ## Anatomy
14
+
15
+ <Canvas of={DotIconStories.Base} />
16
+
17
+ - **Wrapper**: Relative container that preserves the child's layout
18
+ - **Child**: The primary element (e.g. MediaImage, Spot)
19
+ - **Dot**: Absolutely-positioned indicator with a semantic background and an icon inside
20
+
21
+ ## Properties
22
+
23
+ ### Overview
24
+
25
+ <Canvas of={DotIconStories.Base} />
26
+ <Controls of={DotIconStories.Base} />
27
+
28
+ ### Pin
29
+
30
+ Four corner placements are available: `bottom-end` (default), `top-end`, `bottom-start`, and `top-start`.
31
+
32
+ <Canvas of={DotIconStories.PinShowcase} />
33
+
34
+ ### Shapes
35
+
36
+ <Canvas of={DotIconStories.ShapeShowcase} />
37
+
38
+ - **circle** (default): Fully rounded dot
39
+ - **square**: Rounded corners that scale with size
40
+
41
+ ### Appearances
42
+
43
+ The `appearance` prop controls the semantic background color of the dot: `success`, `muted`, or `error`.
44
+
45
+ <Canvas of={DotIconStories.AppearanceShowcase} />
46
+
47
+ ### Sizes
48
+
49
+ The dot size is driven by the parent MediaImage or Spot size via the mapping helpers `mediaImageDotIconSizeMap` and `spotDotIconSizeMap`. Allowed sizes are `16`, `20`, and `24`. The icon size is handled internally by the component.
50
+
51
+ <Canvas of={DotIconStories.SizeShowcase} />
52
+
53
+ ## Accessibility
54
+
55
+ - The icon is purely decorative; the child element should carry its own accessibility label.
56
+ - Pair semantic appearances with meaningful icons so the state can be understood without relying on color alone.
@@ -0,0 +1,154 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
+ import { ArrowDown, ArrowUp, Close, Link, Star } from '../../Symbols';
3
+ import { MediaImage } from '../MediaImage';
4
+ import { Spinner } from '../Spinner';
5
+ import { Box } from '../Utility';
6
+ import { DotIcon, mediaImageDotIconSizeMap } from './DotIcon';
7
+
8
+ const meta = {
9
+ component: DotIcon,
10
+ title: 'Communication/DotIcon',
11
+ parameters: {
12
+ docs: {
13
+ source: {
14
+ language: 'tsx',
15
+ format: true,
16
+ type: 'code',
17
+ },
18
+ },
19
+ },
20
+ } satisfies Meta<typeof DotIcon>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ const parentSrc = 'https://crypto-icons.ledger.com/ADA.png';
26
+
27
+ export const Base: Story = {
28
+ args: {
29
+ appearance: 'success',
30
+ icon: ArrowDown,
31
+ pin: 'bottom-end',
32
+ size: mediaImageDotIconSizeMap[48],
33
+ shape: 'circle',
34
+ children: (
35
+ <MediaImage src={parentSrc} alt='Cardano' size={48} shape='circle' />
36
+ ),
37
+ },
38
+ };
39
+
40
+ export const PinShowcase: Story = {
41
+ args: { appearance: 'success', icon: ArrowDown },
42
+ render: () => (
43
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's32' }}>
44
+ <DotIcon appearance='success' icon={ArrowDown} pin='bottom-end'>
45
+ <MediaImage src={parentSrc} shape='circle' />
46
+ </DotIcon>
47
+ <DotIcon appearance='success' icon={ArrowDown} pin='top-end'>
48
+ <MediaImage src={parentSrc} shape='circle' />
49
+ </DotIcon>
50
+ <DotIcon appearance='success' icon={ArrowDown} pin='bottom-start'>
51
+ <MediaImage src={parentSrc} shape='circle' />
52
+ </DotIcon>
53
+ <DotIcon appearance='success' icon={ArrowDown} pin='top-start'>
54
+ <MediaImage src={parentSrc} shape='circle' />
55
+ </DotIcon>
56
+ </Box>
57
+ ),
58
+ };
59
+
60
+ export const ShapeShowcase: Story = {
61
+ args: { appearance: 'muted', icon: ArrowDown },
62
+ render: () => (
63
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's48' }}>
64
+ <DotIcon
65
+ appearance='muted'
66
+ icon={ArrowDown}
67
+ shape='circle'
68
+ pin='bottom-end'
69
+ >
70
+ <MediaImage src={parentSrc} size={48} shape='circle' />
71
+ </DotIcon>
72
+ <DotIcon
73
+ appearance='muted'
74
+ icon={ArrowDown}
75
+ shape='square'
76
+ pin='bottom-end'
77
+ >
78
+ <MediaImage src={parentSrc} size={48} shape='square' />
79
+ </DotIcon>
80
+ </Box>
81
+ ),
82
+ };
83
+
84
+ export const AppearanceShowcase: Story = {
85
+ args: { appearance: 'success', icon: ArrowDown },
86
+ render: () => (
87
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's32' }}>
88
+ <DotIcon
89
+ appearance='success'
90
+ icon={ArrowDown}
91
+ size={mediaImageDotIconSizeMap[48]}
92
+ pin='bottom-end'
93
+ >
94
+ <MediaImage src={parentSrc} size={48} shape='circle' />
95
+ </DotIcon>
96
+ <DotIcon
97
+ appearance='muted'
98
+ icon={ArrowUp}
99
+ size={mediaImageDotIconSizeMap[48]}
100
+ pin='bottom-end'
101
+ >
102
+ <MediaImage src={parentSrc} size={48} shape='circle' />
103
+ </DotIcon>
104
+ <DotIcon
105
+ appearance='error'
106
+ icon={Close}
107
+ size={mediaImageDotIconSizeMap[48]}
108
+ pin='bottom-end'
109
+ >
110
+ <MediaImage src={parentSrc} size={48} shape='circle' />
111
+ </DotIcon>
112
+ </Box>
113
+ ),
114
+ };
115
+
116
+ export const SizeShowcase: Story = {
117
+ args: { appearance: 'muted', icon: Link },
118
+ render: () => (
119
+ <Box lx={{ flexDirection: 'row', alignItems: 'flex-end', gap: 's24' }}>
120
+ <DotIcon
121
+ appearance='muted'
122
+ icon={Link}
123
+ size={mediaImageDotIconSizeMap[40]}
124
+ pin='bottom-end'
125
+ >
126
+ <MediaImage src={parentSrc} size={40} shape='circle' />
127
+ </DotIcon>
128
+ <DotIcon
129
+ appearance='success'
130
+ icon={Star}
131
+ size={mediaImageDotIconSizeMap[48]}
132
+ pin='bottom-end'
133
+ >
134
+ <MediaImage src={parentSrc} size={48} shape='circle' />
135
+ </DotIcon>
136
+ <DotIcon
137
+ appearance='success'
138
+ icon={ArrowDown}
139
+ size={mediaImageDotIconSizeMap[56]}
140
+ pin='bottom-end'
141
+ >
142
+ <MediaImage src={parentSrc} size={56} shape='circle' />
143
+ </DotIcon>
144
+ <DotIcon
145
+ appearance='muted'
146
+ icon={Spinner}
147
+ size={mediaImageDotIconSizeMap[64]}
148
+ pin='bottom-end'
149
+ >
150
+ <MediaImage src={parentSrc} size={64} shape='circle' />
151
+ </DotIcon>
152
+ </Box>
153
+ ),
154
+ };
@@ -0,0 +1,224 @@
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 type { View } from 'react-native';
6
+ import { Text } from 'react-native';
7
+ import { ArrowDown } from '../../Symbols';
8
+ import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
9
+ import { DotIcon } from './DotIcon';
10
+
11
+ const { colors, sizes, borderRadius } = ledgerLiveThemes.dark;
12
+
13
+ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
14
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
15
+ {children}
16
+ </ThemeProvider>
17
+ );
18
+
19
+ describe('DotIcon Component', () => {
20
+ describe('Rendering', () => {
21
+ it('should render children', () => {
22
+ const { getByText } = render(
23
+ <TestWrapper>
24
+ <DotIcon testID='dot-icon' appearance='success' icon={ArrowDown}>
25
+ <Text>Child</Text>
26
+ </DotIcon>
27
+ </TestWrapper>,
28
+ );
29
+
30
+ expect(getByText('Child')).toBeTruthy();
31
+ });
32
+
33
+ it('should render without children', () => {
34
+ const { getByTestId } = render(
35
+ <TestWrapper>
36
+ <DotIcon testID='dot-icon' appearance='success' icon={ArrowDown} />
37
+ </TestWrapper>,
38
+ );
39
+
40
+ expect(getByTestId('dot-icon')).toBeTruthy();
41
+ });
42
+
43
+ it('should have correct displayName', () => {
44
+ expect(DotIcon.displayName).toBe('DotIcon');
45
+ });
46
+ });
47
+
48
+ describe('Appearances', () => {
49
+ it.each([
50
+ {
51
+ appearance: 'success' as const,
52
+ expectedColor: colors.bg.successStrong,
53
+ },
54
+ { appearance: 'muted' as const, expectedColor: colors.bg.mutedStrong },
55
+ { appearance: 'error' as const, expectedColor: colors.bg.errorStrong },
56
+ ])(
57
+ 'should apply $appearance background color to the dot',
58
+ ({ appearance, expectedColor }) => {
59
+ const { getByTestId } = render(
60
+ <TestWrapper>
61
+ <DotIcon
62
+ testID='dot-icon'
63
+ appearance={appearance}
64
+ icon={ArrowDown}
65
+ />
66
+ </TestWrapper>,
67
+ );
68
+
69
+ const dotView = getByTestId('dot-icon-dot');
70
+ expect(dotView.props.style.backgroundColor).toBe(expectedColor);
71
+ },
72
+ );
73
+ });
74
+
75
+ describe('Sizes', () => {
76
+ it.each([
77
+ { size: 16 as const, expectedSize: sizes.s16 },
78
+ { size: 20 as const, expectedSize: sizes.s20 },
79
+ { size: 24 as const, expectedSize: sizes.s24 },
80
+ ])(
81
+ 'should apply correct width and height for size $size',
82
+ ({ size, expectedSize }) => {
83
+ const { getByTestId } = render(
84
+ <TestWrapper>
85
+ <DotIcon
86
+ testID='dot-icon'
87
+ appearance='success'
88
+ icon={ArrowDown}
89
+ size={size}
90
+ />
91
+ </TestWrapper>,
92
+ );
93
+
94
+ const dotView = getByTestId('dot-icon-dot');
95
+ expect(dotView.props.style.width).toBe(expectedSize);
96
+ expect(dotView.props.style.height).toBe(expectedSize);
97
+ },
98
+ );
99
+ });
100
+
101
+ describe('Pins', () => {
102
+ it.each([
103
+ {
104
+ pin: 'bottom-end' as const,
105
+ verticalKey: 'bottom',
106
+ horizontalKey: 'right',
107
+ },
108
+ {
109
+ pin: 'bottom-start' as const,
110
+ verticalKey: 'bottom',
111
+ horizontalKey: 'left',
112
+ },
113
+ {
114
+ pin: 'top-end' as const,
115
+ verticalKey: 'top',
116
+ horizontalKey: 'right',
117
+ },
118
+ {
119
+ pin: 'top-start' as const,
120
+ verticalKey: 'top',
121
+ horizontalKey: 'left',
122
+ },
123
+ ])('should position dot at $pin', ({ pin, verticalKey, horizontalKey }) => {
124
+ const { getByTestId } = render(
125
+ <TestWrapper>
126
+ <DotIcon
127
+ testID='dot-icon'
128
+ appearance='success'
129
+ icon={ArrowDown}
130
+ pin={pin}
131
+ />
132
+ </TestWrapper>,
133
+ );
134
+
135
+ const dotView = getByTestId('dot-icon-dot');
136
+ expect(dotView.props.style[verticalKey]).toBe(-3);
137
+ expect(dotView.props.style[horizontalKey]).toBe(-3);
138
+ });
139
+ });
140
+
141
+ describe('Shapes', () => {
142
+ it('should apply full border radius for circle shape', () => {
143
+ const { getByTestId } = render(
144
+ <TestWrapper>
145
+ <DotIcon
146
+ testID='dot-icon'
147
+ appearance='success'
148
+ icon={ArrowDown}
149
+ shape='circle'
150
+ />
151
+ </TestWrapper>,
152
+ );
153
+
154
+ const dotView = getByTestId('dot-icon-dot');
155
+ expect(dotView.props.style.borderRadius).toBe(borderRadius.full);
156
+ });
157
+
158
+ it.each([
159
+ { size: 16 as const, expectedRadius: 5 },
160
+ { size: 20 as const, expectedRadius: 6 },
161
+ { size: 24 as const, expectedRadius: 8 },
162
+ ])(
163
+ 'should apply correct border radius for square shape at size $size',
164
+ ({ size, expectedRadius }) => {
165
+ const { getByTestId } = render(
166
+ <TestWrapper>
167
+ <DotIcon
168
+ testID='dot-icon'
169
+ appearance='success'
170
+ icon={ArrowDown}
171
+ shape='square'
172
+ size={size}
173
+ />
174
+ </TestWrapper>,
175
+ );
176
+
177
+ const dotView = getByTestId('dot-icon-dot');
178
+ expect(dotView.props.style.borderRadius).toBe(expectedRadius);
179
+ },
180
+ );
181
+ });
182
+
183
+ describe('Ref forwarding', () => {
184
+ it('should forward ref to the root element', () => {
185
+ const ref = createRef<View>();
186
+
187
+ render(
188
+ <TestWrapper>
189
+ <DotIcon ref={ref} appearance='success' icon={ArrowDown} />
190
+ </TestWrapper>,
191
+ );
192
+
193
+ expect(ref.current).not.toBeNull();
194
+ });
195
+ });
196
+
197
+ describe('Styling', () => {
198
+ it('should apply custom styles', () => {
199
+ const { getByTestId } = render(
200
+ <TestWrapper>
201
+ <DotIcon
202
+ testID='dot-icon'
203
+ appearance='success'
204
+ icon={ArrowDown}
205
+ style={{ marginTop: 10 }}
206
+ />
207
+ </TestWrapper>,
208
+ );
209
+
210
+ const root = getByTestId('dot-icon');
211
+ expect(root.props.style.marginTop).toBe(10);
212
+ });
213
+
214
+ it('should pass additional props', () => {
215
+ const { getByTestId } = render(
216
+ <TestWrapper>
217
+ <DotIcon testID='custom-dot' appearance='success' icon={ArrowDown} />
218
+ </TestWrapper>,
219
+ );
220
+
221
+ expect(getByTestId('custom-dot')).toBeTruthy();
222
+ });
223
+ });
224
+ });