@ledgerhq/lumen-ui-rnative 0.1.30 → 0.1.32
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/i18n/i18n.js +5 -1
- package/dist/module/i18n/i18n.js.map +1 -1
- package/dist/module/i18n/locales/en.json +5 -0
- package/dist/module/lib/Components/AddressInput/AddressInput.mdx +10 -12
- package/dist/module/lib/Components/AddressInput/AddressInput.stories.js +2 -1
- package/dist/module/lib/Components/AddressInput/AddressInput.stories.js.map +1 -1
- package/dist/module/lib/Components/AmountInput/AmountInput.mdx +3 -3
- package/dist/module/lib/Components/BaseInput/BaseInput.js +60 -32
- package/dist/module/lib/Components/BaseInput/BaseInput.js.map +1 -1
- package/dist/module/lib/Components/DotCount/DotCount.stories.js +9 -18
- package/dist/module/lib/Components/DotCount/DotCount.stories.js.map +1 -1
- package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js +3 -5
- package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js.map +1 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.test.js +7 -3
- package/dist/module/lib/Components/MediaImage/MediaImage.test.js.map +1 -1
- package/dist/module/lib/Components/SearchInput/SearchInput.mdx +4 -10
- package/dist/module/lib/Components/SearchInput/SearchInput.stories.js +2 -1
- package/dist/module/lib/Components/SearchInput/SearchInput.stories.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js +2 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js +32 -7
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js +26 -0
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js.map +1 -1
- package/dist/module/lib/Components/TextInput/TextInput.js +4 -3
- package/dist/module/lib/Components/TextInput/TextInput.js.map +1 -1
- package/dist/module/lib/Components/TextInput/TextInput.mdx +8 -10
- package/dist/module/lib/Components/TextInput/TextInput.stories.js +40 -1
- package/dist/module/lib/Components/TextInput/TextInput.stories.js.map +1 -1
- package/dist/module/lib/Components/TextInput/TextInput.test.js +76 -0
- package/dist/module/lib/Components/TextInput/TextInput.test.js.map +1 -0
- package/dist/module/lib/Components/Trend/Trend.js +103 -0
- package/dist/module/lib/Components/Trend/Trend.js.map +1 -0
- package/dist/module/lib/Components/Trend/Trend.mdx +106 -0
- package/dist/module/lib/Components/Trend/Trend.stories.js +79 -0
- package/dist/module/lib/Components/Trend/Trend.stories.js.map +1 -0
- package/dist/module/lib/Components/Trend/Trend.test.js +149 -0
- package/dist/module/lib/Components/Trend/Trend.test.js.map +1 -0
- package/dist/module/lib/Components/Trend/index.js +5 -0
- package/dist/module/lib/Components/Trend/index.js.map +1 -0
- package/dist/module/lib/Components/Trend/types.js +4 -0
- package/dist/module/lib/Components/Trend/types.js.map +1 -0
- package/dist/module/lib/Components/index.js +5 -4
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Ar.js +2 -2
- package/dist/module/lib/Symbols/Icons/Ar.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/ArrowLeft.js +1 -1
- package/dist/module/lib/Symbols/Icons/ArrowLeft.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/ArrowRight.js +1 -1
- package/dist/module/lib/Symbols/Icons/ArrowRight.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/BasketPutIn.js +1 -1
- package/dist/module/lib/Symbols/Icons/BasketPutIn.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Clip.js +1 -1
- package/dist/module/lib/Symbols/Icons/ClockFill.js +5 -14
- package/dist/module/lib/Symbols/Icons/ClockFill.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/CloudDownload.js +1 -1
- package/dist/module/lib/Symbols/Icons/CloudDownload.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/CloudUpload.js +1 -1
- package/dist/module/lib/Symbols/Icons/CloudUpload.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/CoinsAddPlus.js +1 -1
- package/dist/module/lib/Symbols/Icons/CoinsAddPlus.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/CoinsCheck.js +1 -1
- package/dist/module/lib/Symbols/Icons/CoinsCheck.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/CoinsCross.js +1 -1
- package/dist/module/lib/Symbols/Icons/CoinsCross.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/ColorPalette.js +1 -1
- package/dist/module/lib/Symbols/Icons/ColorPalette.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/CryptoBitcoinCoin.js +1 -1
- package/dist/module/lib/Symbols/Icons/Csv.js +1 -1
- package/dist/module/lib/Symbols/Icons/Csv.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Discord.js +1 -1
- package/dist/module/lib/Symbols/Icons/Discord.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Download.js +1 -1
- package/dist/module/lib/Symbols/Icons/Download.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Exchange.js +1 -1
- package/dist/module/lib/Symbols/Icons/Exchange.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/ExchangeFill.js +1 -1
- package/dist/module/lib/Symbols/Icons/ExchangeFill.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/ExitLogout.js +1 -1
- package/dist/module/lib/Symbols/Icons/ExitLogout.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Expand.js +1 -1
- package/dist/module/lib/Symbols/Icons/Expand.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Experiment2.js +1 -1
- package/dist/module/lib/Symbols/Icons/Experiment2.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/ExternalLink.js +1 -1
- package/dist/module/lib/Symbols/Icons/ExternalLink.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/FileDownload.js +1 -1
- package/dist/module/lib/Symbols/Icons/FileDownload.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Github.js +1 -1
- package/dist/module/lib/Symbols/Icons/HandCard.js +1 -1
- package/dist/module/lib/Symbols/Icons/HandCard.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Infinite.js +1 -1
- package/dist/module/lib/Symbols/Icons/MobileArrow.js +1 -1
- package/dist/module/lib/Symbols/Icons/MobileArrow.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/NftHide.js +1 -1
- package/dist/module/lib/Symbols/Icons/NftHide.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Parachute.js +1 -1
- package/dist/module/lib/Symbols/Icons/Parachute.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/PictureImage.js +1 -1
- package/dist/module/lib/Symbols/Icons/Range.js +1 -1
- package/dist/module/lib/Symbols/Icons/Range.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Reddit.js +2 -2
- package/dist/module/lib/Symbols/Icons/Reduce.js +1 -1
- package/dist/module/lib/Symbols/Icons/Reduce.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Screens.js +1 -1
- package/dist/module/lib/Symbols/Icons/Screens.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Shapes.js +1 -1
- package/dist/module/lib/Symbols/Icons/Share.js +1 -1
- package/dist/module/lib/Symbols/Icons/Share.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/StarHalf.js +1 -1
- package/dist/module/lib/Symbols/Icons/StarHalf.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/TransferHorizontal.js +1 -1
- package/dist/module/lib/Symbols/Icons/TransferHorizontal.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/TransferVertical.js +1 -1
- package/dist/module/lib/Symbols/Icons/TransferVertical.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/Truck.js +1 -1
- package/dist/module/lib/Symbols/Icons/Usb.js +1 -1
- package/dist/module/lib/Symbols/Icons/Usb.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/UserArrowRight.js +1 -1
- package/dist/module/lib/Symbols/Icons/UserArrowRight.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/WalletInput.js +1 -1
- package/dist/module/lib/Symbols/Icons/WalletInput.js.map +1 -1
- package/dist/typescript/src/i18n/i18n.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts +1 -1
- package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/BaseInput/types.d.ts +9 -2
- package/dist/typescript/src/lib/Components/BaseInput/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts +4 -0
- package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts +4 -3
- package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Trend/Trend.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Trend/Trend.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Trend/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Trend/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Trend/types.d.ts +20 -0
- package/dist/typescript/src/lib/Components/Trend/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/index.d.ts +5 -4
- package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/Symbols/Icons/ClockFill.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/i18n/i18n.ts +12 -1
- package/src/i18n/locales/en.json +5 -0
- package/src/lib/Components/AddressInput/AddressInput.mdx +10 -12
- package/src/lib/Components/AddressInput/AddressInput.stories.tsx +2 -1
- package/src/lib/Components/AmountInput/AmountInput.mdx +3 -3
- package/src/lib/Components/BaseInput/BaseInput.tsx +61 -29
- package/src/lib/Components/BaseInput/types.ts +10 -2
- package/src/lib/Components/DotCount/DotCount.stories.tsx +12 -10
- package/src/lib/Components/DotIndicator/DotIndicator.stories.tsx +1 -3
- package/src/lib/Components/MediaImage/MediaImage.test.tsx +7 -3
- package/src/lib/Components/SearchInput/SearchInput.mdx +4 -10
- package/src/lib/Components/SearchInput/SearchInput.stories.tsx +2 -1
- package/src/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
- package/src/lib/Components/SegmentedControl/SegmentedControl.stories.tsx +34 -6
- package/src/lib/Components/SegmentedControl/SegmentedControl.test.tsx +27 -0
- package/src/lib/Components/SegmentedControl/SegmentedControl.tsx +2 -0
- package/src/lib/Components/SegmentedControl/types.ts +4 -0
- package/src/lib/Components/TextInput/TextInput.mdx +8 -10
- package/src/lib/Components/TextInput/TextInput.stories.tsx +41 -1
- package/src/lib/Components/TextInput/TextInput.test.tsx +90 -0
- package/src/lib/Components/TextInput/TextInput.tsx +4 -3
- package/src/lib/Components/Trend/Trend.mdx +106 -0
- package/src/lib/Components/Trend/Trend.stories.tsx +61 -0
- package/src/lib/Components/Trend/Trend.test.tsx +125 -0
- package/src/lib/Components/Trend/Trend.tsx +118 -0
- package/src/lib/Components/Trend/index.ts +2 -0
- package/src/lib/Components/Trend/types.ts +20 -0
- package/src/lib/Components/index.ts +5 -4
- package/src/lib/Symbols/Icons/Ar.tsx +2 -2
- package/src/lib/Symbols/Icons/ArrowLeft.tsx +1 -1
- package/src/lib/Symbols/Icons/ArrowRight.tsx +1 -1
- package/src/lib/Symbols/Icons/BasketPutIn.tsx +1 -1
- package/src/lib/Symbols/Icons/Clip.tsx +1 -1
- package/src/lib/Symbols/Icons/ClockFill.tsx +1 -9
- package/src/lib/Symbols/Icons/CloudDownload.tsx +1 -1
- package/src/lib/Symbols/Icons/CloudUpload.tsx +1 -1
- package/src/lib/Symbols/Icons/CoinsAddPlus.tsx +1 -1
- package/src/lib/Symbols/Icons/CoinsCheck.tsx +1 -1
- package/src/lib/Symbols/Icons/CoinsCross.tsx +1 -1
- package/src/lib/Symbols/Icons/ColorPalette.tsx +1 -1
- package/src/lib/Symbols/Icons/CryptoBitcoinCoin.tsx +1 -1
- package/src/lib/Symbols/Icons/Csv.tsx +1 -1
- package/src/lib/Symbols/Icons/Discord.tsx +1 -1
- package/src/lib/Symbols/Icons/Download.tsx +1 -1
- package/src/lib/Symbols/Icons/Exchange.tsx +1 -1
- package/src/lib/Symbols/Icons/ExchangeFill.tsx +1 -1
- package/src/lib/Symbols/Icons/ExitLogout.tsx +1 -1
- package/src/lib/Symbols/Icons/Expand.tsx +1 -1
- package/src/lib/Symbols/Icons/Experiment2.tsx +1 -1
- package/src/lib/Symbols/Icons/ExternalLink.tsx +1 -1
- package/src/lib/Symbols/Icons/FileDownload.tsx +1 -1
- package/src/lib/Symbols/Icons/Github.tsx +1 -1
- package/src/lib/Symbols/Icons/HandCard.tsx +1 -1
- package/src/lib/Symbols/Icons/Infinite.tsx +1 -1
- package/src/lib/Symbols/Icons/MobileArrow.tsx +1 -1
- package/src/lib/Symbols/Icons/NftHide.tsx +1 -1
- package/src/lib/Symbols/Icons/Parachute.tsx +1 -1
- package/src/lib/Symbols/Icons/PictureImage.tsx +1 -1
- package/src/lib/Symbols/Icons/Range.tsx +1 -1
- package/src/lib/Symbols/Icons/Reddit.tsx +2 -2
- package/src/lib/Symbols/Icons/Reduce.tsx +1 -1
- package/src/lib/Symbols/Icons/Screens.tsx +1 -1
- package/src/lib/Symbols/Icons/Shapes.tsx +1 -1
- package/src/lib/Symbols/Icons/Share.tsx +1 -1
- package/src/lib/Symbols/Icons/StarHalf.tsx +1 -1
- package/src/lib/Symbols/Icons/TransferHorizontal.tsx +1 -1
- package/src/lib/Symbols/Icons/TransferVertical.tsx +1 -1
- package/src/lib/Symbols/Icons/Truck.tsx +1 -1
- package/src/lib/Symbols/Icons/Usb.tsx +1 -1
- package/src/lib/Symbols/Icons/UserArrowRight.tsx +1 -1
- package/src/lib/Symbols/Icons/WalletInput.tsx +1 -1
|
@@ -66,11 +66,51 @@ export const WithContent: Story = {
|
|
|
66
66
|
},
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
export const WithLabelAndPlaceholder: Story = {
|
|
70
|
+
render: (args) => <TextInputStory {...args} />,
|
|
71
|
+
args: {
|
|
72
|
+
label: 'Phone',
|
|
73
|
+
placeholder: '+1 (555) 000-0000',
|
|
74
|
+
editable: true,
|
|
75
|
+
hideClearButton: false,
|
|
76
|
+
keyboardType: 'phone-pad',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
69
80
|
export const WithError: Story = {
|
|
70
81
|
render: (args) => <TextInputStory {...args} initialValue='ab' />,
|
|
71
82
|
args: {
|
|
72
83
|
label: 'Username',
|
|
73
|
-
|
|
84
|
+
helperText: 'Username must be at least 3 characters',
|
|
85
|
+
status: 'error',
|
|
86
|
+
editable: true,
|
|
87
|
+
hideClearButton: false,
|
|
88
|
+
keyboardType: 'default',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithSuccess: Story = {
|
|
93
|
+
render: (args) => (
|
|
94
|
+
<TextInputStory
|
|
95
|
+
{...args}
|
|
96
|
+
initialValue='0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb27'
|
|
97
|
+
/>
|
|
98
|
+
),
|
|
99
|
+
args: {
|
|
100
|
+
label: 'Address',
|
|
101
|
+
helperText: 'Address verified',
|
|
102
|
+
status: 'success',
|
|
103
|
+
editable: true,
|
|
104
|
+
hideClearButton: false,
|
|
105
|
+
keyboardType: 'default',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const WithNeutralHint: Story = {
|
|
110
|
+
render: (args) => <TextInputStory {...args} />,
|
|
111
|
+
args: {
|
|
112
|
+
label: 'Address',
|
|
113
|
+
helperText: 'Enter your ETH address',
|
|
74
114
|
editable: true,
|
|
75
115
|
hideClearButton: false,
|
|
76
116
|
keyboardType: 'default',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import { render, screen } from '@testing-library/react-native';
|
|
4
|
+
import type { ReactElement } from 'react';
|
|
5
|
+
import { CheckmarkCircleFill } from '../../Symbols/Icons/CheckmarkCircleFill';
|
|
6
|
+
import { DeleteCircleFill } from '../../Symbols/Icons/DeleteCircleFill';
|
|
7
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
8
|
+
import { TextInput } from './TextInput';
|
|
9
|
+
|
|
10
|
+
const { colors } = ledgerLiveThemes.dark;
|
|
11
|
+
|
|
12
|
+
const renderWithProvider = (
|
|
13
|
+
component: ReactElement,
|
|
14
|
+
): ReturnType<typeof render> => {
|
|
15
|
+
return render(
|
|
16
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
17
|
+
{component}
|
|
18
|
+
</ThemeProvider>,
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('TextInput', () => {
|
|
23
|
+
describe('Placeholder and label (BaseInput parity with web)', () => {
|
|
24
|
+
it('uses the provided placeholder when label and placeholder are both set', async () => {
|
|
25
|
+
await renderWithProvider(
|
|
26
|
+
<TextInput
|
|
27
|
+
label='Username'
|
|
28
|
+
placeholder='jane.doe'
|
|
29
|
+
value=''
|
|
30
|
+
onChangeText={() => {}}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(screen.getByPlaceholderText('jane.doe')).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('Helper text', () => {
|
|
39
|
+
it('renders neutral helper text without feedback icons', () => {
|
|
40
|
+
renderWithProvider(
|
|
41
|
+
<TextInput label='Address' helperText='Enter your ETH address' />,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const helperText = screen.getByText('Enter your ETH address');
|
|
45
|
+
|
|
46
|
+
expect(helperText).toBeTruthy();
|
|
47
|
+
expect(helperText.props.style).toEqual(
|
|
48
|
+
expect.objectContaining({ color: colors.text.muted }),
|
|
49
|
+
);
|
|
50
|
+
expect(screen.UNSAFE_queryByType(DeleteCircleFill)).toBeNull();
|
|
51
|
+
expect(screen.UNSAFE_queryByType(CheckmarkCircleFill)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders error helper text with an error icon', () => {
|
|
55
|
+
renderWithProvider(
|
|
56
|
+
<TextInput
|
|
57
|
+
label='Address'
|
|
58
|
+
helperText='Invalid address format'
|
|
59
|
+
status='error'
|
|
60
|
+
/>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const helperText = screen.getByText('Invalid address format');
|
|
64
|
+
|
|
65
|
+
expect(helperText.props.style).toEqual(
|
|
66
|
+
expect.objectContaining({ color: colors.text.error }),
|
|
67
|
+
);
|
|
68
|
+
expect(screen.UNSAFE_getByType(DeleteCircleFill)).toBeTruthy();
|
|
69
|
+
expect(screen.UNSAFE_queryByType(CheckmarkCircleFill)).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders success helper text with a success icon', () => {
|
|
73
|
+
renderWithProvider(
|
|
74
|
+
<TextInput
|
|
75
|
+
label='Address'
|
|
76
|
+
helperText='Address verified'
|
|
77
|
+
status='success'
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const helperText = screen.getByText('Address verified');
|
|
82
|
+
|
|
83
|
+
expect(helperText.props.style).toEqual(
|
|
84
|
+
expect.objectContaining({ color: colors.text.success }),
|
|
85
|
+
);
|
|
86
|
+
expect(screen.UNSAFE_getByType(CheckmarkCircleFill)).toBeTruthy();
|
|
87
|
+
expect(screen.UNSAFE_queryByType(DeleteCircleFill)).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -8,7 +8,7 @@ import { type TextInputProps } from './types';
|
|
|
8
8
|
* - **Automatic clear button** appears when input has content
|
|
9
9
|
* - **Floating label** with smooth animations
|
|
10
10
|
* - **Suffix elements** for icons, buttons, or custom content
|
|
11
|
-
* - **
|
|
11
|
+
* - **Helper text** with optional `status` (`error` | `success`) for border and helper feedback styling
|
|
12
12
|
* - **Container-based spacing** with padding and gap for clean layout
|
|
13
13
|
* - **Flexible styling** via style props
|
|
14
14
|
* - **React Native TextInput** with proper mobile behavior
|
|
@@ -28,12 +28,13 @@ import { type TextInputProps } from './types';
|
|
|
28
28
|
* // Basic input with automatic clear button
|
|
29
29
|
* <TextInput label="Title" value={title} onChangeText={setTitle} />
|
|
30
30
|
*
|
|
31
|
-
* // Input with error
|
|
31
|
+
* // Input with error helper
|
|
32
32
|
* <TextInput
|
|
33
33
|
* label="Email"
|
|
34
34
|
* value={email}
|
|
35
35
|
* onChangeText={setEmail}
|
|
36
|
-
*
|
|
36
|
+
* helperText="Please enter a valid email address"
|
|
37
|
+
* status="error"
|
|
37
38
|
* />
|
|
38
39
|
*
|
|
39
40
|
* // Input with suffix element
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as TrendStories from './Trend.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
|
+
|
|
7
|
+
<Meta title='Communication/Trend' of={TrendStories} />
|
|
8
|
+
|
|
9
|
+
# Trend
|
|
10
|
+
|
|
11
|
+
<CustomTabs>
|
|
12
|
+
<Tab label="Overview">
|
|
13
|
+
|
|
14
|
+
## Introduction
|
|
15
|
+
|
|
16
|
+
A compact indicator used to communicate directional change in a numeric value, typically a percentage. It combines an icon and a formatted label to convey whether a value has gone up, down, or stayed flat.
|
|
17
|
+
|
|
18
|
+
> View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=892-5393).
|
|
19
|
+
|
|
20
|
+
## Anatomy
|
|
21
|
+
|
|
22
|
+
- **Icon:** A directional triangle (up or down) or a minus symbol that visually reinforces the direction of change at a glance.
|
|
23
|
+
- **Value:** The numeric percentage formatted to exactly two decimal places, ensuring consistent alignment across lists and tables.
|
|
24
|
+
|
|
25
|
+
## Properties
|
|
26
|
+
|
|
27
|
+
### Overview
|
|
28
|
+
|
|
29
|
+
<Canvas of={TrendStories.Base} />
|
|
30
|
+
<Controls of={TrendStories.Base} />
|
|
31
|
+
|
|
32
|
+
### Variants
|
|
33
|
+
|
|
34
|
+
The variant is derived automatically from the `value` prop. No manual selection needed! This ensures the color and icon always stay in sync with the actual data.
|
|
35
|
+
|
|
36
|
+
- **Positive** - value greater than 0: green color, triangle up icon
|
|
37
|
+
- **Negative** - value less than 0: red color, triangle down icon
|
|
38
|
+
- **Neutral** - value equal to 0: muted color, minus icon
|
|
39
|
+
|
|
40
|
+
<Canvas of={TrendStories.VariantShowcase} />
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Size
|
|
45
|
+
|
|
46
|
+
Two sizes are available to accommodate different layout densities. Use `md` in most contexts and `sm` when space is constrained, such as inside a table cell or a compact list item.
|
|
47
|
+
|
|
48
|
+
- **md** (default) - standard body text and icon sizes
|
|
49
|
+
- **sm** - smaller text and icon, suitable for dense UIs
|
|
50
|
+
|
|
51
|
+
<Canvas of={TrendStories.SizeShowcase} />
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### Disabled
|
|
56
|
+
|
|
57
|
+
When `disabled` is `true`, the icon and text both render in a muted, non-interactive style regardless of the underlying value. Use this when the data is unavailable, loading, or the surrounding context is non-interactive.
|
|
58
|
+
|
|
59
|
+
<Canvas of={TrendStories.DisabledShowcase} />
|
|
60
|
+
|
|
61
|
+
</Tab>
|
|
62
|
+
<Tab label="Implementation">
|
|
63
|
+
|
|
64
|
+
## Setup
|
|
65
|
+
|
|
66
|
+
Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
|
|
67
|
+
|
|
68
|
+
### Basic Usage
|
|
69
|
+
|
|
70
|
+
Pass any numeric value - the component handles color, icon, and formatting automatically.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Trend } from '@ledgerhq/lumen-ui-rnative';
|
|
74
|
+
|
|
75
|
+
function MyComponent() {
|
|
76
|
+
return <Trend value={5.25} />;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Sizes
|
|
81
|
+
|
|
82
|
+
Choose `sm` for dense layouts like tables or compact list rows, and `md` for standard content areas.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<Trend value={3.14} size='md' />
|
|
86
|
+
<Trend value={3.14} size='sm' />
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Disabled State
|
|
90
|
+
|
|
91
|
+
Wrap or pass `disabled` when the value is unavailable or the parent context is non-interactive. The component style updates automatically - no extra styling needed.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<Trend value={5.25} disabled />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Layout Adjustments
|
|
98
|
+
|
|
99
|
+
Use `lx` for layout only - not for overriding colors or typography. The component's visual appearance is controlled exclusively via props.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<Trend value={-1.5} lx={{ marginTop: 's4' }} />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
</Tab>
|
|
106
|
+
</CustomTabs>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
|
+
import { Box } from '../Utility/Box';
|
|
3
|
+
import { Trend } from './Trend';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Trend> = {
|
|
6
|
+
component: Trend,
|
|
7
|
+
title: 'Communication/Trend',
|
|
8
|
+
argTypes: {
|
|
9
|
+
value: {
|
|
10
|
+
control: 'number',
|
|
11
|
+
},
|
|
12
|
+
size: {
|
|
13
|
+
control: 'radio',
|
|
14
|
+
options: ['sm', 'md'],
|
|
15
|
+
},
|
|
16
|
+
disabled: {
|
|
17
|
+
control: 'boolean',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
type Story = StoryObj<typeof Trend>;
|
|
25
|
+
|
|
26
|
+
export const Base: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
value: 5.25,
|
|
29
|
+
size: 'md',
|
|
30
|
+
disabled: false,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const VariantShowcase: Story = {
|
|
35
|
+
render: () => (
|
|
36
|
+
<Box lx={{ flexDirection: 'column', gap: 's8' }}>
|
|
37
|
+
<Trend value={5.25} />
|
|
38
|
+
<Trend value={-3.14} />
|
|
39
|
+
<Trend value={0} />
|
|
40
|
+
</Box>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const SizeShowcase: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's16' }}>
|
|
47
|
+
<Trend value={5.25} size='md' />
|
|
48
|
+
<Trend value={5.25} size='sm' />
|
|
49
|
+
</Box>
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const DisabledShowcase: Story = {
|
|
54
|
+
render: () => (
|
|
55
|
+
<Box lx={{ flexDirection: 'column', gap: 's8' }}>
|
|
56
|
+
<Trend value={5.25} disabled />
|
|
57
|
+
<Trend value={-3.14} disabled />
|
|
58
|
+
<Trend value={0} disabled />
|
|
59
|
+
</Box>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
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 { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
5
|
+
import { Trend } from './Trend';
|
|
6
|
+
|
|
7
|
+
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
|
8
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
9
|
+
{children}
|
|
10
|
+
</ThemeProvider>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
describe('Trend Component', () => {
|
|
14
|
+
it('should render positive value', () => {
|
|
15
|
+
const { getByText } = render(
|
|
16
|
+
<TestWrapper>
|
|
17
|
+
<Trend value={5.5} />
|
|
18
|
+
</TestWrapper>,
|
|
19
|
+
);
|
|
20
|
+
expect(getByText('5.50%')).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render negative value', () => {
|
|
24
|
+
const { getByText } = render(
|
|
25
|
+
<TestWrapper>
|
|
26
|
+
<Trend value={-3.2} />
|
|
27
|
+
</TestWrapper>,
|
|
28
|
+
);
|
|
29
|
+
expect(getByText('-3.20%')).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should render neutral when value is zero', () => {
|
|
33
|
+
const { getByText } = render(
|
|
34
|
+
<TestWrapper>
|
|
35
|
+
<Trend value={0} />
|
|
36
|
+
</TestWrapper>,
|
|
37
|
+
);
|
|
38
|
+
expect(getByText('0.00%')).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should render with sm size', () => {
|
|
42
|
+
const { getByText } = render(
|
|
43
|
+
<TestWrapper>
|
|
44
|
+
<Trend value={1.5} size='sm' />
|
|
45
|
+
</TestWrapper>,
|
|
46
|
+
);
|
|
47
|
+
expect(getByText('1.50%')).toBeTruthy();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should render with md size', () => {
|
|
51
|
+
const { getByText } = render(
|
|
52
|
+
<TestWrapper>
|
|
53
|
+
<Trend value={1.5} size='md' />
|
|
54
|
+
</TestWrapper>,
|
|
55
|
+
);
|
|
56
|
+
expect(getByText('1.50%')).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should render in disabled state', () => {
|
|
60
|
+
const { getByText } = render(
|
|
61
|
+
<TestWrapper>
|
|
62
|
+
<Trend value={5} disabled />
|
|
63
|
+
</TestWrapper>,
|
|
64
|
+
);
|
|
65
|
+
expect(getByText('5.00%')).toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should pass testID to root element', () => {
|
|
69
|
+
const { getByTestId } = render(
|
|
70
|
+
<TestWrapper>
|
|
71
|
+
<Trend testID='trend-id' value={10} />
|
|
72
|
+
</TestWrapper>,
|
|
73
|
+
);
|
|
74
|
+
expect(getByTestId('trend-id')).toBeTruthy();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should format value to 2 decimal places', () => {
|
|
78
|
+
const { getByText } = render(
|
|
79
|
+
<TestWrapper>
|
|
80
|
+
<Trend value={1.123456} />
|
|
81
|
+
</TestWrapper>,
|
|
82
|
+
);
|
|
83
|
+
expect(getByText('1.12%')).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should render all variants side by side', () => {
|
|
87
|
+
const { getByText } = render(
|
|
88
|
+
<TestWrapper>
|
|
89
|
+
<Trend value={10} />
|
|
90
|
+
<Trend value={-10} />
|
|
91
|
+
<Trend value={0} />
|
|
92
|
+
</TestWrapper>,
|
|
93
|
+
);
|
|
94
|
+
expect(getByText('10.00%')).toBeTruthy();
|
|
95
|
+
expect(getByText('-10.00%')).toBeTruthy();
|
|
96
|
+
expect(getByText('0.00%')).toBeTruthy();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should expose positive accessibility label', () => {
|
|
100
|
+
const { getByLabelText } = render(
|
|
101
|
+
<TestWrapper>
|
|
102
|
+
<Trend value={5.5} />
|
|
103
|
+
</TestWrapper>,
|
|
104
|
+
);
|
|
105
|
+
expect(getByLabelText('Trending up by 5.50%')).toBeTruthy();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should expose negative accessibility label with absolute value', () => {
|
|
109
|
+
const { getByLabelText } = render(
|
|
110
|
+
<TestWrapper>
|
|
111
|
+
<Trend value={-3.2} />
|
|
112
|
+
</TestWrapper>,
|
|
113
|
+
);
|
|
114
|
+
expect(getByLabelText('Trending down by 3.20%')).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should expose neutral accessibility label', () => {
|
|
118
|
+
const { getByLabelText } = render(
|
|
119
|
+
<TestWrapper>
|
|
120
|
+
<Trend value={0} />
|
|
121
|
+
</TestWrapper>,
|
|
122
|
+
);
|
|
123
|
+
expect(getByLabelText('No change, 0.00%')).toBeTruthy();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import { useCommonTranslation } from '../../../i18n';
|
|
4
|
+
import type { LumenTextStyle } from '../../../styles';
|
|
5
|
+
import { useStyleSheet } from '../../../styles';
|
|
6
|
+
import { Minus, TriangleDown, TriangleUp } from '../../Symbols';
|
|
7
|
+
import type { IconSize } from '../Icon';
|
|
8
|
+
import { Box, Text } from '../Utility';
|
|
9
|
+
import type { TrendProps } from './types';
|
|
10
|
+
|
|
11
|
+
type TrendVariant = 'positive' | 'negative' | 'neutral';
|
|
12
|
+
|
|
13
|
+
function getVariant(value: number): TrendVariant {
|
|
14
|
+
if (value === 0) {
|
|
15
|
+
return 'neutral';
|
|
16
|
+
}
|
|
17
|
+
return value > 0 ? 'positive' : 'negative';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Trend({
|
|
21
|
+
value,
|
|
22
|
+
size = 'md',
|
|
23
|
+
lx = {},
|
|
24
|
+
disabled: disabledProp = false,
|
|
25
|
+
style,
|
|
26
|
+
...props
|
|
27
|
+
}: TrendProps) {
|
|
28
|
+
const variant = getVariant(value);
|
|
29
|
+
|
|
30
|
+
const disabled = useDisabledContext({
|
|
31
|
+
consumerName: 'Trend',
|
|
32
|
+
mergeWith: { disabled: disabledProp },
|
|
33
|
+
});
|
|
34
|
+
const { t } = useCommonTranslation();
|
|
35
|
+
|
|
36
|
+
const styles = useStyles({ size, variant, disabled });
|
|
37
|
+
|
|
38
|
+
const Icon = {
|
|
39
|
+
positive: TriangleUp,
|
|
40
|
+
negative: TriangleDown,
|
|
41
|
+
neutral: Minus,
|
|
42
|
+
}[variant];
|
|
43
|
+
|
|
44
|
+
const iconSize = (
|
|
45
|
+
{
|
|
46
|
+
md: 16,
|
|
47
|
+
sm: 12,
|
|
48
|
+
} as const
|
|
49
|
+
)[size] as IconSize;
|
|
50
|
+
|
|
51
|
+
const iconColor = (
|
|
52
|
+
{
|
|
53
|
+
positive: 'success',
|
|
54
|
+
negative: 'error',
|
|
55
|
+
neutral: 'muted',
|
|
56
|
+
} as const
|
|
57
|
+
)[variant] as LumenTextStyle['color'];
|
|
58
|
+
|
|
59
|
+
const absoluteFormattedValue = `${Math.abs(value).toFixed(2)}%`;
|
|
60
|
+
const formattedValue =
|
|
61
|
+
value < 0 ? `-${absoluteFormattedValue}` : absoluteFormattedValue;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Box
|
|
65
|
+
accessible
|
|
66
|
+
accessibilityLabel={t(`components.trend.${variant}AriaLabel`, {
|
|
67
|
+
value: absoluteFormattedValue,
|
|
68
|
+
})}
|
|
69
|
+
accessibilityState={{ disabled }}
|
|
70
|
+
lx={lx}
|
|
71
|
+
style={[styles.container, style]}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<Icon size={iconSize} color={disabled ? 'disabled' : iconColor} />
|
|
75
|
+
<Text style={styles.text}>{formattedValue}</Text>
|
|
76
|
+
</Box>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const useStyles = ({
|
|
81
|
+
size,
|
|
82
|
+
variant,
|
|
83
|
+
disabled,
|
|
84
|
+
}: {
|
|
85
|
+
size: NonNullable<TrendProps['size']>;
|
|
86
|
+
variant: TrendVariant;
|
|
87
|
+
disabled: boolean;
|
|
88
|
+
}) =>
|
|
89
|
+
useStyleSheet(
|
|
90
|
+
(t) => {
|
|
91
|
+
const color = {
|
|
92
|
+
positive: t.colors.text.success,
|
|
93
|
+
negative: t.colors.text.error,
|
|
94
|
+
neutral: t.colors.text.muted,
|
|
95
|
+
}[variant];
|
|
96
|
+
|
|
97
|
+
const sizeMap = {
|
|
98
|
+
sm: t.typographies.body3,
|
|
99
|
+
md: t.typographies.body2,
|
|
100
|
+
}[size];
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
container: {
|
|
104
|
+
flexDirection: 'row',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
gap: t.spacings.s2,
|
|
107
|
+
},
|
|
108
|
+
text: StyleSheet.flatten([
|
|
109
|
+
{
|
|
110
|
+
...sizeMap,
|
|
111
|
+
color,
|
|
112
|
+
},
|
|
113
|
+
disabled && { color: t.colors.text.disabled },
|
|
114
|
+
]),
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
[size, variant, disabled],
|
|
118
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BoxProps } from '../Utility';
|
|
2
|
+
|
|
3
|
+
export type TrendProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The value to display in the trend. This value affects the appearance of the component in terms of color and icon.
|
|
6
|
+
* @required
|
|
7
|
+
*/
|
|
8
|
+
value: number;
|
|
9
|
+
/**
|
|
10
|
+
* The size of the trend component.
|
|
11
|
+
* @default md
|
|
12
|
+
*/
|
|
13
|
+
size?: 'sm' | 'md';
|
|
14
|
+
/**
|
|
15
|
+
* When `true`, shows a muted appearance on the trend, regardless of value.
|
|
16
|
+
*
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
} & Omit<BoxProps, 'children'>;
|
|
@@ -4,15 +4,15 @@ export * from './AmountInput';
|
|
|
4
4
|
export * from './Avatar';
|
|
5
5
|
export * from './Banner';
|
|
6
6
|
export * from './BottomSheet';
|
|
7
|
-
export * from './DotCount';
|
|
8
|
-
export * from './DotIndicator';
|
|
9
7
|
export * from './Button';
|
|
10
8
|
export * from './Card';
|
|
11
9
|
export * from './CardButton';
|
|
12
|
-
export * from './ContentBanner';
|
|
13
10
|
export * from './Checkbox';
|
|
11
|
+
export * from './ContentBanner';
|
|
14
12
|
export * from './Divider';
|
|
13
|
+
export * from './DotCount';
|
|
15
14
|
export * from './DotIcon';
|
|
15
|
+
export * from './DotIndicator';
|
|
16
16
|
export * from './DotSymbol';
|
|
17
17
|
export * from './Icon';
|
|
18
18
|
export * from './IconButton';
|
|
@@ -37,9 +37,10 @@ export * from './Subheader';
|
|
|
37
37
|
export * from './Switch';
|
|
38
38
|
export * from './TabBar';
|
|
39
39
|
export * from './Tag';
|
|
40
|
-
export * from './Utility';
|
|
41
40
|
export * from './TextInput';
|
|
42
41
|
export * from './ThemeProvider';
|
|
43
42
|
export * from './Tile';
|
|
44
43
|
export * from './TileButton';
|
|
45
44
|
export * from './Tooltip';
|
|
45
|
+
export * from './Trend';
|
|
46
|
+
export * from './Utility';
|
|
@@ -36,11 +36,11 @@ export const Ar = createIcon(
|
|
|
36
36
|
<Svg width={24} height={24} fill='currentColor' viewBox='0 0 17 16'>
|
|
37
37
|
<Path
|
|
38
38
|
fill='currentColor'
|
|
39
|
-
d='M8.197 4.967a.166.166 0 1 1-.333 0 .166.166 0 0 1 .333 0m-3.848 6.83a.167.167 0 0 1-.23-.053.167.167 0 0 1 .283-.176.167.167 0 0 1-.053.229m3.848-6.164a.167.167 0 0 1-.166.167.17.17 0 0 1-.
|
|
39
|
+
d='M8.197 4.967a.166.166 0 1 1-.333 0 .166.166 0 0 1 .333 0m-3.848 6.83a.167.167 0 0 1-.23-.053.167.167 0 0 1 .283-.176.167.167 0 0 1-.053.229m3.848-6.164a.167.167 0 0 1-.166.167.17.17 0 0 1-.167-.167.167.167 0 0 1 .333 0m-3.283 5.812a.166.166 0 1 1-.176-.283.166.166 0 0 1 .176.283M8.197 6.3a.167.167 0 1 1-.334 0 .167.167 0 0 1 .334 0M5.48 11.093a.166.166 0 1 1-.175-.282.166.166 0 0 1 .175.282m2.717-4.126a.166.166 0 1 1-.333 0 .166.166 0 0 1 .333 0m-2.15 3.774a.166.166 0 1 1-.177-.282.166.166 0 0 1 .177.282m2.15-3.108a.167.167 0 0 1-.334 0 .167.167 0 0 1 .334 0M6.612 10.39a.166.166 0 1 1-.175-.283.166.166 0 0 1 .175.283m.567-.352a.166.166 0 1 1-.176-.283.166.166 0 0 1 .176.283M8.197 8.3a.167.167 0 0 1-.166.167.167.167 0 1 1 0-.334c.092 0 .166.075.166.167m.73 1.777a.167.167 0 1 1-.288-.166.167.167 0 0 1 .288.166m.577.333a.167.167 0 1 1-.289-.165.167.167 0 0 1 .289.165m.578.334a.167.167 0 0 1-.228.061.166.166 0 1 1 .228-.061m.577.333a.167.167 0 1 1-.288-.166.167.167 0 0 1 .288.166m.577.333a.167.167 0 1 1-.289-.165.167.167 0 0 1 .29.165m.578.334a.167.167 0 0 1-.228.061.166.166 0 1 1 .228-.061'
|
|
40
40
|
/>
|
|
41
41
|
<Path
|
|
42
42
|
fill='currentColor'
|
|
43
|
-
d='m3.364 7-.
|
|
43
|
+
d='m3.364 7-.29-.581a.65.65 0 0 0-.36.581zm9 0h.65a.65.65 0 0 0-.342-.572zM8.03 14.667l-.29.581a.65.65 0 0 0 .599-.009zM8.013 1.349l.46-.46-.46-.459-.46.46zm-1.843.924a.65.65 0 1 0 .92.919l-.46-.46zm2.766.919a.65.65 0 1 0 .92-.92l-.46.46zM.775 13.966l-.635-.14-.14.634.635.14zm1.77 1.056a.65.65 0 1 0 .28-1.27l-.14.635zm-.714-2.826a.65.65 0 0 0-1.27-.28l.635.14zm13.116 1.77.14.634.635-.14-.14-.634zm-2.05-.214a.65.65 0 0 0 .28 1.27l-.14-.635zm2.263-1.836a.65.65 0 0 0-1.27.28l.636-.14zm-11.796.417h.65V7h-1.3v5.333zm0-5.333.29.581 4.667-2.333-.29-.581-.291-.582L3.073 6.42zM8.03 4.667l-.308.572 4.334 2.333.308-.572.308-.572-4.333-2.334zM12.364 7h-.65v5.333h1.3V7zm0 5.333-.308-.572-4.334 2.333.308.573.309.572 4.333-2.333zM8.03 14.667l.291-.582-4.667-2.333-.29.581-.29.582 4.666 2.333zM12.364 7l-.327-.562L7.73 8.94l.326.562.327.562 4.308-2.502zM8.055 9.502h-.65v5.172h1.3V9.502zm0 0 .306-.573L3.67 6.426 3.364 7l-.306.574 4.691 2.502zM8.03 4.667l.65-.004-.017-3.317-.65.003-.65.004.017 3.317zm-1.4-1.935.46.46 1.382-1.383-.46-.46-.459-.46L6.17 2.274zM8.013 1.35l-.46.46 1.383 1.383.46-.46.46-.46L8.471.89zm-4.65 10.984-.346-.55-2.589 1.633.347.55.346.55 2.59-1.633zm-.678 2.054.14-.635-1.91-.421-.14.635-.14.634 1.91.422zm-1.91-.421.635.14.42-1.91-.634-.14-.635-.14-.421 1.91zm11.589-1.633-.348.55 2.584 1.632.347-.55.347-.549-2.583-1.632zm.673 2.054.14.635 1.91-.422-.14-.634-.14-.635-1.91.421zm1.91-.421.635-.14-.422-1.91-.634.14-.635.14.421 1.91z'
|
|
44
44
|
/>
|
|
45
45
|
</Svg>,
|
|
46
46
|
);
|