@ledgerhq/lumen-ui-rnative 0.1.32 → 0.1.34

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 (77) hide show
  1. package/dist/module/lib/Components/BaseTag/BaseTag.js +122 -0
  2. package/dist/module/lib/Components/BaseTag/BaseTag.js.map +1 -0
  3. package/dist/module/lib/Components/BaseTag/BaseTag.test.js +144 -0
  4. package/dist/module/lib/Components/BaseTag/BaseTag.test.js.map +1 -0
  5. package/dist/module/lib/Components/BaseTag/index.js +5 -0
  6. package/dist/module/lib/Components/BaseTag/index.js.map +1 -0
  7. package/dist/module/lib/Components/BaseTag/types.js +4 -0
  8. package/dist/module/lib/Components/BaseTag/types.js.map +1 -0
  9. package/dist/module/lib/Components/MediaImage/MediaImage.js +5 -1
  10. package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
  11. package/dist/module/lib/Components/MediaTag/MediaTag.js +39 -0
  12. package/dist/module/lib/Components/MediaTag/MediaTag.js.map +1 -0
  13. package/dist/module/lib/Components/MediaTag/MediaTag.mdx +161 -0
  14. package/dist/module/lib/Components/MediaTag/MediaTag.stories.js +122 -0
  15. package/dist/module/lib/Components/MediaTag/MediaTag.stories.js.map +1 -0
  16. package/dist/module/lib/Components/MediaTag/MediaTag.test.js +30 -0
  17. package/dist/module/lib/Components/MediaTag/MediaTag.test.js.map +1 -0
  18. package/dist/module/lib/Components/MediaTag/index.js +5 -0
  19. package/dist/module/lib/Components/MediaTag/index.js.map +1 -0
  20. package/dist/module/lib/Components/MediaTag/types.js +4 -0
  21. package/dist/module/lib/Components/MediaTag/types.js.map +1 -0
  22. package/dist/module/lib/Components/Tag/Tag.js +10 -95
  23. package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
  24. package/dist/module/lib/Components/Tag/Tag.mdx +1 -79
  25. package/dist/module/lib/Components/Tag/Tag.stories.js +8 -1
  26. package/dist/module/lib/Components/Tag/Tag.stories.js.map +1 -1
  27. package/dist/module/lib/Components/Tag/Tag.test.js +69 -0
  28. package/dist/module/lib/Components/Tag/Tag.test.js.map +1 -0
  29. package/dist/module/lib/Components/index.js +1 -0
  30. package/dist/module/lib/Components/index.js.map +1 -1
  31. package/dist/module/lib/Symbols/Icons/Lightbulb.js +50 -0
  32. package/dist/module/lib/Symbols/Icons/Lightbulb.js.map +1 -0
  33. package/dist/module/lib/Symbols/index.js +1 -0
  34. package/dist/module/lib/Symbols/index.js.map +1 -1
  35. package/dist/typescript/src/lib/Components/BaseTag/BaseTag.d.ts +3 -0
  36. package/dist/typescript/src/lib/Components/BaseTag/BaseTag.d.ts.map +1 -0
  37. package/dist/typescript/src/lib/Components/BaseTag/index.d.ts +3 -0
  38. package/dist/typescript/src/lib/Components/BaseTag/index.d.ts.map +1 -0
  39. package/dist/typescript/src/lib/Components/BaseTag/types.d.ts +10 -0
  40. package/dist/typescript/src/lib/Components/BaseTag/types.d.ts.map +1 -0
  41. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
  42. package/dist/typescript/src/lib/Components/MediaTag/MediaTag.d.ts +26 -0
  43. package/dist/typescript/src/lib/Components/MediaTag/MediaTag.d.ts.map +1 -0
  44. package/dist/typescript/src/lib/Components/MediaTag/index.d.ts +3 -0
  45. package/dist/typescript/src/lib/Components/MediaTag/index.d.ts.map +1 -0
  46. package/dist/typescript/src/lib/Components/MediaTag/types.d.ts +10 -0
  47. package/dist/typescript/src/lib/Components/MediaTag/types.d.ts.map +1 -0
  48. package/dist/typescript/src/lib/Components/Tag/Tag.d.ts +1 -1
  49. package/dist/typescript/src/lib/Components/Tag/Tag.d.ts.map +1 -1
  50. package/dist/typescript/src/lib/Components/Tag/types.d.ts +1 -1
  51. package/dist/typescript/src/lib/Components/Tag/types.d.ts.map +1 -1
  52. package/dist/typescript/src/lib/Components/index.d.ts +1 -0
  53. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  54. package/dist/typescript/src/lib/Symbols/Icons/Lightbulb.d.ts +35 -0
  55. package/dist/typescript/src/lib/Symbols/Icons/Lightbulb.d.ts.map +1 -0
  56. package/dist/typescript/src/lib/Symbols/index.d.ts +1 -0
  57. package/dist/typescript/src/lib/Symbols/index.d.ts.map +1 -1
  58. package/package.json +2 -2
  59. package/src/lib/Components/BaseTag/BaseTag.test.tsx +137 -0
  60. package/src/lib/Components/BaseTag/BaseTag.tsx +152 -0
  61. package/src/lib/Components/BaseTag/index.ts +2 -0
  62. package/src/lib/Components/BaseTag/types.ts +11 -0
  63. package/src/lib/Components/MediaImage/MediaImage.tsx +5 -1
  64. package/src/lib/Components/MediaTag/MediaTag.mdx +161 -0
  65. package/src/lib/Components/MediaTag/MediaTag.stories.tsx +112 -0
  66. package/src/lib/Components/MediaTag/MediaTag.test.tsx +27 -0
  67. package/src/lib/Components/MediaTag/MediaTag.tsx +36 -0
  68. package/src/lib/Components/MediaTag/index.ts +2 -0
  69. package/src/lib/Components/MediaTag/types.ts +10 -0
  70. package/src/lib/Components/Tag/Tag.mdx +1 -79
  71. package/src/lib/Components/Tag/Tag.stories.tsx +3 -0
  72. package/src/lib/Components/Tag/Tag.test.tsx +51 -0
  73. package/src/lib/Components/Tag/Tag.tsx +12 -119
  74. package/src/lib/Components/Tag/types.ts +2 -1
  75. package/src/lib/Components/index.ts +1 -0
  76. package/src/lib/Symbols/Icons/Lightbulb.tsx +45 -0
  77. package/src/lib/Symbols/index.ts +1 -0
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
2
2
  import { Image, StyleSheet } from 'react-native';
3
3
  import { useStyleSheet } from '../../../styles';
4
4
  import type { LumenStyleSheetTheme } from '../../../styles';
5
+ import { RuntimeConstants } from '../../utils';
5
6
  import { Skeleton } from '../Skeleton';
6
7
  import { Box, Text } from '../Utility';
7
8
  import type { MediaImageProps, MediaImageSize, MediaImageShape } from './types';
@@ -119,7 +120,10 @@ export const MediaImage = ({
119
120
  {loading && <Skeleton style={StyleSheet.absoluteFillObject} />}
120
121
  {!loading && shouldFallback && fallback && (
121
122
  <Text
122
- style={{ fontSize: fontSizeMap[size] }}
123
+ style={{
124
+ fontSize: fontSizeMap[size],
125
+ ...(RuntimeConstants.isIOS && { lineHeight: 0 }),
126
+ }}
123
127
  lx={{ color: 'base' }}
124
128
  accessible={false}
125
129
  >
@@ -0,0 +1,161 @@
1
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as MediaTagStories from './MediaTag.stories';
3
+ import { CustomTabs, Tab } from '../../../../.storybook/components';
4
+ import { DoVsDontRow, DoBlockItem, DontBlockItem } from '../../../../.storybook/components/DoVsDont';
5
+ import CommonRulesDoAndDont from '../../../../.storybook/components/DoVsDont/CommonRulesDoAndDont.mdx';
6
+ import { Box } from '../Utility/Box';
7
+
8
+ <Meta title='Components/MediaTag' of={MediaTagStories} />
9
+
10
+ # MediaTag
11
+
12
+ <CustomTabs>
13
+ <Tab label="Overview">
14
+
15
+ ## Introduction
16
+
17
+ MediaTag is a compact label used to categorize, classify, or highlight information alongside a required media element such as an image or crypto icon. Like [Tag](/docs/communication-tag--docs), it supports multiple appearances and sizes, but is designed for cases where the icon is richer than a Symbol.
18
+
19
+ > View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=16793-36383&m=dev).
20
+
21
+ ## Anatomy
22
+
23
+ <Canvas of={MediaTagStories.Base} />
24
+
25
+ - **Label:** The text content of the tag.
26
+ - **Leading content:** A required media element (image, crypto icon, etc.) displayed before the label. Passed via the `leadingContent` prop.
27
+
28
+ ## Properties
29
+
30
+ <Canvas of={MediaTagStories.Base} />
31
+ <Controls of={MediaTagStories.Base} />
32
+
33
+ ### Appearance
34
+
35
+ <Canvas of={MediaTagStories.AppearanceShowcase} />
36
+
37
+ ---
38
+
39
+ ### Size
40
+
41
+ MediaTags come in two different sizes:
42
+
43
+ - **md** (default) — the `leadingContent` media should be rendered at **16px**.
44
+ - **sm** — the `leadingContent` media should be rendered at **12px**.
45
+
46
+ The MediaTag does not resize its `leadingContent`; consumers are responsible for passing a media element sized to match the tag's `size`.
47
+
48
+ <Canvas of={MediaTagStories.SizeShowcase} />
49
+
50
+ ### Truncation
51
+
52
+ When a MediaTag's label exceeds the available space, the text is automatically truncated with an ellipsis.
53
+
54
+ <Canvas of={MediaTagStories.TruncateShowcase} />
55
+
56
+ ### Accessibility
57
+
58
+ To be implemented:
59
+
60
+ - **Color contrast**
61
+ - **Text zoom**
62
+ - **Touch targets**
63
+ - **Screen reader support**
64
+ - **Semantic labeling**
65
+
66
+ </Tab>
67
+ <Tab label="Implementation">
68
+
69
+ ## Setup
70
+
71
+ Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
72
+
73
+ ### Basic Usage
74
+
75
+ Match the media element size to the MediaTag `size`: **16px** for `md`, **12px** for `sm`.
76
+
77
+ ```tsx
78
+ import { MediaTag, MediaImage } from '@ledgerhq/lumen-ui-rnative';
79
+
80
+ function MyComponent() {
81
+ return (
82
+ <>
83
+ {/* md → image size 16 */}
84
+ <MediaTag
85
+ size="md"
86
+ appearance="accent"
87
+ label="Ethereum"
88
+ leadingContent={
89
+ <MediaImage
90
+ src="https://crypto-icons.ledger.com/ETH.png"
91
+ alt="Ethereum"
92
+ size={16}
93
+ shape="square"
94
+ />
95
+ }
96
+ />
97
+
98
+ {/* sm → image size 12 */}
99
+ <MediaTag
100
+ size="sm"
101
+ appearance="accent"
102
+ label="Ethereum"
103
+ leadingContent={
104
+ <MediaImage
105
+ src="https://crypto-icons.ledger.com/ETH.png"
106
+ alt="Ethereum"
107
+ size={12}
108
+ shape="square"
109
+ />
110
+ }
111
+ />
112
+ </>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ### Custom Styling
118
+
119
+ While the component comes with predefined styles, you can extend them using the `lx` prop for layout adjustments:
120
+
121
+ ```tsx
122
+ <MediaTag
123
+ size="md"
124
+ appearance="accent"
125
+ label="Custom MediaTag"
126
+ leadingContent={myIcon}
127
+ lx={{ marginTop: 's4' }}
128
+ />
129
+ ```
130
+
131
+ ### With Crypto Icons
132
+
133
+ The MediaTag accepts any `ReactNode` as its `leadingContent`, including crypto icons. Use **16px** for `md` and **12px** for `sm`:
134
+
135
+ ```tsx
136
+ import { MediaTag } from '@ledgerhq/lumen-ui-rnative';
137
+ import CryptoIcon from '@ledgerhq/crypto-icons/native';
138
+
139
+ function MyComponent() {
140
+ return (
141
+ <>
142
+ {/* md → leadingContent size 16 */}
143
+ <MediaTag
144
+ size="md"
145
+ label="Bitcoin"
146
+ leadingContent={<CryptoIcon ledgerId="bitcoin" ticker="BTC" size={16} />}
147
+ />
148
+
149
+ {/* sm → leadingContent size 12 */}
150
+ <MediaTag
151
+ size="sm"
152
+ label="Bitcoin"
153
+ leadingContent={<CryptoIcon ledgerId="bitcoin" ticker="BTC" size={12} />}
154
+ />
155
+ </>
156
+ );
157
+ }
158
+ ```
159
+
160
+ </Tab>
161
+ </CustomTabs>
@@ -0,0 +1,112 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
+ import { MediaImage } from '../MediaImage';
3
+ import { Box } from '../Utility/Box';
4
+ import { MediaTag } from './MediaTag';
5
+
6
+ const ETH_ICON = (
7
+ <MediaImage
8
+ src='https://crypto-icons.ledger.com/ETH.png'
9
+ alt='Ethereum'
10
+ size={16}
11
+ shape='square'
12
+ />
13
+ );
14
+
15
+ const meta: Meta<typeof MediaTag> = {
16
+ component: MediaTag,
17
+ title: 'Communication/MediaTag',
18
+ argTypes: {
19
+ appearance: {
20
+ control: 'select',
21
+ options: [
22
+ 'base',
23
+ 'gray',
24
+ 'accent',
25
+ 'accent-subtle',
26
+ 'success',
27
+ 'error',
28
+ 'warning',
29
+ 'white',
30
+ ],
31
+ },
32
+ size: {
33
+ control: 'radio',
34
+ options: ['sm', 'md'],
35
+ },
36
+ label: {
37
+ control: 'text',
38
+ },
39
+ },
40
+ };
41
+
42
+ export default meta;
43
+
44
+ type Story = StoryObj<typeof MediaTag>;
45
+
46
+ export const Base: Story = {
47
+ args: {
48
+ label: 'Ethereum',
49
+ appearance: 'accent',
50
+ size: 'md',
51
+ },
52
+ render: (args) => <MediaTag {...args} leadingContent={ETH_ICON} />,
53
+ };
54
+
55
+ export const AppearanceShowcase: Story = {
56
+ render: () => (
57
+ <Box lx={{ flexDirection: 'row', flexWrap: 'wrap', gap: 's4' }}>
58
+ <MediaTag appearance='base' label='Base' leadingContent={ETH_ICON} />
59
+ <MediaTag appearance='gray' label='Gray' leadingContent={ETH_ICON} />
60
+ <MediaTag appearance='accent' label='Accent' leadingContent={ETH_ICON} />
61
+ <MediaTag
62
+ appearance='accent-subtle'
63
+ label='Accent subtle'
64
+ leadingContent={ETH_ICON}
65
+ />
66
+ <MediaTag
67
+ appearance='success'
68
+ label='Success'
69
+ leadingContent={ETH_ICON}
70
+ />
71
+ <MediaTag appearance='error' label='Error' leadingContent={ETH_ICON} />
72
+ <MediaTag
73
+ appearance='warning'
74
+ label='Warning'
75
+ leadingContent={ETH_ICON}
76
+ />
77
+ <MediaTag appearance='white' label='White' leadingContent={ETH_ICON} />
78
+ <MediaTag label='Disabled' leadingContent={ETH_ICON} disabled />
79
+ </Box>
80
+ ),
81
+ };
82
+
83
+ export const SizeShowcase: Story = {
84
+ render: () => (
85
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's4' }}>
86
+ <MediaTag size='md' label='Medium' leadingContent={ETH_ICON} />
87
+ <MediaTag
88
+ size='sm'
89
+ label='Small'
90
+ leadingContent={
91
+ <MediaImage
92
+ src='https://crypto-icons.ledger.com/ETH.png'
93
+ alt='Ethereum'
94
+ size={12}
95
+ shape='square'
96
+ />
97
+ }
98
+ />
99
+ </Box>
100
+ ),
101
+ };
102
+
103
+ export const TruncateShowcase: Story = {
104
+ render: () => (
105
+ <Box lx={{ width: 's176' }}>
106
+ <MediaTag
107
+ label='Very long text that truncates'
108
+ leadingContent={ETH_ICON}
109
+ />
110
+ </Box>
111
+ ),
112
+ };
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { render, screen } from '@testing-library/react-native';
4
+ import { Text } from 'react-native';
5
+ import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
6
+ import { MediaTag } from './MediaTag';
7
+
8
+ const renderWithProvider = (component: React.ReactElement) =>
9
+ render(
10
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
11
+ {component}
12
+ </ThemeProvider>,
13
+ );
14
+
15
+ describe('MediaTag Component', () => {
16
+ it('should render label and the provided ReactNode icon as-is', () => {
17
+ renderWithProvider(
18
+ <MediaTag
19
+ label='Bitcoin'
20
+ leadingContent={<Text testID='media-icon'>bitcoin-icon</Text>}
21
+ />,
22
+ );
23
+ expect(screen.getByText('Bitcoin')).toBeTruthy();
24
+ expect(screen.getByTestId('media-icon')).toBeTruthy();
25
+ expect(screen.getByText('bitcoin-icon')).toBeTruthy();
26
+ });
27
+ });
@@ -0,0 +1,36 @@
1
+ import { BaseTag } from '../BaseTag';
2
+ import type { MediaTagProps } from './types';
3
+
4
+ /**
5
+ * A compact label used to categorize, classify, or highlight information with a required media element (image, crypto icon, etc.).
6
+ *
7
+ * The appearance determines the color scheme used.
8
+ *
9
+ * @see {@link https://ldls.vercel.app/?path=/docs/communication-mediatag-overview--docs Storybook}
10
+ * @see {@link https://ldls.vercel.app/?path=/docs/communication-mediatag-implementation--docs#dos-and-donts Guidelines}
11
+ *
12
+ * @warning The `lx` prop should only be used for layout adjustments like margins or positioning.
13
+ * Do not use it to modify the tag's core appearance (colors, padding, etc). Use the `appearance` prop instead.
14
+ *
15
+ * @example
16
+ * import { MediaTag, MediaImage } from '@ledgerhq/lumen-ui-rnative';
17
+ *
18
+ * // MediaTag with image
19
+ * <MediaTag
20
+ * label='Ethereum'
21
+ * leadingContent={<MediaImage src='https://crypto-icons.ledger.com/ETH.png' alt='Ethereum' size={16} shape='square' />}
22
+ * />
23
+ *
24
+ * // Small MediaTag
25
+ * <MediaTag label='Bitcoin' size='sm' leadingContent={myIcon} />
26
+ */
27
+ export const MediaTag = ({ leadingContent, ...props }: MediaTagProps) => {
28
+ return (
29
+ <BaseTag
30
+ {...props}
31
+ variant='media'
32
+ consumerName='MediaTag'
33
+ renderIcon={() => leadingContent}
34
+ />
35
+ );
36
+ };
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './MediaTag';
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { TagProps } from '../Tag';
3
+
4
+ export type MediaTagProps = Omit<TagProps, 'icon'> & {
5
+ /**
6
+ * The media element rendered before the label (e.g. an image or a crypto icon).
7
+ * Should be sized to match the tag's `size`: 16px for `md`, 12px for `sm`.
8
+ */
9
+ leadingContent: ReactNode;
10
+ };
@@ -7,7 +7,7 @@ import { Box } from '../Utility/Box';
7
7
 
8
8
  <Meta title='Components/Tag' of={TagStories} />
9
9
 
10
- # 🏷️ Tag
10
+ # Tag
11
11
 
12
12
  <CustomTabs>
13
13
  <Tab label="Overview">
@@ -53,16 +53,6 @@ When a tag's label exceeds the available space, the text is automatically trunca
53
53
 
54
54
  <Canvas of={TagStories.TruncateShowcase} />
55
55
 
56
- ### Accessibility
57
-
58
- To be implemented:
59
-
60
- - **Color contrast**
61
- - **Text zoom**
62
- - **Touch targets**
63
- - **Screen reader support**
64
- - **Semantic labeling**
65
-
66
56
  </Tab>
67
57
  <Tab label="Implementation">
68
58
 
@@ -106,73 +96,5 @@ function MyComponent() {
106
96
  }
107
97
  ```
108
98
 
109
-
110
- ## Do's and Don'ts
111
-
112
- <Box lx={{ flexDirection: 'column', gap: 's24' }}>
113
- <DoVsDontRow>
114
- <DoBlockItem
115
- title='Use standard size variants'
116
- description='Use the size prop with predefined variants (sm, md)'
117
- >
118
-
119
- {/* prettier-ignore */}
120
- ```tsx
121
- import { Check } from '@ledgerhq/lumen-ui-rnative/symbols';
122
-
123
- <Tag size="sm" icon={Check} label="Success" />
124
- ```
125
-
126
- </DoBlockItem>
127
- <DontBlockItem
128
- title="Don't use custom sizes"
129
- description='Avoid using style prop to override standard sizes'
130
- >
131
-
132
- {/* prettier-ignore */}
133
- ```tsx
134
- import { Check } from '@ledgerhq/lumen-ui-rnative/symbols';
135
-
136
- <Tag style={{ height: 52 }} icon={Check} label="Success" />
137
- ```
138
-
139
- </DontBlockItem>
140
- </DoVsDontRow>
141
-
142
- <DoVsDontRow>
143
- <DoBlockItem
144
- title='Provide proper accessibility props'
145
- description='Use accessible and accessibilityRole props for screen reader support'
146
- >
147
-
148
- {/* prettier-ignore */}
149
- ```tsx
150
- <Tag
151
- label="Important"
152
- accessible={true}
153
- accessibilityRole="text"
154
- />
155
- ```
156
-
157
- </DoBlockItem>
158
- <DontBlockItem
159
- title="Don't omit accessibility props"
160
- description='Avoid creating tags without accessibility support for screen readers'
161
- >
162
-
163
- {/* prettier-ignore */}
164
- ```tsx
165
- <Tag
166
- label="Important"
167
- />
168
- ```
169
-
170
- </DontBlockItem>
171
-
172
- </DoVsDontRow>
173
-
174
- <CommonRulesDoAndDont />
175
- </Box>
176
-
177
99
  </Tab>
178
100
  </CustomTabs>
@@ -17,6 +17,7 @@ const meta: Meta<typeof Tag> = {
17
17
  'success',
18
18
  'error',
19
19
  'warning',
20
+ 'white',
20
21
  ],
21
22
  },
22
23
  size: {
@@ -59,6 +60,7 @@ export const AppearanceShowcase: Story = {
59
60
  <Tag appearance='success' label='Success' />
60
61
  <Tag appearance='error' label='Error' />
61
62
  <Tag appearance='warning' label='Warning' />
63
+ <Tag appearance='white' label='White' />
62
64
  <Tag label='Disabled' disabled />
63
65
  </Box>
64
66
  <Box lx={{ flexDirection: 'row', gap: 's4' }}>
@@ -69,6 +71,7 @@ export const AppearanceShowcase: Story = {
69
71
  <Tag appearance='success' label='Success' icon={Check} />
70
72
  <Tag appearance='error' label='Error' icon={Check} />
71
73
  <Tag appearance='warning' label='Warning' icon={Check} />
74
+ <Tag appearance='white' label='White' icon={Check} />
72
75
  <Tag label='Disabled' icon={Check} disabled />
73
76
  </Box>
74
77
  </Box>
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect, jest } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { render, screen } from '@testing-library/react-native';
4
+ import { Text } from 'react-native';
5
+ import type { IconProps } from '../Icon';
6
+ import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
7
+ import { Tag } from './Tag';
8
+
9
+ const renderWithProvider = (component: React.ReactElement) =>
10
+ render(
11
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
12
+ {component}
13
+ </ThemeProvider>,
14
+ );
15
+
16
+ describe('Tag Component', () => {
17
+ it('should render without icon', () => {
18
+ renderWithProvider(<Tag label='Label' />);
19
+ expect(screen.getByText('Label')).toBeTruthy();
20
+ });
21
+
22
+ it('should render the icon component when icon prop is provided', () => {
23
+ const Icon = jest.fn(({ size }: IconProps) => (
24
+ <Text testID='tag-icon'>icon-{size}</Text>
25
+ ));
26
+ renderWithProvider(<Tag label='Label' icon={Icon} />);
27
+ expect(screen.getByTestId('tag-icon')).toBeTruthy();
28
+ });
29
+
30
+ it('should inject size=16 into the icon when tag size is md', () => {
31
+ const Icon = jest.fn(({ size }: IconProps) => (
32
+ <Text testID='tag-icon'>icon-{size}</Text>
33
+ ));
34
+ renderWithProvider(<Tag label='Label' size='md' icon={Icon} />);
35
+ expect(Icon).toHaveBeenCalledWith(
36
+ expect.objectContaining({ size: 16 }),
37
+ undefined,
38
+ );
39
+ });
40
+
41
+ it('should inject size=12 into the icon when tag size is sm', () => {
42
+ const Icon = jest.fn(({ size }: IconProps) => (
43
+ <Text testID='tag-icon'>icon-{size}</Text>
44
+ ));
45
+ renderWithProvider(<Tag label='Label' size='sm' icon={Icon} />);
46
+ expect(Icon).toHaveBeenCalledWith(
47
+ expect.objectContaining({ size: 12 }),
48
+ undefined,
49
+ );
50
+ });
51
+ });
@@ -1,100 +1,9 @@
1
- import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
2
- import { StyleSheet, Text } from 'react-native';
3
- import { useStyleSheet } from '../../../styles';
1
+ import { BaseTag } from '../BaseTag';
4
2
  import type { IconSize } from '../Icon';
5
- import { Box } from '../Utility';
6
3
  import type { TagProps } from './types';
7
4
 
8
- type Appearance = NonNullable<TagProps['appearance']>;
9
5
  type Size = NonNullable<TagProps['size']>;
10
6
 
11
- const useStyles = ({
12
- appearance,
13
- size,
14
- disabled,
15
- }: {
16
- appearance: Appearance;
17
- size: Size;
18
- disabled: boolean;
19
- }) => {
20
- return useStyleSheet(
21
- (t) => {
22
- const bgColors: Record<Appearance, string> = {
23
- base: t.colors.bg.mutedTransparent,
24
- gray: t.colors.bg.mutedTransparent,
25
- accent: t.colors.bg.accent,
26
- 'accent-subtle': t.colors.bg.activeSubtle,
27
- success: t.colors.bg.success,
28
- error: t.colors.bg.error,
29
- warning: t.colors.bg.warning,
30
- };
31
-
32
- const textColors: Record<Appearance, string> = {
33
- base: t.colors.text.base,
34
- gray: t.colors.text.muted,
35
- accent: t.colors.text.onAccent,
36
- 'accent-subtle': t.colors.text.active,
37
- success: t.colors.text.success,
38
- error: t.colors.text.error,
39
- warning: t.colors.text.warning,
40
- };
41
-
42
- const sizeStyles: Record<
43
- Size,
44
- { paddingHorizontal: number; paddingVertical: number }
45
- > = {
46
- md: {
47
- paddingHorizontal: t.spacings.s8,
48
- paddingVertical: t.spacings.s4,
49
- },
50
- sm: {
51
- paddingHorizontal: t.spacings.s4,
52
- paddingVertical: t.spacings.s2,
53
- },
54
- };
55
-
56
- const textTypography =
57
- size === 'md' ? t.typographies.body3 : t.typographies.body4;
58
-
59
- return {
60
- root: StyleSheet.flatten([
61
- {
62
- flexDirection: 'row',
63
- alignItems: 'center',
64
- justifyContent: 'center',
65
- gap: t.spacings.s4,
66
- borderRadius: t.borderRadius.xs,
67
- backgroundColor: bgColors[appearance],
68
- ...sizeStyles[size],
69
- },
70
- disabled && {
71
- backgroundColor: t.colors.bg.disabled,
72
- },
73
- ]),
74
- text: StyleSheet.flatten([
75
- textTypography,
76
- {
77
- color: textColors[appearance],
78
- },
79
- disabled && {
80
- color: t.colors.text.disabled,
81
- },
82
- ]),
83
- icon: StyleSheet.flatten([
84
- {
85
- flexShrink: 0,
86
- color: textColors[appearance],
87
- },
88
- disabled && {
89
- color: t.colors.text.disabled,
90
- },
91
- ]),
92
- };
93
- },
94
- [appearance, size, disabled],
95
- );
96
- };
97
-
98
7
  const iconSizeMap: Record<Size, IconSize> = {
99
8
  md: 16,
100
9
  sm: 12,
@@ -127,37 +36,21 @@ const iconSizeMap: Record<Size, IconSize> = {
127
36
  * // Small tag
128
37
  * <Tag label="Small" size="sm" />
129
38
  */
130
- export const Tag = ({
131
- appearance = 'accent',
132
- size = 'md',
133
- icon,
134
- label,
135
- disabled: disabledProp = false,
136
- lx = {},
137
- style,
138
- ref,
139
- ...props
140
- }: TagProps) => {
141
- const disabled = useDisabledContext({
142
- consumerName: 'Tag',
143
- mergeWith: { disabled: disabledProp },
144
- });
145
- const styles = useStyles({ appearance, size, disabled: !!disabled });
146
-
39
+ export const Tag = ({ icon, size = 'md', ...props }: TagProps) => {
147
40
  const IconComponent = icon;
148
41
  const iconSize = iconSizeMap[size];
149
42
 
150
43
  return (
151
- <Box
152
- ref={ref}
153
- lx={lx}
154
- style={StyleSheet.flatten([styles.root, style])}
44
+ <BaseTag
155
45
  {...props}
156
- >
157
- {IconComponent && <IconComponent size={iconSize} style={styles.icon} />}
158
- <Text style={styles.text} numberOfLines={1}>
159
- {label}
160
- </Text>
161
- </Box>
46
+ size={size}
47
+ variant='tag'
48
+ consumerName='Tag'
49
+ renderIcon={
50
+ IconComponent
51
+ ? (iconStyle) => <IconComponent size={iconSize} style={iconStyle} />
52
+ : undefined
53
+ }
54
+ />
162
55
  );
163
56
  };