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

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 (95) hide show
  1. package/package.json +9 -4
  2. package/src/common/accordion/NsAccordion.tsx +77 -0
  3. package/src/common/accordion/NsAccordionHeader.tsx +56 -0
  4. package/src/common/accordion/NsAccordionItem.tsx +39 -0
  5. package/src/common/accordion/NsCollapse.tsx +56 -0
  6. package/src/common/accordion/NsToggleSection.tsx +30 -0
  7. package/src/common/accordion/ToggleSection.styled.tsx +28 -0
  8. package/src/common/accordion/index.ts +6 -0
  9. package/src/common/accordion/specs/NsAccordion.spec.tsx +93 -0
  10. package/src/common/accordion/specs/NsAccordionHeader.spec.tsx +63 -0
  11. package/src/common/accordion/specs/NsAccordionItem.spec.tsx +62 -0
  12. package/src/common/accordion/specs/NsCollapse.spec.tsx +37 -0
  13. package/src/common/accordion/specs/NsToggleSection.spec.tsx +38 -0
  14. package/src/common/accordion/stories/NsAccordion.stories.tsx +47 -0
  15. package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +40 -0
  16. package/src/common/accordion/stories/NsAccordionItem.stories.tsx +40 -0
  17. package/src/common/accordion/stories/NsCollapse.stories.tsx +35 -0
  18. package/src/common/accordion/stories/NsToggleSection.stories.tsx +23 -0
  19. package/src/common/accordion/stories/data.tsx +22 -0
  20. package/src/common/accordion/types.d.ts +73 -0
  21. package/src/common/alert/NsAlert.spec.tsx +0 -1
  22. package/src/common/alert/NsAlert.styled.ts +10 -10
  23. package/src/common/alert/NsAlert.tsx +4 -3
  24. package/src/common/alert/types.d.ts +22 -22
  25. package/src/common/auth-dropdown/NsAuthDropdown.styled.ts +12 -11
  26. package/src/common/auth-dropdown/NsAuthDropdown.tsx +2 -1
  27. package/src/common/auth-dropdown/types.d.ts +3 -1
  28. package/src/common/autocomplete/NsAutocomplete.spec.tsx +20 -8
  29. package/src/common/autocomplete/NsAutocomplete.stories.tsx +3 -0
  30. package/src/common/autocomplete/NsAutocomplete.styled.tsx +12 -0
  31. package/src/common/autocomplete/NsAutocomplete.tsx +36 -18
  32. package/src/common/autocomplete/types.d.ts +7 -1
  33. package/src/common/brand-components/NsBrandComponent.tsx +3 -0
  34. package/src/common/breadcrumb/NsBreadcrumb.tsx +4 -2
  35. package/src/common/button/NsButton.spec.tsx +11 -12
  36. package/src/common/button/NsButton.stories.tsx +1 -1
  37. package/src/common/button/NsButton.styled.tsx +25 -23
  38. package/src/common/button/NsButton.tsx +9 -2
  39. package/src/common/button/types.d.ts +1 -0
  40. package/src/common/checkbox/NsCheckbox.styled.tsx +2 -2
  41. package/src/common/common.types.d.ts +2 -0
  42. package/src/common/dropdown/types.d.ts +3 -1
  43. package/src/common/email-simple/NsEmailSimple.spec.tsx +145 -0
  44. package/src/common/email-simple/NsEmailSimple.stories.tsx +61 -0
  45. package/src/common/email-simple/NsEmailSimple.tsx +71 -0
  46. package/src/common/email-simple/index.ts +2 -0
  47. package/src/common/email-simple/types.d.ts +14 -0
  48. package/src/common/flag/NsFlag.spec.tsx +4 -24
  49. package/src/common/flag/NsFlag.styled.tsx +5 -5
  50. package/src/common/flag/NsFlag.tsx +0 -2
  51. package/src/common/icon/types.d.ts +1 -0
  52. package/src/common/image/types.d.ts +1 -0
  53. package/src/common/index.ts +3 -0
  54. package/src/common/input/InputFieldWrapper.tsx +4 -4
  55. package/src/common/input/NsInputField.stories.tsx +11 -9
  56. package/src/common/input/NsInputField.tsx +12 -5
  57. package/src/common/input/getAdornmentProps.tsx +4 -0
  58. package/src/common/input/types.d.ts +3 -1
  59. package/src/common/label/NsLabel.stories.tsx +3 -0
  60. package/src/common/label/NsLabel.tsx +18 -17
  61. package/src/common/label/types.d.ts +2 -1
  62. package/src/common/nav-tab/NsNavTab.spec.tsx +0 -5
  63. package/src/common/nav-tab/NsNavTab.styled.tsx +7 -4
  64. package/src/common/radiogroup/NsRadioGroup.spec.tsx +64 -0
  65. package/src/common/radiogroup/NsRadioGroup.stories.tsx +102 -0
  66. package/src/common/radiogroup/NsRadioGroup.styled.tsx +50 -0
  67. package/src/common/radiogroup/NsRadioGroup.tsx +61 -0
  68. package/src/common/radiogroup/index.ts +2 -0
  69. package/src/common/radiogroup/types.d.ts +20 -0
  70. package/src/common/select/NsSelect.spec.tsx +200 -0
  71. package/src/common/select/NsSelect.styled.tsx +85 -0
  72. package/src/common/select/NsSelect.tsx +117 -0
  73. package/src/common/select/NsSelectOption.tsx +30 -0
  74. package/src/common/select/index.ts +3 -0
  75. package/src/common/select/stories/NsSelect.stories.tsx +134 -0
  76. package/src/common/select/types.d.ts +34 -0
  77. package/src/common/table/NsExpandableRow.tsx +68 -0
  78. package/src/common/table/NsTable.spec.tsx +69 -41
  79. package/src/common/table/NsTable.stories.tsx +45 -7
  80. package/src/common/table/NsTable.styled.ts +106 -12
  81. package/src/common/table/NsTable.tsx +78 -89
  82. package/src/common/table/NsTableHeader.tsx +52 -38
  83. package/src/common/table/NsTablePagination.tsx +21 -13
  84. package/src/common/table/types.d.ts +21 -8
  85. package/src/common/text-strip/NsTextStrip.tsx +10 -7
  86. package/src/common/typography/Typography.styled.tsx +3 -3
  87. package/src/common/validations/regex.ts +3 -0
  88. /package/src/common/card/{tests → specs}/BrandLogo.spec.tsx +0 -0
  89. /package/src/common/card/{tests → specs}/NsBgCard.spec.tsx +0 -0
  90. /package/src/common/card/{tests → specs}/NsBrandProductCard.spec.tsx +0 -0
  91. /package/src/common/card/{tests → specs}/NsFfgCard.spec.tsx +0 -0
  92. /package/src/common/carousal/{tests → specs}/Chevron.spec.tsx +0 -0
  93. /package/src/common/carousal/{tests → specs}/NsCarousal.spec.tsx +0 -0
  94. /package/src/common/carousal/{tests → specs}/NsScrollingCarousal.spec.tsx +0 -0
  95. /package/src/common/carousal/{tests → specs}/RenderBrand.spec.tsx +0 -0
@@ -1,20 +1,23 @@
1
- import * as React from 'react';
2
- import { NsInputField } from '../input';
1
+ import React, { useState, PropsWithChildren, useRef } from 'react';
3
2
  import { Autocomplete, AutocompleteRenderGroupParams, List, AutocompleteRenderInputParams } from '@mui/material';
4
- import { useState, PropsWithChildren, useRef } from 'react';
5
- import { NsTypography } from '../typography';
6
- import { NsAutocompleteProps, OptionsProps, ComponentboxProps } from './types.d';
7
- import { NsButton } from '../button';
3
+ import { NsTypography, NsButton } from '../';
4
+ import { NsAutocompleteProps, OptionsProps, ComponentBoxProps } from './types.d';
5
+ import { StyledTextField } from './NsAutocomplete.styled';
8
6
 
9
7
  const NsAutocomplete = ({
10
8
  id = 'custom_autocomplete_dropdown',
11
9
  options = [],
12
10
  renderOptions,
11
+ loading,
12
+ noOptionsText,
13
13
  onSelect,
14
+ onChange,
14
15
  clear,
15
16
  seeMore,
16
17
  placeholder,
17
18
  className,
19
+ iconStart,
20
+ iconEnd,
18
21
  }: PropsWithChildren<NsAutocompleteProps>) => {
19
22
  const inputRef = useRef(null);
20
23
 
@@ -27,10 +30,21 @@ const NsAutocomplete = ({
27
30
  };
28
31
 
29
32
  const renderInput = (params: AutocompleteRenderInputParams) => (
30
- <NsInputField {...params} placeholder={placeholder} inputRef={inputRef} />
33
+ <StyledTextField
34
+ {...params}
35
+ onChange={onChange}
36
+ placeholder={placeholder}
37
+ inputRef={inputRef}
38
+ InputProps={{
39
+ ...params.InputProps,
40
+ startAdornment: iconStart,
41
+ endAdornment: iconEnd,
42
+ }}
43
+ />
31
44
  );
32
45
 
33
- const renderOption = (props: ComponentboxProps, option: OptionsProps) => (
46
+ // TODO: @vijayprathap need to check if we can remove object spread of ...props and ...params in renderInput
47
+ const renderOption = (props: ComponentBoxProps, option: OptionsProps) => (
34
48
  <li {...props}>
35
49
  <OptionUI {...props} option={option} />
36
50
  </li>
@@ -43,7 +57,7 @@ const NsAutocomplete = ({
43
57
  const renderGroupName = ({ key, group, children }: AutocompleteRenderGroupParams) => (
44
58
  <li key={key}>
45
59
  {group ? (
46
- <NsTypography variant="p-s" className="p-3">
60
+ <NsTypography variant="p-m" weight="bold" className="p-3">
47
61
  {group}
48
62
  </NsTypography>
49
63
  ) : null}
@@ -59,7 +73,9 @@ const NsAutocomplete = ({
59
73
  return (
60
74
  <div className="text-right p-3">
61
75
  <NsButton onClick={onClear} data-testid="autocomplete-clear-id">
62
- <NsTypography variant={'p-xs'}>{text}</NsTypography>
76
+ <NsTypography variant={'p-xs'} weight="bold">
77
+ {text}
78
+ </NsTypography>
63
79
  </NsButton>
64
80
  </div>
65
81
  );
@@ -73,20 +89,20 @@ const NsAutocomplete = ({
73
89
  return (
74
90
  <div className="text-center p-3">
75
91
  <NsButton onClick={onSeeMore} data-testid="autocomplete-loadmore-id">
76
- <NsTypography variant="p-xs">{text}</NsTypography>
92
+ <NsTypography variant="p-xs" weight="bold">
93
+ {text}
94
+ </NsTypography>
77
95
  </NsButton>
78
96
  </div>
79
97
  );
80
98
  };
81
99
 
82
- const renderListBoxComponent = ({ children, ...otherProps }: ComponentboxProps) => (
83
- <>
100
+ const renderListBoxComponent = ({ children, ...otherProps }: ComponentBoxProps) => (
101
+ <List {...otherProps}>
84
102
  {renderClearButton()}
85
- <List {...otherProps}>
86
- {children}
87
- {renderSeeMoreButton()}
88
- </List>
89
- </>
103
+ {children}
104
+ {renderSeeMoreButton()}
105
+ </List>
90
106
  );
91
107
 
92
108
  return (
@@ -95,6 +111,8 @@ const NsAutocomplete = ({
95
111
  disablePortal
96
112
  className={className}
97
113
  options={options}
114
+ loading={loading}
115
+ noOptionsText={noOptionsText}
98
116
  groupBy={renderGroupBy}
99
117
  getOptionLabel={renderOptionLabel}
100
118
  renderOption={renderOption}
@@ -6,9 +6,13 @@ import { ComponentsProps } from '@mui/material';
6
6
  export type NsAutocompleteProps = {
7
7
  id?: string;
8
8
  options: OptionsProps[];
9
+ loading?:boolean;
9
10
  renderOptions: React.ComponentType<any>;
11
+ noOptionsText ?:string;
10
12
  // eslint-disable-next-line no-unused-vars
11
13
  onSelect?: (params: OptionsProps | null) => void;
14
+ // eslint-disable-next-line no-unused-vars
15
+ onChange?: (params) => void;
12
16
  clear?: {
13
17
  text: string;
14
18
  show?: boolean; //default to false
@@ -21,6 +25,8 @@ export type NsAutocompleteProps = {
21
25
  };
22
26
  placeholder?: string;
23
27
  className?: string;
28
+ iconStart?: string | React.ReactNode;
29
+ iconEnd?: string | React.ReactNode;
24
30
  } & AdditionalProps;
25
31
 
26
32
  export type ListUiProps = {
@@ -36,4 +42,4 @@ export type OptionsProps = {
36
42
  groupBgColor?: string;
37
43
  };
38
44
 
39
- export type ComponentboxProps = ComponentsProps & HTMLAttributes<HTMLElement>;
45
+ export type ComponentBoxProps = ComponentsProps & HTMLAttributes<HTMLElement>;
@@ -1,10 +1,12 @@
1
1
  import React, { PropsWithChildren, useCallback } from 'react';
2
+
2
3
  import NSLogoHorizontal from './icons/NSLogoHorizontal';
3
4
  import NSLogoMark from './icons/NSLogoMark';
4
5
  import NSLogoPoweredBy from './icons/NSLogoPoweredBy';
5
6
  import NSLogoStacked from './icons/NSLogoStacked';
6
7
  import NSLogoWordMark from './icons/NSWordMark';
7
8
  import LogoContainer from './NsBrandContanier.styled';
9
+
8
10
  import { NsBrandComponentProps } from './types.d';
9
11
 
10
12
  const VariantMap = {
@@ -19,6 +21,7 @@ const NsBrandComponent = (props: PropsWithChildren<NsBrandComponentProps>) => {
19
21
  const { variant = 'stacked', size = '108px', color = 'primary' } = props;
20
22
  const renderBrand = useCallback(() => {
21
23
  const BrandComponent = VariantMap[variant || 'stacked'];
24
+ // [Todo - Add neutral as FoundationTheme.PaletteColor]
22
25
  return <BrandComponent color={color as any} />;
23
26
  }, [color, variant]);
24
27
  return <LogoContainer size={size}>{renderBrand()}</LogoContainer>;
@@ -12,12 +12,14 @@ import { NsBreadcrumbProps, ItemProps } from './types.d';
12
12
  import { useTheme } from '@mui/material/styles';
13
13
  import { ColorUtils } from '@nuskin/foundation-theme';
14
14
 
15
+ const { color } = ColorUtils;
16
+
15
17
  const NsBreadcrumb = (props: PropsWithChildren<NsBreadcrumbProps>) => {
16
18
  const { items, iconVisibility } = props;
17
19
 
18
20
  const theme = useTheme();
19
- const neutral80 = ColorUtils.getAppColor(theme, 'neutral', '80') as string;
20
- const neutral60 = ColorUtils.getAppColor(theme, 'neutral', '60') as string;
21
+ const neutral80 = color('neutral', '80')(theme);
22
+ const neutral60 = color('neutral', '60')(theme);
21
23
 
22
24
  const getLabel = (item: ItemProps) => {
23
25
  if (item?.href) {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@/test-utils/index';
3
- import nuskinTheme from '@nuskin/foundation-theme/src/themes/genome-foundation';
3
+ import { nuskinTheme } from '@nuskin/foundation-theme';
4
4
  import Button from './NsButton';
5
5
 
6
6
  const { palette } = nuskinTheme;
@@ -8,28 +8,29 @@ const { palette } = nuskinTheme;
8
8
  describe('<Button />', () => {
9
9
  describe(' button primary', () => {
10
10
  test('should render default button styles', () => {
11
+ const text = 'Click Me';
11
12
  render(
12
13
  <Button color="primary" variant="contained">
13
- Click Me
14
+ {text}
14
15
  </Button>
15
16
  );
16
17
 
17
- const button = screen.getByText('Click Me');
18
-
18
+ const button = screen.getByText(text);
19
19
  expect(button).toBeInTheDocument();
20
- expect(button).toHaveStyle(`color: ${palette.primary.contrastText}`);
21
- expect(button).toHaveStyle(`background-color: ${palette.primary.main}`);
20
+ expect(button).toHaveStyle(`color: ${palette?.primary?.contrastText ?? ''}`);
21
+ expect(button).toHaveStyle(`background-color: ${palette?.primary?.main ?? ''}`);
22
22
  });
23
23
  });
24
24
  describe('button secondary', () => {
25
25
  test('should render default button styles', () => {
26
+ const text = 'Click Me';
26
27
  render(
27
28
  <Button color="success" variant="contained" size="large">
28
- Click Me
29
+ {text}
29
30
  </Button>
30
31
  );
31
32
 
32
- const button = screen.getByText('Click Me');
33
+ const button = screen.getByText(text);
33
34
 
34
35
  expect(button).toBeInTheDocument();
35
36
  expect(button).toHaveStyle(`color: ${palette.success.contrastText}`);
@@ -37,7 +38,7 @@ describe('<Button />', () => {
37
38
  });
38
39
  });
39
40
  describe('button outline success', () => {
40
- test('should render outlined sucess button', () => {
41
+ test('should render outlined success button', () => {
41
42
  render(
42
43
  <Button color="success" variant="outlined" size="large">
43
44
  Click Me
@@ -48,11 +49,10 @@ describe('<Button />', () => {
48
49
 
49
50
  expect(button).toBeInTheDocument();
50
51
  expect(button).toHaveStyle(`color: ${palette.success.main}`);
51
- expect(button).toHaveStyle(`border-color: ${palette.success.main}`);
52
52
  });
53
53
  });
54
54
  describe('button outline accentColorA', () => {
55
- test('should render outlined sucess button', () => {
55
+ test('should render outlined success button', () => {
56
56
  render(
57
57
  <Button color="accentColorA" variant="outlined" size="large">
58
58
  Click Me
@@ -63,7 +63,6 @@ describe('<Button />', () => {
63
63
 
64
64
  expect(button).toBeInTheDocument();
65
65
  expect(button).toHaveStyle(`color: ${palette?.accentColorA?.main}`);
66
- expect(button).toHaveStyle(`border-color: ${palette?.accentColorA?.main}`);
67
66
  });
68
67
  });
69
68
  });
@@ -37,7 +37,7 @@ export const button: Story = {};
37
37
 
38
38
  export const IconOnlyButton: Story = {
39
39
  render: (args) => (
40
- <NsButton {...args} iconOnly>
40
+ <NsButton {...args} iconOnly darkModeEnabled={false}>
41
41
  <ShoppingCart />
42
42
  </NsButton>
43
43
  ),
@@ -1,10 +1,11 @@
1
1
  import { Button as MUIButton } from '@mui/material';
2
2
 
3
3
  import { styled, ColorUtils, FoundationTheme } from '@nuskin/foundation-theme';
4
- import { ButtonConfig } from '@nuskin/foundation-theme/src/themes/types/button';
4
+ import { ButtonConfig } from '@nuskin/foundation-theme/src/common/themes/types/button';
5
+
5
6
  import { NsButtonProps } from './types.d';
6
7
 
7
- const { getPaletteColor, getButtonStateColor } = ColorUtils;
8
+ const { buttonColor, paletteColor } = ColorUtils;
8
9
 
9
10
  const getButtonConfig = (theme: FoundationTheme.Theme, attribute: keyof ButtonConfig) => {
10
11
  const { config } = theme || {};
@@ -18,8 +19,8 @@ const StyledButton = styled(MUIButton)<NsButtonProps>`
18
19
  border: 1px solid ${({ theme }) => getButtonConfig(theme, 'focusColor')} !important;
19
20
  }
20
21
  :disabled {
21
- background-color: ${({ theme, color }) => getButtonStateColor(theme, color, 'disabled')};
22
- color: ${({ theme, color }) => getButtonStateColor(theme, color, 'disabledText')};
22
+ background-color: ${(btnProps) => buttonColor(btnProps?.color, 'disabled')};
23
+ color: ${(btnProps) => buttonColor(btnProps?.color, 'disabledText')};
23
24
  }
24
25
  ${({ theme: { config }, size }) => {
25
26
  return size === 'medium' ? config?.components?.button?.size?.medium?.padding : '';
@@ -27,37 +28,38 @@ const StyledButton = styled(MUIButton)<NsButtonProps>`
27
28
  ${({ theme: { config }, size }) => {
28
29
  return size === 'large' ? config?.components?.button?.size?.large?.padding : '';
29
30
  }}
30
- ${({ variant, theme, color = 'primary' }) => {
31
+ ${({ variant, theme, color = 'primary', darkModeEnabled }) => {
31
32
  return variant === 'contained'
32
33
  ? `
33
- background-color: ${getPaletteColor(theme, color)};
34
+ background-color: ${paletteColor(color)({ theme, darkModeEnabled })};
35
+ color: ${paletteColor(color, 'contrastText')({ theme, darkModeEnabled })} !important;
34
36
  :hover {
35
- background-color: ${getButtonStateColor(theme, color, 'hovered')};
37
+ background-color: ${buttonColor(color, 'hovered')({ theme })};
36
38
  }
37
39
  :active {
38
- background-color: ${getButtonStateColor(theme, color, 'pressed')};
40
+ background-color: ${buttonColor(color, 'pressed')({ theme })};
39
41
  }
40
42
  :focus {
41
- background-color: ${getButtonStateColor(theme, color, 'focused')};
43
+ background-color: ${buttonColor(color, 'focused')({ theme })};
42
44
  }
43
45
  `
44
46
  : '';
45
47
  }}
46
- ${({ variant, theme, color = 'primary' }) =>
48
+ ${({ variant, theme, color = 'primary', darkModeEnabled }) =>
47
49
  variant === 'outlined' &&
48
50
  `
49
- border-color: ${getPaletteColor(theme, color)};
50
- color: ${getPaletteColor(theme, color)};
51
+ border-color: ${paletteColor(color)({ theme, darkModeEnabled })};
52
+ color: ${paletteColor(color)({ theme, darkModeEnabled })};
51
53
  :hover {
52
- border-color: ${getButtonStateColor(theme, color, 'hovered')};
53
- color: ${getPaletteColor(theme, color)};
54
+ border-color: ${buttonColor(color, 'hovered')({ theme })};
55
+ color: ${paletteColor(color)({ theme, darkModeEnabled })};
54
56
  }
55
57
  :active {
56
- border-color: ${getButtonStateColor(theme, color, 'pressed')};
57
- color: ${getPaletteColor(theme, color)};
58
+ border-color: ${buttonColor(color, 'pressed')({ theme })};
59
+ color: ${paletteColor(color)({ theme })};
58
60
  }
59
61
  `}
60
- ${({ variant, theme, color = 'primary' }) =>
62
+ ${({ variant, theme, color = 'primary', darkModeEnabled }) =>
61
63
  variant === 'text' &&
62
64
  `
63
65
  text-decoration: underline 1px !important;
@@ -65,15 +67,15 @@ const StyledButton = styled(MUIButton)<NsButtonProps>`
65
67
  padding: 0;
66
68
  min-width: auto;
67
69
  background-color: transparent !important;
68
- text-decoration-color: ${getPaletteColor(theme, color)};
69
- color: ${getPaletteColor(theme, color)};
70
+ text-decoration-color: ${paletteColor(color)({ theme, darkModeEnabled })};
71
+ color: ${paletteColor(color)({ theme, darkModeEnabled })};
70
72
  :hover {
71
- color: ${getButtonStateColor(theme, color, 'hovered')};
72
- text-decoration-color: ${getButtonStateColor(theme, color, 'hovered')};
73
+ color: ${buttonColor(color, 'hovered')({ theme })};
74
+ text-decoration-color: ${buttonColor(color, 'hovered')({ theme })};
73
75
  }
74
76
  :active {
75
- color: ${getPaletteColor(theme, color)};
76
- text-decoration-color: ${getButtonStateColor(theme, color, 'pressed')};
77
+ color: ${paletteColor(color)};
78
+ text-decoration-color: ${buttonColor(color, 'pressed')({ theme })};
77
79
  }
78
80
  `}
79
81
  ${({ iconOnly }) =>
@@ -1,10 +1,17 @@
1
1
  import { PropsWithChildren } from 'react';
2
+ import { useTheme } from '@mui/material/styles';
3
+
2
4
  import StyledButton from './NsButton.styled';
5
+
3
6
  import { NsButtonProps } from './types.d';
7
+ import { FoundationTheme } from '@nuskin/foundation-theme';
4
8
 
5
- const NsButton = ({ children, ...props }: PropsWithChildren<NsButtonProps>) => {
9
+ const NsButton = ({ children, darkModeEnabled = true, ...props }: PropsWithChildren<NsButtonProps>) => {
10
+ const { color } = props;
11
+ const { palette } = useTheme();
12
+ const btnColor = (color && palette?.[color] ? color : 'primary') as FoundationTheme.PaletteColors;
6
13
  return (
7
- <StyledButton {...props} disableRipple>
14
+ <StyledButton {...props} color={btnColor} disableRipple darkModeEnabled={darkModeEnabled}>
8
15
  {children}
9
16
  </StyledButton>
10
17
  );
@@ -16,4 +16,5 @@ export type NsButtonProps = Pick<
16
16
  | 'className'
17
17
  > & {
18
18
  iconOnly?: boolean;
19
+ darkModeEnabled?: boolean;
19
20
  };
@@ -38,8 +38,8 @@ export const BpCheckedIcon = styled(BpIcon)(({ theme: { palette } }) => ({
38
38
  path: {
39
39
  display: 'block',
40
40
  },
41
- background: palette.primary.main,
42
- color: palette.primary.contrastText,
41
+ background: palette?.primary?.main ?? '#fff',
42
+ color: palette?.primary?.contrastText ?? '#000',
43
43
  }));
44
44
 
45
45
  export const StyledLabel = styled('span')<{ $hasDisabled: boolean }>`
@@ -3,8 +3,10 @@ export type ContentStackLiveTags = {
3
3
  };
4
4
 
5
5
  export type AdditionalProps = {
6
+ darkModeEnabled?: boolean;
6
7
  additionalProps?: {
7
8
  ariaLabel?: string;
8
9
  testid?: string;
10
+ className?: string;
9
11
  } & ContentStackLiveTags;
10
12
  };
@@ -1,7 +1,9 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { FoundationTheme } from '@nuskin/foundation-theme';
3
3
 
4
- export interface DropdownProps {
4
+ import { AdditionalProps } from '../common.types';
5
+
6
+ export interface DropdownProps extends AdditionalProps {
5
7
  selectedValue: string;
6
8
  selectedLabel: string;
7
9
  onSelect: (arg: string) => void; // eslint-disable-line
@@ -0,0 +1,145 @@
1
+ import React from 'react';
2
+ import { render, screen, act } from '@testing-library/react';
3
+ import NsEmailSimple from './NsEmailSimple';
4
+ import { userEvent } from '@storybook/testing-library';
5
+ import { emailPattern } from '../validations/regex';
6
+
7
+ describe('NsEmailSimple', () => {
8
+ let mockCheckEmail = jest.fn().mockResolvedValue([true, false]);
9
+ let propsData = {
10
+ disabled: false,
11
+ label: 'Email',
12
+ required: true,
13
+ maxLength: 50,
14
+ invalidEmailMessage: 'Please enter a valid email',
15
+ dupedEmailMessage: 'Email ID already exists',
16
+ requiredErrorMessage: 'Email field is required',
17
+ checkEmail: mockCheckEmail,
18
+ };
19
+ it('renders email field with invalid regex error', async () => {
20
+ render(<NsEmailSimple {...propsData} />);
21
+ const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
22
+ expect(emailField).toBeInTheDocument;
23
+ const inputElement = emailField.querySelector('input');
24
+ if (!inputElement) {
25
+ return;
26
+ }
27
+ act(() => {
28
+ userEvent.type(inputElement, 'test');
29
+ userEvent.tab();
30
+ });
31
+
32
+ expect(inputElement).toHaveValue('test');
33
+
34
+ act(() => {
35
+ userEvent.clear(inputElement);
36
+ userEvent.type(inputElement, 'test@gm');
37
+ userEvent.tab();
38
+ });
39
+ expect(inputElement).toHaveValue('test@gm');
40
+
41
+ mockCheckEmail = jest.fn().mockResolvedValue([true, true]);
42
+ render(<NsEmailSimple {...propsData} />);
43
+ act(() => {
44
+ userEvent.clear(inputElement);
45
+ userEvent.type(inputElement, 'test!gmail');
46
+ userEvent.tab();
47
+ });
48
+ expect(inputElement).toHaveValue('test!gmail');
49
+ act(() => {
50
+ userEvent.clear(inputElement);
51
+ userEvent.tab();
52
+ });
53
+ expect(inputElement).toHaveValue('');
54
+
55
+ mockCheckEmail = jest.fn().mockResolvedValue([true, false]);
56
+ render(<NsEmailSimple {...propsData} />);
57
+ act(() => {
58
+ userEvent.clear(inputElement);
59
+ userEvent.type(inputElement, 'test@gmail.co');
60
+ userEvent.tab();
61
+ });
62
+ expect(inputElement).toHaveValue('test@gmail.co');
63
+ });
64
+ it('renders email field with invalid error', async () => {
65
+ let mockCheckEmail1 = jest.fn().mockResolvedValue([false, false]);
66
+ let propsData1 = {
67
+ disabled: false,
68
+ label: 'Email',
69
+ required: true,
70
+ maxLength: 50,
71
+ invalidEmailMessage: 'Please enter a valid email',
72
+ dupedEmailMessage: 'Email ID already exists',
73
+ requiredErrorMessage: 'Email field is required',
74
+ checkEmail: mockCheckEmail1,
75
+ };
76
+ render(<NsEmailSimple {...propsData1} />);
77
+ const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
78
+ expect(emailField).toBeInTheDocument;
79
+ const inputElement = emailField.querySelector('input');
80
+ if (!inputElement) {
81
+ return;
82
+ }
83
+ act(() => {
84
+ userEvent.clear(inputElement);
85
+ userEvent.type(inputElement, 'test@gmail.com');
86
+ userEvent.tab();
87
+ });
88
+ expect(inputElement).toHaveValue('test@gmail.com');
89
+ expect(inputElement.value).toMatch(emailPattern);
90
+ });
91
+ it('renders email field without error', async () => {
92
+ let mockCheckEmail1 = jest.fn().mockResolvedValue([true, true]);
93
+ let propsData1 = {
94
+ disabled: false,
95
+ label: 'Email',
96
+ required: true,
97
+ maxLength: 50,
98
+ invalidEmailMessage: 'Please enter a valid email',
99
+ dupedEmailMessage: 'Email ID already exists',
100
+ requiredErrorMessage: 'Email field is required',
101
+ checkEmail: mockCheckEmail1,
102
+ };
103
+ render(<NsEmailSimple {...propsData1} />);
104
+ const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
105
+ expect(emailField).toBeInTheDocument;
106
+ const inputElement = emailField.querySelector('input');
107
+ if (!inputElement) {
108
+ return;
109
+ }
110
+ act(() => {
111
+ userEvent.clear(inputElement);
112
+ userEvent.type(inputElement, 'test@gmail.com');
113
+ userEvent.tab();
114
+ });
115
+ expect(inputElement).toHaveValue('test@gmail.com');
116
+ expect(inputElement.value).toMatch(emailPattern);
117
+ });
118
+ it('renders email field with API error', async () => {
119
+ let mockCheckEmail1 = jest.fn().mockResolvedValue(undefined);
120
+ let propsData1 = {
121
+ disabled: false,
122
+ label: 'Email',
123
+ required: true,
124
+ maxLength: 50,
125
+ invalidEmailMessage: 'Please enter a valid email',
126
+ dupedEmailMessage: 'Email ID already exists',
127
+ requiredErrorMessage: 'Email field is required',
128
+ checkEmail: mockCheckEmail1,
129
+ };
130
+ render(<NsEmailSimple {...propsData1} />);
131
+ const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
132
+ expect(emailField).toBeInTheDocument;
133
+ const inputElement = emailField.querySelector('input');
134
+ if (!inputElement) {
135
+ return;
136
+ }
137
+ act(() => {
138
+ userEvent.clear(inputElement);
139
+ userEvent.type(inputElement, 'test@gmail.com');
140
+ userEvent.tab();
141
+ });
142
+ expect(inputElement).toHaveValue('test@gmail.com');
143
+ expect(inputElement.value).toMatch(emailPattern);
144
+ });
145
+ });
@@ -0,0 +1,61 @@
1
+ import type { StoryObj, Meta } from '@storybook/react';
2
+ import NsEmailSimple from './NsEmailSimple';
3
+ import { within } from '@storybook/testing-library';
4
+ import { expect } from '@storybook/jest';
5
+ import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
6
+
7
+ async function checkEmail() {
8
+ const isServiceValid = Promise.resolve(true);
9
+ const isEmailAvailable = Promise.resolve(true);
10
+ return [isServiceValid, isEmailAvailable];
11
+ }
12
+ type ComponentType = typeof NsEmailSimple;
13
+ type Story = StoryObj<ComponentType>;
14
+ type MetaStory = Meta<ComponentType>;
15
+
16
+ export default {
17
+ component: NsEmailSimple,
18
+ title: 'Components/Inputs/NsEmailSimple',
19
+ decorators: [withCenterDecorator],
20
+ play: ({ canvasElement }) => {
21
+ const canvas = within(canvasElement);
22
+ const el = canvas.getByTestId('email-field-test');
23
+ expect(el).toBeInTheDocument();
24
+ },
25
+
26
+ argTypes: {
27
+ disabled: {
28
+ description: 'Indicates whether the email field is enabled/disabled',
29
+ },
30
+ label: {
31
+ description: 'Label of the email component',
32
+ },
33
+ required: {
34
+ description: 'Indicates whether the email field is mandatory or not',
35
+ },
36
+ maxLength: {
37
+ description: 'Maximum number of characters allowed',
38
+ },
39
+ invalidEmailMessage: {
40
+ description: 'Displayed when an invalid email is entered',
41
+ },
42
+ dupedEmailMessage: {
43
+ description: 'Displayed when a duplicate email is entered',
44
+ },
45
+ requiredErrorMessage: {
46
+ description: 'Displayed when email is not entered',
47
+ },
48
+ },
49
+ args: {
50
+ disabled: false,
51
+ label: 'Email',
52
+ required: true,
53
+ maxLength: 50,
54
+ invalidEmailMessage: 'Please enter a valid email',
55
+ dupedEmailMessage: 'Email ID already exists',
56
+ requiredErrorMessage: 'Email field is required',
57
+ checkEmail: checkEmail,
58
+ },
59
+ } as MetaStory;
60
+
61
+ export const NsEmail: Story = {};