@nuskin/foundation-ui-components 1.0.0-develop.6 → 1.0.0-develop.8
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 +17 -12
- package/src/common/accordion/NsAccordion.tsx +30 -40
- package/src/common/accordion/NsAccordionHeader.tsx +27 -7
- package/src/common/accordion/NsAccordionItem.tsx +16 -4
- package/src/common/accordion/NsCollapse.tsx +71 -35
- package/src/common/accordion/NsToggleSection.tsx +14 -3
- package/src/common/accordion/ToggleSection.styled.tsx +11 -7
- package/src/common/accordion/colorMap.ts +117 -0
- package/src/common/accordion/enums.ts +8 -0
- package/src/common/accordion/index.ts +1 -0
- package/src/common/accordion/specs/NsAccordion.spec.tsx +100 -31
- package/src/common/accordion/specs/NsAccordionHeader.spec.tsx +77 -10
- package/src/common/accordion/specs/NsAccordionItem.spec.tsx +23 -10
- package/src/common/accordion/specs/NsCollapse.spec.tsx +106 -17
- package/src/common/accordion/specs/NsToggleSection.spec.tsx +46 -14
- package/src/common/accordion/stories/NsAccordion.stories.tsx +32 -14
- package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +4 -1
- package/src/common/accordion/stories/NsAccordionItem.stories.tsx +3 -3
- package/src/common/accordion/stories/NsCollapse.stories.tsx +3 -3
- package/src/common/accordion/stories/NsToggleSection.stories.tsx +2 -3
- package/src/common/accordion/stories/data.tsx +157 -19
- package/src/common/accordion/types.d.ts +56 -49
- package/src/common/alert/Header.tsx +29 -0
- package/src/common/alert/NsAlert.spec.tsx +46 -13
- package/src/common/alert/NsAlert.stories.tsx +3 -3
- package/src/common/alert/{NsAlert.styled.ts → NsAlert.styled.tsx} +22 -22
- package/src/common/alert/NsAlert.tsx +22 -87
- package/src/common/alert/types.d.ts +7 -7
- package/src/common/auth-dropdown/NsAuthDropdown.spec.tsx +23 -5
- package/src/common/auth-dropdown/NsAuthDropdown.stories.tsx +1 -1
- package/src/common/auth-dropdown/NsAuthDropdown.styled.ts +11 -7
- package/src/common/auth-dropdown/NsAuthDropdown.tsx +4 -4
- package/src/common/autocomplete/NsAutocomplete.spec.tsx +29 -6
- package/src/common/autocomplete/NsAutocomplete.stories.tsx +1 -1
- package/src/common/autocomplete/NsAutocomplete.tsx +18 -8
- package/src/common/autocomplete/mock/MockUI.tsx +1 -0
- package/src/common/banner/NsBanner.spec.tsx +12 -3
- package/src/common/banner/NsBanner.stories.tsx +1 -1
- package/src/common/brand-components/NsBrand.stories.tsx +1 -1
- package/src/common/brand-components/NsBrandComponent.tsx +2 -2
- package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +8 -3
- package/src/common/breadcrumb/NsBreadcrumb.stories.tsx +1 -1
- package/src/common/breadcrumb/data.ts +8 -5
- package/src/common/button/NsButton.spec.tsx +65 -49
- package/src/common/button/NsButton.stories.tsx +59 -2
- package/src/common/button/NsButton.styled.tsx +49 -66
- package/src/common/button/NsButton.tsx +1 -2
- package/src/common/button/helper.ts +82 -0
- package/src/common/button/types.d.ts +2 -0
- package/src/common/card/stories/NsBgCard.stories.tsx +1 -1
- package/src/common/card/stories/NsBrandProductCard.stories.tsx +1 -1
- package/src/common/card/stories/NsFfgCard.stories.tsx +5 -2
- package/src/common/card/styled/NsFfgStyled.tsx +1 -1
- package/src/common/carousal/stories/NSCarousal.stories.tsx +1 -1
- package/src/common/carousal/stories/NsScrollingCarousal.stories.tsx +1 -1
- package/src/common/cart-icon/NsCartIcon.stories.tsx +1 -1
- package/src/common/checkbox/NsCheckbox.stories.tsx +1 -1
- package/src/common/checkbox/NsCheckbox.styled.tsx +15 -16
- package/src/common/checkbox/NsCheckbox.tsx +1 -1
- package/src/common/column-control/stories/DivArray.stories.tsx +1 -1
- package/src/common/column-control/stories/NsColumnControl.stories.tsx +1 -1
- package/src/common/column-control/stories/NsFlexColumns.stories.tsx +1 -1
- package/src/common/column-control/stories/NsGridColumns.stories.tsx +1 -1
- package/src/common/column-control/styled/common.ts +1 -1
- package/src/common/column-control/utils.ts +4 -4
- package/src/common/common.types.d.ts +11 -1
- package/src/common/dropdown/NsDropdown.styled.tsx +23 -18
- package/src/common/dropdown/stories/NsDropdown.stories.tsx +1 -1
- package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +1 -1
- package/src/common/flag/NsFlag.stories.tsx +1 -1
- package/src/common/hooks/index.ts +2 -0
- package/src/common/hooks/specs/useWindowSize.spec.tsx +19 -0
- package/src/common/hooks/useAccordionMap.tsx +39 -0
- package/src/common/hooks/useWindowSize.tsx +30 -0
- package/src/common/icon/NsIcon.stories.tsx +1 -1
- package/src/common/icon/NsIconVariants.ts +1 -0
- package/src/common/image/NsImage.spec.tsx +56 -6
- package/src/common/image/NsImage.stories.tsx +1 -1
- package/src/common/image/NsImage.tsx +23 -11
- package/src/common/image/NsNextImage.stories.tsx +1 -1
- package/src/common/index.ts +6 -2
- package/src/common/input/InputWrapper.tsx +12 -7
- package/src/common/input/NsInputField.tsx +1 -1
- package/src/common/label/NsLabel.stories.tsx +1 -1
- package/src/common/link/NsLink.stories.tsx +1 -1
- package/src/common/modal/NsModal.spec.tsx +46 -0
- package/src/common/modal/NsModal.styled.tsx +28 -0
- package/src/common/modal/NsModal.tsx +37 -0
- package/src/common/modal/helper-component/NsModalContentBox.tsx +32 -0
- package/src/common/modal/helper-component/NsModalDefault.tsx +25 -0
- package/src/common/modal/helper-component/NsModalStatus.tsx +15 -0
- package/src/common/modal/helper-component/NsModalStatusIcon.tsx +14 -0
- package/src/common/modal/helper-component/NsModalTitle.tsx +26 -0
- package/src/common/modal/index.ts +2 -0
- package/src/common/modal/model/NsModalVariantMap.tsx +24 -0
- package/src/common/modal/stories/NsModal.stories.tsx +86 -0
- package/src/common/modal/stories/mock.tsx +46 -0
- package/src/common/modal/types.ts +56 -0
- package/src/common/nav-product-card/NsNavProductCard.stories.tsx +1 -1
- package/src/common/nav-tab/NsNavTab.stories.tsx +1 -1
- package/src/common/radiogroup/NsRadioGroup.spec.tsx +24 -25
- package/src/common/radiogroup/NsRadioGroup.stories.tsx +14 -32
- package/src/common/radiogroup/NsRadioGroup.styled.tsx +17 -15
- package/src/common/radiogroup/NsRadioGroup.tsx +6 -5
- package/src/common/search-bar/NsSearchBar.tsx +1 -3
- package/src/common/select/NsSelect.spec.tsx +10 -17
- package/src/common/select/NsSelect.styled.tsx +53 -34
- package/src/common/select/stories/NsSelect.stories.tsx +11 -11
- package/src/common/select/types.d.ts +3 -3
- package/src/common/spinner/NsSpinner.spec.tsx +21 -0
- package/src/common/spinner/NsSpinner.stories.tsx +26 -0
- package/src/common/spinner/NsSpinner.styled.tsx +344 -0
- package/src/common/spinner/NsSpinner.tsx +14 -0
- package/src/common/spinner/icons/Spinner.tsx +161 -0
- package/src/common/spinner/index.ts +2 -0
- package/src/common/spinner/types.ts +5 -0
- package/src/common/table/NsTable.stories.tsx +1 -1
- package/src/common/table/NsTable.tsx +32 -49
- package/src/common/table/NsTableHeader.tsx +30 -51
- package/src/common/text-strip/NsTextStrip.stories.tsx +1 -1
- package/src/common/typography/NsTypography.stories.tsx +1 -1
- package/src/common/util.ts +1 -0
- package/src/common/utility/Show.tsx +13 -0
- package/src/common/utility/index.ts +1 -0
- package/src/common/videos/NsVideo.stories.tsx +1 -1
- package/src/common/videos/types.d.ts +1 -1
- /package/src/common/brand-components/{NsBrandContanier.styled.tsx → NsBrandContainer.styled.tsx} +0 -0
|
@@ -3,27 +3,27 @@ import { styled, css, ColorUtils, FoundationTheme } from '@nuskin/foundation-the
|
|
|
3
3
|
|
|
4
4
|
const { color } = ColorUtils;
|
|
5
5
|
|
|
6
|
-
import type { AlertContainerParams, AlertType, AlertParams, TypeMapCSS } from './types
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
6
|
+
import type { AlertContainerParams, AlertType, AlertParams, TypeMapCSS } from './types';
|
|
7
|
+
const gutterWidth = '10px';
|
|
8
|
+
const closeButtonWidth = '12px';
|
|
9
|
+
const iconWidth = '24px';
|
|
10
10
|
|
|
11
11
|
const TypeMap: TypeMapCSS = {
|
|
12
12
|
banner: {
|
|
13
|
-
|
|
13
|
+
minHeight: '114px',
|
|
14
14
|
padding: '15px 45px',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
contentColumnStart: 'row-center',
|
|
16
|
+
contentColumnEnd: 'row-center',
|
|
17
|
+
barPosition: 'top',
|
|
18
|
+
footVisibility: 'block',
|
|
19
19
|
},
|
|
20
20
|
card: {
|
|
21
|
-
|
|
21
|
+
minHeight: '72px',
|
|
22
22
|
padding: '24px 24px',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
contentColumnStart: 'row-left',
|
|
24
|
+
contentColumnEnd: 'row-right',
|
|
25
|
+
barPosition: 'left',
|
|
26
|
+
footDisplay: 'none',
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
|
|
@@ -53,14 +53,14 @@ const fontFamilyStyles = css`
|
|
|
53
53
|
export const Alert = styled.div<AlertParams>`
|
|
54
54
|
position: relative;
|
|
55
55
|
display: grid;
|
|
56
|
-
grid-template-columns: [row-left] ${
|
|
56
|
+
grid-template-columns: [row-left] ${iconWidth} [space] ${gutterWidth} [row-center] auto [space] ${gutterWidth} [row-right] ${closeButtonWidth};
|
|
57
57
|
grid-template-rows: [row1] auto;
|
|
58
58
|
grid-auto-row: auto;
|
|
59
59
|
grid-auto-flow: row;
|
|
60
60
|
background-color: ${color('primary', 'white')};
|
|
61
61
|
border: 1px solid ${color('primary', 'black')};
|
|
62
|
-
min-height: ${({ type }) => TypeMap?.[type]?.
|
|
63
|
-
padding: ${({ type }) => TypeMap?.[type]?.
|
|
62
|
+
min-height: ${({ type }) => TypeMap?.[type]?.minHeight};
|
|
63
|
+
padding: ${({ type }) => TypeMap?.[type]?.padding};
|
|
64
64
|
color: ${(props) => color(contextType?.[props?.context] as FoundationTheme.AppColors, '40')(props)} };
|
|
65
65
|
`;
|
|
66
66
|
|
|
@@ -78,8 +78,8 @@ export const AlertIcon = styled.div`
|
|
|
78
78
|
grid-column-end: row-left;
|
|
79
79
|
grid-row-start: row1;
|
|
80
80
|
grid-row-end: row1;
|
|
81
|
-
width: ${
|
|
82
|
-
height: ${
|
|
81
|
+
width: ${iconWidth};
|
|
82
|
+
height: ${iconWidth};
|
|
83
83
|
`;
|
|
84
84
|
|
|
85
85
|
export const AlertHead = styled.div`
|
|
@@ -102,14 +102,14 @@ export const AlertHead = styled.div`
|
|
|
102
102
|
export const AlertContent = styled.div<{ type: AlertType }>`
|
|
103
103
|
margin-top: 8px;
|
|
104
104
|
color: ${color('primary', 'black')};
|
|
105
|
-
grid-column-start: ${({ type }) => TypeMap?.[type]?.
|
|
106
|
-
grid-column-end: ${({ type }) => TypeMap?.[type]?.
|
|
105
|
+
grid-column-start: ${({ type }) => TypeMap?.[type]?.contentColumnStart};
|
|
106
|
+
grid-column-end: ${({ type }) => TypeMap?.[type]?.contentColumnEnd};
|
|
107
107
|
`;
|
|
108
108
|
|
|
109
109
|
export const AlertFoot = styled.div<{ type: AlertType }>`
|
|
110
110
|
margin-top: 8px;
|
|
111
111
|
color: ${color('primary', 'black')};
|
|
112
|
-
display: ${({ type }) => TypeMap?.[type]?.['
|
|
112
|
+
display: ${({ type }) => TypeMap?.[type]?.['footDisplay']};
|
|
113
113
|
${fontFamilyStyles}
|
|
114
114
|
font-weight: 300;
|
|
115
115
|
font-size: 14px;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { FoundationTheme } from '@nuskin/foundation-theme';
|
|
3
|
-
|
|
4
2
|
import {
|
|
5
3
|
AlertContainer,
|
|
6
4
|
Alert,
|
|
@@ -11,44 +9,15 @@ import {
|
|
|
11
9
|
AlertFoot,
|
|
12
10
|
AlertBody,
|
|
13
11
|
} from './NsAlert.styled';
|
|
14
|
-
import type { NsAlertProps,
|
|
12
|
+
import type { NsAlertProps, ContextType, AlertType } from './types.d';
|
|
15
13
|
import { NsIcon } from '../icon';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
const Header: React.FC<HeaderProps> = ({ children, head }) => (
|
|
19
|
-
<>
|
|
20
|
-
{children}
|
|
21
|
-
{head}
|
|
22
|
-
</>
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
function isValidContextValue(value: string): boolean {
|
|
26
|
-
return (value as ContextType) !== undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isValidTypeValue(value: string): boolean {
|
|
30
|
-
return (value as AlertType) !== undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const colorMap: { [key: string]: FoundationTheme.PaletteColors } = {
|
|
34
|
-
error: 'error',
|
|
35
|
-
notice: 'info',
|
|
36
|
-
success: 'secondary',
|
|
37
|
-
warning: 'warning',
|
|
38
|
-
default: 'info',
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const iconMap: { [key: string]: NsIconNameType } = {
|
|
42
|
-
error: 'alertError',
|
|
43
|
-
success: 'success',
|
|
44
|
-
warning: 'warning',
|
|
45
|
-
default: 'infoFilled',
|
|
46
|
-
};
|
|
14
|
+
import Header, { colorMap, iconMap } from './Header';
|
|
15
|
+
import useWindowSize from '../hooks/useWindowSize';
|
|
47
16
|
|
|
48
17
|
export default function NsAlert({
|
|
49
18
|
context = 'info',
|
|
50
19
|
title = 'Information',
|
|
51
|
-
|
|
20
|
+
threshold = 600,
|
|
52
21
|
type = 'banner',
|
|
53
22
|
visible = false,
|
|
54
23
|
head,
|
|
@@ -57,76 +26,42 @@ export default function NsAlert({
|
|
|
57
26
|
onDismiss = undefined,
|
|
58
27
|
}: NsAlertProps) {
|
|
59
28
|
const [alertType, setAlertType] = useState('banner');
|
|
60
|
-
const
|
|
29
|
+
const windowSize = useWindowSize();
|
|
61
30
|
|
|
62
31
|
const hasDismissListener = typeof onDismiss === 'function';
|
|
63
32
|
|
|
64
|
-
const getColor = () => {
|
|
65
|
-
return colorMap[context] || colorMap.default;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const getIcon = () => {
|
|
69
|
-
return iconMap[context] || iconMap.default;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const getTitleText = () => {
|
|
73
|
-
if (title) {
|
|
74
|
-
return title;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const handleDismiss = () => {
|
|
79
|
-
if (hasDismissListener) {
|
|
80
|
-
onDismiss();
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const handleResize = () => {
|
|
85
|
-
setWindowWidth(window.innerWidth);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
let aType = '';
|
|
90
|
-
if (type === 'auto') {
|
|
91
|
-
aType = windowWidth < threshhold ? 'card' : 'banner';
|
|
92
|
-
} else {
|
|
93
|
-
aType = type;
|
|
94
|
-
}
|
|
95
|
-
setAlertType(aType);
|
|
96
|
-
}, [threshhold, type, windowWidth]);
|
|
97
|
-
|
|
98
33
|
useEffect(() => {
|
|
99
|
-
if (!
|
|
34
|
+
if (!context) {
|
|
100
35
|
console.error(`Invalid prop value for 'context': ${context}`);
|
|
101
36
|
}
|
|
102
|
-
|
|
103
|
-
if (!isValidTypeValue(type)) {
|
|
37
|
+
if (!type) {
|
|
104
38
|
console.error(`Invalid prop value for 'type': ${type}`);
|
|
105
39
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
window.removeEventListener('resize', handleResize);
|
|
113
|
-
};
|
|
114
|
-
}, []);
|
|
40
|
+
if (type === 'auto') {
|
|
41
|
+
setAlertType(windowSize.width < threshold ? 'card' : 'banner');
|
|
42
|
+
} else {
|
|
43
|
+
setAlertType(type);
|
|
44
|
+
}
|
|
45
|
+
}, [threshold, type, windowSize.width, context]);
|
|
115
46
|
|
|
116
47
|
return (
|
|
117
48
|
<AlertContainer visible={visible} data-testid="alert-container">
|
|
118
49
|
<Alert type={alertType as AlertType} context={context as ContextType}>
|
|
119
50
|
<AlertIcon>
|
|
120
|
-
<NsIcon
|
|
51
|
+
<NsIcon
|
|
52
|
+
name={iconMap[context] || iconMap.default}
|
|
53
|
+
color={colorMap[context] || colorMap.default}
|
|
54
|
+
size="large"
|
|
55
|
+
/>
|
|
121
56
|
</AlertIcon>
|
|
122
57
|
<AlertHead>
|
|
123
|
-
<Header head={<span>{
|
|
58
|
+
<Header head={<span>{title}</span>}>{head}</Header>
|
|
124
59
|
</AlertHead>
|
|
125
|
-
{hasDismissListener
|
|
126
|
-
<AlertCloseButton data-testid="alert-close-button" onClick={
|
|
60
|
+
{hasDismissListener && (
|
|
61
|
+
<AlertCloseButton data-testid="alert-close-button" onClick={onDismiss}>
|
|
127
62
|
<NsIcon name="close" size="small" />
|
|
128
63
|
</AlertCloseButton>
|
|
129
|
-
)
|
|
64
|
+
)}
|
|
130
65
|
<AlertContent type={alertType as AlertType}>
|
|
131
66
|
<AlertBody>{body}</AlertBody>
|
|
132
67
|
{foot ? <AlertFoot type={alertType as AlertType}>{foot}</AlertFoot> : null}
|
|
@@ -3,7 +3,7 @@ import { ReactNode, CSSProperties } from 'react';
|
|
|
3
3
|
export interface NsAlertProps {
|
|
4
4
|
context?: string;
|
|
5
5
|
title?: string;
|
|
6
|
-
|
|
6
|
+
threshold?: number;
|
|
7
7
|
type?: string;
|
|
8
8
|
visible?: boolean;
|
|
9
9
|
head?: ReactNode;
|
|
@@ -30,12 +30,12 @@ export type AlertParams = {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export type CustomCSSProperties = CSSProperties & {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
minHeight?: string;
|
|
34
|
+
contentColumnStart?: string;
|
|
35
|
+
contentColumnEnd?: string;
|
|
36
|
+
barPosition?: string;
|
|
37
|
+
footVisibility?: string;
|
|
38
|
+
footDisplay?: string;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
export interface TypeMapCSS {
|
|
@@ -10,27 +10,45 @@ describe('NsAuthDropdown', () => {
|
|
|
10
10
|
signUpLinkText: 'Sign Up',
|
|
11
11
|
signUpLinkPrefixText: 'Do not have an account?',
|
|
12
12
|
titleText: 'Welcome',
|
|
13
|
-
closeOnMouseLeave: false,
|
|
14
13
|
};
|
|
15
14
|
|
|
16
|
-
it('should render component with title text', () => {
|
|
15
|
+
it('1. should render component with title text', () => {
|
|
17
16
|
render(<NsAuthDropdown {...props} />);
|
|
18
17
|
expect(screen.getByText('Welcome')).toBeInTheDocument();
|
|
19
18
|
});
|
|
20
19
|
|
|
21
|
-
it('should render the sign in button with text', () => {
|
|
20
|
+
it('2. should render the sign in button with text', () => {
|
|
22
21
|
render(<NsAuthDropdown {...props} />);
|
|
23
22
|
const authHead = screen.getByTestId('auth-head');
|
|
24
23
|
fireEvent.click(authHead);
|
|
25
24
|
expect(screen.getByTestId('sign-in-btn')).toBeInTheDocument();
|
|
26
|
-
expect(screen.getByTestId('sign-in-btn')).toHaveTextContent(
|
|
25
|
+
expect(screen.getByTestId('sign-in-btn')).toHaveTextContent(props.signInBtnText);
|
|
26
|
+
expect(screen.getByTestId('signup-link')).toHaveTextContent(props.signUpLinkText);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it('should have the correct href attribute in SignUpLink', () => {
|
|
29
|
+
it('3. should have the correct href attribute in SignUpLink', () => {
|
|
30
30
|
render(<NsAuthDropdown {...props} />);
|
|
31
31
|
const authHead = screen.getByTestId('auth-head');
|
|
32
32
|
fireEvent.click(authHead);
|
|
33
33
|
const signUpLink = screen.getByTestId('signup-link');
|
|
34
34
|
expect(signUpLink).toHaveAttribute('href', '/signUp');
|
|
35
35
|
});
|
|
36
|
+
it('4. should close dropdown on mouse leave', () => {
|
|
37
|
+
const { container } = render(<NsAuthDropdown {...props} closeOnMouseLeave={true} />);
|
|
38
|
+
const authHead = screen.getByTestId('auth-head');
|
|
39
|
+
fireEvent.click(authHead);
|
|
40
|
+
expect(screen.getByTestId('sign-in-btn')).toHaveTextContent('Sign In');
|
|
41
|
+
const wrapper = container.querySelector('.auth-wrapper');
|
|
42
|
+
if (wrapper) fireEvent.mouseLeave(wrapper);
|
|
43
|
+
expect(container.querySelector('.auth-wrapper')).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
it('5. should not close dropdown on mouse leave', () => {
|
|
46
|
+
const { container } = render(<NsAuthDropdown {...props} closeOnMouseLeave={false} />);
|
|
47
|
+
const authHead = screen.getByTestId('auth-head');
|
|
48
|
+
fireEvent.click(authHead);
|
|
49
|
+
expect(screen.getByTestId('sign-in-btn')).toHaveTextContent('Sign In');
|
|
50
|
+
const wrapper = container.querySelector('.auth-wrapper');
|
|
51
|
+
if (wrapper) fireEvent.mouseLeave(wrapper);
|
|
52
|
+
expect(container.querySelector('.auth-wrapper')).not.toBeNull();
|
|
53
|
+
});
|
|
36
54
|
});
|
|
@@ -7,7 +7,7 @@ type Story = StoryObj<ComponentType>;
|
|
|
7
7
|
type MetaStory = Meta<ComponentType>;
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
|
-
title: 'Components/NsAuthDropdown',
|
|
10
|
+
title: 'Components/Data Display/NsAuthDropdown',
|
|
11
11
|
component: NsAuthDropdown,
|
|
12
12
|
decorators: [withCenterDecorator],
|
|
13
13
|
argTypes: {},
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { styled, ColorUtils } from '@nuskin/foundation-theme';
|
|
2
|
+
import { DarkModeEnabledOption } from '../common.types';
|
|
3
|
+
import { skipDarkModeEnabled } from '../util';
|
|
2
4
|
|
|
3
5
|
//[TODO-Arun] - Use from custom color variables
|
|
4
|
-
const MAGENTA_COLOR = '#bb29bb';
|
|
6
|
+
// const MAGENTA_COLOR = '#bb29bb';
|
|
5
7
|
|
|
6
8
|
const { color, paletteColor } = ColorUtils;
|
|
7
9
|
|
|
@@ -47,8 +49,10 @@ export const Triangle = styled.div`
|
|
|
47
49
|
box-shadow: 0 0 5px 0 ${color('neutral', '20')};
|
|
48
50
|
}
|
|
49
51
|
`;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const options = {
|
|
53
|
+
shouldForwardProp: skipDarkModeEnabled,
|
|
54
|
+
};
|
|
55
|
+
export const AuthHead = styled('div', options)<DarkModeEnabledOption>`
|
|
52
56
|
cursor: pointer;
|
|
53
57
|
padding: 5px 10px;
|
|
54
58
|
display: flex;
|
|
@@ -69,7 +73,7 @@ export const SignInButton = styled(Link)`
|
|
|
69
73
|
display: block;
|
|
70
74
|
padding: 13px;
|
|
71
75
|
width: 100%;
|
|
72
|
-
background-color: ${
|
|
76
|
+
background-color: ${color('primary', 'black')};
|
|
73
77
|
color: ${color('primary', 'white')};
|
|
74
78
|
border: 0;
|
|
75
79
|
font-size: 16px;
|
|
@@ -80,7 +84,7 @@ export const SignInButton = styled(Link)`
|
|
|
80
84
|
|
|
81
85
|
export const SignUpLinkContainer = styled.div`
|
|
82
86
|
margin-top: 15px;
|
|
83
|
-
color: ${color('primary', '
|
|
87
|
+
color: ${color('primary', 'black')};
|
|
84
88
|
font-size: 13px;
|
|
85
89
|
line-height: 16px;
|
|
86
90
|
text-align: left;
|
|
@@ -92,7 +96,7 @@ export const SignUpLink = styled(Link)`
|
|
|
92
96
|
font-size: 13px;
|
|
93
97
|
line-height: 16px;
|
|
94
98
|
text-decoration: none;
|
|
95
|
-
color: ${
|
|
99
|
+
color: ${color('primary', 'black')};
|
|
96
100
|
position: relative;
|
|
97
101
|
transition: all 0.15s ease-in-out;
|
|
98
102
|
&:hover {
|
|
@@ -111,7 +115,7 @@ export const SignUpLink = styled(Link)`
|
|
|
111
115
|
right: 0;
|
|
112
116
|
width: 100%;
|
|
113
117
|
height: 1px;
|
|
114
|
-
background-color: ${
|
|
118
|
+
background-color: ${color('primary', 'black')};
|
|
115
119
|
transition: all 0.15s ease-in-out;
|
|
116
120
|
opacity: 0;
|
|
117
121
|
visibility: hidden;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
2
|
import { NsIcon } from '../icon';
|
|
3
3
|
import {
|
|
4
4
|
AuthDropdownContainer,
|
|
@@ -28,11 +28,11 @@ const NsAuthDropdown = ({
|
|
|
28
28
|
setIsOpen((prevIsOpen) => !prevIsOpen);
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const closeDropdown = () => {
|
|
31
|
+
const closeDropdown = useCallback(() => {
|
|
32
32
|
if (closeOnMouseLeave) {
|
|
33
33
|
setIsOpen(false);
|
|
34
34
|
}
|
|
35
|
-
};
|
|
35
|
+
}, [closeOnMouseLeave]);
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
38
|
<AuthDropdownContainer>
|
|
@@ -40,7 +40,7 @@ const NsAuthDropdown = ({
|
|
|
40
40
|
<Button>{titleText}</Button>
|
|
41
41
|
<NsIcon testId="chevron-icon" name={isOpen ? 'arrowUp' : 'arrowDown'} color="primary" />
|
|
42
42
|
{isOpen ? (
|
|
43
|
-
<div onMouseLeave={closeDropdown}>
|
|
43
|
+
<div onMouseLeave={closeDropdown} className="auth-wrapper">
|
|
44
44
|
<Triangle />
|
|
45
45
|
<AuthDropdown>
|
|
46
46
|
<SignInButton href={signInRedirectLink} data-testid="sign-in-btn">
|
|
@@ -7,11 +7,10 @@ import userEvent from '@testing-library/user-event';
|
|
|
7
7
|
import { act } from 'react-dom/test-utils';
|
|
8
8
|
|
|
9
9
|
describe('CustomAutocomplete', () => {
|
|
10
|
-
|
|
10
|
+
it('1. should render autocomplete component', () => {
|
|
11
11
|
render(
|
|
12
12
|
<NsAutocomplete
|
|
13
13
|
options={AutocompleteMockList}
|
|
14
|
-
onSelect={() => {}}
|
|
15
14
|
placeholder="autocomplete-input"
|
|
16
15
|
renderOptions={OptionUiComponent}
|
|
17
16
|
inputValue=""
|
|
@@ -21,8 +20,29 @@ describe('CustomAutocomplete', () => {
|
|
|
21
20
|
const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
|
|
22
21
|
expect(autocompleteInput).toBeInTheDocument();
|
|
23
22
|
});
|
|
23
|
+
it('2. check that onChange gets called', () => {
|
|
24
|
+
const onSelectMock = jest.fn();
|
|
25
|
+
const onChangeMock = jest.fn();
|
|
26
|
+
render(
|
|
27
|
+
<NsAutocomplete
|
|
28
|
+
options={AutocompleteMockList}
|
|
29
|
+
onSelect={onSelectMock}
|
|
30
|
+
onChange={onChangeMock}
|
|
31
|
+
placeholder="autocomplete-input"
|
|
32
|
+
renderOptions={OptionUiComponent}
|
|
33
|
+
inputValue=""
|
|
34
|
+
value=""
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
|
|
38
|
+
expect(autocompleteInput).toBeInTheDocument();
|
|
39
|
+
act(() => {
|
|
40
|
+
userEvent.type(autocompleteInput, 'GS Facial');
|
|
41
|
+
});
|
|
42
|
+
expect(onChangeMock).toHaveBeenCalled();
|
|
43
|
+
});
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
it('3. displays options when typing in the input', async () => {
|
|
26
46
|
render(
|
|
27
47
|
<NsAutocomplete
|
|
28
48
|
options={AutocompleteMockList}
|
|
@@ -46,9 +66,11 @@ describe('CustomAutocomplete', () => {
|
|
|
46
66
|
);
|
|
47
67
|
}, 40000);
|
|
48
68
|
|
|
49
|
-
|
|
69
|
+
it('4. selects an option when clicking on it', async () => {
|
|
70
|
+
const onSelectMock = jest.fn();
|
|
50
71
|
render(
|
|
51
72
|
<NsAutocomplete
|
|
73
|
+
onSelect={onSelectMock}
|
|
52
74
|
options={AutocompleteMockList}
|
|
53
75
|
renderOptions={OptionUiComponent}
|
|
54
76
|
placeholder="autocomplete-input"
|
|
@@ -64,9 +86,10 @@ describe('CustomAutocomplete', () => {
|
|
|
64
86
|
const optionLi = screen.getByText('GS Facial Spa');
|
|
65
87
|
userEvent.click(optionLi);
|
|
66
88
|
});
|
|
89
|
+
expect(onSelectMock).toHaveBeenCalled();
|
|
67
90
|
}, 40000);
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
it('5. should load more options when clicking load more button', async () => {
|
|
70
93
|
render(
|
|
71
94
|
<NsAutocomplete
|
|
72
95
|
options={AutocompleteMockList}
|
|
@@ -91,7 +114,7 @@ describe('CustomAutocomplete', () => {
|
|
|
91
114
|
});
|
|
92
115
|
}, 40000);
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
it('6. should clear options when clicking clear button', async () => {
|
|
95
118
|
render(
|
|
96
119
|
<NsAutocomplete
|
|
97
120
|
options={AutocompleteMockList}
|
|
@@ -20,7 +20,7 @@ type Story = StoryObj<ComponentType>;
|
|
|
20
20
|
type MetaStory = Meta<ComponentType>;
|
|
21
21
|
|
|
22
22
|
export default {
|
|
23
|
-
title: 'Components/NsAutocomplete',
|
|
23
|
+
title: 'Components/Inputs/NsAutocomplete',
|
|
24
24
|
component: NsAutocomplete,
|
|
25
25
|
decorators: [withCenterDecorator],
|
|
26
26
|
argTypes: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { PropsWithChildren } from 'react';
|
|
1
|
+
import React, { PropsWithChildren, useCallback } from 'react';
|
|
2
2
|
import { Autocomplete, AutocompleteRenderGroupParams, List, AutocompleteRenderInputParams } from '@mui/material';
|
|
3
3
|
import { NsTypography, NsButton } from '../';
|
|
4
4
|
import { NsAutocompleteProps, OptionsProps, ComponentBoxProps } from './types.d';
|
|
@@ -6,7 +6,7 @@ import { StyledTextField } from './NsAutocomplete.styled';
|
|
|
6
6
|
|
|
7
7
|
const NsAutocomplete = ({
|
|
8
8
|
id = 'custom_autocomplete_dropdown',
|
|
9
|
-
options
|
|
9
|
+
options,
|
|
10
10
|
renderOptions,
|
|
11
11
|
loading,
|
|
12
12
|
noOptionsText,
|
|
@@ -23,13 +23,23 @@ const NsAutocomplete = ({
|
|
|
23
23
|
}: PropsWithChildren<NsAutocompleteProps>) => {
|
|
24
24
|
const OptionUI = renderOptions;
|
|
25
25
|
|
|
26
|
-
const handleSelect = (
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const handleSelect = useCallback(
|
|
27
|
+
(event: React.SyntheticEvent, value: string | OptionsProps | null) => {
|
|
28
|
+
if (typeof onSelect === 'function') {
|
|
29
|
+
onSelect(value);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
[onSelect]
|
|
33
|
+
);
|
|
29
34
|
|
|
30
|
-
const handleInputChange = (
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const handleInputChange = useCallback(
|
|
36
|
+
(event: React.SyntheticEvent, newInputValue: string) => {
|
|
37
|
+
if (typeof onChange === 'function') {
|
|
38
|
+
onChange(newInputValue);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[onChange]
|
|
42
|
+
);
|
|
33
43
|
|
|
34
44
|
const renderInput = (params: AutocompleteRenderInputParams) => (
|
|
35
45
|
<StyledTextField
|
|
@@ -3,9 +3,9 @@ import { render } from '@testing-library/react';
|
|
|
3
3
|
import { NsBanner } from '@/common/';
|
|
4
4
|
|
|
5
5
|
describe('Hero component', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const childText = 'Welcome to My Website';
|
|
7
|
+
const imageURL = 'https://example.com/banner.jpg';
|
|
8
|
+
it('1. renders banner image and title', () => {
|
|
9
9
|
const { getByAltText, getByText } = render(
|
|
10
10
|
<NsBanner imageUrl={imageURL} altText="Banner image">
|
|
11
11
|
<h1>{childText}</h1>
|
|
@@ -16,4 +16,13 @@ describe('Hero component', () => {
|
|
|
16
16
|
expect(childElement).toBeInTheDocument();
|
|
17
17
|
expect(imageElement).toBeInTheDocument();
|
|
18
18
|
});
|
|
19
|
+
it('2. renders banner image and title without altText', () => {
|
|
20
|
+
const { getByAltText } = render(
|
|
21
|
+
<NsBanner imageUrl={imageURL}>
|
|
22
|
+
<h1>{childText}</h1>
|
|
23
|
+
</NsBanner>
|
|
24
|
+
);
|
|
25
|
+
const imageElement = getByAltText('NsBanner');
|
|
26
|
+
expect(imageElement).toBeInTheDocument();
|
|
27
|
+
});
|
|
19
28
|
});
|
|
@@ -7,7 +7,7 @@ type Story = StoryObj<ComponentType>;
|
|
|
7
7
|
type MetaStory = Meta<ComponentType>;
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
|
-
title: 'Components/NsBrandComponent',
|
|
10
|
+
title: 'Components/Data Display/NsBrandComponent',
|
|
11
11
|
component: NsBrandComponent,
|
|
12
12
|
decorators: [withCenterDecorator],
|
|
13
13
|
argTypes: {
|
|
@@ -5,7 +5,7 @@ import NSLogoMark from './icons/NSLogoMark';
|
|
|
5
5
|
import NSLogoPoweredBy from './icons/NSLogoPoweredBy';
|
|
6
6
|
import NSLogoStacked from './icons/NSLogoStacked';
|
|
7
7
|
import NSLogoWordMark from './icons/NSWordMark';
|
|
8
|
-
import LogoContainer from './
|
|
8
|
+
import LogoContainer from './NsBrandContainer.styled';
|
|
9
9
|
|
|
10
10
|
import { NsBrandComponentProps } from './types.d';
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ const VariantMap = {
|
|
|
20
20
|
const NsBrandComponent = (props: PropsWithChildren<NsBrandComponentProps>) => {
|
|
21
21
|
const { variant = 'stacked', size = '108px', color = 'primary' } = props;
|
|
22
22
|
const renderBrand = useCallback(() => {
|
|
23
|
-
const BrandComponent = VariantMap[variant
|
|
23
|
+
const BrandComponent = VariantMap[variant];
|
|
24
24
|
// [Todo - Add neutral as FoundationTheme.PaletteColor]
|
|
25
25
|
return <BrandComponent color={color as any} />;
|
|
26
26
|
}, [color, variant]);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { NsBreadcrumb } from './';
|
|
4
|
-
import { breadcrumbItems } from './data';
|
|
4
|
+
import { breadcrumbItems, dummyItems } from './data';
|
|
5
5
|
const { getByText } = screen;
|
|
6
6
|
|
|
7
7
|
describe('Breadcrumb', () => {
|
|
8
|
-
it('renders breadcrumb items', () => {
|
|
8
|
+
it('1. renders breadcrumb items', () => {
|
|
9
9
|
render(<NsBreadcrumb items={breadcrumbItems} />);
|
|
10
10
|
expect(getByText('Home')).toBeInTheDocument();
|
|
11
11
|
expect(getByText('Products')).toBeInTheDocument();
|
|
@@ -13,11 +13,16 @@ describe('Breadcrumb', () => {
|
|
|
13
13
|
expect(getByText('Blue shirt')).toBeInTheDocument();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it('renders breadcrumb items with navigation icons', () => {
|
|
16
|
+
it('2. renders breadcrumb items with navigation icons', () => {
|
|
17
17
|
render(<NsBreadcrumb items={breadcrumbItems} iconVisibility />);
|
|
18
18
|
expect(getByText('Home')).toBeInTheDocument();
|
|
19
19
|
expect(getByText('Products')).toBeInTheDocument();
|
|
20
20
|
expect(getByText('Shirts')).toBeInTheDocument();
|
|
21
21
|
expect(getByText('Blue shirt')).toBeInTheDocument();
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
it('3. renders breadcrumb items with navigation icons', () => {
|
|
25
|
+
render(<NsBreadcrumb items={dummyItems} iconVisibility />);
|
|
26
|
+
expect(getByText('Home')).toBeInTheDocument();
|
|
27
|
+
});
|
|
23
28
|
});
|
|
@@ -13,7 +13,7 @@ type Story = StoryObj<ComponentType>;
|
|
|
13
13
|
type MetaStory = Meta<ComponentType>;
|
|
14
14
|
|
|
15
15
|
export default {
|
|
16
|
-
title: 'Components/NsBreadcrumb',
|
|
16
|
+
title: 'Components/Navigation/NsBreadcrumb',
|
|
17
17
|
component: NsBreadcrumb,
|
|
18
18
|
decorators: [withCenterDecorator],
|
|
19
19
|
argTypes: {
|