@nuskin/foundation-ui-components 1.0.0-cft-35.4 → 1.0.0-cft-35.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 (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +6 -2
  3. package/src/common/brand-components/BrandComponent.tsx +7 -7
  4. package/src/common/brand-components/types.d.ts +2 -0
  5. package/src/common/button/Button.spec.tsx +5 -7
  6. package/src/common/button/Button.stories.tsx +2 -8
  7. package/src/common/card/NsBgCard.tsx +24 -0
  8. package/src/common/card/NsBrandLogo.tsx +16 -0
  9. package/src/common/card/NsBrandProductCard.tsx +47 -0
  10. package/src/common/card/NsBrandWrapper.tsx +26 -0
  11. package/src/common/card/index.ts +4 -0
  12. package/src/common/card/stories/NsBgCard.stories.tsx +19 -0
  13. package/src/common/card/stories/NsBrandProductCard.stories.tsx +18 -0
  14. package/src/common/card/stories/data.js +67 -0
  15. package/src/common/card/tests/BrandLogo.spec.tsx +13 -0
  16. package/src/common/card/tests/NsBgCard.spec.tsx +23 -0
  17. package/src/common/card/tests/NsBrandProductCard.spec.tsx +32 -0
  18. package/src/common/common.types.ts +0 -1
  19. package/src/common/link/NsLink.styled.tsx +5 -2
  20. package/src/common/nav-tab/NsNavTab.styled.tsx +3 -2
  21. package/src/common/nav-tab/NsNavTab.tsx +2 -1
  22. package/src/common/nav-tab/types.d.ts +2 -1
  23. package/src/common/slick/CarousalControl.tsx +44 -0
  24. package/src/common/slick/IconWrapper.tsx +32 -0
  25. package/src/common/slick/LeftChevron.tsx +34 -0
  26. package/src/common/slick/NsCarousal.tsx +51 -0
  27. package/src/common/slick/RenderBrand.tsx +18 -0
  28. package/src/common/slick/RightChevron.tsx +36 -0
  29. package/src/common/slick/index.ts +2 -0
  30. package/src/common/slick/slickSetting.ts +61 -0
  31. package/src/common/slick/stories/NSCarousal.stories.tsx +21 -0
  32. package/src/common/slick/tests/Chevron.spec.tsx +10 -0
  33. package/src/common/slick/tests/NsCarousal.spec.tsx +106 -0
  34. package/src/common/slick/tests/RenderBrand.spec.tsx +22 -0
  35. package/src/common/typography/Typography.styled.tsx +2 -1
  36. package/src/common/typography/types.d.ts +3 -1
  37. /package/src/common/brand-components/{BrandComponent.test.tsx → BrandComponent.spec.tsx} +0 -0
  38. /package/src/common/brand-components/icons/{Logo.test.tsx → Logo.spec.tsx} +0 -0
  39. /package/src/common/typography/{Typography.test.tsx → Typography.spec.tsx} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [1.0.0-cft-35.5](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/compare/v1.0.0-cft-35.4...v1.0.0-cft-35.5) (2023-03-29)
2
+
3
+
4
+ ### Fix
5
+
6
+ * Lint fixed ([929112c](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/929112ce66e5e5e86dcf3a64ee50288cfe244e27))
7
+
8
+ ### New
9
+
10
+ * adding card component for showing brands ([e5aad64](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/e5aad64b0704f0054028d0eacc0ce603bbb814e4))
11
+ * Brand and Live Tag ([4913a03](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/4913a031130fc28b3259038e6ab87d487690ac63))
12
+
1
13
  # [1.0.0-cft-35.4](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/compare/v1.0.0-cft-35.3...v1.0.0-cft-35.4) (2023-03-28)
2
14
 
3
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/foundation-ui-components",
3
- "version": "1.0.0-cft-35.4",
3
+ "version": "1.0.0-cft-35.5",
4
4
  "description": "It is a foundation library written in react, that provides commom components to all UI pages",
5
5
  "private": false,
6
6
  "main": "src/common/index.ts",
@@ -27,7 +27,7 @@
27
27
  "build": "next build",
28
28
  "start": "next start",
29
29
  "lint": "next lint",
30
- "storybook": "start-storybook -p 6006",
30
+ "storybook": "start-storybook -p 6007",
31
31
  "test": "jest",
32
32
  "test:coverage": "yarn test --coverage",
33
33
  "test:watch": "jest --watch",
@@ -45,6 +45,9 @@
45
45
  "next": "13.2.4",
46
46
  "react": "18.2.0",
47
47
  "react-dom": "18.2.0",
48
+ "react-material-ui-carousel": "^3.4.2",
49
+ "react-slick": "^0.29.0",
50
+ "slick-carousel": "^1.8.1",
48
51
  "typescript": "4.9.5"
49
52
  },
50
53
  "devDependencies": {
@@ -68,6 +71,7 @@
68
71
  "@types/node": "18.15.0",
69
72
  "@types/react": "18.0.28",
70
73
  "@types/react-dom": "18.0.11",
74
+ "@types/react-slick": "^0.23.10",
71
75
  "@types/react-test-renderer": "^18.0.0",
72
76
  "@types/testing-library__jest-dom": "^5.14.5",
73
77
  "babel-jest": "^29.5.0",
@@ -8,18 +8,18 @@ import LogoContainer from './BrandContanier.styled';
8
8
  import { BrandComponentProps } from './types';
9
9
 
10
10
  const VariantMap = {
11
- 'stacked': NSLogoStacked,
12
- 'horizontal': NSLogoHorizontal,
13
- 'wordmark': NSLogoWordMark,
14
- 'logomark': NSLogoMark,
15
- 'poweredBy': NSLogoPoweredBy,
11
+ stacked: NSLogoStacked,
12
+ horizontal: NSLogoHorizontal,
13
+ wordmark: NSLogoWordMark,
14
+ logomark: NSLogoMark,
15
+ poweredBy: NSLogoPoweredBy,
16
16
  };
17
17
 
18
18
  const BrandComponent = (props: PropsWithChildren<BrandComponentProps>) => {
19
- const { variant = 'stacked', size = '108px' } = props;
19
+ const { variant = 'stacked', size = '108px', color = 'primary' } = props;
20
20
  const renderBrand = useCallback(() => {
21
21
  const BrandComponent = VariantMap[variant || 'stacked'];
22
- return <BrandComponent color='primary' />
22
+ return <BrandComponent color={color as any} />;
23
23
  }, [variant]);
24
24
  return <LogoContainer size={size}>{renderBrand()}</LogoContainer>;
25
25
  };
@@ -1,7 +1,9 @@
1
+ import { PaletteColorNames } from '@nuskin/foundation-theme/src/themes/types';
1
2
 
2
3
  export type BrandComponentProps = {
3
4
  variant?: 'stacked' | 'horizontal' | 'poweredBy' | 'logomark' | 'wordmark';
4
5
  size?: string;
6
+ color?: PaletteColorNames;
5
7
  }
6
8
 
7
9
  export type SVGLogoProps = {
@@ -10,7 +10,7 @@ describe('<Button />', () => {
10
10
  test('should render default button styles', () => {
11
11
  render(
12
12
  <Button color="primary" variant="contained">
13
- Click Me
13
+ Click Me
14
14
  </Button>
15
15
  );
16
16
 
@@ -25,7 +25,7 @@ describe('<Button />', () => {
25
25
  test('should render default button styles', () => {
26
26
  render(
27
27
  <Button color="success" variant="contained" size="large">
28
- Click Me
28
+ Click Me
29
29
  </Button>
30
30
  );
31
31
 
@@ -40,7 +40,7 @@ describe('<Button />', () => {
40
40
  test('should render outlined sucess button', () => {
41
41
  render(
42
42
  <Button color="success" variant="outlined" size="large">
43
- Click Me
43
+ Click Me
44
44
  </Button>
45
45
  );
46
46
 
@@ -55,7 +55,7 @@ describe('<Button />', () => {
55
55
  test('should render outlined sucess button', () => {
56
56
  render(
57
57
  <Button color="accentColorA" variant="outlined" size="large">
58
- Click Me
58
+ Click Me
59
59
  </Button>
60
60
  );
61
61
 
@@ -63,9 +63,7 @@ describe('<Button />', () => {
63
63
 
64
64
  expect(button).toBeInTheDocument();
65
65
  expect(button).toHaveStyle(`color: ${palette?.accentColorA?.main}`);
66
- expect(button).toHaveStyle(
67
- `border-color: ${palette?.accentColorA?.main}`
68
- );
66
+ expect(button).toHaveStyle(`border-color: ${palette?.accentColorA?.main}`);
69
67
  });
70
68
  });
71
69
  });
@@ -6,11 +6,7 @@ import { ShoppingCart } from '@mui/icons-material';
6
6
  export default {
7
7
  title: 'Components/Button',
8
8
  component: Button,
9
- decorators: [
10
- (story) => (
11
- <div style={{ textAlign: 'center', marginTop: '20px' }}>{story()}</div>
12
- ),
13
- ],
9
+ decorators: [(story) => <div style={{ textAlign: 'center', marginTop: '20px' }}>{story()}</div>],
14
10
  argTypes: {
15
11
  children: {
16
12
  control: 'text',
@@ -32,9 +28,7 @@ export default {
32
28
  },
33
29
  } as ComponentMeta<typeof Button>;
34
30
 
35
- export const button: ComponentStory<typeof Button> = (args) => (
36
- <Button {...args} />
37
- );
31
+ export const button: ComponentStory<typeof Button> = (args) => <Button {...args} />;
38
32
 
39
33
  export const IconOnlyButton: ComponentStory<typeof Button> = (args) => (
40
34
  <Button {...args} iconOnly>
@@ -0,0 +1,24 @@
1
+ import Card from '@mui/material/Card';
2
+ import { styled } from '@nuskin/foundation-theme';
3
+
4
+ type NsBgCardProps = {
5
+ backgroundImageUrl: string;
6
+ };
7
+ const options = {
8
+ shouldForwardProp: (propKey: string) => propKey !== 'backgroundImageUrl',
9
+ };
10
+ const Component = ({ backgroundImageUrl }: NsBgCardProps) => {
11
+ const width = 256;
12
+ return {
13
+ backgroundImage: `url("${backgroundImageUrl}")`,
14
+ backgroundSize: 'cover',
15
+ backgroundPosition: 'center',
16
+ height: width * 1.2,
17
+ width,
18
+ };
19
+ };
20
+ /*
21
+ This styled component shows the background image on a card
22
+ */
23
+ const NsBgCard = styled(Card, options)<NsBgCardProps>(Component);
24
+ export default NsBgCard;
@@ -0,0 +1,16 @@
1
+ import CardMedia from '@mui/material/CardMedia';
2
+ import { styled } from '@nuskin/foundation-theme';
3
+
4
+ const NsBrandLogo = styled(CardMedia)<{
5
+ component: 'img';
6
+ }>({
7
+ width: 'auto',
8
+ height: 'auto',
9
+ maxWidth: '80%',
10
+ maxHeight: '80%',
11
+ display: 'flex',
12
+ alignItems: 'center',
13
+ justifyContent: 'center',
14
+ });
15
+
16
+ export default NsBrandLogo;
@@ -0,0 +1,47 @@
1
+ import Box from '@mui/material/Box';
2
+ import { styled } from '@nuskin/foundation-theme';
3
+ import { Typography } from '../index';
4
+ import NsBrandWrapper from './NsBrandWrapper';
5
+ import NsBrandLogo from './NsBrandLogo';
6
+ import NsBgCard from './NsBgCard';
7
+
8
+ type NsBrandProductCardProps = {
9
+ title?: string;
10
+ brandImageUrl: string;
11
+ backgroundImageUrl: string;
12
+ };
13
+ const CardContainer = styled(Box)(() => {
14
+ return {
15
+ width: '256px',
16
+ display: 'flex',
17
+ alignItems: 'start',
18
+ justifyContent: 'center',
19
+ position: 'relative',
20
+ flexDirection: 'column',
21
+ '.brand-title': {
22
+ marginTop: '20px',
23
+ fontSize: '20px',
24
+ },
25
+ ':hover': {
26
+ '.brand-title': {
27
+ opacity: '0.7',
28
+ },
29
+ },
30
+ };
31
+ });
32
+ export default function NsBrandProductCard({ title, brandImageUrl, backgroundImageUrl }: NsBrandProductCardProps) {
33
+ return (
34
+ <CardContainer role="brand-card" component="div" data-testid="brand-product-card">
35
+ <NsBgCard backgroundImageUrl={backgroundImageUrl}>
36
+ <NsBrandWrapper>
37
+ <NsBrandLogo component="img" image={brandImageUrl} />
38
+ </NsBrandWrapper>
39
+ </NsBgCard>
40
+ {title ? (
41
+ <Typography variant="h3" className="brand-title">
42
+ {title}
43
+ </Typography>
44
+ ) : null}
45
+ </CardContainer>
46
+ );
47
+ }
@@ -0,0 +1,26 @@
1
+ import Box from '@mui/material/Box';
2
+ import { PropsWithChildren } from 'react';
3
+ import { styled } from '@nuskin/foundation-theme';
4
+
5
+ const Wrapper = styled(Box)(() => {
6
+ return {
7
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
8
+ width: '100%',
9
+ height: '100%',
10
+ zIndex: 100,
11
+ display: 'flex',
12
+ flexDirection: 'column',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ '&:hover': {
16
+ backgroundColor: 'rgba(0, 0, 0, 0)',
17
+ '& img': {
18
+ visibility: 'hidden',
19
+ },
20
+ },
21
+ };
22
+ });
23
+
24
+ export default function NsBrandWrapper({ children }: PropsWithChildren) {
25
+ return <Wrapper className="logo-wrapper">{children}</Wrapper>;
26
+ }
@@ -0,0 +1,4 @@
1
+ export { default as NsBrandWrapper } from './NsBrandWrapper';
2
+ export { default as NsBgCard } from './NsBgCard';
3
+ export { default as NsBrandLogo } from './NsBrandLogo';
4
+ export { default as NsBrandProductCard } from './NsBrandProductCard';
@@ -0,0 +1,19 @@
1
+ import { ComponentMeta, ComponentStory } from '@storybook/react';
2
+ import NsBgCard from '../NsBgCard';
3
+
4
+ export default {
5
+ component: NsBgCard,
6
+ title: 'Components/Card/NsBgCard',
7
+ argTypes: {},
8
+ decorators: [(story) => <div style={{ textAlign: 'center', marginTop: '20px' }}>{story()}</div>],
9
+ } as ComponentMeta<typeof NsBgCard>;
10
+
11
+ const Template: ComponentStory<typeof NsBgCard> = (args: any) => <NsBgCard {...args} />;
12
+
13
+ export const Default = Template.bind({});
14
+ Default.args = {
15
+ title: 'ageLOC LumiSpa',
16
+ backgroundImageUrl: 'https://media.nuskin.com/m/3604b5202b0b64c6/webimage-brand-lumispa-optweb.png',
17
+ // brandImageUrl:
18
+ // 'https://media.nuskin.com/m/2571a0407433a6e8/webimage-brand-lumispa.png',
19
+ };
@@ -0,0 +1,18 @@
1
+ import { ComponentMeta, ComponentStory } from '@storybook/react';
2
+ import NsBrandProductCard from '../NsBrandProductCard';
3
+
4
+ export default {
5
+ component: NsBrandProductCard,
6
+ title: 'Components/Card/NsBrandProductCard',
7
+ argTypes: {},
8
+ decorators: [(story) => <div style={{ textAlign: 'center', marginTop: '20px' }}>{story()}</div>],
9
+ } as ComponentMeta<typeof NsBrandProductCard>;
10
+
11
+ const Template: ComponentStory<typeof NsBrandProductCard> = (args: any) => <NsBrandProductCard {...args} />;
12
+
13
+ export const Default = Template.bind({});
14
+ Default.args = {
15
+ title: 'ageLOC LumiSpa',
16
+ backgroundImageUrl: 'https://media.nuskin.com/m/3604b5202b0b64c6/webimage-brand-lumispa-optweb.png',
17
+ brandImageUrl: 'https://media.nuskin.com/m/2571a0407433a6e8/webimage-brand-lumispa.png',
18
+ };
@@ -0,0 +1,67 @@
1
+ export const brandImages = [
2
+ 'https://media.nuskin.com/m/63a454ff90d038b4/webimage-brand-epoch.png',
3
+ 'https://media.nuskin.com/m/3eb406f465d4b0fe/webimage-brand-sunright.png',
4
+ 'https://media.nuskin.com/m/2d3276518912283b/webimage-brand-nu_colour.png',
5
+ 'https://media.nuskin.com/m/3855d3836d920639/webimage-brand-nuskin180.png',
6
+ 'https://media.nuskin.com/m/6245528b8f76ad32/webimage-brand-ageLOC.png',
7
+ 'https://media.nuskin.com/m/2571a0407433a6e8/webimage-brand-lumispa.png',
8
+ 'https://media.nuskin.com/m/4bd7ab9b46bd456d/webimage-brand-pharmanex.png',
9
+ 'https://media.nuskin.com/m/501d638a8af80087/webimage-brand-ap24.png',
10
+ 'https://media.nuskin.com/m/f3a91129cbbae32/webimage-brand-nutricentials.png',
11
+ 'https://media.nuskin.com/m/63a454ff90d038b4/webimage-brand-epoch.png',
12
+ 'https://media.nuskin.com/m/3eb406f465d4b0fe/webimage-brand-sunright.png',
13
+ 'https://media.nuskin.com/m/2d3276518912283b/webimage-brand-nu_colour.png',
14
+ 'https://media.nuskin.com/m/3855d3836d920639/webimage-brand-nuskin180.png',
15
+ 'https://media.nuskin.com/m/6245528b8f76ad32/webimage-brand-ageLOC.png',
16
+ 'https://media.nuskin.com/m/2571a0407433a6e8/webimage-brand-lumispa.png',
17
+ 'https://media.nuskin.com/m/4bd7ab9b46bd456d/webimage-brand-pharmanex.png',
18
+ 'https://media.nuskin.com/m/501d638a8af80087/webimage-brand-ap24.png',
19
+ ];
20
+
21
+ export const brandBackImages = [
22
+ 'https://media.nuskin.com/m/549aa3e5184434d8/webimage-brand-epoch-optweb.png',
23
+ 'https://media.nuskin.com/m/14e238d7599e1811/webimage-brand-sunright.png',
24
+ 'https://media.nuskin.com/m/2543e79419276727/webimage-brand-nu_colour-optweb.png',
25
+ 'https://media.nuskin.com/m/446c7a2aad513f58/webimage-brand-nu_skin_180-optweb.png',
26
+ 'https://media.nuskin.com/m/42911c8b05600476/webimage-brand-ageloc-optweb.png',
27
+ 'https://media.nuskin.com/m/3604b5202b0b64c6/webimage-brand-lumispa-optweb.png',
28
+ 'https://media.nuskin.com/m/4a1245a63c595262/webimage-brand-pharmanex-optweb.png',
29
+ 'https://media.nuskin.com/m/14a0fd6ef63e5dbb/webimage-brand-ap_24.png',
30
+ 'https://media.nuskin.com/m/298104036e885b40/webimage-brand-nutricentials.png',
31
+ 'https://media.nuskin.com/m/549aa3e5184434d8/webimage-brand-epoch-optweb.png',
32
+ 'https://media.nuskin.com/m/14e238d7599e1811/webimage-brand-sunright.png',
33
+ 'https://media.nuskin.com/m/2543e79419276727/webimage-brand-nu_colour-optweb.png',
34
+ 'https://media.nuskin.com/m/446c7a2aad513f58/webimage-brand-nu_skin_180-optweb.png',
35
+ 'https://media.nuskin.com/m/42911c8b05600476/webimage-brand-ageloc-optweb.png',
36
+ 'https://media.nuskin.com/m/3604b5202b0b64c6/webimage-brand-lumispa-optweb.png',
37
+ 'https://media.nuskin.com/m/4a1245a63c595262/webimage-brand-pharmanex-optweb.png',
38
+ 'https://media.nuskin.com/m/14a0fd6ef63e5dbb/webimage-brand-ap_24.png',
39
+ ];
40
+
41
+ export const brandTitles = [
42
+ 'Epoch',
43
+ 'Sunright',
44
+ 'Nu Colour',
45
+ '180 System',
46
+ 'ageLOC',
47
+ 'ageLOC LumiSpa',
48
+ 'Pharmanex',
49
+ 'AP 24',
50
+ 'Nutricentials',
51
+ 'Epoch',
52
+ 'Sunright',
53
+ 'Nu Colour',
54
+ '180 System',
55
+ 'ageLOC',
56
+ 'ageLOC LumiSpa',
57
+ 'Pharmanex',
58
+ 'AP 24',
59
+ ];
60
+ export const brandEntries = brandTitles.map((title, index) => {
61
+ return {
62
+ title,
63
+ backgroundImage: brandBackImages[index],
64
+ brandImage: brandImages[index],
65
+ id: index,
66
+ };
67
+ });
@@ -0,0 +1,13 @@
1
+ import { render } from '@testing-library/react';
2
+ import NsBrandLogo from '../NsBrandLogo';
3
+
4
+ describe('BrandLogo component', () => {
5
+ it('renders with cover size', () => {
6
+ const { container } = render(<NsBrandLogo component="img" />);
7
+ const cardElement = container.querySelector('img');
8
+ expect(cardElement).toHaveStyle(`
9
+ align-items: center;
10
+ justify-content: center;
11
+ `);
12
+ });
13
+ });
@@ -0,0 +1,23 @@
1
+ import { render } from '@testing-library/react';
2
+ import NsBgCard from '../NsBgCard';
3
+
4
+ describe('NsBgCard component', () => {
5
+ const props = {
6
+ backgroundImageUrl: 'https://media.nuskin.com/m/6245528b8f76ad32/webimage-brand-ageLOC.png',
7
+ };
8
+ it('renders with background image', () => {
9
+ const { container } = render(<NsBgCard {...props} />);
10
+ const cardElement = container.querySelector('div');
11
+ expect(cardElement).toHaveStyle(`
12
+ background-image: url(${props.backgroundImageUrl});
13
+ `);
14
+ });
15
+ it('renders with cover size', () => {
16
+ const { container } = render(<NsBgCard {...props} />);
17
+ const cardElement = container.querySelector('div');
18
+ expect(cardElement).toHaveStyle(`
19
+ background-size: cover;
20
+ background-position: center;
21
+ `);
22
+ });
23
+ });
@@ -0,0 +1,32 @@
1
+ import { render } from '@testing-library/react';
2
+ import NsBrandProductCard from '../NsBrandProductCard';
3
+
4
+ describe('NSBgCard component', () => {
5
+ const props = {
6
+ title: 'Nutricentials',
7
+ brandImageUrl: 'https://media.nuskin.com/m/f3a91129cbbae32/webimage-brand-nutricentials.png',
8
+ backgroundImageUrl: 'https://media.nuskin.com/m/298104036e885b40/webimage-brand-nutricentials.png',
9
+ };
10
+ it('render brand image', () => {
11
+ const { container } = render(<NsBrandProductCard {...props} />);
12
+ const img = container.querySelector('img');
13
+ expect(img).toHaveAttribute('src', props.brandImageUrl);
14
+ });
15
+ it('render background image', () => {
16
+ const { container } = render(<NsBrandProductCard {...props} />);
17
+ const card = container.querySelector('div.MuiPaper-root');
18
+ expect(card).toHaveStyle(`
19
+ background-image: url(${props.backgroundImageUrl});
20
+ `);
21
+ });
22
+ it('renders title', () => {
23
+ const { getByText } = render(<NsBrandProductCard {...props} />);
24
+ const cardElement = getByText(props.title);
25
+ expect(cardElement).toBeInTheDocument();
26
+ });
27
+ it('does not render title, when title is not passed', () => {
28
+ const { container } = render(<NsBrandProductCard {...{ ...props, title: undefined }} />);
29
+ const cardElement = container.querySelector('.brand-title');
30
+ expect(cardElement).toBeNull();
31
+ });
32
+ });
@@ -8,4 +8,3 @@ export type AdditionalProps = {
8
8
  testid?: string;
9
9
  } & ContentStackLiveTags;
10
10
  };
11
-
@@ -53,13 +53,16 @@ const getLinkStyles = ({ disabled, $underlineOnHover = true }: StyledProps) => {
53
53
  }
54
54
  return '';
55
55
  };
56
+ const options = {
57
+ shouldForwardProp: (propKey: string) => propKey !== '$underlineOnHover',
58
+ };
56
59
 
57
- export const StyledNextLink = styled(NextLink)<StyledProps>`
60
+ export const StyledNextLink = styled(NextLink, options)<StyledProps>`
58
61
  ${linkDefaultStyles};
59
62
  ${getLinkStyles}
60
63
  `;
61
64
 
62
- export const StyledLink = styled(MUILink)<StyledProps>`
65
+ export const StyledLink = styled(MUILink, options)<StyledProps>`
63
66
  ${linkDefaultStyles};
64
67
  ${getLinkStyles}
65
68
  `;
@@ -3,6 +3,7 @@ import { PaletteColorNames } from '@nuskin/foundation-theme/src/themes/types';
3
3
  import { motion } from 'framer-motion';
4
4
 
5
5
  export const NavTabContainer = styled.div`
6
+
6
7
  .shadow {
7
8
  box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.15);
8
9
  }
@@ -38,7 +39,7 @@ export const NavDrawer = styled.div`
38
39
  width: 100%;
39
40
  max-height: 590px;
40
41
 
41
- background-color: #fff;
42
+ background-color: ${({ theme }) => theme?.palette?.primary.contrastText};
42
43
  box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.15);
43
44
  transition: all 0.15s ease-in-out;
44
45
  overflow: hidden;
@@ -55,7 +56,7 @@ export const NavDrawerContainer = styled(motion.div)`
55
56
  z-index: 1;
56
57
  }
57
58
  box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.15);
58
- background-color: #fff;
59
+ background-color: ${({ theme }) => theme?.palette?.primary.contrastText};
59
60
  width: 100%;
60
61
  text-align: left;
61
62
  max-height: 590px;
@@ -40,7 +40,8 @@ export const NsNavTabComponent = (props: PropsWithChildren<NSNavTabComponentProp
40
40
  >
41
41
  <Typography
42
42
  variant={tab.textVariant}
43
- className={`mb-2 ${tabTitleClassName ? tabTitleClassName : null}`}
43
+ additionalProps={{ ...tab?.liveTag }}
44
+ className={`mb-2 ${tabTitleClassName} ${!active ? 'opacity-8' : ''}`}
44
45
  color={tab.tabName === active ? tab.activeColor : tab.color}
45
46
  >
46
47
  {tab.tabName}
@@ -1,7 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { PaletteColorNames } from '@nuskin/foundation-theme/src/themes/types';
3
3
  import { Variants } from '../typography/types';
4
- import { AdditionalProps } from '../common.types';
4
+ import { AdditionalProps, ContentStackLiveTags } from '../common.types';
5
5
 
6
6
  export type NavTabs = {
7
7
  tabName: string;
@@ -9,6 +9,7 @@ export type NavTabs = {
9
9
  activeColor: PaletteColorNames;
10
10
  color?: PaletteColorNames;
11
11
  textVariant: Variants;
12
+ liveTag?: ContentStackLiveTags;
12
13
  };
13
14
 
14
15
  export type NSNavTabComponentProps = {
@@ -0,0 +1,44 @@
1
+ import { styled } from '@nuskin/foundation-theme';
2
+ import LeftChevron from './LeftChevron';
3
+ import IconWrapper from './IconWrapper';
4
+ import RightChevron from './RightChevron';
5
+ import { Typography } from '../';
6
+ import Link from 'next/link';
7
+ interface CarousalControlProps {
8
+ next(): void;
9
+ prev(): void;
10
+ viewAllUrl: string;
11
+ }
12
+ /*
13
+ This component is visible over the carousal and it has prev and next buttons and view all link
14
+ */
15
+ const Wrapper = styled.div`
16
+ display: flex;
17
+ justify-content: flex-end;
18
+ padding: 0 30px;
19
+ margin: 10px 20px;
20
+ `;
21
+ const InnerWrapper = styled.div`
22
+ display: flex;
23
+ width: 220px;
24
+ gap: 20px;
25
+ align-items: center;
26
+ `;
27
+ const CarousalControl = ({ next, prev, viewAllUrl }: CarousalControlProps) => {
28
+ return (
29
+ <Wrapper>
30
+ <InnerWrapper>
31
+ <Link href={viewAllUrl}>
32
+ <Typography variant={'h5'}>View All</Typography>
33
+ </Link>
34
+ <IconWrapper onClick={prev} className="carousal-prev-button">
35
+ <LeftChevron />
36
+ </IconWrapper>
37
+ <IconWrapper onClick={next} className="carousal-next-button">
38
+ <RightChevron />
39
+ </IconWrapper>
40
+ </InnerWrapper>
41
+ </Wrapper>
42
+ );
43
+ };
44
+ export default CarousalControl;
@@ -0,0 +1,32 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import { styled } from '@nuskin/foundation-theme';
3
+ import { Box } from '@mui/material';
4
+
5
+ type IconWrapperProps = PropsWithChildren<{
6
+ onClick(): void;
7
+ className?: string;
8
+ }>;
9
+ const options = {};
10
+ const Wrapper = styled(
11
+ Box,
12
+ options
13
+ )(({ theme }) => {
14
+ const strokeColor = theme.palette?.primary?.main ?? '#252525';
15
+ return {
16
+ display: 'flex',
17
+ height: '20px',
18
+ width: '20px',
19
+ border: `2px solid ${strokeColor}`,
20
+ borderRadius: '3px',
21
+ cursor: 'pointer',
22
+ padding: '10px',
23
+ };
24
+ });
25
+ const IconWrapper = ({ children, onClick, className }: IconWrapperProps) => {
26
+ return (
27
+ <Wrapper component="span" className={className || ''} onClick={onClick}>
28
+ {children}
29
+ </Wrapper>
30
+ );
31
+ };
32
+ export default IconWrapper;
@@ -0,0 +1,34 @@
1
+ import { useTheme } from '@mui/material/styles';
2
+
3
+ const LeftChevron = () => {
4
+ const { palette } = useTheme();
5
+ const strokeColor = palette.primary.main;
6
+ return (
7
+ <svg
8
+ data-testid="prev-chevron"
9
+ viewBox="0 0 24 24"
10
+ preserveAspectRatio="xMidYMid meet"
11
+ focusable="false"
12
+ style={{ pointerEvents: 'none', display: 'block' }}
13
+ >
14
+ <g
15
+ id="icon-narrow-angle-left-24"
16
+ stroke="none"
17
+ strokeWidth="1"
18
+ fill="none"
19
+ fillRule="evenodd"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ >
23
+ <g transform="translate(-196, -226)" stroke={strokeColor}>
24
+ <polyline
25
+ // eslint-disable-next-line max-len
26
+ transform="translate(209.000000, 237.500000) scale(-1.2, 1.2) rotate(90.000000) translate(-209.000000, -237.500000)"
27
+ points="200 242 209 233 218 242"
28
+ ></polyline>
29
+ </g>
30
+ </g>
31
+ </svg>
32
+ );
33
+ };
34
+ export default LeftChevron;
@@ -0,0 +1,51 @@
1
+ import Box from '@mui/material/Box';
2
+ import { styled } from '@nuskin/foundation-theme';
3
+ import { useRef, useCallback, ReactNode } from 'react';
4
+ import Slider from 'react-slick';
5
+ import 'slick-carousel/slick/slick.css';
6
+ import 'slick-carousel/slick/slick-theme.css';
7
+ import defaultCarousalSettings from './slickSetting';
8
+ import CarousalControl from './CarousalControl';
9
+
10
+ const SlickContainer = styled(Box)(() => {
11
+ return {
12
+ padding: '20px 0 50px',
13
+ '.slick-slide': {
14
+ display: 'flex',
15
+ justifyContent: 'center',
16
+ },
17
+ };
18
+ });
19
+ interface CarousalProps<T = any> {
20
+ entries: T[];
21
+ // eslint-disable-next-line no-unused-vars
22
+ renderEntry(entry: T, index: number): ReactNode;
23
+ allEntryLink: string;
24
+ settings: any;
25
+ }
26
+
27
+ export default function NsCarousal({ entries, renderEntry, allEntryLink, settings }: CarousalProps) {
28
+ const sliderRef = useRef();
29
+ const prev = useCallback(() => {
30
+ if (sliderRef.current) {
31
+ const slider = sliderRef.current as any;
32
+ slider.slickPrev();
33
+ }
34
+ }, [sliderRef]);
35
+ const next = useCallback(() => {
36
+ if (sliderRef.current) {
37
+ const slider = sliderRef.current as any;
38
+ slider.slickNext();
39
+ }
40
+ }, [sliderRef]);
41
+ return (
42
+ <SlickContainer className="brand-carousal">
43
+ <CarousalControl prev={prev} next={next} viewAllUrl={allEntryLink} />
44
+ <Slider {...{ ...defaultCarousalSettings, ...settings }} ref={sliderRef as any}>
45
+ {entries.map((entry, index) => {
46
+ return renderEntry(entry, index);
47
+ })}
48
+ </Slider>
49
+ </SlickContainer>
50
+ );
51
+ }
@@ -0,0 +1,18 @@
1
+ import NsBrandProductCard from '../card/NsBrandProductCard';
2
+
3
+ interface BrandType {
4
+ title: string;
5
+ backgroundImage: string;
6
+ brandImage: string;
7
+ }
8
+ export const RenderBrand = (entry: BrandType, index: number) => {
9
+ const { title, backgroundImage, brandImage } = entry;
10
+ return (
11
+ <NsBrandProductCard
12
+ key={title + index}
13
+ backgroundImageUrl={backgroundImage}
14
+ title={title}
15
+ brandImageUrl={brandImage}
16
+ />
17
+ );
18
+ };
@@ -0,0 +1,36 @@
1
+ import { useTheme } from '@mui/material/styles';
2
+
3
+ export const RightChevron = () => {
4
+ const { palette } = useTheme();
5
+ const strokeColor = palette.primary.main;
6
+
7
+ return (
8
+ <svg
9
+ data-testid="next-chevron"
10
+ viewBox="0 0 24 24"
11
+ preserveAspectRatio="xMidYMid meet"
12
+ focusable="false"
13
+ style={{ pointerEvents: 'none', display: 'block' }}
14
+ >
15
+ <g
16
+ id="icon-narrow-angle-right-24"
17
+ stroke="none"
18
+ strokeWidth="1"
19
+ fill="none"
20
+ fillRule="evenodd"
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ >
24
+ <g transform="translate(-196, -226)" stroke={strokeColor}>
25
+ <polyline
26
+ // eslint-disable-next-line max-len
27
+ transform="translate(209.000000, 237.500000) scale(-1.2, 1.2) rotate(-90.000000) translate(-209.000000, -237.500000)"
28
+ points="200 242 209 233 218 242"
29
+ ></polyline>
30
+ </g>
31
+ </g>
32
+ </svg>
33
+ );
34
+ };
35
+
36
+ export default RightChevron;
@@ -0,0 +1,2 @@
1
+ export { default as NsCarousal } from './NsCarousal';
2
+ export { default as NsDefaultCarousalSettings } from './slickSetting';
@@ -0,0 +1,61 @@
1
+ const settings = {
2
+ dots: true,
3
+ infinite: false,
4
+ speed: 2000,
5
+ slidesToShow: 6,
6
+ slidesToScroll: 1,
7
+ swipeToSlide: true,
8
+ responsive: [
9
+ {
10
+ breakpoint: 1800,
11
+ settings: {
12
+ slidesToShow: 5,
13
+ slidesToScroll: 1,
14
+ infinite: true,
15
+ dots: true,
16
+ },
17
+ },
18
+ {
19
+ breakpoint: 1480,
20
+ settings: {
21
+ slidesToShow: 4,
22
+ slidesToScroll: 1,
23
+ infinite: true,
24
+ dots: true,
25
+ },
26
+ },
27
+ {
28
+ breakpoint: 1160,
29
+ settings: {
30
+ slidesToShow: 3,
31
+ slidesToScroll: 1,
32
+ infinite: true,
33
+ dots: true,
34
+ },
35
+ },
36
+ {
37
+ breakpoint: 840,
38
+ settings: {
39
+ slidesToShow: 2,
40
+ slidesToScroll: 1,
41
+ initialSlide: 1,
42
+ },
43
+ },
44
+ {
45
+ breakpoint: 580,
46
+ settings: {
47
+ slidesToShow: 1.5,
48
+ slidesToScroll: 1,
49
+ },
50
+ },
51
+ {
52
+ breakpoint: 440,
53
+ settings: {
54
+ slidesToShow: 1,
55
+ slidesToScroll: 1,
56
+ },
57
+ },
58
+ ],
59
+ };
60
+
61
+ export default settings;
@@ -0,0 +1,21 @@
1
+ import { ComponentMeta, ComponentStory } from '@storybook/react';
2
+ import NsCarousal from '../NsCarousal';
3
+ import { brandEntries } from '../../card/stories/data';
4
+ import { RenderBrand } from '../RenderBrand';
5
+ import settings from '../slickSetting';
6
+
7
+ export default {
8
+ title: 'Components/NsCarousal/Brand',
9
+ component: NsCarousal,
10
+ argTypes: {},
11
+ } as ComponentMeta<typeof NsCarousal>;
12
+
13
+ const Template: ComponentStory<typeof NsCarousal> = (args: any) => <NsCarousal {...args} />;
14
+
15
+ export const Default = Template.bind({});
16
+ Default.args = {
17
+ entries: brandEntries,
18
+ renderEntry: RenderBrand,
19
+ allEntryLink: '',
20
+ settings: settings,
21
+ };
@@ -0,0 +1,10 @@
1
+ import { render } from '@testing-library/react';
2
+ import IconWrapper from '../IconWrapper';
3
+
4
+ describe('IconCover', () => {
5
+ it('render the component', () => {
6
+ const { container } = render(<IconWrapper className="icon-cover" onClick={() => {}} />);
7
+ const element = container.querySelector('.icon-cover');
8
+ expect(element).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,106 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import NsCarousal from '../NsCarousal';
3
+
4
+ const mockEntries = [
5
+ { id: 1, title: 'First entry' },
6
+ { id: 2, title: 'Second entry' },
7
+ { id: 3, title: 'Third entry' },
8
+ { id: 4, title: 'Fourth entry' },
9
+ { id: 5, title: 'Fifth entry' },
10
+ ];
11
+
12
+ const mockRenderEntry = (entry: any, index: number) => {
13
+ return <div key={index}>{entry.title}</div>;
14
+ };
15
+ const mockAllEntryLink = '/view-all';
16
+ const mockSettings = {
17
+ slidesToShow: 2,
18
+ slidesToScroll: 1,
19
+ };
20
+ describe('NsCarousal', () => {
21
+ it('should have prev and next chevron', () => {
22
+ const { getByTestId } = render(
23
+ <NsCarousal
24
+ entries={[]}
25
+ renderEntry={mockRenderEntry}
26
+ allEntryLink={mockAllEntryLink}
27
+ settings={mockSettings}
28
+ />
29
+ );
30
+ const prevSvg = getByTestId('prev-chevron');
31
+ const nextSvg = getByTestId('next-chevron');
32
+ expect(prevSvg).toBeInTheDocument();
33
+ expect(nextSvg).toBeInTheDocument();
34
+ });
35
+
36
+ it('should view all link with correct href and slides', () => {
37
+ const { container } = render(
38
+ <NsCarousal
39
+ entries={mockEntries}
40
+ renderEntry={mockRenderEntry}
41
+ allEntryLink={mockAllEntryLink}
42
+ settings={mockSettings}
43
+ />
44
+ );
45
+
46
+ const viewAllLink = screen.getByRole('link', { name: /view all/i });
47
+ const slides = container.querySelectorAll('.slick-slide');
48
+
49
+ expect(viewAllLink).toBeInTheDocument();
50
+ expect(viewAllLink).toHaveAttribute('href', mockAllEntryLink);
51
+ expect(slides).toHaveLength(mockEntries.length);
52
+ expect(slides[0]).toHaveTextContent('First entry');
53
+ expect(slides[1]).toHaveTextContent('Second entry');
54
+ expect(slides[2]).toHaveTextContent('Third entry');
55
+ });
56
+
57
+ it('should change slides when clicking on prev or next buttons', () => {
58
+ const { container } = render(
59
+ <NsCarousal
60
+ entries={mockEntries}
61
+ renderEntry={mockRenderEntry}
62
+ allEntryLink="/view-all"
63
+ settings={mockSettings}
64
+ />
65
+ );
66
+
67
+ let currentSlide = container.querySelector('.slick-slide.slick-current');
68
+ expect(currentSlide!.attributes['data-index' as any].value).toEqual('0');
69
+
70
+ /* this is library's button, not our button, it is used for changing slides,
71
+ clicking svg is not making any slide change in this test */
72
+ const nextButton = screen.getByRole('button', { name: /next/i });
73
+ fireEvent.click(nextButton);
74
+
75
+ let slickTrack = container.querySelector('.slick-track');
76
+ currentSlide = slickTrack!.querySelector('.slick-slide.slick-current');
77
+ expect(currentSlide!.attributes['data-index' as any].value).toEqual('1');
78
+
79
+ /* below code is commented because prev button is not working as expected in tests */
80
+ // const prevButton = screen.getByRole('button', { name: /prev/i })
81
+ // fireEvent.click(prevButton)
82
+ // screen.debug(prevButton)
83
+ // let slickList = container.querySelector('.slick-list')
84
+ // currentSlide = slickList!.querySelector('.slick-slide.slick-current')
85
+ // expect(currentSlide!.attributes['data-index' as any].value).toEqual('0')
86
+
87
+ // // currentSlide = screen.getByText(mockEntries[0].title)
88
+ // currentSlide = slickTrack!.querySelector('.slick-slide.slick-current')
89
+ // expect(currentSlide!.attributes['data-index' as any].value).toEqual('0')
90
+ });
91
+
92
+ it('should change slides when clicking on prev or next buttons', () => {
93
+ const { container } = render(
94
+ <NsCarousal
95
+ entries={mockEntries}
96
+ renderEntry={mockRenderEntry}
97
+ allEntryLink="/view-all"
98
+ settings={mockSettings}
99
+ />
100
+ );
101
+ let next = container.querySelector('.carousal-next-button');
102
+ let prev = container.querySelector('.carousal-prev-button');
103
+ fireEvent.click(next!);
104
+ fireEvent.click(prev!);
105
+ });
106
+ });
@@ -0,0 +1,22 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { RenderBrand } from '../RenderBrand';
3
+
4
+ const mockEntry = {
5
+ title: 'Epoch',
6
+ backgroundImage: 'https://media.nuskin.com/m/549aa3e5184434d8/webimage-brand-epoch-optweb.png',
7
+ brandImage: 'https://media.nuskin.com/m/63a454ff90d038b4/webimage-brand-epoch.png',
8
+ };
9
+
10
+ const mockIndex = 0;
11
+
12
+ describe('RenderBrand function', () => {
13
+ it('should render a BrandProductCard component with given props', () => {
14
+ render(RenderBrand(mockEntry, mockIndex));
15
+
16
+ const card = screen.getByRole('brand-card');
17
+ const title = screen.getByText(mockEntry.title);
18
+
19
+ expect(card).toBeInTheDocument();
20
+ expect(title).toBeInTheDocument();
21
+ });
22
+ });
@@ -15,7 +15,8 @@ const variantStyles = {
15
15
  'p-l': 'font-size: 18px; line-height: 24px',
16
16
  'p-m': 'font-size: 16px; line-height: 22px',
17
17
  'p-s': 'font-size: 14px; line-height: 20px',
18
- 'p-xs': 'font-size: 11px; line-height: 16px',
18
+ 'p-xs': 'font-size: 12px; line-height: 16px',
19
+ 'p-xxs': 'font-size: 11px; line-height: 14px',
19
20
  CTA: 'font-size: 11px; line-height: 16px',
20
21
  };
21
22
 
@@ -15,6 +15,7 @@ export type Variants =
15
15
  | 'p-m'
16
16
  | 'p-s'
17
17
  | 'p-xs'
18
+ | 'p-xxs'
18
19
  | 'CTA';
19
20
 
20
21
  export type TypographyProps = {
@@ -23,5 +24,6 @@ export type TypographyProps = {
23
24
  fontStyle?: 'italic' | 'normal';
24
25
  weight?: 'bold' | 'normal' | 'light';
25
26
  color?: PaletteColorNames;
27
+ component?: 'div' | 'span' | 'p'
26
28
  className?: string;
27
- } & AdditionalProps;
29
+ } & AdditionalProps;