@nuskin/foundation-ui-components 1.14.0 → 1.15.1
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/README.md +100 -17
- package/package.json +2 -3
- package/src/common/card/CardButton.tsx +3 -5
- package/src/common/card/NsMediumCard.tsx +3 -1
- package/src/common/card/types.d.ts +1 -0
- package/src/common/dropdown/NsDropdown.spec.tsx +32 -31
- package/src/common/dropdown/NsDropdown.styled.tsx +13 -22
- package/src/common/dropdown/NsDropdown.tsx +37 -19
- package/src/common/dropdown/NsDropdownOption.tsx +5 -17
- package/src/common/dropdown/stories/NsDropdown.stories.tsx +5 -7
- package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +4 -7
- package/src/common/dropdown/types.d.ts +6 -8
package/README.md
CHANGED
|
@@ -1,39 +1,122 @@
|
|
|
1
|
+
<div align="right">
|
|
2
|
+
<a href='https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/badges/master/pipeline.svg'><img src='https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/badges/master/pipeline.svg' alt='Pipeline Badge' /></a>
|
|
3
|
+
<a href='https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/-/badges/release.svg'><img src='https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/-/badges/release.svg' alt='Latest Release Badge' /></a>
|
|
4
|
+
<a href='https://img.shields.io/npm/dt/%40nuskin/foundation-ui-components'><img src='https://img.shields.io/npm/dt/%40nuskin/foundation-ui-components' alt='Downloads Badge' /></a>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
1
7
|
# @nuskin/foundation-ui-components
|
|
2
8
|
|
|
3
|
-
This
|
|
9
|
+
This comprehensive library houses a collection of reusable React components specifically designed for NuSkin Pages. These components are meticulously crafted to ensure consistent UI/UX across all NuSkin digital properties, fostering a seamless user experience.
|
|
10
|
+
|
|
11
|
+
## Releases
|
|
12
|
+
|
|
13
|
+
### [CHANGELOG](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/-/blob/master/CHANGELOG.md)
|
|
14
|
+
|
|
15
|
+
## Installation Guide
|
|
16
|
+
|
|
17
|
+
To integrate the NuSkin React Components Library into your NuSkin Pages project, follow these steps:
|
|
18
|
+
|
|
19
|
+
### Clone the repository to your local machine
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
git clone git@code.tls.nuskin.io:ns-am/content-foundation/foundation-ui-components.git
|
|
23
|
+
```
|
|
4
24
|
|
|
5
|
-
##
|
|
25
|
+
## Running the project
|
|
6
26
|
|
|
7
|
-
|
|
27
|
+
### Run the main application using yarn
|
|
8
28
|
|
|
9
|
-
```
|
|
10
|
-
|
|
29
|
+
```shell
|
|
30
|
+
yarn dev
|
|
11
31
|
```
|
|
12
32
|
|
|
13
|
-
|
|
33
|
+
### Storybook
|
|
34
|
+
|
|
35
|
+
See how React components look and behave in isolation using Storybook. To get started, follow these steps:
|
|
14
36
|
|
|
15
|
-
|
|
16
|
-
|
|
37
|
+
#### Run storybook using yarn.
|
|
38
|
+
|
|
39
|
+
```shell
|
|
40
|
+
yarn storybook
|
|
17
41
|
```
|
|
18
42
|
|
|
19
|
-
|
|
43
|
+
#### To view the Storybook, use the following URLs
|
|
44
|
+
|
|
45
|
+
| Environment | URL |
|
|
46
|
+
| ----------- | ------------------------------------------------------- |
|
|
47
|
+
| Local | http://localhost:6007 |
|
|
48
|
+
| Test | https://test.nuskin.com/static/foundation-ui-components |
|
|
20
49
|
|
|
21
|
-
|
|
22
|
-
import { Button } from "@nuskin/foundation-ui-components";
|
|
50
|
+
## Usage
|
|
23
51
|
|
|
52
|
+
The following example illustrates how to use this project
|
|
53
|
+
|
|
54
|
+
```shell
|
|
55
|
+
import { NsButton } from '@nuskin/foundation-ui-components';
|
|
24
56
|
const Page = () => {
|
|
25
57
|
return (
|
|
26
|
-
<
|
|
58
|
+
<NsButton variant="contained" color="primary">
|
|
27
59
|
Sample
|
|
28
|
-
</
|
|
60
|
+
</NsButton>
|
|
29
61
|
);
|
|
30
62
|
};
|
|
31
63
|
```
|
|
32
64
|
|
|
33
|
-
##
|
|
65
|
+
## Dependencies
|
|
66
|
+
|
|
67
|
+
This project relies on the following dependencies:
|
|
68
|
+
|
|
69
|
+
**Emotion UI Libraries**
|
|
70
|
+
|
|
71
|
+
- `@emotion/react@^11.11.1`
|
|
72
|
+
- `@emotion/styled@^11.11.0`
|
|
73
|
+
|
|
74
|
+
**Material-UI Libraries**
|
|
75
|
+
|
|
76
|
+
- `@mui/material@5.14.16`
|
|
77
|
+
- `@mui/icons-material@^5.14.16`
|
|
78
|
+
- `@mui/utils@^5.14.16`
|
|
79
|
+
|
|
80
|
+
**NuSkin Foundation Theme**
|
|
81
|
+
|
|
82
|
+
- `@nuskin/foundation-theme@1.2.0`
|
|
83
|
+
|
|
84
|
+
**Carousel Libraries**
|
|
85
|
+
|
|
86
|
+
- `react-material-ui-carousel@^3.4.2`
|
|
87
|
+
- `react-slick@^0.29.0`
|
|
88
|
+
- `slick-carousel@^1.8.1`
|
|
89
|
+
|
|
90
|
+
**Other Libraries**
|
|
91
|
+
|
|
92
|
+
- `cliui@^8.0.1`
|
|
93
|
+
- `framer-motion@^10.16.4`
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
We use Jest for testing our project. Below are the available test scripts:
|
|
98
|
+
|
|
99
|
+
- Run all tests and generate coverage report:\*\*
|
|
100
|
+
```bash
|
|
101
|
+
yarn test
|
|
102
|
+
```
|
|
103
|
+
- Run tests for a specific pattern (e.g., accordion/\*):
|
|
104
|
+
```bash
|
|
105
|
+
yarn run test:pattern
|
|
106
|
+
```
|
|
107
|
+
- Run only failed tests from the last test run:
|
|
108
|
+
```bash
|
|
109
|
+
yarn run test:failed
|
|
110
|
+
```
|
|
111
|
+
- Generate and view code coverage report:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
yarn run test:coverage
|
|
115
|
+
```
|
|
34
116
|
|
|
35
|
-
-
|
|
117
|
+
- The yarn test script builds the project using Next.js before running Jest tests.
|
|
118
|
+
- Feel free to customize the test patterns or add additional options as needed.
|
|
36
119
|
|
|
37
|
-
|
|
120
|
+
### Coverage Report:
|
|
38
121
|
|
|
39
|
-
|
|
122
|
+
After running tests, open the `index.html` file in the `coverage` directory to view the coverage report.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/foundation-ui-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "It is a foundation library written in react, that provides common components to all UI pages",
|
|
5
5
|
"main": "src/common/index.ts",
|
|
6
6
|
"repository": {
|
|
@@ -102,6 +102,5 @@
|
|
|
102
102
|
"files": [
|
|
103
103
|
"src/common"
|
|
104
104
|
],
|
|
105
|
-
"readme": "./README.md"
|
|
106
|
-
"packageManager": "yarn@3.6.3"
|
|
105
|
+
"readme": "./README.md"
|
|
107
106
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NsButton,
|
|
3
|
-
NsIcon,
|
|
4
|
-
} from '../';
|
|
1
|
+
import { NsButton, NsIcon } from '../';
|
|
5
2
|
import { NsCardButton } from './types';
|
|
6
3
|
|
|
7
4
|
const CardButton = ({
|
|
@@ -12,6 +9,7 @@ const CardButton = ({
|
|
|
12
9
|
buttonVariant = 'outlined',
|
|
13
10
|
buttonColor = 'primary',
|
|
14
11
|
fullWidth = false,
|
|
12
|
+
showArrowWithText = false,
|
|
15
13
|
}: NsCardButton) => {
|
|
16
14
|
if (showButton && url) {
|
|
17
15
|
return (
|
|
@@ -22,7 +20,7 @@ const CardButton = ({
|
|
|
22
20
|
variant={buttonVariant}
|
|
23
21
|
fullWidth={fullWidth}
|
|
24
22
|
endIcon={
|
|
25
|
-
buttonVariant === 'text' ? (
|
|
23
|
+
showArrowWithText && buttonVariant === 'text' ? (
|
|
26
24
|
<NsIcon name="arrowRight" size="small" color="primary" />
|
|
27
25
|
) : endIcon ? (
|
|
28
26
|
<NsIcon name={endIcon} color={buttonColor} />
|
|
@@ -14,6 +14,7 @@ type NsMediumCardProps = {
|
|
|
14
14
|
button?: NsCardButton;
|
|
15
15
|
className?: string;
|
|
16
16
|
fluid?: boolean;
|
|
17
|
+
showArrowWithButtonText?: boolean;
|
|
17
18
|
};
|
|
18
19
|
const textVariantButton = {
|
|
19
20
|
padding: '0 0 4px',
|
|
@@ -76,6 +77,7 @@ export default function NsMediumCard({
|
|
|
76
77
|
button,
|
|
77
78
|
className,
|
|
78
79
|
fluid,
|
|
80
|
+
showArrowWithButtonText = false,
|
|
79
81
|
}: NsMediumCardProps) {
|
|
80
82
|
return (
|
|
81
83
|
<CardContainer
|
|
@@ -92,7 +94,7 @@ export default function NsMediumCard({
|
|
|
92
94
|
{title}
|
|
93
95
|
</NsTypography>
|
|
94
96
|
<article className="subtitle">{subtitle}</article>
|
|
95
|
-
{button ? <CardButton {...button} /> : null}
|
|
97
|
+
{button ? <CardButton {...button} showArrowWithText={showArrowWithButtonText} /> : null}
|
|
96
98
|
</div>
|
|
97
99
|
</CardContainer>
|
|
98
100
|
);
|
|
@@ -4,12 +4,15 @@ import NsDropdown from './NsDropdown';
|
|
|
4
4
|
import NsDropdownOption from './NsDropdownOption';
|
|
5
5
|
|
|
6
6
|
function createDropdownOptions(options: { value: string; label: string }[]) {
|
|
7
|
-
return options.map((option) =>
|
|
7
|
+
return options.map((option) => (
|
|
8
|
+
<NsDropdownOption key={option.value} value={option.value}>
|
|
9
|
+
{option.label}
|
|
10
|
+
</NsDropdownOption>
|
|
11
|
+
));
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
describe('NsDropdown', () => {
|
|
11
15
|
const onSelect = jest.fn();
|
|
12
|
-
const selectedValue = '';
|
|
13
16
|
const selectedLabel = '';
|
|
14
17
|
|
|
15
18
|
const options = [
|
|
@@ -18,14 +21,9 @@ describe('NsDropdown', () => {
|
|
|
18
21
|
{ value: 'Option 3', label: 'Option 3' },
|
|
19
22
|
];
|
|
20
23
|
|
|
21
|
-
it('
|
|
24
|
+
it('1. Should render the placeholder text when no value is selected', () => {
|
|
22
25
|
render(
|
|
23
|
-
<NsDropdown
|
|
24
|
-
placeholder="Select an option"
|
|
25
|
-
selectedValue={selectedValue}
|
|
26
|
-
selectedLabel={selectedLabel}
|
|
27
|
-
onSelect={onSelect}
|
|
28
|
-
>
|
|
26
|
+
<NsDropdown placeholder="Select an option" selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
29
27
|
{createDropdownOptions(options)}
|
|
30
28
|
</NsDropdown>
|
|
31
29
|
);
|
|
@@ -33,20 +31,23 @@ describe('NsDropdown', () => {
|
|
|
33
31
|
expect(screen.getByText('Select an option')).toBeInTheDocument();
|
|
34
32
|
});
|
|
35
33
|
|
|
36
|
-
it('
|
|
37
|
-
const selectedLabel = 'Option 1';
|
|
34
|
+
it('2. Should render the selected value label', () => {
|
|
38
35
|
render(
|
|
39
|
-
<NsDropdown
|
|
36
|
+
<NsDropdown selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
40
37
|
{createDropdownOptions(options)}
|
|
41
38
|
</NsDropdown>
|
|
42
39
|
);
|
|
40
|
+
const dropdownHead = screen.getByTestId('dropdown-head');
|
|
41
|
+
fireEvent.click(dropdownHead);
|
|
42
|
+
const dropdownOptionContainer = screen.getByTestId('dropdown-option-container-0');
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
fireEvent.click(dropdownOptionContainer);
|
|
45
|
+
expect(onSelect).toHaveBeenCalledWith('Option 1');
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
it('
|
|
48
|
+
it('3. Should open and close Dropdown on click', () => {
|
|
48
49
|
render(
|
|
49
|
-
<NsDropdown
|
|
50
|
+
<NsDropdown selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
50
51
|
{createDropdownOptions(options)}
|
|
51
52
|
</NsDropdown>
|
|
52
53
|
);
|
|
@@ -64,14 +65,9 @@ describe('NsDropdown', () => {
|
|
|
64
65
|
expect(screen.queryByTestId('option-Option 3')).not.toBeInTheDocument();
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
it('dropdown
|
|
68
|
+
it('4. Should close dropdown on mouse leave when closeOnMouseLeave is set to true', async () => {
|
|
68
69
|
render(
|
|
69
|
-
<NsDropdown
|
|
70
|
-
closeOnMouseLeave
|
|
71
|
-
selectedValue={selectedValue}
|
|
72
|
-
selectedLabel={selectedLabel}
|
|
73
|
-
onSelect={onSelect}
|
|
74
|
-
>
|
|
70
|
+
<NsDropdown closeOnMouseLeave selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
75
71
|
{createDropdownOptions(options)}
|
|
76
72
|
</NsDropdown>
|
|
77
73
|
);
|
|
@@ -88,14 +84,9 @@ describe('NsDropdown', () => {
|
|
|
88
84
|
expect(screen.queryByTestId('option-Option 3')).not.toBeInTheDocument();
|
|
89
85
|
});
|
|
90
86
|
});
|
|
91
|
-
it('
|
|
87
|
+
it('5. Should not close dropdown on mouse leave when closeOnMouseLeave is set to false', () => {
|
|
92
88
|
render(
|
|
93
|
-
<NsDropdown
|
|
94
|
-
selectedValue={selectedValue}
|
|
95
|
-
selectedLabel={selectedLabel}
|
|
96
|
-
onSelect={onSelect}
|
|
97
|
-
closeOnMouseLeave={false}
|
|
98
|
-
>
|
|
89
|
+
<NsDropdown selectedLabel={selectedLabel} onSelect={onSelect} closeOnMouseLeave={false}>
|
|
99
90
|
{createDropdownOptions(options)}
|
|
100
91
|
</NsDropdown>
|
|
101
92
|
);
|
|
@@ -110,9 +101,9 @@ describe('NsDropdown', () => {
|
|
|
110
101
|
expect(screen.getByTestId('option-Option 2')).toBeInTheDocument();
|
|
111
102
|
expect(screen.getByTestId('option-Option 3')).toBeInTheDocument();
|
|
112
103
|
});
|
|
113
|
-
it('
|
|
104
|
+
it('6. Should not open dropdown when disabled is set to true', () => {
|
|
114
105
|
render(
|
|
115
|
-
<NsDropdown
|
|
106
|
+
<NsDropdown selectedLabel={selectedLabel} onSelect={onSelect} disabled>
|
|
116
107
|
{createDropdownOptions(options)}
|
|
117
108
|
</NsDropdown>
|
|
118
109
|
);
|
|
@@ -122,4 +113,14 @@ describe('NsDropdown', () => {
|
|
|
122
113
|
|
|
123
114
|
expect(screen.queryByTestId('dropdown-list')).not.toBeInTheDocument();
|
|
124
115
|
});
|
|
116
|
+
it('7. Should apply no border style when type is not outlined', () => {
|
|
117
|
+
render(
|
|
118
|
+
<NsDropdown selectedLabel={selectedLabel} onSelect={onSelect} type="unfilled">
|
|
119
|
+
{createDropdownOptions(options)}
|
|
120
|
+
</NsDropdown>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const dropdownHead = screen.getByTestId('dropdown-head');
|
|
124
|
+
expect(dropdownHead).toHaveStyle('border: none');
|
|
125
|
+
});
|
|
125
126
|
});
|
|
@@ -6,6 +6,7 @@ export const DropdownContainer = styled.div<{ disabled: boolean }>`
|
|
|
6
6
|
position: relative;
|
|
7
7
|
display: inline-block;
|
|
8
8
|
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
|
|
9
|
+
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
|
9
10
|
`;
|
|
10
11
|
|
|
11
12
|
/** this function also used for SelectHead */
|
|
@@ -22,7 +23,7 @@ export const DropdownHead = styled.div<{ type: string; disabled: boolean }>`
|
|
|
22
23
|
display: flex;
|
|
23
24
|
justify-content: space-between;
|
|
24
25
|
align-items: center;
|
|
25
|
-
border: ${getDropdownHeadBorder}
|
|
26
|
+
border: ${getDropdownHeadBorder};
|
|
26
27
|
color: ${paletteColor('primary')};
|
|
27
28
|
`;
|
|
28
29
|
|
|
@@ -37,39 +38,29 @@ export const DropdownList = styled.div<{ borderColor: FoundationTheme.PaletteCol
|
|
|
37
38
|
position: absolute;
|
|
38
39
|
z-index: 100;
|
|
39
40
|
border: 1px solid ${({ borderColor, theme }) => paletteColor(borderColor)({ theme })};
|
|
40
|
-
|
|
41
|
-
margin: 0;
|
|
41
|
+
margin-top: 10px;
|
|
42
42
|
width: 100%;
|
|
43
43
|
color: ${paletteColor('primary', 'main')};
|
|
44
44
|
background-color: ${paletteColor('primary', 'contrastText')};
|
|
45
45
|
`;
|
|
46
46
|
|
|
47
|
-
const getDividerColor = (theme: FoundationTheme.Theme) => {
|
|
48
|
-
return color('neutral', isDarkMode({ theme }) ? '20' : '30')({ theme });
|
|
49
|
-
};
|
|
50
47
|
const getListBgColor = (theme: FoundationTheme.Theme) => {
|
|
51
48
|
return color('accentColorB', isDarkMode({ theme }) ? '50' : '10')({ theme });
|
|
52
49
|
};
|
|
53
|
-
export const ListItemContainer = styled.div<{ showListDivider: boolean }>`
|
|
54
|
-
cursor: pointer;
|
|
55
|
-
padding: 5px 0;
|
|
56
|
-
border-bottom: 1px solid ${({ showListDivider, theme }) => (showListDivider ? getDividerColor(theme) : 'none')};
|
|
57
|
-
|
|
58
|
-
&:hover {
|
|
59
|
-
background-color: ${({ theme }) => getListBgColor(theme)};
|
|
60
|
-
}
|
|
61
50
|
|
|
51
|
+
export const DropdownOptionContainer = styled.div`
|
|
52
|
+
cursor: pointer;
|
|
62
53
|
&:last-child {
|
|
63
54
|
border-bottom: none;
|
|
64
55
|
}
|
|
65
56
|
`;
|
|
66
57
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
export const ListItem = styled.div`
|
|
59
|
+
&:hover {
|
|
60
|
+
background-color: ${({ theme }) => getListBgColor(theme)};
|
|
61
|
+
}
|
|
62
|
+
&:active {
|
|
63
|
+
background-color: #7de3e8;
|
|
64
|
+
}
|
|
65
|
+
padding: 10px;
|
|
75
66
|
`;
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import React, { useCallback, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DropdownContainer,
|
|
5
|
+
DropdownButton,
|
|
6
|
+
DropdownList,
|
|
7
|
+
DropdownHead,
|
|
8
|
+
DropdownOptionContainer,
|
|
9
|
+
} from './NsDropdown.styled';
|
|
4
10
|
import { NsIcon } from '../icon';
|
|
5
11
|
import { DropdownProps } from './types.d';
|
|
6
12
|
import { useOnClickOutside } from '../hooks/useOnClickOutside';
|
|
7
13
|
|
|
8
14
|
const NsDropdown: React.FC<DropdownProps> = ({
|
|
9
|
-
selectedValue,
|
|
10
15
|
selectedLabel,
|
|
11
16
|
onSelect,
|
|
12
17
|
children,
|
|
13
18
|
disabled = false,
|
|
14
19
|
type = 'outlined',
|
|
15
|
-
placeholder = 'Select',
|
|
16
20
|
dropdownBorderColor = 'accentColorB',
|
|
17
21
|
closeOnMouseLeave = false,
|
|
18
|
-
|
|
22
|
+
className,
|
|
23
|
+
placeholder,
|
|
19
24
|
}) => {
|
|
20
25
|
const [isOpen, setIsOpen] = useState(false);
|
|
21
|
-
const [value, setValue] = useState(selectedValue || '');
|
|
22
26
|
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
|
23
27
|
|
|
24
28
|
const handleToggle = useCallback(() => {
|
|
@@ -41,31 +45,45 @@ const NsDropdown: React.FC<DropdownProps> = ({
|
|
|
41
45
|
const handleSelect = (input: string) => {
|
|
42
46
|
onSelect(input);
|
|
43
47
|
setIsOpen(false);
|
|
44
|
-
setValue(input);
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
return (
|
|
48
|
-
<DropdownContainer disabled={disabled} ref={dropdownRef}>
|
|
49
|
-
<DropdownHead
|
|
51
|
+
<DropdownContainer disabled={disabled} ref={dropdownRef} className={className}>
|
|
52
|
+
<DropdownHead
|
|
53
|
+
data-testid="dropdown-head"
|
|
54
|
+
id="dropdown-head"
|
|
55
|
+
disabled={disabled}
|
|
56
|
+
type={type}
|
|
57
|
+
onClick={handleToggle}
|
|
58
|
+
>
|
|
50
59
|
<DropdownButton>{selectedLabel || placeholder}</DropdownButton>
|
|
51
|
-
<NsIcon
|
|
60
|
+
<NsIcon
|
|
61
|
+
testId="chevron-icon"
|
|
62
|
+
id="chevron-icon"
|
|
63
|
+
name="arrowDown"
|
|
64
|
+
color="primary"
|
|
65
|
+
cursor={disabled ? 'not-allowed' : 'pointer'}
|
|
66
|
+
/>
|
|
52
67
|
</DropdownHead>
|
|
53
68
|
{isOpen ? (
|
|
54
69
|
<DropdownList
|
|
55
70
|
data-testid="dropdown-list"
|
|
71
|
+
id="dropdown-list"
|
|
56
72
|
onMouseLeave={handleOnMouseLeave}
|
|
57
73
|
borderColor={dropdownBorderColor}
|
|
58
74
|
>
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
{children.map((child, key) => {
|
|
76
|
+
const value = React.isValidElement(child) ? child.props.value : child;
|
|
77
|
+
return (
|
|
78
|
+
<DropdownOptionContainer
|
|
79
|
+
data-testid={`dropdown-option-container-${key}`}
|
|
80
|
+
className="dropdown-option-container"
|
|
81
|
+
key={key}
|
|
82
|
+
onClick={() => handleSelect(value)}
|
|
83
|
+
>
|
|
84
|
+
{child}
|
|
85
|
+
</DropdownOptionContainer>
|
|
86
|
+
);
|
|
69
87
|
})}
|
|
70
88
|
</DropdownList>
|
|
71
89
|
) : null}
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ListItem, ListItemContainer } from './NsDropdown.styled';
|
|
3
2
|
import { DropdownOptionProps } from './types.d';
|
|
3
|
+
import { ListItem } from './NsDropdown.styled';
|
|
4
4
|
|
|
5
|
-
export const NsDropdownOption: React.FC<DropdownOptionProps> = ({
|
|
6
|
-
value,
|
|
7
|
-
label,
|
|
8
|
-
onSelect,
|
|
9
|
-
selectedValue,
|
|
10
|
-
showListDivider = true,
|
|
11
|
-
}) => {
|
|
12
|
-
const handleClick = () => {
|
|
13
|
-
if (onSelect && typeof onSelect === 'function') {
|
|
14
|
-
onSelect(value);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
5
|
+
export const NsDropdownOption: React.FC<DropdownOptionProps> = ({ value, children }) => {
|
|
18
6
|
return (
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
</
|
|
7
|
+
<ListItem data-testid={`option-${value}`} id="dropdown-item">
|
|
8
|
+
{children}
|
|
9
|
+
</ListItem>
|
|
22
10
|
);
|
|
23
11
|
};
|
|
24
12
|
export default NsDropdownOption;
|
|
@@ -20,7 +20,6 @@ export default {
|
|
|
20
20
|
},
|
|
21
21
|
placeholder: { control: 'text' },
|
|
22
22
|
closeOnMouseLeave: { control: 'boolean' },
|
|
23
|
-
showListDivider: { control: 'boolean' },
|
|
24
23
|
},
|
|
25
24
|
parameters: {
|
|
26
25
|
options: [
|
|
@@ -34,19 +33,16 @@ export default {
|
|
|
34
33
|
disabled: false,
|
|
35
34
|
dropdownBorderColor: 'primary',
|
|
36
35
|
closeOnMouseLeave: false,
|
|
37
|
-
showListDivider: true,
|
|
38
|
-
selectedValue: '',
|
|
39
36
|
selectedLabel: '',
|
|
37
|
+
placeholder: 'options',
|
|
40
38
|
},
|
|
41
39
|
} as MetaStory;
|
|
42
40
|
|
|
43
41
|
const RenderStory = (args: any, context: any) => {
|
|
44
42
|
const [selectedLabel, setSelectedLabel] = useState(args.selectedLabel);
|
|
45
|
-
const [selectedValue, setSelectedValue] = useState(args.selectedValue);
|
|
46
43
|
|
|
47
44
|
const options = context.parameters.options;
|
|
48
45
|
const handleSelect = (value: string) => {
|
|
49
|
-
setSelectedValue(value);
|
|
50
46
|
options.find((option: any) => {
|
|
51
47
|
if (option.value === value) {
|
|
52
48
|
setSelectedLabel(option.label);
|
|
@@ -55,9 +51,11 @@ const RenderStory = (args: any, context: any) => {
|
|
|
55
51
|
};
|
|
56
52
|
|
|
57
53
|
return (
|
|
58
|
-
<NsDropdown {...args}
|
|
54
|
+
<NsDropdown {...args} selectedLabel={selectedLabel} onSelect={handleSelect}>
|
|
59
55
|
{options.map((option: any) => (
|
|
60
|
-
<NsDropdownOption key={option.value} value={option.value}
|
|
56
|
+
<NsDropdownOption key={option.value} value={option.value}>
|
|
57
|
+
{option.label}
|
|
58
|
+
</NsDropdownOption>
|
|
61
59
|
))}
|
|
62
60
|
</NsDropdown>
|
|
63
61
|
);
|
|
@@ -20,7 +20,6 @@ export default {
|
|
|
20
20
|
},
|
|
21
21
|
placeholder: { control: 'text' },
|
|
22
22
|
closeOnMouseLeave: { control: 'boolean' },
|
|
23
|
-
showListDivider: { control: 'boolean' },
|
|
24
23
|
},
|
|
25
24
|
parameters: {
|
|
26
25
|
options: [
|
|
@@ -30,23 +29,19 @@ export default {
|
|
|
30
29
|
],
|
|
31
30
|
},
|
|
32
31
|
args: {
|
|
33
|
-
selectedValue: 'english',
|
|
34
32
|
selectedLabel: 'United States (US) | English',
|
|
35
33
|
type: 'unfilled',
|
|
36
34
|
disabled: false,
|
|
37
35
|
dropdownBorderColor: 'accentColorB',
|
|
38
36
|
closeOnMouseLeave: true,
|
|
39
|
-
showListDivider: true,
|
|
40
37
|
},
|
|
41
38
|
} as MetaStory;
|
|
42
39
|
|
|
43
40
|
const RenderStory = (args: any, context: any) => {
|
|
44
41
|
const [selectedLabel, setSelectedLabel] = useState(args.selectedLabel);
|
|
45
|
-
const [selectedValue, setSelectedValue] = useState(args.selectedValue);
|
|
46
42
|
|
|
47
43
|
const options = context.parameters.options;
|
|
48
44
|
const handleSelect = (value: string) => {
|
|
49
|
-
setSelectedValue(value);
|
|
50
45
|
options.find((option: any) => {
|
|
51
46
|
if (option.value === value) {
|
|
52
47
|
setSelectedLabel(`${option.country} | ${option.label}`);
|
|
@@ -55,9 +50,11 @@ const RenderStory = (args: any, context: any) => {
|
|
|
55
50
|
};
|
|
56
51
|
|
|
57
52
|
return (
|
|
58
|
-
<NsDropdown {...args}
|
|
53
|
+
<NsDropdown {...args} selectedLabel={selectedLabel} onSelect={handleSelect}>
|
|
59
54
|
{options.map((option: any) => (
|
|
60
|
-
<NsDropdownOption key={option.value} value={option.value}
|
|
55
|
+
<NsDropdownOption key={option.value} value={option.value}>
|
|
56
|
+
{option.label}
|
|
57
|
+
</NsDropdownOption>
|
|
61
58
|
))}
|
|
62
59
|
</NsDropdown>
|
|
63
60
|
);
|
|
@@ -4,22 +4,20 @@ import { FoundationTheme } from '@nuskin/foundation-theme';
|
|
|
4
4
|
import { AdditionalProps } from '../common.types';
|
|
5
5
|
|
|
6
6
|
export interface DropdownProps extends AdditionalProps {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
children: ReactNode;
|
|
7
|
+
selectedLabel: ReactNode;
|
|
8
|
+
onSelect: (arg: ReactNode) => void; // eslint-disable-line
|
|
9
|
+
children: ReactNode[];
|
|
11
10
|
disabled?: boolean;
|
|
12
11
|
type?: 'outlined' | 'unfilled';
|
|
13
12
|
placeholder?: string;
|
|
14
13
|
dropdownBorderColor?: FoundationTheme.PaletteColors;
|
|
15
14
|
closeOnMouseLeave?: boolean;
|
|
16
15
|
showListDivider?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
placeholder?: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export interface DropdownOptionProps {
|
|
20
21
|
value: string;
|
|
21
|
-
|
|
22
|
-
selectedValue?: string;
|
|
23
|
-
onSelect?: (arg: string) => void; // eslint-disable-line
|
|
24
|
-
showListDivider?: boolean;
|
|
22
|
+
children: ReactNode;
|
|
25
23
|
}
|