@ledgerhq/lumen-ui-rnative 0.1.24 → 0.1.25
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/DotIcon/DotIcon.js +134 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.mdx +56 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.stories.js +217 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.stories.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.test.js +238 -0
- package/dist/module/lib/Components/DotIcon/DotIcon.test.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/index.js +5 -0
- package/dist/module/lib/Components/DotIcon/index.js.map +1 -0
- package/dist/module/lib/Components/DotIcon/types.js +4 -0
- package/dist/module/lib/Components/DotIcon/types.js.map +1 -0
- package/dist/module/lib/Components/DotSymbol/DotSymbol.js +29 -22
- package/dist/module/lib/Components/DotSymbol/DotSymbol.js.map +1 -1
- package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js +31 -9
- package/dist/module/lib/Components/DotSymbol/DotSymbol.stories.js.map +1 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.js +2 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.stories.js +4 -0
- package/dist/module/lib/Components/MediaImage/MediaImage.stories.js.map +1 -1
- package/dist/module/lib/Components/Spinner/Spinner.js +6 -1
- package/dist/module/lib/Components/Spinner/Spinner.js.map +1 -1
- package/dist/module/lib/Components/Tag/Tag.js +2 -0
- package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
- 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/index.js +1 -0
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/typescript/src/lib/Components/DotIcon/DotIcon.d.ts +30 -0
- package/dist/typescript/src/lib/Components/DotIcon/DotIcon.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIcon/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/DotIcon/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotIcon/types.d.ts +40 -0
- package/dist/typescript/src/lib/Components/DotIcon/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/DotSymbol/DotSymbol.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/MediaImage/types.d.ts +1 -1
- package/dist/typescript/src/lib/Components/MediaImage/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Spinner/Spinner.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Spinner/Spinner.d.ts.map +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/DotIcon/DotIcon.mdx +56 -0
- package/src/lib/Components/DotIcon/DotIcon.stories.tsx +154 -0
- package/src/lib/Components/DotIcon/DotIcon.test.tsx +224 -0
- package/src/lib/Components/DotIcon/DotIcon.tsx +146 -0
- package/src/lib/Components/DotIcon/index.ts +6 -0
- package/src/lib/Components/DotIcon/types.ts +44 -0
- package/src/lib/Components/DotSymbol/DotSymbol.stories.tsx +26 -7
- package/src/lib/Components/DotSymbol/DotSymbol.tsx +22 -23
- package/src/lib/Components/MediaImage/MediaImage.stories.tsx +1 -0
- package/src/lib/Components/MediaImage/MediaImage.tsx +3 -1
- package/src/lib/Components/MediaImage/types.ts +1 -1
- package/src/lib/Components/Spinner/Spinner.tsx +6 -2
- package/src/lib/Components/Tag/Tag.stories.tsx +11 -1
- package/src/lib/Components/Tag/Tag.tsx +2 -0
- package/src/lib/Components/Tag/types.ts +8 -1
- package/src/lib/Components/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tag.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Tag/Tag.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Tag.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Tag/Tag.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAiGxC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,GAAG,GAAI,qFAUjB,QAAQ,4CAuBV,CAAC"}
|
|
@@ -5,7 +5,7 @@ export type TagProps = {
|
|
|
5
5
|
/**
|
|
6
6
|
* The appearance of the tag.
|
|
7
7
|
*/
|
|
8
|
-
appearance?: 'base' | 'gray' | 'accent' | 'success' | 'error' | 'warning';
|
|
8
|
+
appearance?: 'base' | 'gray' | 'accent' | 'accent-subtle' | 'success' | 'error' | 'warning';
|
|
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,
|
|
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;IACd;;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,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,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,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,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,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,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,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -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
|
+
});
|
|
@@ -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,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'>;
|