@ledgerhq/lumen-ui-rnative 0.1.33 → 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.
- package/dist/module/lib/Components/BaseTag/BaseTag.js +122 -0
- package/dist/module/lib/Components/BaseTag/BaseTag.js.map +1 -0
- package/dist/module/lib/Components/BaseTag/BaseTag.test.js +144 -0
- package/dist/module/lib/Components/BaseTag/BaseTag.test.js.map +1 -0
- package/dist/module/lib/Components/BaseTag/index.js +5 -0
- package/dist/module/lib/Components/BaseTag/index.js.map +1 -0
- package/dist/module/lib/Components/BaseTag/types.js +4 -0
- package/dist/module/lib/Components/BaseTag/types.js.map +1 -0
- package/dist/module/lib/Components/MediaImage/MediaImage.js +5 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
- package/dist/module/lib/Components/MediaTag/MediaTag.js +39 -0
- package/dist/module/lib/Components/MediaTag/MediaTag.js.map +1 -0
- package/dist/module/lib/Components/MediaTag/MediaTag.mdx +161 -0
- package/dist/module/lib/Components/MediaTag/MediaTag.stories.js +122 -0
- package/dist/module/lib/Components/MediaTag/MediaTag.stories.js.map +1 -0
- package/dist/module/lib/Components/MediaTag/MediaTag.test.js +30 -0
- package/dist/module/lib/Components/MediaTag/MediaTag.test.js.map +1 -0
- package/dist/module/lib/Components/MediaTag/index.js +5 -0
- package/dist/module/lib/Components/MediaTag/index.js.map +1 -0
- package/dist/module/lib/Components/MediaTag/types.js +4 -0
- package/dist/module/lib/Components/MediaTag/types.js.map +1 -0
- package/dist/module/lib/Components/Tag/Tag.js +10 -95
- package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
- package/dist/module/lib/Components/Tag/Tag.mdx +1 -79
- package/dist/module/lib/Components/Tag/Tag.stories.js +8 -1
- package/dist/module/lib/Components/Tag/Tag.stories.js.map +1 -1
- package/dist/module/lib/Components/Tag/Tag.test.js +69 -0
- package/dist/module/lib/Components/Tag/Tag.test.js.map +1 -0
- package/dist/module/lib/Components/index.js +1 -0
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/typescript/src/lib/Components/BaseTag/BaseTag.d.ts +3 -0
- package/dist/typescript/src/lib/Components/BaseTag/BaseTag.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/BaseTag/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/BaseTag/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/BaseTag/types.d.ts +10 -0
- package/dist/typescript/src/lib/Components/BaseTag/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/MediaTag/MediaTag.d.ts +26 -0
- package/dist/typescript/src/lib/Components/MediaTag/MediaTag.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaTag/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/MediaTag/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaTag/types.d.ts +10 -0
- package/dist/typescript/src/lib/Components/MediaTag/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Tag/Tag.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Tag/Tag.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Tag/types.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Tag/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/index.d.ts +1 -0
- package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/Components/BaseTag/BaseTag.test.tsx +137 -0
- package/src/lib/Components/BaseTag/BaseTag.tsx +152 -0
- package/src/lib/Components/BaseTag/index.ts +2 -0
- package/src/lib/Components/BaseTag/types.ts +11 -0
- package/src/lib/Components/MediaImage/MediaImage.tsx +5 -1
- package/src/lib/Components/MediaTag/MediaTag.mdx +161 -0
- package/src/lib/Components/MediaTag/MediaTag.stories.tsx +112 -0
- package/src/lib/Components/MediaTag/MediaTag.test.tsx +27 -0
- package/src/lib/Components/MediaTag/MediaTag.tsx +36 -0
- package/src/lib/Components/MediaTag/index.ts +2 -0
- package/src/lib/Components/MediaTag/types.ts +10 -0
- package/src/lib/Components/Tag/Tag.mdx +1 -79
- package/src/lib/Components/Tag/Tag.stories.tsx +3 -0
- package/src/lib/Components/Tag/Tag.test.tsx +51 -0
- package/src/lib/Components/Tag/Tag.tsx +12 -119
- package/src/lib/Components/Tag/types.ts +2 -1
- package/src/lib/Components/index.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/MediaTag/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { TagProps } from '../Tag';
|
|
3
|
+
export type MediaTagProps = Omit<TagProps, 'icon'> & {
|
|
4
|
+
/**
|
|
5
|
+
* The media element rendered before the label (e.g. an image or a crypto icon).
|
|
6
|
+
* Should be sized to match the tag's `size`: 16px for `md`, 12px for `sm`.
|
|
7
|
+
*/
|
|
8
|
+
leadingContent: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/MediaTag/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG;IACnD;;;OAGG;IACH,cAAc,EAAE,SAAS,CAAC;CAC3B,CAAC"}
|
|
@@ -26,5 +26,5 @@ import type { TagProps } from './types';
|
|
|
26
26
|
* // Small tag
|
|
27
27
|
* <Tag label="Small" size="sm" />
|
|
28
28
|
*/
|
|
29
|
-
export declare const Tag: ({
|
|
29
|
+
export declare const Tag: ({ icon, size, ...props }: TagProps) => import("react/jsx-runtime").JSX.Element;
|
|
30
30
|
//# sourceMappingURL=Tag.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tag.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Tag/Tag.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Tag.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Tag/Tag.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AASxC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,GAAG,GAAI,0BAAiC,QAAQ,4CAiB5D,CAAC"}
|
|
@@ -5,7 +5,7 @@ export type TagProps = {
|
|
|
5
5
|
/**
|
|
6
6
|
* The appearance of the tag.
|
|
7
7
|
*/
|
|
8
|
-
appearance?: 'base' | 'gray' | 'accent' | 'accent-subtle' | 'success' | 'error' | 'warning';
|
|
8
|
+
appearance?: 'base' | 'gray' | 'accent' | 'accent-subtle' | 'success' | 'error' | 'warning' | 'white';
|
|
9
9
|
/**
|
|
10
10
|
* The icon of the tag.
|
|
11
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Tag/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG;IACrB;;OAEG;IACH,UAAU,CAAC,EACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,eAAe,GACf,SAAS,GACT,OAAO,GACP,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Tag/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG;IACrB;;OAEG;IACH,UAAU,CAAC,EACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,eAAe,GACf,SAAS,GACT,OAAO,GACP,SAAS,GACT,OAAO,CAAC;IACZ;;OAEG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
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 type { ViewStyle } from 'react-native';
|
|
5
|
+
import { Text } from 'react-native';
|
|
6
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
7
|
+
import { BaseTag } from './BaseTag';
|
|
8
|
+
import type { BaseTagProps } from './types';
|
|
9
|
+
|
|
10
|
+
const { colors } = ledgerLiveThemes.dark;
|
|
11
|
+
|
|
12
|
+
const renderWithProvider = (component: React.ReactElement) =>
|
|
13
|
+
render(
|
|
14
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
15
|
+
{component}
|
|
16
|
+
</ThemeProvider>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const baseProps = {
|
|
20
|
+
consumerName: 'BaseTagTest',
|
|
21
|
+
variant: 'tag',
|
|
22
|
+
label: 'Label',
|
|
23
|
+
testID: 'base-tag',
|
|
24
|
+
} satisfies BaseTagProps;
|
|
25
|
+
|
|
26
|
+
describe('BaseTag Component', () => {
|
|
27
|
+
describe('Rendering', () => {
|
|
28
|
+
it('should render label text', () => {
|
|
29
|
+
renderWithProvider(<BaseTag {...baseProps} label='Bitcoin' />);
|
|
30
|
+
expect(screen.getByText('Bitcoin')).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render testID on root element', () => {
|
|
34
|
+
renderWithProvider(<BaseTag {...baseProps} />);
|
|
35
|
+
expect(screen.getByTestId('base-tag')).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should not render any icon when renderIcon is not provided', () => {
|
|
39
|
+
renderWithProvider(<BaseTag {...baseProps} />);
|
|
40
|
+
expect(screen.queryByTestId('test-icon')).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should call renderIcon with the computed icon style', () => {
|
|
44
|
+
const renderIcon = jest.fn(() => (
|
|
45
|
+
<Text testID='test-icon'>icon</Text>
|
|
46
|
+
)) as BaseTagProps['renderIcon'];
|
|
47
|
+
renderWithProvider(<BaseTag {...baseProps} renderIcon={renderIcon} />);
|
|
48
|
+
expect(screen.getByTestId('test-icon')).toBeTruthy();
|
|
49
|
+
expect(renderIcon).toHaveBeenCalledWith(
|
|
50
|
+
expect.objectContaining({ color: expect.any(String) }),
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Appearances', () => {
|
|
56
|
+
const cases: [NonNullable<BaseTagProps['appearance']>, string][] = [
|
|
57
|
+
['base', colors.bg.mutedTransparent],
|
|
58
|
+
['gray', colors.bg.mutedTransparent],
|
|
59
|
+
['accent', colors.bg.accent],
|
|
60
|
+
['accent-subtle', colors.bg.activeSubtle],
|
|
61
|
+
['success', colors.bg.success],
|
|
62
|
+
['error', colors.bg.error],
|
|
63
|
+
['warning', colors.bg.warning],
|
|
64
|
+
['white', colors.bg.white],
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
it.each(cases)(
|
|
68
|
+
'should render with %s background color',
|
|
69
|
+
(appearance, expected) => {
|
|
70
|
+
renderWithProvider(<BaseTag {...baseProps} appearance={appearance} />);
|
|
71
|
+
expect(screen.getByTestId('base-tag').props.style.backgroundColor).toBe(
|
|
72
|
+
expected,
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('Sizes', () => {
|
|
79
|
+
it.each(['sm', 'md'] as const)('should render with %s size', (size) => {
|
|
80
|
+
renderWithProvider(<BaseTag {...baseProps} size={size} />);
|
|
81
|
+
expect(screen.getByTestId('base-tag')).toBeTruthy();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should use smaller padding for sm than md (tag variant)', () => {
|
|
85
|
+
const { rerender } = renderWithProvider(
|
|
86
|
+
<BaseTag {...baseProps} size='md' />,
|
|
87
|
+
);
|
|
88
|
+
const mdPadding =
|
|
89
|
+
screen.getByTestId('base-tag').props.style.paddingHorizontal;
|
|
90
|
+
|
|
91
|
+
rerender(
|
|
92
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
93
|
+
<BaseTag {...baseProps} size='sm' />
|
|
94
|
+
</ThemeProvider>,
|
|
95
|
+
);
|
|
96
|
+
const smPadding =
|
|
97
|
+
screen.getByTestId('base-tag').props.style.paddingHorizontal;
|
|
98
|
+
|
|
99
|
+
expect(smPadding).toBeLessThan(mdPadding);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('Variants', () => {
|
|
104
|
+
it('should use paddingHorizontal for tag variant', () => {
|
|
105
|
+
renderWithProvider(<BaseTag {...baseProps} variant='tag' />);
|
|
106
|
+
const style = screen.getByTestId('base-tag').props.style;
|
|
107
|
+
expect(style.paddingHorizontal).toBeDefined();
|
|
108
|
+
expect(style.paddingLeft).toBeUndefined();
|
|
109
|
+
expect(style.paddingRight).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should use asymmetric paddingLeft / paddingRight for media variant', () => {
|
|
113
|
+
renderWithProvider(<BaseTag {...baseProps} variant='media' />);
|
|
114
|
+
const style = screen.getByTestId('base-tag').props.style;
|
|
115
|
+
expect(style.paddingLeft).toBeDefined();
|
|
116
|
+
expect(style.paddingRight).toBeDefined();
|
|
117
|
+
expect(style.paddingHorizontal).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Disabled', () => {
|
|
122
|
+
it('should apply disabled background when disabled', () => {
|
|
123
|
+
renderWithProvider(<BaseTag {...baseProps} disabled />);
|
|
124
|
+
expect(screen.getByTestId('base-tag').props.style.backgroundColor).toBe(
|
|
125
|
+
colors.bg.disabled,
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('Styling', () => {
|
|
131
|
+
it('should merge custom style with computed root style', () => {
|
|
132
|
+
const custom: ViewStyle = { marginTop: 16 };
|
|
133
|
+
renderWithProvider(<BaseTag {...baseProps} style={custom} />);
|
|
134
|
+
expect(screen.getByTestId('base-tag').props.style.marginTop).toBe(16);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
import { StyleSheet, Text } from 'react-native';
|
|
3
|
+
import { useStyleSheet } from '../../../styles';
|
|
4
|
+
import type { TagProps } from '../Tag/types';
|
|
5
|
+
import { Box } from '../Utility';
|
|
6
|
+
import type { BaseTagProps, BaseTagVariant } from './types';
|
|
7
|
+
|
|
8
|
+
type Appearance = NonNullable<TagProps['appearance']>;
|
|
9
|
+
type Size = NonNullable<TagProps['size']>;
|
|
10
|
+
|
|
11
|
+
const useBaseTagStyles = ({
|
|
12
|
+
appearance,
|
|
13
|
+
size,
|
|
14
|
+
disabled,
|
|
15
|
+
variant,
|
|
16
|
+
}: {
|
|
17
|
+
appearance: Appearance;
|
|
18
|
+
size: Size;
|
|
19
|
+
disabled: boolean;
|
|
20
|
+
variant: BaseTagVariant;
|
|
21
|
+
}) => {
|
|
22
|
+
return useStyleSheet(
|
|
23
|
+
(t) => {
|
|
24
|
+
const bgColors: Record<Appearance, string> = {
|
|
25
|
+
base: t.colors.bg.mutedTransparent,
|
|
26
|
+
gray: t.colors.bg.mutedTransparent,
|
|
27
|
+
accent: t.colors.bg.accent,
|
|
28
|
+
'accent-subtle': t.colors.bg.activeSubtle,
|
|
29
|
+
success: t.colors.bg.success,
|
|
30
|
+
error: t.colors.bg.error,
|
|
31
|
+
warning: t.colors.bg.warning,
|
|
32
|
+
white: t.colors.bg.white,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const textColors: Record<Appearance, string> = {
|
|
36
|
+
base: t.colors.text.base,
|
|
37
|
+
gray: t.colors.text.muted,
|
|
38
|
+
accent: t.colors.text.onAccent,
|
|
39
|
+
'accent-subtle': t.colors.text.active,
|
|
40
|
+
success: t.colors.text.success,
|
|
41
|
+
error: t.colors.text.error,
|
|
42
|
+
warning: t.colors.text.warning,
|
|
43
|
+
white: t.colors.text.black,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const tagPadding = {
|
|
47
|
+
md: {
|
|
48
|
+
paddingHorizontal: t.spacings.s8,
|
|
49
|
+
paddingVertical: t.spacings.s4,
|
|
50
|
+
},
|
|
51
|
+
sm: {
|
|
52
|
+
paddingHorizontal: t.spacings.s4,
|
|
53
|
+
paddingVertical: t.spacings.s2,
|
|
54
|
+
},
|
|
55
|
+
} satisfies Record<Size, object>;
|
|
56
|
+
|
|
57
|
+
const mediaPadding = {
|
|
58
|
+
md: {
|
|
59
|
+
paddingLeft: t.spacings.s4,
|
|
60
|
+
paddingRight: t.spacings.s8,
|
|
61
|
+
paddingVertical: t.spacings.s4,
|
|
62
|
+
},
|
|
63
|
+
sm: {
|
|
64
|
+
paddingLeft: t.spacings.s4,
|
|
65
|
+
paddingRight: t.spacings.s4,
|
|
66
|
+
paddingVertical: t.spacings.s2,
|
|
67
|
+
},
|
|
68
|
+
} satisfies Record<Size, object>;
|
|
69
|
+
|
|
70
|
+
const padding =
|
|
71
|
+
variant === 'media' ? mediaPadding[size] : tagPadding[size];
|
|
72
|
+
|
|
73
|
+
const textTypography =
|
|
74
|
+
size === 'md' ? t.typographies.body3 : t.typographies.body4;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
root: StyleSheet.flatten([
|
|
78
|
+
{
|
|
79
|
+
flexDirection: 'row',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
gap: t.spacings.s4,
|
|
83
|
+
borderRadius: t.borderRadius.xs,
|
|
84
|
+
backgroundColor: bgColors[appearance],
|
|
85
|
+
...padding,
|
|
86
|
+
},
|
|
87
|
+
disabled && {
|
|
88
|
+
backgroundColor: t.colors.bg.disabled,
|
|
89
|
+
},
|
|
90
|
+
]),
|
|
91
|
+
text: StyleSheet.flatten([
|
|
92
|
+
textTypography,
|
|
93
|
+
{
|
|
94
|
+
color: textColors[appearance],
|
|
95
|
+
},
|
|
96
|
+
disabled && {
|
|
97
|
+
color: t.colors.text.disabled,
|
|
98
|
+
},
|
|
99
|
+
]),
|
|
100
|
+
icon: StyleSheet.flatten([
|
|
101
|
+
{
|
|
102
|
+
flexShrink: 0,
|
|
103
|
+
color: textColors[appearance],
|
|
104
|
+
},
|
|
105
|
+
disabled && {
|
|
106
|
+
color: t.colors.text.disabled,
|
|
107
|
+
},
|
|
108
|
+
]),
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
[appearance, size, disabled, variant],
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const BaseTag = ({
|
|
116
|
+
appearance = 'accent',
|
|
117
|
+
size = 'md',
|
|
118
|
+
variant,
|
|
119
|
+
consumerName,
|
|
120
|
+
label,
|
|
121
|
+
renderIcon,
|
|
122
|
+
disabled: disabledProp = false,
|
|
123
|
+
lx = {},
|
|
124
|
+
style,
|
|
125
|
+
ref,
|
|
126
|
+
...props
|
|
127
|
+
}: BaseTagProps) => {
|
|
128
|
+
const disabled = useDisabledContext({
|
|
129
|
+
consumerName,
|
|
130
|
+
mergeWith: { disabled: disabledProp },
|
|
131
|
+
});
|
|
132
|
+
const styles = useBaseTagStyles({
|
|
133
|
+
appearance,
|
|
134
|
+
size,
|
|
135
|
+
disabled: !!disabled,
|
|
136
|
+
variant,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<Box
|
|
141
|
+
ref={ref}
|
|
142
|
+
lx={lx}
|
|
143
|
+
style={StyleSheet.flatten([styles.root, style])}
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
{renderIcon?.(styles.icon)}
|
|
147
|
+
<Text style={styles.text} numberOfLines={1}>
|
|
148
|
+
{label}
|
|
149
|
+
</Text>
|
|
150
|
+
</Box>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, TextStyle } from 'react-native';
|
|
3
|
+
import type { TagProps } from '../Tag/types';
|
|
4
|
+
|
|
5
|
+
export type BaseTagVariant = 'tag' | 'media';
|
|
6
|
+
|
|
7
|
+
export type BaseTagProps = Omit<TagProps, 'icon'> & {
|
|
8
|
+
variant: BaseTagVariant;
|
|
9
|
+
consumerName: string;
|
|
10
|
+
renderIcon?: (style: StyleProp<TextStyle>) => ReactNode;
|
|
11
|
+
};
|
|
@@ -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={{
|
|
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
|
+
};
|