@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,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,6 @@
1
+ export {
2
+ DotIcon,
3
+ mediaImageDotIconSizeMap,
4
+ spotDotIconSizeMap,
5
+ } from './DotIcon';
6
+ export * from './types';
@@ -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,2 @@
1
+ export * from './DotIndicator';
2
+ export * from './types';
@@ -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='circle' />
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='circle' />
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='circle' />
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='circle' />
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='circle' />
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='circle' />
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>