@nuskin/foundation-ui-components 1.1.1 → 1.3.0
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/package.json +5 -5
- package/src/common/auth-dropdown/NsAuthDropdown.tsx +16 -7
- package/src/common/autocomplete/NsAutocomplete.spec.tsx +3 -3
- package/src/common/autocomplete/mock/MockData.ts +1 -0
- package/src/common/button/NsButton.stories.tsx +0 -1
- package/src/common/card/styled/NsFfgStyled.tsx +0 -1
- package/src/common/carousal/NsCarousal.tsx +9 -3
- package/src/common/cart-icon/NsCartIcon.spec.tsx +5 -4
- package/src/common/cart-icon/NsCartIcon.stories.tsx +1 -1
- package/src/common/cart-icon/NsCartIcon.tsx +1 -1
- package/src/common/cart-icon/types.d.ts +2 -2
- package/src/common/checkbox/types.d.ts +1 -1
- package/src/common/dropdown/NsDropdown.tsx +17 -8
- package/src/common/hooks/useOnClickOutside.tsx +28 -0
- package/src/common/icon/NsIcon.spec.tsx +11 -5
- package/src/common/icon/NsIconVariants.ts +3 -1
- package/src/common/icon/icons/AlertErrorIcon.tsx +2 -1
- package/src/common/icon/icons/ArrowLeft.tsx +2 -2
- package/src/common/icon/icons/ArrowRight.tsx +2 -2
- package/src/common/icon/icons/CartBadge.tsx +2 -2
- package/src/common/icon/icons/Check.tsx +25 -0
- package/src/common/icon/icons/CloseIcon.tsx +1 -1
- package/src/common/icon/icons/ErrorIcon.tsx +3 -2
- package/src/common/icon/icons/SearchIcon.tsx +3 -2
- package/src/common/icon/icons/SuccessIcon.tsx +7 -1
- package/src/common/icon/icons/TrashIcon.tsx +2 -1
- package/src/common/icon/icons/UserOutline.tsx +4 -1
- package/src/common/icon/icons/WarningIcon.tsx +3 -2
- package/src/common/index.ts +8 -4
- package/src/common/label/NsLabel.stories.tsx +0 -3
- package/src/common/label/NsLabel.tsx +1 -1
- package/src/common/progress/NsProgress.spec.tsx +47 -0
- package/src/common/progress/NsProgress.stories.tsx +37 -0
- package/src/common/progress/NsProgress.styled.tsx +39 -0
- package/src/common/progress/NsProgress.tsx +27 -0
- package/src/common/progress/index.ts +2 -0
- package/src/common/progress/types.d.ts +21 -0
- package/src/common/radiogroup/NsRadioGroup.styled.tsx +4 -3
- package/src/common/select/NsSelect.spec.tsx +98 -17
- package/src/common/select/NsSelect.styled.tsx +52 -22
- package/src/common/select/NsSelect.tsx +109 -44
- package/src/common/select/NsSelectOption.tsx +0 -1
- package/src/common/select/stories/NsSelect.stories.tsx +23 -14
- package/src/common/select/types.d.ts +3 -1
|
@@ -16,7 +16,6 @@ export const Default: Story = {
|
|
|
16
16
|
args: {
|
|
17
17
|
children: 'Label',
|
|
18
18
|
required: false,
|
|
19
|
-
size: 16,
|
|
20
19
|
},
|
|
21
20
|
};
|
|
22
21
|
|
|
@@ -24,7 +23,6 @@ export const Optional: Story = {
|
|
|
24
23
|
args: {
|
|
25
24
|
children: 'Label',
|
|
26
25
|
showOptional: true,
|
|
27
|
-
size: 16,
|
|
28
26
|
},
|
|
29
27
|
};
|
|
30
28
|
|
|
@@ -33,6 +31,5 @@ export const Required: Story = {
|
|
|
33
31
|
children: 'Label',
|
|
34
32
|
showOptional: true,
|
|
35
33
|
required: true,
|
|
36
|
-
size: 16,
|
|
37
34
|
},
|
|
38
35
|
};
|
|
@@ -27,7 +27,7 @@ const StyledLabel = styled(FormLabel)<{ fontSize?: number; style?: CSSProperties
|
|
|
27
27
|
const NsLabel = (props: NsLabelProps) => {
|
|
28
28
|
const { required, showOptional, children, size } = props;
|
|
29
29
|
return (
|
|
30
|
-
<StyledLabel
|
|
30
|
+
<StyledLabel fontSize={size} {...props} required={required}>
|
|
31
31
|
{children}
|
|
32
32
|
{!required && showOptional ? <span className="optional-label">{' (Optional)'}</span> : null}
|
|
33
33
|
</StyledLabel>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Progress from './NsProgress';
|
|
4
|
+
|
|
5
|
+
describe(' NsProgress Component', () => {
|
|
6
|
+
test('should render Progress bar', () => {
|
|
7
|
+
render(<Progress color="primary" value={10}></Progress>);
|
|
8
|
+
|
|
9
|
+
const pbar = screen.getByRole('progressbar');
|
|
10
|
+
|
|
11
|
+
expect(pbar).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('matches the height of the progress bar to the width of the container', () => {
|
|
15
|
+
// Define the props
|
|
16
|
+
const value = 50;
|
|
17
|
+
|
|
18
|
+
// Render the StyledProgress component with the value prop
|
|
19
|
+
const { container } = render(<Progress color="primary" value={value} />);
|
|
20
|
+
|
|
21
|
+
// Get the progress bar element
|
|
22
|
+
const progressBar = container.querySelector('.MuiLinearProgress-bar');
|
|
23
|
+
|
|
24
|
+
// Check if the progress bar element exists
|
|
25
|
+
if (progressBar) {
|
|
26
|
+
// Retrieve the computed width of the container element
|
|
27
|
+
const containerWidth = container.clientWidth;
|
|
28
|
+
|
|
29
|
+
// Retrieve the computed styles of the progress bar
|
|
30
|
+
const progressBarStyle = getComputedStyle(progressBar);
|
|
31
|
+
const progressBarHeight = parseFloat(progressBarStyle.height);
|
|
32
|
+
const progressBarPadding =
|
|
33
|
+
parseFloat(progressBarStyle.paddingTop) + parseFloat(progressBarStyle.paddingBottom);
|
|
34
|
+
const progressBarBorder =
|
|
35
|
+
parseFloat(progressBarStyle.borderTopWidth) + parseFloat(progressBarStyle.borderBottomWidth);
|
|
36
|
+
|
|
37
|
+
// Calculate the expected height of the progress bar
|
|
38
|
+
const expectedHeight = containerWidth - progressBarPadding - progressBarBorder;
|
|
39
|
+
|
|
40
|
+
// Assert that the height of the progress bar matches the width of the container
|
|
41
|
+
expect(progressBarHeight).toBe(expectedHeight);
|
|
42
|
+
} else {
|
|
43
|
+
// Handle the case where the progress bar element is not found
|
|
44
|
+
throw new Error('Progress bar element not found');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
2
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
3
|
+
import NsProgress from './NsProgress';
|
|
4
|
+
|
|
5
|
+
type ComponentType = typeof NsProgress;
|
|
6
|
+
type Story = StoryObj<ComponentType>;
|
|
7
|
+
type MetaStory = Meta<ComponentType>;
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
component: NsProgress,
|
|
11
|
+
title: 'Input/NsProgress',
|
|
12
|
+
decorators: [withCenterDecorator],
|
|
13
|
+
argTypes: {
|
|
14
|
+
color: {
|
|
15
|
+
description: 'defines the color for the completed progress',
|
|
16
|
+
},
|
|
17
|
+
height: {
|
|
18
|
+
description: 'height of the bar',
|
|
19
|
+
},
|
|
20
|
+
value: {
|
|
21
|
+
description: 'value of the progress done',
|
|
22
|
+
},
|
|
23
|
+
backgroundColor: {
|
|
24
|
+
description: 'color of incomplete progress',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
} as MetaStory;
|
|
28
|
+
|
|
29
|
+
export const ProgressBar: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
color: 'primary',
|
|
32
|
+
backgroundColor: '#19dbdb',
|
|
33
|
+
value: undefined,
|
|
34
|
+
height: '10px',
|
|
35
|
+
body: <span>{'% Complete'}</span>,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { styled, ColorUtils } from '@nuskin/foundation-theme';
|
|
2
|
+
import { LinearProgress } from '@mui/material';
|
|
3
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
4
|
+
import { NsProgressProps } from './types';
|
|
5
|
+
|
|
6
|
+
const { color } = ColorUtils;
|
|
7
|
+
export const StyledCheckCircle = styled(CheckCircleIcon)`
|
|
8
|
+
color: ${color('neutral', '50')};
|
|
9
|
+
padding-top: 0.6em;
|
|
10
|
+
padding-right: 0.5em;
|
|
11
|
+
justify-self: flex-end;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const PercentDiv = styled('div')`
|
|
15
|
+
color: ${color('primary', '100')};
|
|
16
|
+
display: flex;
|
|
17
|
+
justify-content: flex-end;
|
|
18
|
+
font-family: Inter;
|
|
19
|
+
font-size: 12px;
|
|
20
|
+
font-style: normal;
|
|
21
|
+
font-weight: 700;
|
|
22
|
+
text-transform: uppercase;
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export const StyledLinearProgress = styled(LinearProgress)<Omit<NsProgressProps, 'value'>>(
|
|
26
|
+
({ height, backgroundColor, color }) => ({
|
|
27
|
+
width: '334px',
|
|
28
|
+
height,
|
|
29
|
+
backgroundColor,
|
|
30
|
+
borderRadius: '3px',
|
|
31
|
+
display: 'inline-block',
|
|
32
|
+
|
|
33
|
+
'.MuiLinearProgress-bar': {
|
|
34
|
+
backgroundColor: color,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export default StyledLinearProgress;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { NsProgressProps } from './types';
|
|
4
|
+
import StyledLinearProgress, { PercentDiv, StyledCheckCircle } from './NsProgress.styled';
|
|
5
|
+
|
|
6
|
+
const NsProgress = (props: NsProgressProps) => {
|
|
7
|
+
const { color, backgroundColor, height, value, body } = props;
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<PercentDiv>
|
|
11
|
+
{value}
|
|
12
|
+
{body}
|
|
13
|
+
</PercentDiv>
|
|
14
|
+
<StyledLinearProgress
|
|
15
|
+
variant="determinate"
|
|
16
|
+
role="progressbar"
|
|
17
|
+
color={color}
|
|
18
|
+
value={value}
|
|
19
|
+
height={height}
|
|
20
|
+
backgroundColor={backgroundColor}
|
|
21
|
+
/>
|
|
22
|
+
<StyledCheckCircle />
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default NsProgress;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FoundationTheme } from '@nuskin/foundation-theme';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
declare module '@mui/material/LinearProgress' {
|
|
5
|
+
// eslint-disable-next-line no-unused-vars
|
|
6
|
+
interface LinearProgressPropsColorOverrides {
|
|
7
|
+
neutral: true;
|
|
8
|
+
accentColorA: true;
|
|
9
|
+
accentColorB: true;
|
|
10
|
+
accentColorC: true;
|
|
11
|
+
accentColorD: true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type NsProgressProps = {
|
|
16
|
+
color?: FoundationTheme.PaletteColors;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
height?: string;
|
|
19
|
+
value: number;
|
|
20
|
+
body?: ReactNode;
|
|
21
|
+
};
|
|
@@ -5,9 +5,10 @@ import { skipDarkModeEnabled } from '../util';
|
|
|
5
5
|
const { paletteColor, isDarkMode, color } = ColorUtils;
|
|
6
6
|
|
|
7
7
|
export const StyledHelperText = styled('div')`
|
|
8
|
-
margin-top :10px;
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
margin-top :10px;
|
|
9
|
+
padding-left: 0.5em;
|
|
10
|
+
text-align: left;
|
|
11
|
+
justify-self: flex-start;
|
|
11
12
|
)`;
|
|
12
13
|
|
|
13
14
|
export const StyledSpan = styled('span')`
|
|
@@ -8,7 +8,7 @@ function createSelectOptions(options: { value: string; label: string }[]) {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
describe('NsSelectOption', () => {
|
|
11
|
-
|
|
11
|
+
it('1. renders NsSelectOption component without errors', () => {
|
|
12
12
|
const value = 'option1';
|
|
13
13
|
const label = 'Option 1';
|
|
14
14
|
const { getByTestId, getByText } = render(<NsSelectOption value={value} label={label} />);
|
|
@@ -21,7 +21,7 @@ describe('NsSelectOption', () => {
|
|
|
21
21
|
const optionLabel = getByText(label);
|
|
22
22
|
expect(optionLabel).toBeInTheDocument();
|
|
23
23
|
});
|
|
24
|
-
|
|
24
|
+
it('2. clicking on NsSelectOption selects the option', () => {
|
|
25
25
|
const value = 'option1';
|
|
26
26
|
const label = 'Option 1';
|
|
27
27
|
const onSelect = jest.fn();
|
|
@@ -43,7 +43,7 @@ describe('NsSelect', () => {
|
|
|
43
43
|
{ value: 'Option3', label: 'Option 3' },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
-
it('renders the placeholder text when no value is selected', () => {
|
|
46
|
+
it('1. renders the placeholder text when no value is selected', () => {
|
|
47
47
|
render(
|
|
48
48
|
<NsSelect
|
|
49
49
|
placeholder="Select an option"
|
|
@@ -58,9 +58,7 @@ describe('NsSelect', () => {
|
|
|
58
58
|
expect(screen.getByText('Select an option')).toBeInTheDocument();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
// const value = 'option1';
|
|
63
|
-
// const label = 'Option 1';
|
|
61
|
+
it('2. clicking outside the select container closes the dropdown', () => {
|
|
64
62
|
const onSelect = jest.fn();
|
|
65
63
|
const selectedLabel = 'Option 1';
|
|
66
64
|
const { getByTestId, queryByTestId } = render(
|
|
@@ -74,7 +72,7 @@ describe('NsSelect', () => {
|
|
|
74
72
|
expect(queryByTestId('option-Option1')).toBeNull();
|
|
75
73
|
});
|
|
76
74
|
|
|
77
|
-
it('renders the selected value label', () => {
|
|
75
|
+
it('3. renders the selected value label', () => {
|
|
78
76
|
const selectedLabel = 'Option 1';
|
|
79
77
|
render(
|
|
80
78
|
<NsSelect selectedValue="Option1" selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
@@ -85,7 +83,7 @@ describe('NsSelect', () => {
|
|
|
85
83
|
expect(screen.getByText(selectedLabel)).toBeInTheDocument();
|
|
86
84
|
});
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
it('4. clicking on select button opens the dropdown', () => {
|
|
89
87
|
const { getByTestId, getByText } = render(
|
|
90
88
|
<NsSelect selectedValue="Option1" selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
91
89
|
{createSelectOptions(options)}
|
|
@@ -105,7 +103,7 @@ describe('NsSelect', () => {
|
|
|
105
103
|
expect(option2).toBeInTheDocument();
|
|
106
104
|
});
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
it('5. disabling the select component prevents interaction', () => {
|
|
109
107
|
const onSelect = jest.fn();
|
|
110
108
|
const { getByTestId, queryByTestId } = render(
|
|
111
109
|
<NsSelect selectedValue="Option1" selectedLabel={selectedLabel} onSelect={onSelect} disabled>
|
|
@@ -124,7 +122,7 @@ describe('NsSelect', () => {
|
|
|
124
122
|
expect(onSelect).not.toHaveBeenCalled();
|
|
125
123
|
});
|
|
126
124
|
|
|
127
|
-
it('select closes on mouse leave when closeOnMouseLeave is set to true', async () => {
|
|
125
|
+
it('6. select closes on mouse leave when closeOnMouseLeave is set to true', async () => {
|
|
128
126
|
render(
|
|
129
127
|
<NsSelect closeOnMouseLeave selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
130
128
|
{createSelectOptions(options)}
|
|
@@ -146,7 +144,7 @@ describe('NsSelect', () => {
|
|
|
146
144
|
expect(option3).not.toBeInTheDocument();
|
|
147
145
|
});
|
|
148
146
|
});
|
|
149
|
-
it('select does not open when disabled', () => {
|
|
147
|
+
it('7. select does not open when disabled', () => {
|
|
150
148
|
render(
|
|
151
149
|
<NsSelect selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect} disabled>
|
|
152
150
|
{createSelectOptions(options)}
|
|
@@ -159,7 +157,7 @@ describe('NsSelect', () => {
|
|
|
159
157
|
expect(screen.queryByTestId('select-list')).not.toBeInTheDocument();
|
|
160
158
|
});
|
|
161
159
|
|
|
162
|
-
it('opens the select on "ArrowDown" key press', () => {
|
|
160
|
+
it('8. opens the select on "ArrowDown" key press', () => {
|
|
163
161
|
render(
|
|
164
162
|
<NsSelect selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
165
163
|
{createSelectOptions(options)}
|
|
@@ -172,9 +170,77 @@ describe('NsSelect', () => {
|
|
|
172
170
|
expect(selectHead).toBeInTheDocument();
|
|
173
171
|
});
|
|
174
172
|
|
|
175
|
-
|
|
176
|
-
const
|
|
173
|
+
it('9. closes the select when closeSelect is called', () => {
|
|
174
|
+
const { getByTestId } = render(
|
|
175
|
+
<NsSelect selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect} closeOnMouseLeave>
|
|
176
|
+
{createSelectOptions(options)}
|
|
177
|
+
</NsSelect>
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const selectContainer = getByTestId('select-container');
|
|
181
|
+
fireEvent.mouseLeave(selectContainer);
|
|
182
|
+
|
|
183
|
+
expect(screen.queryByTestId('select-list')).not.toBeInTheDocument();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('10. does not open the select when disabled', () => {
|
|
177
187
|
render(
|
|
188
|
+
<NsSelect selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect} disabled>
|
|
189
|
+
{createSelectOptions(options)}
|
|
190
|
+
</NsSelect>
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const selectHead = screen.getByTestId('select-head');
|
|
194
|
+
fireEvent.mouseDown(selectHead);
|
|
195
|
+
|
|
196
|
+
expect(screen.queryByTestId('select-list')).not.toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('11. selects the next option on arrow right click', () => {
|
|
200
|
+
// Render the NsSelect component with options and onSelect function
|
|
201
|
+
const onSelect = jest.fn();
|
|
202
|
+
const { getByTestId } = render(
|
|
203
|
+
<NsSelect selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect} hasArrows>
|
|
204
|
+
{createSelectOptions(options)}
|
|
205
|
+
</NsSelect>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Simulate the arrow right click on the NsSelect component
|
|
209
|
+
const selectHead = getByTestId('select-head');
|
|
210
|
+
fireEvent.mouseDown(selectHead);
|
|
211
|
+
|
|
212
|
+
const rightArrow = document.getElementsByClassName('RightArrow')[0];
|
|
213
|
+
fireEvent.click(rightArrow);
|
|
214
|
+
|
|
215
|
+
// Verify that the next option is selected and the onSelect function is called
|
|
216
|
+
expect(onSelect).toHaveBeenCalledWith('Option1');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('12. selects the previous option on arrow left click', () => {
|
|
220
|
+
const { getByTestId } = render(
|
|
221
|
+
<NsSelect
|
|
222
|
+
selectedValue={selectedValue}
|
|
223
|
+
selectedLabel={selectedLabel}
|
|
224
|
+
onSelect={onSelect}
|
|
225
|
+
hasArrows
|
|
226
|
+
reverseArrowControls
|
|
227
|
+
>
|
|
228
|
+
{createSelectOptions(options)}
|
|
229
|
+
</NsSelect>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const selectHead = getByTestId('select-head');
|
|
233
|
+
fireEvent.mouseDown(selectHead);
|
|
234
|
+
|
|
235
|
+
const leftArrow = document.getElementsByClassName('LeftArrow')[0];
|
|
236
|
+
fireEvent.click(leftArrow);
|
|
237
|
+
|
|
238
|
+
expect(onSelect).toHaveBeenCalledWith('Option1');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('13. renders an error message with red styles', () => {
|
|
242
|
+
const errorMessage = 'Invalid value';
|
|
243
|
+
const { getByText } = render(
|
|
178
244
|
<NsSelect
|
|
179
245
|
selectedValue={selectedValue}
|
|
180
246
|
selectedLabel={selectedLabel}
|
|
@@ -185,9 +251,24 @@ describe('NsSelect', () => {
|
|
|
185
251
|
</NsSelect>
|
|
186
252
|
);
|
|
187
253
|
|
|
188
|
-
|
|
254
|
+
// Check if the error message is rendered correctly with red styles
|
|
255
|
+
const errorText = getByText(errorMessage);
|
|
256
|
+
expect(errorText).toBeInTheDocument();
|
|
257
|
+
});
|
|
258
|
+
it('14. selecting an option calls the onSelect callback', () => {
|
|
259
|
+
const onSelect = jest.fn();
|
|
260
|
+
const { getByTestId } = render(
|
|
261
|
+
<NsSelect selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
262
|
+
{createSelectOptions(options)}
|
|
263
|
+
</NsSelect>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const selectHead = getByTestId('select-head');
|
|
267
|
+
fireEvent.mouseDown(selectHead);
|
|
268
|
+
|
|
269
|
+
const option1 = getByTestId('option-Option1');
|
|
270
|
+
fireEvent.click(option1);
|
|
189
271
|
|
|
190
|
-
expect(
|
|
191
|
-
expect(selectContainer).toHaveStyle('color: red');
|
|
272
|
+
expect(onSelect).toHaveBeenCalledWith('Option1');
|
|
192
273
|
});
|
|
193
274
|
});
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
1
2
|
import { styled, ColorUtils, FoundationTheme } from '@nuskin/foundation-theme';
|
|
2
3
|
import { Themed } from '../common.types';
|
|
3
4
|
import { getDropdownHeadBorder } from '../dropdown/NsDropdown.styled';
|
|
4
5
|
const { color, paletteColor, isDarkMode } = ColorUtils;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
interface SelectLabelProps extends HTMLAttributes<HTMLLabelElement> {
|
|
8
|
+
errorMessage?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const SelectContainer = styled.div<{ disabled: boolean; errorMessage?: string }>`
|
|
7
12
|
position: relative;
|
|
8
13
|
display: inline-block;
|
|
9
14
|
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
|
|
15
|
+
color: ${({ theme, errorMessage }) =>
|
|
16
|
+
errorMessage ? paletteColor('error')({ theme }) : paletteColor('primary')({ theme })};
|
|
17
|
+
`;
|
|
18
|
+
export const SelectMain = styled.div`
|
|
19
|
+
position: relative;
|
|
20
|
+
display: flex;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const ErrorContainer = styled.div`
|
|
24
|
+
color: ${paletteColor('error')};
|
|
10
25
|
`;
|
|
11
26
|
|
|
12
27
|
export const SelectHead = styled.div<{ type: string; disabled: boolean }>`
|
|
@@ -26,6 +41,18 @@ export const SelectButton = styled.div`
|
|
|
26
41
|
padding-right: 10px;
|
|
27
42
|
`;
|
|
28
43
|
|
|
44
|
+
export const LeftArrowContainer = styled.div`
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
padding-top: 30px;
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
export const RightArrowContainer = styled.div`
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
padding-top: 30px;
|
|
54
|
+
`;
|
|
55
|
+
|
|
29
56
|
export const SelectList = styled.div<{ borderColor: FoundationTheme.PaletteColors }>`
|
|
30
57
|
position: absolute;
|
|
31
58
|
z-index: 100;
|
|
@@ -33,18 +60,28 @@ export const SelectList = styled.div<{ borderColor: FoundationTheme.PaletteColor
|
|
|
33
60
|
padding: 0;
|
|
34
61
|
margin: 0;
|
|
35
62
|
width: -webkit-fill-available;
|
|
36
|
-
color: ${paletteColor('primary')};
|
|
63
|
+
color: ${paletteColor('primary', 'main')};
|
|
37
64
|
background-color: ${paletteColor('primary', 'contrastText')};
|
|
65
|
+
max-height: 200px;
|
|
66
|
+
overflow-y: auto;
|
|
38
67
|
`;
|
|
39
68
|
|
|
40
|
-
const getDividerColor = (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const getListBgColor = (props: { theme: FoundationTheme.Theme }) => {
|
|
69
|
+
const getDividerColor = ({ theme }: Themed) =>
|
|
70
|
+
isDarkMode({ theme }) ? color('neutral', '20')({ theme }) : color('neutral', '30')({ theme });
|
|
71
|
+
const getListBgColor = (props: Themed) => {
|
|
45
72
|
return !isDarkMode(props) ? color('accentColorB', '10')(props) : color('accentColorB', '50')(props);
|
|
46
73
|
};
|
|
47
74
|
type ContainerParam = { borderBottom: string; bgColor: string; color: string; hoveredBg: string };
|
|
75
|
+
|
|
76
|
+
export const ListItemContainer = styled.div<{ showListDivider: boolean; isSelected: boolean }>`
|
|
77
|
+
${({ showListDivider, isSelected, theme }) => {
|
|
78
|
+
const borderBottom = showListDivider ? getDividerColor({ theme }) : 'none';
|
|
79
|
+
const bgColor = isSelected ? paletteColor('primary')({ theme }) : 'white';
|
|
80
|
+
const color = isSelected ? 'white' : 'black';
|
|
81
|
+
const hoveredBg = getListBgColor({ theme });
|
|
82
|
+
return createListContainerStyle({ borderBottom, bgColor, color, hoveredBg });
|
|
83
|
+
}}
|
|
84
|
+
`;
|
|
48
85
|
function createListContainerStyle({ borderBottom, bgColor, color, hoveredBg }: ContainerParam) {
|
|
49
86
|
return `
|
|
50
87
|
cursor: pointer;
|
|
@@ -62,15 +99,6 @@ function createListContainerStyle({ borderBottom, bgColor, color, hoveredBg }: C
|
|
|
62
99
|
}
|
|
63
100
|
`;
|
|
64
101
|
}
|
|
65
|
-
export const ListItemContainer = styled.div<{ showListDivider: boolean; isSelected: boolean }>`
|
|
66
|
-
${({ showListDivider, isSelected, theme }) => {
|
|
67
|
-
const borderBottom = showListDivider ? getDividerColor({ theme }) : 'none';
|
|
68
|
-
const bgColor = isSelected ? paletteColor('primary')({ theme }) : 'white';
|
|
69
|
-
const color = isSelected ? 'white' : 'black';
|
|
70
|
-
const hoveredBg = getListBgColor({ theme });
|
|
71
|
-
return createListContainerStyle({ borderBottom, bgColor, color, hoveredBg });
|
|
72
|
-
}}
|
|
73
|
-
`;
|
|
74
102
|
const getListItemBgColor = ({ isSelected, theme }: { isSelected: boolean } & Themed) => {
|
|
75
103
|
if (isSelected) {
|
|
76
104
|
return paletteColor('primary')({ theme });
|
|
@@ -86,6 +114,14 @@ const getListItemBorderLeftColor = ({ isSelected, theme }: { isSelected: boolean
|
|
|
86
114
|
return 'none';
|
|
87
115
|
};
|
|
88
116
|
|
|
117
|
+
export const SelectLabel = styled.label<SelectLabelProps>`
|
|
118
|
+
display: block;
|
|
119
|
+
margin-bottom: 5px;
|
|
120
|
+
color: ${({ theme, errorMessage }) =>
|
|
121
|
+
errorMessage ? paletteColor('error')({ theme }) : paletteColor('primary')({ theme })};
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
|
|
89
125
|
export const ListItem = styled.div<{ isSelected: boolean }>(({ isSelected, theme }) => ({
|
|
90
126
|
padding: '5px 10px',
|
|
91
127
|
borderLeft: getListItemBorderLeftColor({ isSelected, theme }),
|
|
@@ -96,9 +132,3 @@ export const ListItem = styled.div<{ isSelected: boolean }>(({ isSelected, theme
|
|
|
96
132
|
fontWeight: 'bold',
|
|
97
133
|
},
|
|
98
134
|
}));
|
|
99
|
-
|
|
100
|
-
export const SelectLabel = styled.label`
|
|
101
|
-
display: block;
|
|
102
|
-
margin-bottom: 5px;
|
|
103
|
-
color: ${paletteColor('primary')} !important;
|
|
104
|
-
`;
|