@nuskin/foundation-ui-components 1.0.0-develop.5 → 1.0.0-develop.7

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 (114) hide show
  1. package/package.json +17 -12
  2. package/src/common/accordion/NsAccordion.tsx +29 -39
  3. package/src/common/accordion/NsAccordionHeader.tsx +32 -17
  4. package/src/common/accordion/NsAccordionItem.tsx +16 -4
  5. package/src/common/accordion/NsCollapse.tsx +73 -35
  6. package/src/common/accordion/NsToggleSection.tsx +14 -3
  7. package/src/common/accordion/ToggleSection.styled.tsx +8 -7
  8. package/src/common/accordion/colorMap.ts +117 -0
  9. package/src/common/accordion/enums.ts +15 -0
  10. package/src/common/accordion/index.ts +1 -0
  11. package/src/common/accordion/specs/NsAccordion.spec.tsx +100 -31
  12. package/src/common/accordion/specs/NsAccordionHeader.spec.tsx +77 -10
  13. package/src/common/accordion/specs/NsAccordionItem.spec.tsx +23 -10
  14. package/src/common/accordion/specs/NsCollapse.spec.tsx +106 -17
  15. package/src/common/accordion/specs/NsToggleSection.spec.tsx +46 -14
  16. package/src/common/accordion/stories/NsAccordion.stories.tsx +27 -14
  17. package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +4 -1
  18. package/src/common/accordion/stories/NsAccordionItem.stories.tsx +3 -3
  19. package/src/common/accordion/stories/NsCollapse.stories.tsx +3 -3
  20. package/src/common/accordion/stories/NsToggleSection.stories.tsx +2 -3
  21. package/src/common/accordion/stories/data.tsx +157 -19
  22. package/src/common/accordion/types.d.ts +56 -49
  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 +39 -6
  34. package/src/common/autocomplete/NsAutocomplete.stories.tsx +45 -7
  35. package/src/common/autocomplete/NsAutocomplete.tsx +31 -18
  36. package/src/common/autocomplete/mock/MockUI.tsx +1 -0
  37. package/src/common/autocomplete/types.d.ts +8 -6
  38. package/src/common/banner/NsBanner.spec.tsx +12 -3
  39. package/src/common/banner/NsBanner.stories.tsx +1 -1
  40. package/src/common/brand-components/NsBrand.stories.tsx +1 -1
  41. package/src/common/brand-components/NsBrandComponent.tsx +2 -2
  42. package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +8 -3
  43. package/src/common/breadcrumb/NsBreadcrumb.stories.tsx +1 -1
  44. package/src/common/breadcrumb/NsBreadcrumb.tsx +2 -2
  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/stories/NsBgCard.stories.tsx +1 -1
  53. package/src/common/card/stories/NsBrandProductCard.stories.tsx +1 -1
  54. package/src/common/card/stories/NsFfgCard.stories.tsx +5 -2
  55. package/src/common/card/styled/NsFfgStyled.tsx +1 -1
  56. package/src/common/carousal/stories/NSCarousal.stories.tsx +1 -1
  57. package/src/common/carousal/stories/NsScrollingCarousal.stories.tsx +1 -1
  58. package/src/common/cart-icon/NsCartIcon.stories.tsx +1 -1
  59. package/src/common/checkbox/NsCheckbox.stories.tsx +1 -1
  60. package/src/common/checkbox/NsCheckbox.styled.tsx +15 -16
  61. package/src/common/checkbox/NsCheckbox.tsx +1 -1
  62. package/src/common/column-control/stories/DivArray.stories.tsx +1 -1
  63. package/src/common/column-control/stories/NsColumnControl.stories.tsx +1 -1
  64. package/src/common/column-control/stories/NsFlexColumns.stories.tsx +1 -1
  65. package/src/common/column-control/stories/NsGridColumns.stories.tsx +1 -1
  66. package/src/common/column-control/styled/common.ts +1 -1
  67. package/src/common/column-control/utils.ts +4 -4
  68. package/src/common/common.types.d.ts +11 -1
  69. package/src/common/dropdown/NsDropdown.styled.tsx +23 -18
  70. package/src/common/dropdown/stories/NsDropdown.stories.tsx +1 -1
  71. package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +1 -1
  72. package/src/common/flag/NsFlag.stories.tsx +1 -1
  73. package/src/common/hooks/index.ts +2 -0
  74. package/src/common/hooks/specs/useWindowSize.spec.tsx +19 -0
  75. package/src/common/hooks/useAccordionMap.tsx +39 -0
  76. package/src/common/hooks/useWindowSize.tsx +30 -0
  77. package/src/common/icon/NsIcon.stories.tsx +1 -1
  78. package/src/common/icon/NsIconVariants.ts +1 -0
  79. package/src/common/image/NsImage.spec.tsx +56 -6
  80. package/src/common/image/NsImage.stories.tsx +1 -1
  81. package/src/common/image/NsImage.tsx +23 -11
  82. package/src/common/image/NsNextImage.stories.tsx +1 -1
  83. package/src/common/index.ts +2 -0
  84. package/src/common/input/InputWrapper.tsx +12 -7
  85. package/src/common/input/NsInputField.tsx +1 -1
  86. package/src/common/label/NsLabel.stories.tsx +1 -1
  87. package/src/common/link/NsLink.stories.tsx +1 -1
  88. package/src/common/nav-product-card/NsNavProductCard.stories.tsx +1 -1
  89. package/src/common/nav-tab/NsNavTab.stories.tsx +1 -1
  90. package/src/common/radiogroup/NsRadioGroup.spec.tsx +24 -25
  91. package/src/common/radiogroup/NsRadioGroup.stories.tsx +14 -32
  92. package/src/common/radiogroup/NsRadioGroup.styled.tsx +17 -15
  93. package/src/common/radiogroup/NsRadioGroup.tsx +6 -5
  94. package/src/common/search-bar/NsSearchBar.tsx +1 -3
  95. package/src/common/select/NsSelect.spec.tsx +10 -17
  96. package/src/common/select/NsSelect.styled.tsx +53 -34
  97. package/src/common/select/stories/NsSelect.stories.tsx +11 -11
  98. package/src/common/select/types.d.ts +3 -3
  99. package/src/common/spinner/NsSpinner.spec.tsx +21 -0
  100. package/src/common/spinner/NsSpinner.stories.tsx +26 -0
  101. package/src/common/spinner/NsSpinner.styled.tsx +344 -0
  102. package/src/common/spinner/NsSpinner.tsx +14 -0
  103. package/src/common/spinner/icons/Spinner.tsx +161 -0
  104. package/src/common/spinner/index.ts +2 -0
  105. package/src/common/spinner/types.ts +5 -0
  106. package/src/common/table/NsTable.stories.tsx +1 -1
  107. package/src/common/table/NsTable.tsx +32 -49
  108. package/src/common/table/NsTableHeader.tsx +30 -51
  109. package/src/common/text-strip/NsTextStrip.stories.tsx +1 -1
  110. package/src/common/typography/NsTypography.stories.tsx +1 -1
  111. package/src/common/util.ts +1 -0
  112. package/src/common/videos/NsVideo.stories.tsx +1 -1
  113. package/src/common/videos/types.d.ts +1 -1
  114. /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.d';
7
- const gutter_width = '10px';
8
- const close_button_width = '12px';
9
- const icon_width = '24px';
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
- 'min-height': '114px',
13
+ minHeight: '114px',
14
14
  padding: '15px 45px',
15
- 'content-column-start': 'row-center',
16
- 'content-column-end': 'row-center',
17
- 'bar-position': 'top',
18
- 'foot-visibility': 'block',
15
+ contentColumnStart: 'row-center',
16
+ contentColumnEnd: 'row-center',
17
+ barPosition: 'top',
18
+ footVisibility: 'block',
19
19
  },
20
20
  card: {
21
- 'min-height': '72px',
21
+ minHeight: '72px',
22
22
  padding: '24px 24px',
23
- 'content-column-start': 'row-left',
24
- 'content-column-end': 'row-right',
25
- 'bar-position': 'left',
26
- 'foot-display': 'none',
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] ${icon_width} [space] ${gutter_width} [row-center] auto [space] ${gutter_width} [row-right] ${close_button_width};
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]?.['min-height']};
63
- padding: ${({ type }) => TypeMap?.[type]?.['padding']};
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: ${icon_width};
82
- height: ${icon_width};
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]?.['content-column-start']};
106
- grid-column-end: ${({ type }) => TypeMap?.[type]?.['content-column-end']};
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]?.['foot-display']};
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, HeaderProps, ContextType, AlertType } from './types.d';
12
+ import type { NsAlertProps, ContextType, AlertType } from './types.d';
15
13
  import { NsIcon } from '../icon';
16
- import { NsIconNameType } from '../icon/types';
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
- threshhold = 600,
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 [windowWidth, setWindowWidth] = useState(0);
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 (!isValidContextValue(context)) {
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
- }, [context, type]);
107
-
108
- useEffect(() => {
109
- setWindowWidth(window?.innerWidth);
110
- window.addEventListener('resize', handleResize);
111
- return () => {
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 name={getIcon()} color={getColor()} size="large" />
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>{getTitleText()}</span>}>{head}</Header>
58
+ <Header head={<span>{title}</span>}>{head}</Header>
124
59
  </AlertHead>
125
- {hasDismissListener ? (
126
- <AlertCloseButton data-testid="alert-close-button" onClick={handleDismiss}>
60
+ {hasDismissListener && (
61
+ <AlertCloseButton data-testid="alert-close-button" onClick={onDismiss}>
127
62
  <NsIcon name="close" size="small" />
128
63
  </AlertCloseButton>
129
- ) : null}
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
- threshhold?: number; // TODO: update to threshold, i.e. remove extra h
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
- 'min-height'?: string;
34
- 'content-column-start'?: string;
35
- 'content-column-end'?: string;
36
- 'bar-position'?: string;
37
- 'foot-visibility'?: string;
38
- 'foot-display'?: string;
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('Sign In');
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
- 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,26 +7,50 @@ 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
15
  renderOptions={OptionUiComponent}
16
+ inputValue=""
17
+ value=""
18
+ />
19
+ );
20
+ const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
21
+ expect(autocompleteInput).toBeInTheDocument();
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=""
17
35
  />
18
36
  );
19
37
  const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
20
38
  expect(autocompleteInput).toBeInTheDocument();
39
+ act(() => {
40
+ userEvent.type(autocompleteInput, 'GS Facial');
41
+ });
42
+ expect(onChangeMock).toHaveBeenCalled();
21
43
  });
22
44
 
23
- test('displays options when typing in the input', async () => {
45
+ it('3. displays options when typing in the input', async () => {
24
46
  render(
25
47
  <NsAutocomplete
26
48
  options={AutocompleteMockList}
27
49
  onSelect={() => {}}
28
50
  renderOptions={OptionUiComponent}
29
51
  placeholder="autocomplete-input"
52
+ inputValue=""
53
+ value=""
30
54
  />
31
55
  );
32
56
  const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
@@ -42,12 +66,16 @@ describe('CustomAutocomplete', () => {
42
66
  );
43
67
  }, 40000);
44
68
 
45
- 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();
46
71
  render(
47
72
  <NsAutocomplete
73
+ onSelect={onSelectMock}
48
74
  options={AutocompleteMockList}
49
75
  renderOptions={OptionUiComponent}
50
76
  placeholder="autocomplete-input"
77
+ inputValue=""
78
+ value=""
51
79
  />
52
80
  );
53
81
  const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
@@ -58,9 +86,10 @@ describe('CustomAutocomplete', () => {
58
86
  const optionLi = screen.getByText('GS Facial Spa');
59
87
  userEvent.click(optionLi);
60
88
  });
89
+ expect(onSelectMock).toHaveBeenCalled();
61
90
  }, 40000);
62
91
 
63
- test('should load more options when clicking load more button', async () => {
92
+ it('5. should load more options when clicking load more button', async () => {
64
93
  render(
65
94
  <NsAutocomplete
66
95
  options={AutocompleteMockList}
@@ -71,6 +100,8 @@ describe('CustomAutocomplete', () => {
71
100
  show: true,
72
101
  onSeeMore: () => ({}),
73
102
  }}
103
+ inputValue=""
104
+ value=""
74
105
  />
75
106
  );
76
107
  const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
@@ -83,7 +114,7 @@ describe('CustomAutocomplete', () => {
83
114
  });
84
115
  }, 40000);
85
116
 
86
- test('should clear options when clicking clear button', async () => {
117
+ it('6. should clear options when clicking clear button', async () => {
87
118
  render(
88
119
  <NsAutocomplete
89
120
  options={AutocompleteMockList}
@@ -94,6 +125,8 @@ describe('CustomAutocomplete', () => {
94
125
  show: true,
95
126
  onClear: () => ({}),
96
127
  }}
128
+ inputValue=""
129
+ value=""
97
130
  />
98
131
  );
99
132
  const autocompleteInput = screen.getByPlaceholderText('autocomplete-input');
@@ -4,22 +4,23 @@
4
4
  * @description NsAutocomplete component.
5
5
  */
6
6
 
7
- import React from 'react';
7
+ import React, { useState } from 'react';
8
8
  import type { StoryObj, Meta } from '@storybook/react';
9
9
 
10
10
  import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
11
11
  import { NsAutocomplete } from './';
12
12
  import { AutocompleteMockList } from './mock/MockData';
13
13
  import { OptionUiComponent } from './mock/MockUI';
14
- import { NsAutocompleteProps } from './types.d';
14
+ import { NsAutocompleteProps, OptionsProps } from './types.d';
15
15
  import { NsIcon } from '../icon';
16
+ import { IconButton } from '@mui/material';
16
17
 
17
18
  type ComponentType = typeof NsAutocomplete;
18
19
  type Story = StoryObj<ComponentType>;
19
20
  type MetaStory = Meta<ComponentType>;
20
21
 
21
22
  export default {
22
- title: 'Components/NsAutocomplete',
23
+ title: 'Components/Inputs/NsAutocomplete',
23
24
  component: NsAutocomplete,
24
25
  decorators: [withCenterDecorator],
25
26
  argTypes: {
@@ -31,24 +32,61 @@ export default {
31
32
  options: AutocompleteMockList,
32
33
  seeMore: {
33
34
  text: 'See More Results',
34
- show: false,
35
+ show: true,
35
36
  onSeeMore: () => {
36
37
  throw new Error('Function not implemented.');
37
38
  },
38
39
  },
39
40
  clear: {
40
41
  text: 'Clear Recent Searches',
41
- show: false,
42
+ show: true,
42
43
  onClear: () => {
43
44
  throw new Error('Function not implemented.');
44
45
  },
45
46
  },
46
47
  iconStart: <NsIcon name="search" />,
47
- iconEnd: <NsIcon name="close" size='small'/>,
48
+ iconEnd: <NsIcon name="close" size="small" />,
48
49
  placeholder: 'search',
49
50
  },
50
51
  } as MetaStory;
51
52
 
53
+ const RenderAutocomplete = (args: NsAutocompleteProps) => {
54
+ const [selectedOption, setSelectedOption] = useState<string | OptionsProps | null>(null);
55
+ const [searchTerms, setSearchTerms] = useState<string>('');
56
+
57
+ const onSelect = (value: string | OptionsProps | null) => {
58
+ setSelectedOption(value);
59
+ setSearchTerms((value = ''));
60
+ };
61
+
62
+ const handleInputChange = (newValue: string) => {
63
+ setSearchTerms(newValue);
64
+ };
65
+
66
+ const onInputClear = () => {
67
+ setSelectedOption(null);
68
+ setSearchTerms('');
69
+ };
70
+
71
+ return (
72
+ <div>
73
+ <NsAutocomplete
74
+ {...args}
75
+ onChange={handleInputChange}
76
+ onSelect={onSelect}
77
+ renderOptions={OptionUiComponent}
78
+ iconEnd={
79
+ <IconButton onClick={onInputClear}>
80
+ <NsIcon name="close" size="small" />
81
+ </IconButton>
82
+ }
83
+ value={selectedOption}
84
+ inputValue={searchTerms}
85
+ />
86
+ </div>
87
+ );
88
+ };
89
+
52
90
  export const Autocomplete: Story = {
53
- render: (args: NsAutocompleteProps) => <NsAutocomplete {...args} renderOptions={OptionUiComponent} />,
91
+ render: RenderAutocomplete,
54
92
  };