@nuskin/foundation-ui-components 1.0.2 → 1.1.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/package.json +105 -101
- package/src/common/accordion/NsAccordion.tsx +32 -40
- package/src/common/accordion/NsAccordionHeader.tsx +29 -8
- package/src/common/accordion/NsAccordionItem.tsx +24 -4
- package/src/common/accordion/NsCollapse.tsx +96 -35
- package/src/common/accordion/NsToggleSection.tsx +16 -3
- package/src/common/accordion/ToggleSection.styled.tsx +11 -7
- package/src/common/accordion/colorMap.ts +117 -0
- package/src/common/accordion/enums.ts +18 -0
- package/src/common/accordion/index.ts +1 -0
- package/src/common/accordion/specs/NsAccordion.spec.tsx +101 -31
- package/src/common/accordion/specs/NsAccordionHeader.spec.tsx +77 -10
- package/src/common/accordion/specs/NsAccordionItem.spec.tsx +27 -10
- package/src/common/accordion/specs/NsCollapse.spec.tsx +109 -17
- package/src/common/accordion/specs/NsToggleSection.spec.tsx +50 -14
- package/src/common/accordion/stories/NsAccordion.stories.tsx +32 -14
- package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +6 -2
- package/src/common/accordion/stories/NsAccordionItem.stories.tsx +9 -4
- package/src/common/accordion/stories/NsCollapse.stories.tsx +20 -5
- package/src/common/accordion/stories/NsToggleSection.stories.tsx +7 -4
- package/src/common/accordion/stories/data.tsx +140 -19
- package/src/common/accordion/types.d.ts +55 -47
- 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 +34 -11
- package/src/common/autocomplete/NsAutocomplete.stories.tsx +19 -3
- package/src/common/autocomplete/NsAutocomplete.tsx +63 -24
- package/src/common/autocomplete/mock/MockData.ts +50 -7
- package/src/common/autocomplete/mock/MockUI.tsx +2 -1
- package/src/common/autocomplete/types.d.ts +7 -4
- 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/NsFfgCard.tsx +13 -7
- package/src/common/card/specs/NsFfgCard.spec.tsx +18 -8
- 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 +48 -15
- package/src/common/card/styled/NsFfgStyled.tsx +10 -7
- package/src/common/card/types.d.ts +7 -4
- package/src/common/carousal/NsCarousal.tsx +3 -9
- 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.spec.tsx +5 -16
- package/src/common/cart-icon/NsCartIcon.stories.tsx +2 -2
- package/src/common/cart-icon/NsCartIcon.styled.ts +7 -33
- package/src/common/cart-icon/NsCartIcon.tsx +5 -14
- package/src/common/cart-icon/types.d.ts +1 -0
- 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 +14 -5
- 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.spec.tsx +1 -1
- package/src/common/icon/NsIcon.stories.tsx +4 -1
- package/src/common/icon/NsIcon.tsx +6 -2
- package/src/common/icon/NsIconVariants.ts +4 -1
- package/src/common/icon/icons/AlertErrorIcon.tsx +2 -2
- package/src/common/icon/icons/ArrowLeft.tsx +3 -6
- package/src/common/icon/icons/ArrowRight.tsx +6 -8
- package/src/common/icon/icons/Cart.tsx +7 -32
- package/src/common/icon/icons/CartBadge.tsx +2 -2
- package/src/common/icon/icons/CloseIcon.tsx +1 -1
- package/src/common/icon/icons/ErrorIcon.tsx +2 -2
- package/src/common/icon/icons/HamburgerIcon.tsx +5 -2
- package/src/common/icon/icons/SearchIcon.tsx +1 -1
- package/src/common/icon/icons/SuccessIcon.tsx +2 -7
- package/src/common/icon/icons/TrashIcon.tsx +1 -1
- package/src/common/icon/icons/UserIcon.tsx +3 -6
- package/src/common/icon/icons/UserOutline.tsx +24 -0
- package/src/common/icon/icons/WarningIcon.tsx +1 -1
- package/src/common/icon/icons/home.tsx +1 -1
- 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 +85 -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/NavDrawer.tsx +34 -0
- package/src/common/nav-tab/NavTitles.tsx +52 -0
- package/src/common/nav-tab/NsNavTab.spec.tsx +4 -5
- package/src/common/nav-tab/NsNavTab.stories.tsx +4 -4
- package/src/common/nav-tab/NsNavTab.styled.tsx +13 -7
- package/src/common/nav-tab/NsNavTab.tsx +21 -75
- package/src/common/nav-tab/index.ts +1 -1
- package/src/common/nav-tab/types.d.ts +3 -3
- 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 +20 -19
- 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
|
@@ -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,13 +7,12 @@ 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
|
+
OptionsUI={OptionUiComponent}
|
|
17
16
|
inputValue=""
|
|
18
17
|
value=""
|
|
19
18
|
/>
|
|
@@ -21,13 +20,34 @@ 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
|
+
OptionsUI={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}
|
|
29
49
|
onSelect={() => {}}
|
|
30
|
-
|
|
50
|
+
OptionsUI={OptionUiComponent}
|
|
31
51
|
placeholder="autocomplete-input"
|
|
32
52
|
inputValue=""
|
|
33
53
|
value=""
|
|
@@ -46,11 +66,13 @@ 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
|
+
OptionsUI={OptionUiComponent}
|
|
54
76
|
placeholder="autocomplete-input"
|
|
55
77
|
inputValue=""
|
|
56
78
|
value=""
|
|
@@ -64,13 +86,14 @@ 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}
|
|
73
|
-
|
|
96
|
+
OptionsUI={OptionUiComponent}
|
|
74
97
|
placeholder="autocomplete-input"
|
|
75
98
|
seeMore={{
|
|
76
99
|
text: '',
|
|
@@ -91,11 +114,11 @@ 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}
|
|
98
|
-
|
|
121
|
+
OptionsUI={OptionUiComponent}
|
|
99
122
|
placeholder="autocomplete-input"
|
|
100
123
|
clear={{
|
|
101
124
|
text: '',
|
|
@@ -19,8 +19,10 @@ type ComponentType = typeof NsAutocomplete;
|
|
|
19
19
|
type Story = StoryObj<ComponentType>;
|
|
20
20
|
type MetaStory = Meta<ComponentType>;
|
|
21
21
|
|
|
22
|
+
const PAGE_SIZE = 6;
|
|
23
|
+
|
|
22
24
|
export default {
|
|
23
|
-
title: 'Components/NsAutocomplete',
|
|
25
|
+
title: 'Components/Inputs/NsAutocomplete',
|
|
24
26
|
component: NsAutocomplete,
|
|
25
27
|
decorators: [withCenterDecorator],
|
|
26
28
|
argTypes: {
|
|
@@ -53,6 +55,19 @@ export default {
|
|
|
53
55
|
const RenderAutocomplete = (args: NsAutocompleteProps) => {
|
|
54
56
|
const [selectedOption, setSelectedOption] = useState<string | OptionsProps | null>(null);
|
|
55
57
|
const [searchTerms, setSearchTerms] = useState<string>('');
|
|
58
|
+
const [pageNo, setPageNo] = useState(PAGE_SIZE);
|
|
59
|
+
|
|
60
|
+
const onSeeMoreHandler = () => {
|
|
61
|
+
setPageNo(PAGE_SIZE + PAGE_SIZE);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const AutoCompleteProps = {
|
|
65
|
+
...args,
|
|
66
|
+
seeMore: {
|
|
67
|
+
...args.seeMore,
|
|
68
|
+
onSeeMore: onSeeMoreHandler,
|
|
69
|
+
},
|
|
70
|
+
} as NsAutocompleteProps;
|
|
56
71
|
|
|
57
72
|
const onSelect = (value: string | OptionsProps | null) => {
|
|
58
73
|
setSelectedOption(value);
|
|
@@ -71,10 +86,11 @@ const RenderAutocomplete = (args: NsAutocompleteProps) => {
|
|
|
71
86
|
return (
|
|
72
87
|
<div>
|
|
73
88
|
<NsAutocomplete
|
|
74
|
-
{...
|
|
89
|
+
{...AutoCompleteProps}
|
|
90
|
+
options={args.options.slice(0, pageNo)}
|
|
75
91
|
onChange={handleInputChange}
|
|
76
92
|
onSelect={onSelect}
|
|
77
|
-
|
|
93
|
+
OptionsUI={OptionUiComponent}
|
|
78
94
|
iconEnd={
|
|
79
95
|
<IconButton onClick={onInputClear}>
|
|
80
96
|
<NsIcon name="close" size="small" />
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import React, { PropsWithChildren } from 'react';
|
|
1
|
+
import React, { HTMLAttributes, PropsWithChildren, useCallback, useEffect, useRef, useState } 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';
|
|
5
5
|
import { StyledTextField } from './NsAutocomplete.styled';
|
|
6
6
|
|
|
7
|
+
const renderOptionLabel = (option: OptionsProps) => option.value;
|
|
8
|
+
|
|
9
|
+
const renderGroupBy = (option: OptionsProps) => option.group as string;
|
|
10
|
+
|
|
7
11
|
const NsAutocomplete = ({
|
|
8
12
|
id = 'custom_autocomplete_dropdown',
|
|
9
|
-
options
|
|
10
|
-
|
|
13
|
+
options,
|
|
14
|
+
OptionsUI,
|
|
11
15
|
loading,
|
|
12
16
|
noOptionsText,
|
|
13
17
|
onSelect,
|
|
@@ -20,16 +24,34 @@ const NsAutocomplete = ({
|
|
|
20
24
|
iconEnd,
|
|
21
25
|
inputValue,
|
|
22
26
|
value,
|
|
27
|
+
isFilterEnabled = true,
|
|
23
28
|
}: PropsWithChildren<NsAutocompleteProps>) => {
|
|
24
|
-
const
|
|
29
|
+
const [scrollTopPosition, setScrollTopPosition] = useState(0);
|
|
30
|
+
const listboxRef = useRef<HTMLUListElement | null>(null);
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (listboxRef?.current && options.length > 0) {
|
|
34
|
+
listboxRef.current.scrollTop = scrollTopPosition;
|
|
35
|
+
}
|
|
36
|
+
}, [options, scrollTopPosition]);
|
|
29
37
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
const handleSelect = useCallback(
|
|
39
|
+
(event: React.SyntheticEvent, value: string | OptionsProps | null) => {
|
|
40
|
+
if (typeof onSelect === 'function') {
|
|
41
|
+
onSelect(value);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[onSelect]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const handleInputChange = useCallback(
|
|
48
|
+
(event: React.SyntheticEvent, newInputValue: string) => {
|
|
49
|
+
if (typeof onChange === 'function') {
|
|
50
|
+
onChange(newInputValue);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[onChange]
|
|
54
|
+
);
|
|
33
55
|
|
|
34
56
|
const renderInput = (params: AutocompleteRenderInputParams) => (
|
|
35
57
|
<StyledTextField
|
|
@@ -45,15 +67,11 @@ const NsAutocomplete = ({
|
|
|
45
67
|
|
|
46
68
|
// TODO: @vijayprathap need to check if we can remove object spread of ...props and ...params in renderInput
|
|
47
69
|
const renderOption = (props: ComponentBoxProps, option: OptionsProps) => (
|
|
48
|
-
<li {...props}>
|
|
49
|
-
<
|
|
70
|
+
<li {...props} key={props.id}>
|
|
71
|
+
{OptionsUI ? <OptionsUI {...props} option={option} /> : null}
|
|
50
72
|
</li>
|
|
51
73
|
);
|
|
52
74
|
|
|
53
|
-
const renderOptionLabel = (option: OptionsProps) => option.value;
|
|
54
|
-
|
|
55
|
-
const renderGroupBy = (option: OptionsProps) => option.group as string;
|
|
56
|
-
|
|
57
75
|
const renderGroupName = ({ key, group, children }: AutocompleteRenderGroupParams) => (
|
|
58
76
|
<li key={key}>
|
|
59
77
|
{group ? (
|
|
@@ -65,7 +83,7 @@ const NsAutocomplete = ({
|
|
|
65
83
|
</li>
|
|
66
84
|
);
|
|
67
85
|
|
|
68
|
-
const renderClearButton = () => {
|
|
86
|
+
const renderClearButton = useCallback(() => {
|
|
69
87
|
const { show, onClear, text = 'Clear Search' } = clear || {};
|
|
70
88
|
if (!show) {
|
|
71
89
|
return null;
|
|
@@ -73,32 +91,47 @@ const NsAutocomplete = ({
|
|
|
73
91
|
return (
|
|
74
92
|
<li className="text-right p-3">
|
|
75
93
|
<NsButton onClick={onClear} data-testid="autocomplete-clear-id">
|
|
76
|
-
<NsTypography variant=
|
|
94
|
+
<NsTypography variant="p-xs" weight="bold">
|
|
77
95
|
{text}
|
|
78
96
|
</NsTypography>
|
|
79
97
|
</NsButton>
|
|
80
98
|
</li>
|
|
81
99
|
);
|
|
82
|
-
};
|
|
100
|
+
}, [clear]);
|
|
101
|
+
|
|
102
|
+
const handleSeeMore = useCallback(() => {
|
|
103
|
+
const { onSeeMore } = seeMore || {};
|
|
104
|
+
// Remember the current scroll position
|
|
105
|
+
if (options?.length > 0 && listboxRef?.current) {
|
|
106
|
+
const lastScrollPosition = listboxRef.current?.scrollTop;
|
|
107
|
+
if (lastScrollPosition) {
|
|
108
|
+
setScrollTopPosition(lastScrollPosition);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (onSeeMore && typeof onSeeMore === 'function') {
|
|
112
|
+
onSeeMore();
|
|
113
|
+
}
|
|
114
|
+
}, [seeMore, options]);
|
|
83
115
|
|
|
84
|
-
const renderSeeMoreButton = () => {
|
|
85
|
-
const { show,
|
|
116
|
+
const renderSeeMoreButton = useCallback(() => {
|
|
117
|
+
const { show, text = 'See More Results' } = seeMore || {};
|
|
86
118
|
if (!show) {
|
|
87
119
|
return null;
|
|
88
120
|
}
|
|
121
|
+
|
|
89
122
|
return (
|
|
90
123
|
<li className="text-center p-3">
|
|
91
|
-
<NsButton onClick={
|
|
124
|
+
<NsButton onClick={handleSeeMore} data-testid="autocomplete-loadmore-id">
|
|
92
125
|
<NsTypography variant="p-xs" weight="bold">
|
|
93
126
|
{text}
|
|
94
127
|
</NsTypography>
|
|
95
128
|
</NsButton>
|
|
96
129
|
</li>
|
|
97
130
|
);
|
|
98
|
-
};
|
|
131
|
+
}, [seeMore, handleSeeMore]);
|
|
99
132
|
|
|
100
133
|
const renderListBoxComponent = ({ children, ...otherProps }: ComponentBoxProps) => (
|
|
101
|
-
<List {...otherProps}>
|
|
134
|
+
<List {...otherProps} ref={listboxRef}>
|
|
102
135
|
{renderClearButton()}
|
|
103
136
|
{children}
|
|
104
137
|
{renderSeeMoreButton()}
|
|
@@ -118,11 +151,17 @@ const NsAutocomplete = ({
|
|
|
118
151
|
renderOption={renderOption}
|
|
119
152
|
renderInput={renderInput}
|
|
120
153
|
ListboxComponent={renderListBoxComponent}
|
|
154
|
+
ListboxProps={{ scrollTopPosition } as HTMLAttributes<HTMLUListElement>} // Pass the current scroll position
|
|
121
155
|
renderGroup={renderGroupName}
|
|
122
156
|
onChange={handleSelect}
|
|
123
157
|
onInputChange={handleInputChange}
|
|
124
158
|
value={value}
|
|
125
159
|
inputValue={inputValue}
|
|
160
|
+
filterOptions={
|
|
161
|
+
isFilterEnabled
|
|
162
|
+
? undefined // Use default filtering behavior
|
|
163
|
+
: (options) => options
|
|
164
|
+
}
|
|
126
165
|
freeSolo
|
|
127
166
|
/>
|
|
128
167
|
);
|
|
@@ -4,24 +4,25 @@ export const AutocompleteMockList: OptionsProps[] = [
|
|
|
4
4
|
{
|
|
5
5
|
id: '1',
|
|
6
6
|
value: 'GS Facial Gels 2pk Subscription',
|
|
7
|
-
|
|
7
|
+
group: 'Popular Searches',
|
|
8
8
|
image: 'https://www.nuskin.com/content/products/86/99/01/86990168/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
id: '2',
|
|
12
12
|
value: 'GS Facial',
|
|
13
|
-
|
|
13
|
+
group: 'Popular Searches',
|
|
14
14
|
image: 'https://www.nuskin.com/content/products/01/55/00/01550014/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
id: '3',
|
|
18
|
-
value: 'GS Facial Spa',
|
|
18
|
+
value: 'GS Facial Spa',
|
|
19
|
+
group: 'Popular Searches',
|
|
19
20
|
image: 'https://www.nuskin.com/content/products/01/10/12/01101251/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
id: '4',
|
|
23
24
|
value: 'Nu Skin® Facial Spa',
|
|
24
|
-
group: 'Popular Searches',
|
|
25
|
+
group: 'Popular Searches',
|
|
25
26
|
image: 'https://www.nuskin.com/content/products/01/55/00/01550013/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
26
27
|
icon: 'cart',
|
|
27
28
|
groupBgColor: '#000',
|
|
@@ -29,7 +30,7 @@ export const AutocompleteMockList: OptionsProps[] = [
|
|
|
29
30
|
{
|
|
30
31
|
id: '5',
|
|
31
32
|
value: 'AP 24® Whitening Fluoride Toothpaste',
|
|
32
|
-
group: 'Popular Searches',
|
|
33
|
+
group: 'Popular Searches',
|
|
33
34
|
image: 'https://www.nuskin.com/content/products/86/99/01/86990100/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
34
35
|
icon: 'cart',
|
|
35
36
|
groupBgColor: '#000',
|
|
@@ -37,10 +38,52 @@ export const AutocompleteMockList: OptionsProps[] = [
|
|
|
37
38
|
{
|
|
38
39
|
id: '6',
|
|
39
40
|
value: 'PowerMask + LumiSpa Normal Subscription',
|
|
40
|
-
group: 'Popular Searches',
|
|
41
|
+
group: 'Popular Searches',
|
|
41
42
|
image: 'https://www.nuskin.com/content/products/01/00/16/01001647/jcr:content/fullImage.img.160.160.png/1611763135883-cache.png.64.64.png',
|
|
42
43
|
icon: 'cart',
|
|
43
44
|
groupBgColor: '#000',
|
|
44
45
|
},
|
|
45
|
-
];
|
|
46
46
|
|
|
47
|
+
{
|
|
48
|
+
id: '7',
|
|
49
|
+
value: 'Rejuvenating Cream',
|
|
50
|
+
group: 'Popular Searches',
|
|
51
|
+
image: 'https://www.nuskin.com/content/products/86/99/01/86990168/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: '8',
|
|
55
|
+
value: 'Epoch® Firewalker® Foot Cream',
|
|
56
|
+
group: 'Popular Searches',
|
|
57
|
+
image: 'https://www.nuskin.com/content/products/01/55/00/01550014/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: '9',
|
|
61
|
+
value: 'Tri-Phasic White® Night Cream',
|
|
62
|
+
group: 'Popular Searches',
|
|
63
|
+
image: 'https://www.nuskin.com/content/products/01/10/12/01101251/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: '10',
|
|
67
|
+
value: 'ThirstFix Hydrating Gel Cream (Sample Size)',
|
|
68
|
+
group: 'Popular Searches',
|
|
69
|
+
image: 'https://www.nuskin.com/content/products/01/55/00/01550013/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
70
|
+
icon: 'cart',
|
|
71
|
+
groupBgColor: '#000',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: '11',
|
|
75
|
+
value: 'Epoch® Baby Hair',
|
|
76
|
+
group: 'Popular Searches',
|
|
77
|
+
image: 'https://www.nuskin.com/content/products/86/99/01/86990100/_jcr_content/fullImage.img.160.160.png.64.64.png',
|
|
78
|
+
icon: 'cart',
|
|
79
|
+
groupBgColor: '#000',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: '12',
|
|
83
|
+
value: '180° Face Wash',
|
|
84
|
+
group: 'Popular Searches',
|
|
85
|
+
image: 'https://www.nuskin.com/content/products/01/00/16/01001647/jcr:content/fullImage.img.160.160.png/1611763135883-cache.png.64.64.png',
|
|
86
|
+
icon: 'cart',
|
|
87
|
+
groupBgColor: '#000',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @next/next/no-img-element */
|
|
1
2
|
import { ListStyled } from './Mock.styled';
|
|
2
3
|
import { NsTypography } from '../../typography';
|
|
3
4
|
import { NsIcon } from '../../icon';
|
|
@@ -5,7 +6,7 @@ import { ListUiProps } from '../types';
|
|
|
5
6
|
|
|
6
7
|
const OptionUiComponent = ({ option }: ListUiProps) => {
|
|
7
8
|
return (
|
|
8
|
-
<ListStyled
|
|
9
|
+
<ListStyled>
|
|
9
10
|
<div className="item-description" role="link" tabIndex={0}>
|
|
10
11
|
<NsTypography aria-label="Nu Skin Tumbler" variant="p-m" colorOverride="#63666A">
|
|
11
12
|
<img src={option.image} alt="No-image" />
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import React, { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ComponentsProps } from '@mui/material';
|
|
4
|
+
|
|
2
5
|
import { AdditionalProps } from '../common.types';
|
|
3
6
|
import { NsIconNameType } from '../icon';
|
|
4
|
-
import { ComponentsProps } from '@mui/material';
|
|
5
7
|
|
|
6
8
|
export type NsAutocompleteProps = {
|
|
7
9
|
id?: string;
|
|
8
10
|
options: OptionsProps[];
|
|
9
11
|
loading?: boolean;
|
|
10
|
-
|
|
12
|
+
OptionsUI: React.ComponentType<any>;
|
|
11
13
|
noOptionsText?: string;
|
|
12
14
|
// eslint-disable-next-line no-unused-vars
|
|
13
15
|
onSelect?: (params: string | OptionsProps | null) => void;
|
|
14
16
|
// eslint-disable-next-line no-unused-vars
|
|
15
17
|
onChange?: (params: string) => void;
|
|
16
18
|
clear?: {
|
|
17
|
-
text
|
|
19
|
+
text?: string;
|
|
18
20
|
show?: boolean; //default to false
|
|
19
21
|
onClear: () => void;
|
|
20
22
|
};
|
|
21
23
|
seeMore?: {
|
|
22
|
-
text
|
|
24
|
+
text?: string;
|
|
23
25
|
show?: boolean; //default to false
|
|
24
26
|
onSeeMore: () => void;
|
|
25
27
|
};
|
|
@@ -29,6 +31,7 @@ export type NsAutocompleteProps = {
|
|
|
29
31
|
iconEnd?: string | React.ReactNode;
|
|
30
32
|
inputValue: string;
|
|
31
33
|
value: string | OptionsProps | null;
|
|
34
|
+
isFilterEnabled?:boolean
|
|
32
35
|
} & AdditionalProps;
|
|
33
36
|
|
|
34
37
|
export type ListUiProps = {
|
|
@@ -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: {
|