@nuskin/foundation-ui-components 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/package.json +18 -14
  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 +31 -13
  17. package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +5 -1
  18. package/src/common/accordion/stories/NsAccordionItem.stories.tsx +8 -3
  19. package/src/common/accordion/stories/NsCollapse.stories.tsx +19 -4
  20. package/src/common/accordion/stories/NsToggleSection.stories.tsx +6 -3
  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 +1 -1
  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.styled.ts +9 -2
  31. package/src/common/auth-dropdown/NsAuthDropdown.tsx +11 -6
  32. package/src/common/autocomplete/NsAutocomplete.spec.tsx +37 -14
  33. package/src/common/autocomplete/NsAutocomplete.stories.tsx +18 -2
  34. package/src/common/autocomplete/NsAutocomplete.tsx +63 -24
  35. package/src/common/autocomplete/mock/MockData.ts +51 -7
  36. package/src/common/autocomplete/mock/MockUI.tsx +2 -1
  37. package/src/common/autocomplete/types.d.ts +7 -4
  38. package/src/common/banner/NsBanner.spec.tsx +12 -3
  39. package/src/common/brand-components/NsBrandComponent.tsx +2 -2
  40. package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +8 -3
  41. package/src/common/breadcrumb/data.ts +8 -5
  42. package/src/common/button/NsButton.spec.tsx +65 -49
  43. package/src/common/button/NsButton.stories.tsx +39 -4
  44. package/src/common/button/NsButton.styled.tsx +49 -66
  45. package/src/common/button/NsButton.tsx +1 -2
  46. package/src/common/button/helper.ts +82 -0
  47. package/src/common/card/NsFfgCard.tsx +13 -7
  48. package/src/common/card/specs/NsFfgCard.spec.tsx +18 -8
  49. package/src/common/card/stories/NsFfgCard.stories.tsx +47 -14
  50. package/src/common/card/styled/NsFfgStyled.tsx +9 -7
  51. package/src/common/card/types.d.ts +7 -4
  52. package/src/common/cart-icon/NsCartIcon.spec.tsx +7 -18
  53. package/src/common/cart-icon/NsCartIcon.stories.tsx +1 -1
  54. package/src/common/cart-icon/NsCartIcon.styled.ts +7 -33
  55. package/src/common/cart-icon/NsCartIcon.tsx +5 -14
  56. package/src/common/cart-icon/types.d.ts +1 -0
  57. package/src/common/checkbox/NsCheckbox.styled.tsx +15 -16
  58. package/src/common/checkbox/NsCheckbox.tsx +1 -1
  59. package/src/common/checkbox/types.d.ts +1 -1
  60. package/src/common/column-control/styled/common.ts +1 -1
  61. package/src/common/column-control/utils.ts +4 -4
  62. package/src/common/common.types.d.ts +14 -5
  63. package/src/common/dropdown/NsDropdown.styled.tsx +23 -18
  64. package/src/common/dropdown/NsDropdown.tsx +7 -2
  65. package/src/common/hooks/index.ts +2 -0
  66. package/src/common/hooks/specs/useWindowSize.spec.tsx +19 -0
  67. package/src/common/hooks/useAccordionMap.tsx +39 -0
  68. package/src/common/hooks/useOnClickOutside.tsx +25 -0
  69. package/src/common/hooks/useWindowSize.tsx +30 -0
  70. package/src/common/icon/NsIcon.spec.tsx +1 -1
  71. package/src/common/icon/NsIcon.stories.tsx +3 -0
  72. package/src/common/icon/NsIcon.tsx +6 -2
  73. package/src/common/icon/NsIconVariants.ts +3 -0
  74. package/src/common/icon/icons/AlertErrorIcon.tsx +2 -2
  75. package/src/common/icon/icons/ArrowLeft.tsx +3 -6
  76. package/src/common/icon/icons/ArrowRight.tsx +6 -8
  77. package/src/common/icon/icons/Cart.tsx +7 -32
  78. package/src/common/icon/icons/CartBadge.tsx +2 -2
  79. package/src/common/icon/icons/CloseIcon.tsx +1 -1
  80. package/src/common/icon/icons/ErrorIcon.tsx +2 -2
  81. package/src/common/icon/icons/HamburgerIcon.tsx +5 -2
  82. package/src/common/icon/icons/SearchIcon.tsx +1 -1
  83. package/src/common/icon/icons/SuccessIcon.tsx +2 -7
  84. package/src/common/icon/icons/TrashIcon.tsx +1 -1
  85. package/src/common/icon/icons/UserIcon.tsx +3 -6
  86. package/src/common/icon/icons/UserOutline.tsx +24 -0
  87. package/src/common/icon/icons/WarningIcon.tsx +1 -1
  88. package/src/common/icon/icons/home.tsx +1 -1
  89. package/src/common/image/NsImage.spec.tsx +56 -6
  90. package/src/common/image/NsImage.tsx +23 -11
  91. package/src/common/index.ts +7 -3
  92. package/src/common/input/InputWrapper.tsx +12 -7
  93. package/src/common/input/NsInputField.tsx +1 -1
  94. package/src/common/label/NsLabel.stories.tsx +0 -3
  95. package/src/common/label/NsLabel.tsx +1 -1
  96. package/src/common/modal/NsModal.spec.tsx +46 -0
  97. package/src/common/modal/NsModal.styled.tsx +28 -0
  98. package/src/common/modal/NsModal.tsx +37 -0
  99. package/src/common/modal/helper-component/NsModalContentBox.tsx +32 -0
  100. package/src/common/modal/helper-component/NsModalDefault.tsx +25 -0
  101. package/src/common/modal/helper-component/NsModalStatus.tsx +15 -0
  102. package/src/common/modal/helper-component/NsModalStatusIcon.tsx +14 -0
  103. package/src/common/modal/helper-component/NsModalTitle.tsx +26 -0
  104. package/src/common/modal/index.ts +2 -0
  105. package/src/common/modal/model/NsModalVariantMap.tsx +24 -0
  106. package/src/common/modal/stories/NsModal.stories.tsx +85 -0
  107. package/src/common/modal/stories/mock.tsx +46 -0
  108. package/src/common/modal/types.ts +56 -0
  109. package/src/common/nav-tab/NavDrawer.tsx +34 -0
  110. package/src/common/nav-tab/NavTitles.tsx +52 -0
  111. package/src/common/nav-tab/NsNavTab.spec.tsx +4 -5
  112. package/src/common/nav-tab/NsNavTab.stories.tsx +3 -3
  113. package/src/common/nav-tab/NsNavTab.styled.tsx +13 -7
  114. package/src/common/nav-tab/NsNavTab.tsx +21 -75
  115. package/src/common/nav-tab/index.ts +1 -1
  116. package/src/common/nav-tab/types.d.ts +3 -3
  117. package/src/common/progress/NsProgress.spec.tsx +47 -0
  118. package/src/common/progress/NsProgress.stories.tsx +37 -0
  119. package/src/common/progress/NsProgress.styled.tsx +39 -0
  120. package/src/common/progress/NsProgress.tsx +27 -0
  121. package/src/common/progress/index.ts +2 -0
  122. package/src/common/progress/types.d.ts +21 -0
  123. package/src/common/radiogroup/NsRadioGroup.spec.tsx +24 -25
  124. package/src/common/radiogroup/NsRadioGroup.stories.tsx +13 -31
  125. package/src/common/radiogroup/NsRadioGroup.styled.tsx +17 -15
  126. package/src/common/radiogroup/NsRadioGroup.tsx +6 -5
  127. package/src/common/search-bar/NsSearchBar.tsx +1 -3
  128. package/src/common/select/NsSelect.spec.tsx +107 -33
  129. package/src/common/select/NsSelect.styled.tsx +89 -39
  130. package/src/common/select/NsSelect.tsx +109 -44
  131. package/src/common/select/NsSelectOption.tsx +0 -1
  132. package/src/common/select/stories/NsSelect.stories.tsx +33 -24
  133. package/src/common/select/types.d.ts +6 -4
  134. package/src/common/spinner/NsSpinner.spec.tsx +21 -0
  135. package/src/common/spinner/NsSpinner.stories.tsx +26 -0
  136. package/src/common/spinner/NsSpinner.styled.tsx +344 -0
  137. package/src/common/spinner/NsSpinner.tsx +14 -0
  138. package/src/common/spinner/icons/Spinner.tsx +161 -0
  139. package/src/common/spinner/index.ts +2 -0
  140. package/src/common/spinner/types.ts +5 -0
  141. package/src/common/table/NsTable.tsx +32 -49
  142. package/src/common/table/NsTableHeader.tsx +30 -51
  143. package/src/common/util.ts +1 -0
  144. package/src/common/utility/Show.tsx +13 -0
  145. package/src/common/utility/index.ts +1 -0
  146. package/src/common/videos/types.d.ts +1 -1
  147. /package/src/common/brand-components/{NsBrandContanier.styled.tsx → NsBrandContainer.styled.tsx} +0 -0
@@ -1,10 +1,27 @@
1
+ import { HTMLAttributes } from 'react';
1
2
  import { styled, ColorUtils, FoundationTheme } from '@nuskin/foundation-theme';
3
+ import { Themed } from '../common.types';
4
+ import { getDropdownHeadBorder } from '../dropdown/NsDropdown.styled';
2
5
  const { color, paletteColor, isDarkMode } = ColorUtils;
3
6
 
4
- export const SelectContainer = styled.div<{ disabled: boolean }>`
7
+ interface SelectLabelProps extends HTMLAttributes<HTMLLabelElement> {
8
+ errorMessage?: string;
9
+ }
10
+
11
+ export const SelectContainer = styled.div<{ disabled: boolean; errorMessage?: string }>`
5
12
  position: relative;
6
13
  display: inline-block;
7
14
  opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
15
+ color: ${({ theme, errorMessage }) =>
16
+ errorMessage ? paletteColor('error')({ theme }) : paletteColor('primary')({ theme })};
17
+ `;
18
+ export const SelectMain = styled.div`
19
+ position: relative;
20
+ display: flex;
21
+ `;
22
+
23
+ export const ErrorContainer = styled.div`
24
+ color: ${paletteColor('error')};
8
25
  `;
9
26
 
10
27
  export const SelectHead = styled.div<{ type: string; disabled: boolean }>`
@@ -13,8 +30,7 @@ export const SelectHead = styled.div<{ type: string; disabled: boolean }>`
13
30
  display: flex;
14
31
  justify-content: space-between;
15
32
  align-items: center;
16
- border: ${({ type, theme }) =>
17
- type === 'outlined' ? `1px solid ${color('primary', 'black')({ theme })}` : 'none'};
33
+ border: ${getDropdownHeadBorder}
18
34
  color: ${paletteColor('primary')};
19
35
  width: 207px;
20
36
  position: relative;
@@ -25,6 +41,18 @@ export const SelectButton = styled.div`
25
41
  padding-right: 10px;
26
42
  `;
27
43
 
44
+ export const LeftArrowContainer = styled.div`
45
+ display: flex;
46
+ align-items: center;
47
+ padding-top: 30px;
48
+ `;
49
+
50
+ export const RightArrowContainer = styled.div`
51
+ display: flex;
52
+ align-items: center;
53
+ padding-top: 30px;
54
+ `;
55
+
28
56
  export const SelectList = styled.div<{ borderColor: FoundationTheme.PaletteColors }>`
29
57
  position: absolute;
30
58
  z-index: 100;
@@ -32,54 +60,76 @@ export const SelectList = styled.div<{ borderColor: FoundationTheme.PaletteColor
32
60
  padding: 0;
33
61
  margin: 0;
34
62
  width: -webkit-fill-available;
35
- color: ${paletteColor('primary')};
63
+ color: ${paletteColor('primary', 'main')};
36
64
  background-color: ${paletteColor('primary', 'contrastText')};
65
+ max-height: 200px;
66
+ overflow-y: auto;
37
67
  `;
38
68
 
39
- const getDividerColor = (props: { theme: FoundationTheme.Theme}) => {
40
- const dividerColor = !isDarkMode(props) ? color('neutral', '30')(props) : color('neutral', '20')(props);
41
- return dividerColor;
42
- };
43
-
44
- const getListBgColor = (props: { theme: FoundationTheme.Theme }) => {
45
- const bgColor = !isDarkMode(props) ? color('accentColorB', '10')(props) : color('accentColorB', '50')(props);
46
- return bgColor;
69
+ const getDividerColor = ({ theme }: Themed) =>
70
+ isDarkMode({ theme }) ? color('neutral', '20')({ theme }) : color('neutral', '30')({ theme });
71
+ const getListBgColor = (props: Themed) => {
72
+ return !isDarkMode(props) ? color('accentColorB', '10')(props) : color('accentColorB', '50')(props);
47
73
  };
74
+ type ContainerParam = { borderBottom: string; bgColor: string; color: string; hoveredBg: string };
48
75
 
49
76
  export const ListItemContainer = styled.div<{ showListDivider: boolean; isSelected: boolean }>`
50
- ${({ showListDivider, isSelected, theme }) => `
51
- cursor: pointer;
52
- padding: 5px 0;
53
- border-bottom: 1px solid ${showListDivider ? getDividerColor({ theme }) : 'none'};
54
- background-color: ${isSelected ? paletteColor('primary')({ theme }) : 'white'};
55
- color: ${isSelected ? 'white' : 'black'};
56
-
57
- &:hover {
58
- background-color: ${getListBgColor({ theme })};
59
- }
60
-
61
- &:last-child {
62
- border-bottom: none;
63
- }
64
- `}
77
+ ${({ showListDivider, isSelected, theme }) => {
78
+ const borderBottom = showListDivider ? getDividerColor({ theme }) : 'none';
79
+ const bgColor = isSelected ? paletteColor('primary')({ theme }) : 'white';
80
+ const color = isSelected ? 'white' : 'black';
81
+ const hoveredBg = getListBgColor({ theme });
82
+ return createListContainerStyle({ borderBottom, bgColor, color, hoveredBg });
83
+ }}
65
84
  `;
85
+ function createListContainerStyle({ borderBottom, bgColor, color, hoveredBg }: ContainerParam) {
86
+ return `
87
+ cursor: pointer;
88
+ padding: 5px 0;
89
+ border-bottom: 1px solid ${borderBottom};
90
+ background-color: ${bgColor};
91
+ color: ${color};
66
92
 
67
- export const ListItem = styled.div<{ isSelected: boolean}>`
68
- padding: 5px 10px;
69
- border-left: ${({ isSelected, theme }) =>
70
- isSelected ? `2px solid ${paletteColor('primary')({ theme })}` : 'none'};
71
- background-color: ${({ isSelected, theme }) =>
72
- isSelected ? paletteColor('primary', 'main')({ theme }) : 'white'};
73
- color: ${({ isSelected }) => (isSelected ? 'white' : 'black')};
74
93
  &:hover {
75
- background-color: ${({ isSelected, theme }) =>
76
- isSelected ? paletteColor('primary')({ theme }) : getListBgColor({ theme })};
77
- font-weight: bold;
94
+ background-color: ${hoveredBg};
95
+ }
96
+
97
+ &:last-child {
98
+ border-bottom: none;
78
99
  }
79
100
  `;
101
+ }
80
102
 
81
- export const SelectLabel = styled.label`
103
+ const getListItemBgColor = ({ isSelected, theme }: { isSelected: boolean } & Themed) => {
104
+ if (isSelected) {
105
+ return paletteColor('primary')({ theme });
106
+ }
107
+ return getListBgColor({ theme });
108
+ };
109
+
110
+ const getListItemBorderLeftColor = ({ isSelected, theme }: { isSelected: boolean } & Themed) => {
111
+ if (isSelected) {
112
+ const color = paletteColor('primary')({ theme });
113
+ return `2px solid ${color}`;
114
+ }
115
+ return 'none';
116
+ };
117
+
118
+ export const SelectLabel = styled.label<SelectLabelProps>`
82
119
  display: block;
83
120
  margin-bottom: 5px;
84
- color: ${paletteColor('primary')} !important;
121
+ color: ${({ theme, errorMessage }) =>
122
+ errorMessage ? paletteColor('error')({ theme }) : paletteColor('primary')({ theme })};
123
+ }
85
124
  `;
125
+
126
+ export const ListItem = styled.div<{ isSelected: boolean }>(({ isSelected, theme }) => ({
127
+ padding: '5px 10px',
128
+ borderLeft: getListItemBorderLeftColor({ isSelected, theme }),
129
+ backgroundColor: isSelected ? paletteColor('primary', 'main')({ theme }) : 'white',
130
+ color: isSelected ? 'white' : 'black',
131
+ '&:hover': {
132
+ backgroundColor: getListItemBgColor({ isSelected, theme }),
133
+ fontWeight: 'bold',
134
+ },
135
+ }));
@@ -1,8 +1,17 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import { SelectContainer, SelectButton, SelectList, SelectHead, SelectLabel } from './NsSelect.styled';
1
+ import React, { useState, useRef, useEffect, Children } from 'react';
2
+ import {
3
+ SelectContainer,
4
+ SelectButton,
5
+ SelectList,
6
+ SelectHead,
7
+ SelectLabel,
8
+ SelectMain,
9
+ LeftArrowContainer,
10
+ RightArrowContainer,
11
+ ErrorContainer,
12
+ } from './NsSelect.styled';
3
13
  import { NsIcon } from '../icon';
4
14
  import { SelectProps } from './types.d';
5
-
6
15
  const NsSelect: React.FC<SelectProps> = ({
7
16
  selectedValue,
8
17
  selectedLabel,
@@ -17,9 +26,14 @@ const NsSelect: React.FC<SelectProps> = ({
17
26
  label,
18
27
  readOnly = false,
19
28
  errorMessage,
29
+ hasArrows,
30
+ // eslint-disable-next-line no-unused-vars
31
+ rules,
32
+ reverseArrowControls,
20
33
  }) => {
21
34
  const [isOpen, setIsOpen] = useState(false);
22
35
  const [value, setValue] = useState(selectedValue || '');
36
+ const [selectedIndex, setSelectedIndex] = useState(-1);
23
37
  const selectContainerRef = useRef<HTMLDivElement>(null);
24
38
  const selectHeadRef = useRef<HTMLDivElement>(null);
25
39
 
@@ -29,19 +43,16 @@ const NsSelect: React.FC<SelectProps> = ({
29
43
  }
30
44
  };
31
45
 
32
- const handleSelect = (input: string) => {
46
+ const handleSelect = (input: string, index: number) => {
33
47
  onSelect(input);
34
48
  setIsOpen(false);
35
49
  setValue(input);
50
+ setSelectedIndex(index);
36
51
  };
37
52
 
38
53
  // eslint-disable-next-line react-hooks/exhaustive-deps
39
54
  const handleClickOutside = (event: MouseEvent) => {
40
- if (
41
- selectContainerRef.current &&
42
- !selectContainerRef.current.contains(event.target as Node) &&
43
- isOpen
44
- ) {
55
+ if (selectContainerRef.current && !selectContainerRef.current.contains(event.target as Node) && isOpen) {
45
56
  setIsOpen(false);
46
57
  }
47
58
  };
@@ -55,6 +66,40 @@ const NsSelect: React.FC<SelectProps> = ({
55
66
  }, 0);
56
67
  }
57
68
  };
69
+ const handleArrowRightClick = () => {
70
+ if (selectedIndex !== -1) {
71
+ const nextIndex = reverseArrowControls ? selectedIndex - 1 : selectedIndex + 1;
72
+ const childrenArray = Children.toArray(children);
73
+ const optionAtIndex = childrenArray[nextIndex];
74
+ const { value } = (optionAtIndex as any)?.props ?? {};
75
+ handleSelect(value, nextIndex);
76
+ } else {
77
+ if (!reverseArrowControls) {
78
+ const nextIndex = selectedIndex + 1;
79
+ const childrenArray = Children.toArray(children);
80
+ const optionAtIndex = childrenArray[nextIndex];
81
+ const { value } = (optionAtIndex as any)?.props ?? {};
82
+ handleSelect(value, nextIndex);
83
+ }
84
+ }
85
+ };
86
+ const handleArrowLeftClick = () => {
87
+ if (selectedIndex !== -1) {
88
+ const nextIndex = reverseArrowControls ? selectedIndex + 1 : selectedIndex - 1;
89
+ const childrenArray = Children.toArray(children);
90
+ const optionAtIndex = childrenArray[nextIndex];
91
+ const { value } = (optionAtIndex as any)?.props ?? {};
92
+ handleSelect(value, nextIndex);
93
+ } else {
94
+ if (reverseArrowControls) {
95
+ const nextIndex = selectedIndex + 1;
96
+ const childrenArray = Children.toArray(children);
97
+ const optionAtIndex = childrenArray[nextIndex];
98
+ const { value } = (optionAtIndex as any)?.props ?? {};
99
+ handleSelect(value, nextIndex);
100
+ }
101
+ }
102
+ };
58
103
 
59
104
  useEffect(() => {
60
105
  document.addEventListener('click', handleClickOutside);
@@ -64,53 +109,73 @@ const NsSelect: React.FC<SelectProps> = ({
64
109
  }, [handleClickOutside, isOpen]);
65
110
 
66
111
  return (
67
- <SelectContainer
68
- data-testid="select-container"
69
- disabled={disabled || readOnly}
70
- ref={selectContainerRef}
71
- style={{
72
- borderColor: errorMessage ? 'red' : undefined,
73
- color: errorMessage ? 'red' : 'inherit',
74
- }}
75
- >
76
- {label && <SelectLabel style={{ color: errorMessage ? 'red' : 'inherit' }}>{label}</SelectLabel>}
77
- <SelectHead
78
- data-testid="select-head"
112
+ <SelectMain>
113
+ {hasArrows && (
114
+ <LeftArrowContainer
115
+ onClick={handleArrowLeftClick}
116
+ className="LeftArrow"
117
+ style={{
118
+ visibility:
119
+ !reverseArrowControls && (selectedIndex === 0 || selectedIndex < 0) ? 'hidden' : 'visible',
120
+ }}
121
+ >
122
+ <NsIcon name="arrowLeft" color="primary" />
123
+ </LeftArrowContainer>
124
+ )}
125
+ <SelectContainer
126
+ data-testid="select-container"
79
127
  disabled={disabled || readOnly}
80
- type={type}
81
- onMouseDown={handleMouseClick}
82
- tabIndex={0}
83
- ref={selectHeadRef}
128
+ ref={selectContainerRef}
129
+ errorMessage={errorMessage}
84
130
  >
85
- <SelectButton>{selectedLabel || placeholder}</SelectButton>
86
- {!readOnly && (
87
- <NsIcon testId="chevron-icon" name={isOpen ? 'arrowUp' : 'arrowDown'} color="primary" />
88
- )}
89
- </SelectHead>
90
- {isOpen ? (
91
- <SelectList
92
- data-testid="select-list"
93
- onMouseLeave={closeSelect}
94
- borderColor={selectBorderColor}
95
- className='selectList'
131
+ {label && <SelectLabel errorMessage={errorMessage}>{label}</SelectLabel>}
132
+ <SelectHead
133
+ data-testid="select-head"
134
+ disabled={disabled || readOnly}
135
+ type={type}
136
+ onMouseDown={handleMouseClick}
137
+ tabIndex={0}
138
+ ref={selectHeadRef}
96
139
  >
97
- <div style={{ maxHeight: '200px', overflowY: 'auto' }}>
98
- {React.Children.map(children, (child) => {
140
+ <SelectButton>{selectedLabel || placeholder}</SelectButton>
141
+ {!readOnly && <NsIcon name={isOpen ? 'arrowUp' : 'arrowDown'} color="primary" />}
142
+ </SelectHead>
143
+ {isOpen ? (
144
+ <SelectList
145
+ data-testid="select-list"
146
+ onMouseLeave={closeSelect}
147
+ borderColor={selectBorderColor}
148
+ className="selectList"
149
+ >
150
+ {React.Children.map(children, (child, index) => {
99
151
  if (React.isValidElement(child)) {
100
152
  return React.cloneElement(child, {
101
153
  // @ts-ignore
102
- onSelect: handleSelect,
154
+ onSelect: (input: string) => handleSelect(input, index),
103
155
  selectedValue: value,
104
156
  showListDivider,
157
+ index,
105
158
  });
106
159
  }
107
160
  return null;
108
161
  })}
109
- </div>
110
- </SelectList>
111
- ) : null}
112
- {errorMessage && <div style={{ color: 'red' }}>{errorMessage}</div>}
113
- </SelectContainer>
162
+ </SelectList>
163
+ ) : null}
164
+ {errorMessage && <ErrorContainer>{errorMessage}</ErrorContainer>}
165
+ </SelectContainer>
166
+ {hasArrows && (
167
+ <RightArrowContainer
168
+ onClick={handleArrowRightClick}
169
+ className="RightArrow"
170
+ style={{
171
+ visibility:
172
+ reverseArrowControls && (selectedIndex === 0 || selectedIndex < 0) ? 'hidden' : 'visible',
173
+ }}
174
+ >
175
+ <NsIcon name="arrowRight" color="primary" />
176
+ </RightArrowContainer>
177
+ )}
178
+ </SelectMain>
114
179
  );
115
180
  };
116
181
 
@@ -24,7 +24,6 @@ export const NsSelectOption: React.FC<SelectOptionProps> = ({
24
24
  >
25
25
  <ListItem isSelected={value === selectedValue}>{label}</ListItem>
26
26
  </ListItemContainer>
27
-
28
27
  );
29
28
  };
30
29
  export default NsSelectOption;
@@ -1,13 +1,16 @@
1
1
  import React, { useState } from 'react';
2
- import { ComponentStory, ComponentMeta } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
3
  import NsSelect from '../NsSelect';
4
4
  import NsSelectOption from '../NsSelectOption';
5
+ import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
6
+
7
+ type ComponentType = typeof NsSelect;
8
+ type Story = StoryObj<ComponentType>;
9
+ type MetaStory = Meta<ComponentType>;
5
10
 
6
11
  export default {
7
12
  title: 'Components/Inputs/NsSelect',
8
- decorators: [
9
- (story) => <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>{story()}</div>,
10
- ],
13
+ decorators: [withCenterDecorator],
11
14
  component: NsSelect,
12
15
  argTypes: {
13
16
  type: { control: 'select', options: ['unfilled', 'outlined'] },
@@ -21,11 +24,12 @@ export default {
21
24
  showListDivider: { control: 'boolean' },
22
25
  label: { control: 'text' },
23
26
  name: { control: 'text' },
24
- menuProps:{ control: 'text' },
25
- reverseArrowControls:{ control: 'boolean' },
26
- validateOnBlur:{ control: 'boolean' },
27
+ menuProps: { control: 'text' },
28
+ reverseArrowControls: { control: 'boolean' },
29
+ validateOnBlur: { control: 'boolean' },
27
30
  autoComplete: { control: 'text' },
28
- rules: {control: 'array'},
31
+ rules: { control: 'array' },
32
+ hasArrows: { control: 'boolean' },
29
33
  },
30
34
  parameters: {
31
35
  options: [
@@ -91,26 +95,25 @@ export default {
91
95
  selectedValue: '',
92
96
  selectedLabel: '',
93
97
  label: 'State',
94
- placeholder:'',
95
- name:'',
96
- menuProps:'auto',
98
+ placeholder: '',
99
+ name: '',
100
+ menuProps: 'auto',
97
101
  reverseArrowControls: false,
98
- itemText:'Text',
99
- itemValue:'Value',
102
+ itemText: 'Text',
103
+ itemValue: 'Value',
100
104
  validateOnBlur: false,
101
105
  autoComplete: '',
102
- rules:[]
106
+ rules: [],
107
+ hasArrows: false,
103
108
  },
104
- } as ComponentMeta<typeof NsSelect>;
105
-
106
- export const Select: ComponentStory<typeof NsSelect> = (args, context) => {
107
- const [selectedLabel, setSelectedLabel] = useState(args.selectedLabel);
108
- const [selectedValue, setSelectedValue] = useState(args.selectedValue);
109
+ } as MetaStory;
109
110
 
110
- const options = context.parameters.options;
111
+ const AppSelect = (props: any) => {
112
+ const [selectedLabel, setSelectedLabel] = useState(props.selectedLabel);
113
+ const [selectedValue, setSelectedValue] = useState(props.selectedValue);
111
114
  const handleSelect = (value: string) => {
112
115
  setSelectedValue(value);
113
- options.find((option: any) => {
116
+ props.options.find((option: any) => {
114
117
  if (option.value === value) {
115
118
  setSelectedLabel(option.label);
116
119
  }
@@ -120,15 +123,21 @@ export const Select: ComponentStory<typeof NsSelect> = (args, context) => {
120
123
 
121
124
  return (
122
125
  <NsSelect
123
- {...args}
126
+ {...props}
124
127
  selectedValue={selectedValue}
125
128
  selectedLabel={selectedLabel}
126
129
  onSelect={handleSelect}
127
- label={args.label}
130
+ label={props.label}
128
131
  >
129
- {options.map((option: any) => (
132
+ {props.options.map((option: any) => (
130
133
  <NsSelectOption key={option.value} value={option.value} label={option.label} />
131
134
  ))}
132
135
  </NsSelect>
133
136
  );
134
137
  };
138
+ export const Select: Story = {
139
+ render: (args, context) => {
140
+ const options = context.parameters.options;
141
+ return <AppSelect options={options} {...args} />;
142
+ },
143
+ };
@@ -5,7 +5,7 @@ export interface SelectProps {
5
5
  selectedValue: string;
6
6
  selectedLabel: string;
7
7
  onSelect: (arg: string) => void; // eslint-disable-line
8
- children: ReactNode;
8
+ children?: ReactNode | null;
9
9
  disabled?: boolean;
10
10
  type?: 'outlined' | 'unfilled';
11
11
  placeholder?: string;
@@ -14,15 +14,17 @@ export interface SelectProps {
14
14
  showListDivider?: boolean;
15
15
  label?: string;
16
16
  readOnly?: boolean;
17
- name?: string;
17
+ name?: string;
18
18
  errorMessage?: string;
19
19
  menuProps?: string | object;
20
20
  reverseArrowControls?: boolean;
21
- itemText?: string;
21
+ itemText?: string;
22
22
  itemValue?: string;
23
23
  validateOnBlur?: boolean;
24
24
  autoComplete?: string;
25
- rules?: any[];
25
+ // eslint-disable-next-line no-undef
26
+ rules?: Array;
27
+ hasArrows?: boolean;
26
28
  }
27
29
 
28
30
  export interface SelectOptionProps {
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@/test-utils/index';
3
+ import NsSpinner from './NsSpinner';
4
+
5
+ describe('NsSpinner', () => {
6
+ test('1.renders spinner', () => {
7
+ const { container } = render(<NsSpinner additionalProps={{ 'data-testid': 'spinner' }} />);
8
+
9
+ expect(screen.getByTestId('spinner')).toBeInTheDocument();
10
+ expect(container.getElementsByTagName('svg')).toHaveLength(1);
11
+ expect(screen.getByTestId('spinner')).toHaveStyle('width:100px');
12
+ });
13
+
14
+ test('2.renders spinner with different size', () => {
15
+ render(<NsSpinner size={200} additionalProps={{ 'data-testid': 'spinner' }} />);
16
+
17
+ expect(screen.getByTestId('spinner')).toBeInTheDocument();
18
+ expect(screen.getByTestId('spinner')).toHaveStyle('width:200px');
19
+ expect(screen.getByTestId('spinner')).toHaveStyle('height:200px');
20
+ });
21
+ });
@@ -0,0 +1,26 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import NsSpinner from './NsSpinner';
3
+ import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
4
+
5
+ type ComponentType = typeof NsSpinner;
6
+ type Story = StoryObj<ComponentType>;
7
+ type MetaStory = Meta<ComponentType>;
8
+
9
+ export default {
10
+ title: 'Components/Feedback/NsSpinner',
11
+ component: NsSpinner,
12
+ decorators: [
13
+ (Story) => (
14
+ <div className="flex place-content-center">
15
+ <Story />
16
+ </div>
17
+ ),
18
+ withCenterDecorator,
19
+ ],
20
+ argTypes: {},
21
+ args: {
22
+ size: 200,
23
+ },
24
+ } as MetaStory;
25
+
26
+ export const Spinner: Story = {};