@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.
Files changed (161) hide show
  1. package/package.json +105 -101
  2. package/src/common/accordion/NsAccordion.tsx +32 -40
  3. package/src/common/accordion/NsAccordionHeader.tsx +29 -8
  4. package/src/common/accordion/NsAccordionItem.tsx +24 -4
  5. package/src/common/accordion/NsCollapse.tsx +96 -35
  6. package/src/common/accordion/NsToggleSection.tsx +16 -3
  7. package/src/common/accordion/ToggleSection.styled.tsx +11 -7
  8. package/src/common/accordion/colorMap.ts +117 -0
  9. package/src/common/accordion/enums.ts +18 -0
  10. package/src/common/accordion/index.ts +1 -0
  11. package/src/common/accordion/specs/NsAccordion.spec.tsx +101 -31
  12. package/src/common/accordion/specs/NsAccordionHeader.spec.tsx +77 -10
  13. package/src/common/accordion/specs/NsAccordionItem.spec.tsx +27 -10
  14. package/src/common/accordion/specs/NsCollapse.spec.tsx +109 -17
  15. package/src/common/accordion/specs/NsToggleSection.spec.tsx +50 -14
  16. package/src/common/accordion/stories/NsAccordion.stories.tsx +32 -14
  17. package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +6 -2
  18. package/src/common/accordion/stories/NsAccordionItem.stories.tsx +9 -4
  19. package/src/common/accordion/stories/NsCollapse.stories.tsx +20 -5
  20. package/src/common/accordion/stories/NsToggleSection.stories.tsx +7 -4
  21. package/src/common/accordion/stories/data.tsx +140 -19
  22. package/src/common/accordion/types.d.ts +55 -47
  23. package/src/common/alert/Header.tsx +29 -0
  24. package/src/common/alert/NsAlert.spec.tsx +46 -13
  25. package/src/common/alert/NsAlert.stories.tsx +3 -3
  26. package/src/common/alert/{NsAlert.styled.ts → NsAlert.styled.tsx} +22 -22
  27. package/src/common/alert/NsAlert.tsx +22 -87
  28. package/src/common/alert/types.d.ts +7 -7
  29. package/src/common/auth-dropdown/NsAuthDropdown.spec.tsx +23 -5
  30. package/src/common/auth-dropdown/NsAuthDropdown.stories.tsx +1 -1
  31. package/src/common/auth-dropdown/NsAuthDropdown.styled.ts +11 -7
  32. package/src/common/auth-dropdown/NsAuthDropdown.tsx +4 -4
  33. package/src/common/autocomplete/NsAutocomplete.spec.tsx +34 -11
  34. package/src/common/autocomplete/NsAutocomplete.stories.tsx +19 -3
  35. package/src/common/autocomplete/NsAutocomplete.tsx +63 -24
  36. package/src/common/autocomplete/mock/MockData.ts +50 -7
  37. package/src/common/autocomplete/mock/MockUI.tsx +2 -1
  38. package/src/common/autocomplete/types.d.ts +7 -4
  39. package/src/common/banner/NsBanner.spec.tsx +12 -3
  40. package/src/common/banner/NsBanner.stories.tsx +1 -1
  41. package/src/common/brand-components/NsBrand.stories.tsx +1 -1
  42. package/src/common/brand-components/NsBrandComponent.tsx +2 -2
  43. package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +8 -3
  44. package/src/common/breadcrumb/NsBreadcrumb.stories.tsx +1 -1
  45. package/src/common/breadcrumb/data.ts +8 -5
  46. package/src/common/button/NsButton.spec.tsx +65 -49
  47. package/src/common/button/NsButton.stories.tsx +59 -2
  48. package/src/common/button/NsButton.styled.tsx +49 -66
  49. package/src/common/button/NsButton.tsx +1 -2
  50. package/src/common/button/helper.ts +82 -0
  51. package/src/common/button/types.d.ts +2 -0
  52. package/src/common/card/NsFfgCard.tsx +13 -7
  53. package/src/common/card/specs/NsFfgCard.spec.tsx +18 -8
  54. package/src/common/card/stories/NsBgCard.stories.tsx +1 -1
  55. package/src/common/card/stories/NsBrandProductCard.stories.tsx +1 -1
  56. package/src/common/card/stories/NsFfgCard.stories.tsx +48 -15
  57. package/src/common/card/styled/NsFfgStyled.tsx +10 -7
  58. package/src/common/card/types.d.ts +7 -4
  59. package/src/common/carousal/NsCarousal.tsx +3 -9
  60. package/src/common/carousal/stories/NSCarousal.stories.tsx +1 -1
  61. package/src/common/carousal/stories/NsScrollingCarousal.stories.tsx +1 -1
  62. package/src/common/cart-icon/NsCartIcon.spec.tsx +5 -16
  63. package/src/common/cart-icon/NsCartIcon.stories.tsx +2 -2
  64. package/src/common/cart-icon/NsCartIcon.styled.ts +7 -33
  65. package/src/common/cart-icon/NsCartIcon.tsx +5 -14
  66. package/src/common/cart-icon/types.d.ts +1 -0
  67. package/src/common/checkbox/NsCheckbox.stories.tsx +1 -1
  68. package/src/common/checkbox/NsCheckbox.styled.tsx +15 -16
  69. package/src/common/checkbox/NsCheckbox.tsx +1 -1
  70. package/src/common/column-control/stories/DivArray.stories.tsx +1 -1
  71. package/src/common/column-control/stories/NsColumnControl.stories.tsx +1 -1
  72. package/src/common/column-control/stories/NsFlexColumns.stories.tsx +1 -1
  73. package/src/common/column-control/stories/NsGridColumns.stories.tsx +1 -1
  74. package/src/common/column-control/styled/common.ts +1 -1
  75. package/src/common/column-control/utils.ts +4 -4
  76. package/src/common/common.types.d.ts +14 -5
  77. package/src/common/dropdown/NsDropdown.styled.tsx +23 -18
  78. package/src/common/dropdown/stories/NsDropdown.stories.tsx +1 -1
  79. package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +1 -1
  80. package/src/common/flag/NsFlag.stories.tsx +1 -1
  81. package/src/common/hooks/index.ts +2 -0
  82. package/src/common/hooks/specs/useWindowSize.spec.tsx +19 -0
  83. package/src/common/hooks/useAccordionMap.tsx +39 -0
  84. package/src/common/hooks/useWindowSize.tsx +30 -0
  85. package/src/common/icon/NsIcon.spec.tsx +1 -1
  86. package/src/common/icon/NsIcon.stories.tsx +4 -1
  87. package/src/common/icon/NsIcon.tsx +6 -2
  88. package/src/common/icon/NsIconVariants.ts +4 -1
  89. package/src/common/icon/icons/AlertErrorIcon.tsx +2 -2
  90. package/src/common/icon/icons/ArrowLeft.tsx +3 -6
  91. package/src/common/icon/icons/ArrowRight.tsx +6 -8
  92. package/src/common/icon/icons/Cart.tsx +7 -32
  93. package/src/common/icon/icons/CartBadge.tsx +2 -2
  94. package/src/common/icon/icons/CloseIcon.tsx +1 -1
  95. package/src/common/icon/icons/ErrorIcon.tsx +2 -2
  96. package/src/common/icon/icons/HamburgerIcon.tsx +5 -2
  97. package/src/common/icon/icons/SearchIcon.tsx +1 -1
  98. package/src/common/icon/icons/SuccessIcon.tsx +2 -7
  99. package/src/common/icon/icons/TrashIcon.tsx +1 -1
  100. package/src/common/icon/icons/UserIcon.tsx +3 -6
  101. package/src/common/icon/icons/UserOutline.tsx +24 -0
  102. package/src/common/icon/icons/WarningIcon.tsx +1 -1
  103. package/src/common/icon/icons/home.tsx +1 -1
  104. package/src/common/image/NsImage.spec.tsx +56 -6
  105. package/src/common/image/NsImage.stories.tsx +1 -1
  106. package/src/common/image/NsImage.tsx +23 -11
  107. package/src/common/image/NsNextImage.stories.tsx +1 -1
  108. package/src/common/index.ts +6 -2
  109. package/src/common/input/InputWrapper.tsx +12 -7
  110. package/src/common/input/NsInputField.tsx +1 -1
  111. package/src/common/label/NsLabel.stories.tsx +1 -1
  112. package/src/common/link/NsLink.stories.tsx +1 -1
  113. package/src/common/modal/NsModal.spec.tsx +46 -0
  114. package/src/common/modal/NsModal.styled.tsx +28 -0
  115. package/src/common/modal/NsModal.tsx +37 -0
  116. package/src/common/modal/helper-component/NsModalContentBox.tsx +32 -0
  117. package/src/common/modal/helper-component/NsModalDefault.tsx +25 -0
  118. package/src/common/modal/helper-component/NsModalStatus.tsx +15 -0
  119. package/src/common/modal/helper-component/NsModalStatusIcon.tsx +14 -0
  120. package/src/common/modal/helper-component/NsModalTitle.tsx +26 -0
  121. package/src/common/modal/index.ts +2 -0
  122. package/src/common/modal/model/NsModalVariantMap.tsx +24 -0
  123. package/src/common/modal/stories/NsModal.stories.tsx +85 -0
  124. package/src/common/modal/stories/mock.tsx +46 -0
  125. package/src/common/modal/types.ts +56 -0
  126. package/src/common/nav-product-card/NsNavProductCard.stories.tsx +1 -1
  127. package/src/common/nav-tab/NavDrawer.tsx +34 -0
  128. package/src/common/nav-tab/NavTitles.tsx +52 -0
  129. package/src/common/nav-tab/NsNavTab.spec.tsx +4 -5
  130. package/src/common/nav-tab/NsNavTab.stories.tsx +4 -4
  131. package/src/common/nav-tab/NsNavTab.styled.tsx +13 -7
  132. package/src/common/nav-tab/NsNavTab.tsx +21 -75
  133. package/src/common/nav-tab/index.ts +1 -1
  134. package/src/common/nav-tab/types.d.ts +3 -3
  135. package/src/common/radiogroup/NsRadioGroup.spec.tsx +24 -25
  136. package/src/common/radiogroup/NsRadioGroup.stories.tsx +14 -32
  137. package/src/common/radiogroup/NsRadioGroup.styled.tsx +20 -19
  138. package/src/common/radiogroup/NsRadioGroup.tsx +6 -5
  139. package/src/common/search-bar/NsSearchBar.tsx +1 -3
  140. package/src/common/select/NsSelect.spec.tsx +10 -17
  141. package/src/common/select/NsSelect.styled.tsx +53 -34
  142. package/src/common/select/stories/NsSelect.stories.tsx +11 -11
  143. package/src/common/select/types.d.ts +3 -3
  144. package/src/common/spinner/NsSpinner.spec.tsx +21 -0
  145. package/src/common/spinner/NsSpinner.stories.tsx +26 -0
  146. package/src/common/spinner/NsSpinner.styled.tsx +344 -0
  147. package/src/common/spinner/NsSpinner.tsx +14 -0
  148. package/src/common/spinner/icons/Spinner.tsx +161 -0
  149. package/src/common/spinner/index.ts +2 -0
  150. package/src/common/spinner/types.ts +5 -0
  151. package/src/common/table/NsTable.stories.tsx +1 -1
  152. package/src/common/table/NsTable.tsx +32 -49
  153. package/src/common/table/NsTableHeader.tsx +30 -51
  154. package/src/common/text-strip/NsTextStrip.stories.tsx +1 -1
  155. package/src/common/typography/NsTypography.stories.tsx +1 -1
  156. package/src/common/util.ts +1 -0
  157. package/src/common/utility/Show.tsx +13 -0
  158. package/src/common/utility/index.ts +1 -0
  159. package/src/common/videos/NsVideo.stories.tsx +1 -1
  160. package/src/common/videos/types.d.ts +1 -1
  161. /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
- export const AuthHead = styled.div<{ darkModeEnabled: boolean }>`
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: ${MAGENTA_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', 'main')};
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: ${MAGENTA_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: ${MAGENTA_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
- test('should render autocomplete component', () => {
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
- renderOptions={OptionUiComponent}
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
- test('displays options when typing in the input', async () => {
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
- renderOptions={OptionUiComponent}
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
- test('selects an option when clicking on it', async () => {
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
- renderOptions={OptionUiComponent}
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
- test('should load more options when clicking load more button', async () => {
92
+ it('5. should load more options when clicking load more button', async () => {
70
93
  render(
71
94
  <NsAutocomplete
72
95
  options={AutocompleteMockList}
73
- renderOptions={OptionUiComponent}
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
- test('should clear options when clicking clear button', async () => {
117
+ it('6. should clear options when clicking clear button', async () => {
95
118
  render(
96
119
  <NsAutocomplete
97
120
  options={AutocompleteMockList}
98
- renderOptions={OptionUiComponent}
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
- {...args}
89
+ {...AutoCompleteProps}
90
+ options={args.options.slice(0, pageNo)}
75
91
  onChange={handleInputChange}
76
92
  onSelect={onSelect}
77
- renderOptions={OptionUiComponent}
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
- renderOptions,
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 OptionUI = renderOptions;
29
+ const [scrollTopPosition, setScrollTopPosition] = useState(0);
30
+ const listboxRef = useRef<HTMLUListElement | null>(null);
25
31
 
26
- const handleSelect = (event: React.SyntheticEvent<Element, Event>, value: string | OptionsProps | null) => {
27
- onSelect ? onSelect(value) : null;
28
- };
32
+ useEffect(() => {
33
+ if (listboxRef?.current && options.length > 0) {
34
+ listboxRef.current.scrollTop = scrollTopPosition;
35
+ }
36
+ }, [options, scrollTopPosition]);
29
37
 
30
- const handleInputChange = (event: React.SyntheticEvent<Element, Event>, newInputValue: string) => {
31
- onChange ? onChange(newInputValue) : null;
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
- <OptionUI {...props} option={option} />
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={'p-xs'} weight="bold">
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, onSeeMore, text = 'See More Results' } = seeMore || {};
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={onSeeMore} data-testid="autocomplete-loadmore-id">
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
- // eslint-disable-next-line max-len
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
- // eslint-disable-next-line max-len
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', // eslint-disable-next-line max-len
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', // eslint-disable-next-line max-len
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', // eslint-disable-next-line max-len
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', // eslint-disable-next-line max-len
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 className="checker">
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
- renderOptions: React.ComponentType<any>;
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: string;
19
+ text?: string;
18
20
  show?: boolean; //default to false
19
21
  onClear: () => void;
20
22
  };
21
23
  seeMore?: {
22
- text: string;
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
- it('renders banner image and title', () => {
7
- const childText = 'Welcome to My Website';
8
- const imageURL = 'https://example.com/banner.jpg';
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
  });
@@ -14,7 +14,7 @@ type Story = StoryObj<ComponentType>;
14
14
  type MetaStory = Meta<ComponentType>;
15
15
 
16
16
  export default {
17
- title: 'Components/NsBanner',
17
+ title: 'Components/Data Display/NsBanner',
18
18
  component: NsBanner,
19
19
  decorators: [withCenterDecorator],
20
20
  argTypes: {
@@ -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 './NsBrandContanier.styled';
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 || 'stacked'];
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: {