@nuskin/foundation-ui-components 1.1.1 → 1.3.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 (44) hide show
  1. package/package.json +5 -5
  2. package/src/common/auth-dropdown/NsAuthDropdown.tsx +16 -7
  3. package/src/common/autocomplete/NsAutocomplete.spec.tsx +3 -3
  4. package/src/common/autocomplete/mock/MockData.ts +1 -0
  5. package/src/common/button/NsButton.stories.tsx +0 -1
  6. package/src/common/card/styled/NsFfgStyled.tsx +0 -1
  7. package/src/common/carousal/NsCarousal.tsx +9 -3
  8. package/src/common/cart-icon/NsCartIcon.spec.tsx +5 -4
  9. package/src/common/cart-icon/NsCartIcon.stories.tsx +1 -1
  10. package/src/common/cart-icon/NsCartIcon.tsx +1 -1
  11. package/src/common/cart-icon/types.d.ts +2 -2
  12. package/src/common/checkbox/types.d.ts +1 -1
  13. package/src/common/dropdown/NsDropdown.tsx +17 -8
  14. package/src/common/hooks/useOnClickOutside.tsx +28 -0
  15. package/src/common/icon/NsIcon.spec.tsx +11 -5
  16. package/src/common/icon/NsIconVariants.ts +3 -1
  17. package/src/common/icon/icons/AlertErrorIcon.tsx +2 -1
  18. package/src/common/icon/icons/ArrowLeft.tsx +2 -2
  19. package/src/common/icon/icons/ArrowRight.tsx +2 -2
  20. package/src/common/icon/icons/CartBadge.tsx +2 -2
  21. package/src/common/icon/icons/Check.tsx +25 -0
  22. package/src/common/icon/icons/CloseIcon.tsx +1 -1
  23. package/src/common/icon/icons/ErrorIcon.tsx +3 -2
  24. package/src/common/icon/icons/SearchIcon.tsx +3 -2
  25. package/src/common/icon/icons/SuccessIcon.tsx +7 -1
  26. package/src/common/icon/icons/TrashIcon.tsx +2 -1
  27. package/src/common/icon/icons/UserOutline.tsx +4 -1
  28. package/src/common/icon/icons/WarningIcon.tsx +3 -2
  29. package/src/common/index.ts +8 -4
  30. package/src/common/label/NsLabel.stories.tsx +0 -3
  31. package/src/common/label/NsLabel.tsx +1 -1
  32. package/src/common/progress/NsProgress.spec.tsx +47 -0
  33. package/src/common/progress/NsProgress.stories.tsx +37 -0
  34. package/src/common/progress/NsProgress.styled.tsx +39 -0
  35. package/src/common/progress/NsProgress.tsx +27 -0
  36. package/src/common/progress/index.ts +2 -0
  37. package/src/common/progress/types.d.ts +21 -0
  38. package/src/common/radiogroup/NsRadioGroup.styled.tsx +4 -3
  39. package/src/common/select/NsSelect.spec.tsx +98 -17
  40. package/src/common/select/NsSelect.styled.tsx +52 -22
  41. package/src/common/select/NsSelect.tsx +109 -44
  42. package/src/common/select/NsSelectOption.tsx +0 -1
  43. package/src/common/select/stories/NsSelect.stories.tsx +23 -14
  44. package/src/common/select/types.d.ts +3 -1
@@ -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'] },
@@ -26,6 +29,7 @@ export default {
26
29
  validateOnBlur: { control: 'boolean' },
27
30
  autoComplete: { control: 'text' },
28
31
  rules: { control: 'array' },
32
+ hasArrows: { control: 'boolean' },
29
33
  },
30
34
  parameters: {
31
35
  options: [
@@ -100,17 +104,16 @@ export default {
100
104
  validateOnBlur: false,
101
105
  autoComplete: '',
102
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
+ };
@@ -22,7 +22,9 @@ export interface SelectProps {
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 {