@nuskin/foundation-ui-components 1.0.0-cft-35.7 → 1.0.0-cft-35.9
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.
- package/CHANGELOG.md +35 -0
- package/package.json +1 -1
- package/src/common/banner/NsBanner.spec.tsx +19 -0
- package/src/common/banner/NsBanner.stories.tsx +42 -0
- package/src/common/banner/NsBanner.styled.tsx +23 -0
- package/src/common/banner/NsBanner.tsx +33 -0
- package/src/common/banner/index.ts +1 -0
- package/src/common/banner/types.d.ts +14 -0
- package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +21 -0
- package/src/common/breadcrumb/NsBreadcrumb.stories.tsx +30 -0
- package/src/common/breadcrumb/NsBreadcrumb.styled.tsx +29 -0
- package/src/common/breadcrumb/NsBreadcrumb.tsx +61 -0
- package/src/common/breadcrumb/index.ts +1 -0
- package/src/common/breadcrumb/types.ts +13 -0
- package/src/common/dropdown/NsDropdown.spec.tsx +125 -0
- package/src/common/dropdown/NsDropdown.styled.tsx +64 -0
- package/src/common/dropdown/NsDropdown.tsx +67 -0
- package/src/common/dropdown/NsDropdownOption.tsx +24 -0
- package/src/common/dropdown/index.ts +3 -0
- package/src/common/dropdown/stories/NsDropdown.stories.tsx +63 -0
- package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +63 -0
- package/src/common/dropdown/types.d.ts +23 -0
- package/src/common/icon/IconMap.ts +8 -0
- package/src/common/icon/NsIcon.stories.tsx +2 -0
- package/src/common/icon/NsIcon.styled.tsx +2 -2
- package/src/common/icon/NsIcon.tsx +10 -3
- package/src/common/icon/icons/ArrowDown.tsx +18 -0
- package/src/common/icon/icons/ArrowUp.tsx +18 -0
- package/src/common/icon/icons/SlashIcon.tsx +17 -0
- package/src/common/icon/icons/home.tsx +16 -0
- package/src/common/icon/types.d.ts +7 -1
- package/src/common/index.ts +8 -0
- package/src/common/nav-tab/NsNavTab.tsx +3 -3
- package/src/common/typography/NsTypography.tsx +2 -0
- package/src/common/typography/Typography.styled.tsx +3 -4
- package/src/common/typography/types.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
# [1.0.0-cft-35.9](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/compare/v1.0.0-cft-35.8...v1.0.0-cft-35.9) (2023-04-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### New
|
|
5
|
+
|
|
6
|
+
* Breadcrum ui ([33d6cc8](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/33d6cc8eb2a535965582b91e26f0196f17b930d8))
|
|
7
|
+
* NsBreadcrumb ui ([e40b089](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/e40b089376db9cf37af7d59b2022972f17c333ad))
|
|
8
|
+
|
|
9
|
+
# [1.0.0-cft-35.8](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/compare/v1.0.0-cft-35.7...v1.0.0-cft-35.8) (2023-04-10)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Fis
|
|
13
|
+
|
|
14
|
+
* format code ([8587727](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/85877278980afed666a55eabd7ba8a86ba14f236))
|
|
15
|
+
|
|
16
|
+
### Fix
|
|
17
|
+
|
|
18
|
+
* button import name chnaged ([47522e3](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/47522e3f8c2e940ac720d204d69448a0b46442ce))
|
|
19
|
+
* conflict ([971e4a5](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/971e4a50c88a545f2890a62349f2ebd06759384a))
|
|
20
|
+
* merge issue ([67a72ce](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/67a72ce440c27b88a339e68f4c74a39c1c23b9a1))
|
|
21
|
+
* Typography and banner ui updates ([99a8322](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/99a8322c968c9b31dca4bc1d0d3652d47a65338b))
|
|
22
|
+
|
|
23
|
+
### FIX
|
|
24
|
+
|
|
25
|
+
* build issue ([935b132](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/935b1322d47841eb554ad514cb9880b9e9f48e52))
|
|
26
|
+
* typo fix ([32d5fa7](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/32d5fa7dc09ec60ce3698b6c638ea9351405ef28))
|
|
27
|
+
|
|
28
|
+
### New
|
|
29
|
+
|
|
30
|
+
* Nsbaner Ui #CFT-118 ([d1896fd](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/d1896fd00fc1c7cc22a0db931507ec6096e7fdef)), closes [#CFT-118](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/issues/CFT-118)
|
|
31
|
+
|
|
32
|
+
### Update
|
|
33
|
+
|
|
34
|
+
* Code format ([7244ea1](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/commit/7244ea1fcef2bdb75dd45d7af04809e763cff8dd))
|
|
35
|
+
|
|
1
36
|
# [1.0.0-cft-35.7](https://code.tls.nuskin.io/ns-am/content-foundation/foundation-ui-components/compare/v1.0.0-cft-35.6...v1.0.0-cft-35.7) (2023-04-10)
|
|
2
37
|
|
|
3
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/foundation-ui-components",
|
|
3
|
-
"version": "1.0.0-cft-35.
|
|
3
|
+
"version": "1.0.0-cft-35.9",
|
|
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",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import NsBanner from './';
|
|
4
|
+
|
|
5
|
+
describe('Hero component', () => {
|
|
6
|
+
it('renders banner image and title', () => {
|
|
7
|
+
const childText = 'Welcome to My Website';
|
|
8
|
+
const image = 'https://example.com/banner.jpg';
|
|
9
|
+
const { getByAltText, getByText } = render(
|
|
10
|
+
<NsBanner imageUrl={image} altText="Banner image">
|
|
11
|
+
<h1>{childText}</h1>
|
|
12
|
+
</NsBanner>
|
|
13
|
+
);
|
|
14
|
+
const childElement = getByText(childText);
|
|
15
|
+
const imageElement = getByAltText('Banner image');
|
|
16
|
+
expect(childElement).toBeInTheDocument();
|
|
17
|
+
expect(imageElement).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author [Vijayaprathap Kumar] (vpkumar@nuskin.com)
|
|
3
|
+
* NsBanner.stories.tsx
|
|
4
|
+
* @description NsBanner component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
9
|
+
|
|
10
|
+
import Banner from './';
|
|
11
|
+
import Typography from '../typography';
|
|
12
|
+
import Button from '../button/NsButton';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
title: 'Components',
|
|
16
|
+
component: Banner,
|
|
17
|
+
decorators: [(story) => <div style={{ textAlign: 'center', marginTop: '20px' }}>{story()}</div>],
|
|
18
|
+
argTypes: {
|
|
19
|
+
children: {
|
|
20
|
+
control: 'text', // use the text control to edit children prop
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
imageUrl: 'https://media.nuskin.com/m/e2f6d26058b2e7d/webimage-us-collagen-strawberry-hompage-banner.png',
|
|
25
|
+
altText: 'NsBanner',
|
|
26
|
+
alignment: 'center',
|
|
27
|
+
justify: 'start',
|
|
28
|
+
children: (
|
|
29
|
+
<div>
|
|
30
|
+
<Typography className="w-80" variant="h1" colorOverride="#000000">
|
|
31
|
+
Limited time offers on LumiSpa iO cleansers and bundles!
|
|
32
|
+
</Typography>
|
|
33
|
+
<Typography variant="p-m" className="my-5" colorOverride="#000000">
|
|
34
|
+
Learn more about what I do and how I can help you
|
|
35
|
+
</Typography>
|
|
36
|
+
<Button variant="contained">Contact me</Button>
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
} as ComponentMeta<typeof Banner>;
|
|
41
|
+
|
|
42
|
+
export const NsBanner: ComponentStory<typeof Banner> = (args) => <Banner {...args} />;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { styled } from '@nuskin/foundation-theme';
|
|
2
|
+
import { AlignmentType } from './types';
|
|
3
|
+
|
|
4
|
+
export const StyledBanner = styled.section`
|
|
5
|
+
position: relative;
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: space-evenly;
|
|
8
|
+
img {
|
|
9
|
+
width: 100%;
|
|
10
|
+
object-fit: cover;
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const StyledHeroText = styled.div<{ $justify: AlignmentType; $alignment: AlignmentType }>`
|
|
15
|
+
height: 100%;
|
|
16
|
+
display: flex;
|
|
17
|
+
width: 100%;
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 0;
|
|
20
|
+
justify-content: ${({ $justify }) => $justify};
|
|
21
|
+
align-items: ${({ $alignment }) => $alignment};
|
|
22
|
+
text-align: left;
|
|
23
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author [Vijayaprathap Kumar] (vpkumar@nuskin.com)
|
|
3
|
+
* NsBanner.tsx
|
|
4
|
+
* @description NsBanner component with a image background
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { PropsWithChildren } from 'react';
|
|
8
|
+
|
|
9
|
+
import { NsBannerProps } from './types';
|
|
10
|
+
import { StyledBanner, StyledHeroText } from './NsBanner.styled';
|
|
11
|
+
|
|
12
|
+
const NsBanner = (props: PropsWithChildren<NsBannerProps>) => {
|
|
13
|
+
const {
|
|
14
|
+
children,
|
|
15
|
+
imageUrl = '',
|
|
16
|
+
altText = 'NsBanner',
|
|
17
|
+
alignment = 'center',
|
|
18
|
+
justify = 'start',
|
|
19
|
+
className = '',
|
|
20
|
+
additionalProps = {},
|
|
21
|
+
} = props;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<StyledBanner className={className}>
|
|
25
|
+
<img src={imageUrl} alt={altText} id="bgImage" {...additionalProps} />
|
|
26
|
+
<StyledHeroText $alignment={alignment} $justify={justify}>
|
|
27
|
+
<div className="mx-14">{children}</div>
|
|
28
|
+
</StyledHeroText>
|
|
29
|
+
</StyledBanner>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default NsBanner;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './NsBanner';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { AdditionalProps } from '../common.types';
|
|
4
|
+
|
|
5
|
+
export type AlignmentType = 'start' | 'center' | 'end';
|
|
6
|
+
|
|
7
|
+
export type NsBannerProps = {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
imageUrl: string;
|
|
10
|
+
altText?: string;
|
|
11
|
+
alignment?: AlignmentType;
|
|
12
|
+
justify?: AlignmentType;
|
|
13
|
+
className?: string;
|
|
14
|
+
} & AdditionalProps;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import NsBreacrumb from './';
|
|
4
|
+
import { ItemProps } from './types';
|
|
5
|
+
|
|
6
|
+
const breadcrumbItems: ItemProps[] = [
|
|
7
|
+
{ label: 'Home', href: '/', icon: 'home' },
|
|
8
|
+
{ label: 'Products', href: '/products' },
|
|
9
|
+
{ label: 'Shirts', href: '/products/shirts' },
|
|
10
|
+
{ label: 'Blue shirt', href: '/products/shirts/blue' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
describe('Breadcrumb', () => {
|
|
14
|
+
it('renders breadcrumb items with navigation icons', () => {
|
|
15
|
+
const { getByText } = render(<NsBreacrumb items={breadcrumbItems} />);
|
|
16
|
+
expect(getByText('Home')).toBeInTheDocument();
|
|
17
|
+
expect(getByText('Products')).toBeInTheDocument();
|
|
18
|
+
expect(getByText('Shirts')).toBeInTheDocument();
|
|
19
|
+
expect(getByText('Blue shirt')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author [Vijayaprathap Kumar] (vpkumar@nuskin.com)
|
|
3
|
+
* NsBanner.stories.tsx
|
|
4
|
+
* @description NsBanner component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
9
|
+
|
|
10
|
+
import Breadcrumb from './';
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
title: 'Components',
|
|
14
|
+
component: Breadcrumb,
|
|
15
|
+
decorators: [(story) => <div style={{ textAlign: 'center', marginTop: '20px' }}>{story()}</div>],
|
|
16
|
+
argTypes: {
|
|
17
|
+
iconVisibility: { control: 'boolean' },
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
items: [
|
|
21
|
+
{ label: 'Home', href: 'home', icon: 'home' },
|
|
22
|
+
{ label: 'Call to action', href: '/products' },
|
|
23
|
+
{ label: 'Call to action', href: '/products/shirts' },
|
|
24
|
+
{ label: 'Product', href: '' },
|
|
25
|
+
],
|
|
26
|
+
iconVisibility: false,
|
|
27
|
+
},
|
|
28
|
+
} as ComponentMeta<typeof Breadcrumb>;
|
|
29
|
+
|
|
30
|
+
export const NsBreadcrumb: ComponentStory<typeof Breadcrumb> = (args) => <Breadcrumb {...args} />;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { styled } from '@nuskin/foundation-theme';
|
|
2
|
+
|
|
3
|
+
export const BreadcrumbContainer = styled.nav`
|
|
4
|
+
margin-bottom: 10px;
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
export const BreadcrumbList = styled.ol`
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
padding: 0;
|
|
11
|
+
margin: 0;
|
|
12
|
+
list-style: none;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const BreadcrumbItem = styled.li`
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
:not(:first-child) {
|
|
19
|
+
margin-left: 12px;
|
|
20
|
+
}
|
|
21
|
+
& a {
|
|
22
|
+
display: flex;
|
|
23
|
+
gap: 8px;
|
|
24
|
+
margin-right: 12px;
|
|
25
|
+
}
|
|
26
|
+
.slashIcon {
|
|
27
|
+
transform: rotate(20deg);
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author [Vijayaprathap Kumar] (vpkumar@nuskin.com)
|
|
3
|
+
* NsBreadcrumb.tsx
|
|
4
|
+
* @description NsBreadcrumb component with a navigation items array props
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PropsWithChildren } from 'react';
|
|
8
|
+
|
|
9
|
+
import { BreadcrumbContainer, BreadcrumbItem, BreadcrumbList } from './NsBreadcrumb.styled';
|
|
10
|
+
import { NsIcon } from '../icon';
|
|
11
|
+
import NsLink from '../link/NsLink';
|
|
12
|
+
import Typography from '../typography';
|
|
13
|
+
import { NsBredacrumbProps, ItemProps } from './types';
|
|
14
|
+
import { useTheme } from '@emotion/react';
|
|
15
|
+
import { ColorUtils } from '@nuskin/foundation-theme';
|
|
16
|
+
|
|
17
|
+
const NsBreadcrumb = (props: PropsWithChildren<NsBredacrumbProps>) => {
|
|
18
|
+
const { items, iconVisibility } = props;
|
|
19
|
+
|
|
20
|
+
const theme = useTheme();
|
|
21
|
+
const neutral80 = ColorUtils.getAppColor(theme, 'neutral', '80') as string;
|
|
22
|
+
const neutral60 = ColorUtils.getAppColor(theme, 'neutral', '60') as string;
|
|
23
|
+
|
|
24
|
+
const getLabel = (item: ItemProps) => {
|
|
25
|
+
if (item?.href) {
|
|
26
|
+
return (
|
|
27
|
+
<Typography variant="h6" colorOverride={neutral60}>
|
|
28
|
+
<NsLink href={item.href} underlineOnHover>
|
|
29
|
+
{iconVisibility && item.icon ? (
|
|
30
|
+
<NsIcon name={item.icon} size="small" testId="nav-icon" colorOverride={neutral60} />
|
|
31
|
+
) : null}
|
|
32
|
+
{item.label}
|
|
33
|
+
</NsLink>
|
|
34
|
+
</Typography>
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
return (
|
|
38
|
+
<Typography variant="h6" colorOverride={neutral80}>
|
|
39
|
+
{item.label}
|
|
40
|
+
</Typography>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<BreadcrumbContainer aria-label="breadcrumb">
|
|
47
|
+
<BreadcrumbList>
|
|
48
|
+
{items?.map((item: ItemProps, index: number) => (
|
|
49
|
+
<BreadcrumbItem key={item.label}>
|
|
50
|
+
{getLabel(item)}
|
|
51
|
+
{index !== items.length - 1 ? (
|
|
52
|
+
<NsIcon className="slashIcon" name={'slash'} size="small" colorOverride={neutral60} />
|
|
53
|
+
) : null}
|
|
54
|
+
</BreadcrumbItem>
|
|
55
|
+
))}
|
|
56
|
+
</BreadcrumbList>
|
|
57
|
+
</BreadcrumbContainer>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default NsBreadcrumb;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './NsBreadcrumb';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AdditionalProps } from '../common.types';
|
|
2
|
+
import { NsIconNameType } from '../icon';
|
|
3
|
+
|
|
4
|
+
export interface NsBredacrumbProps {
|
|
5
|
+
items?: ItemProps[];
|
|
6
|
+
iconVisibility?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ItemProps = {
|
|
10
|
+
label?: string;
|
|
11
|
+
href?: string;
|
|
12
|
+
icon?: NsIconNameType;
|
|
13
|
+
} & AdditionalProps;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen, waitFor } from '../../../test-utils';
|
|
3
|
+
import NsDropdown from './NsDropdown';
|
|
4
|
+
import NsDropdownOption from './NsDropdownOption';
|
|
5
|
+
|
|
6
|
+
function createDropdownOptions(options: { value: string; label: string }[]) {
|
|
7
|
+
return options.map((option) => <NsDropdownOption key={option.value} value={option.value} label={option.label} />);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('NsDropdown', () => {
|
|
11
|
+
const onSelect = jest.fn();
|
|
12
|
+
const selectedValue = '';
|
|
13
|
+
const selectedLabel = '';
|
|
14
|
+
|
|
15
|
+
const options = [
|
|
16
|
+
{ value: 'Option 1', label: 'Option 1' },
|
|
17
|
+
{ value: 'Option 2', label: 'Option 2' },
|
|
18
|
+
{ value: 'Option 3', label: 'Option 3' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
it('renders the placeholder text when no value is selected', () => {
|
|
22
|
+
render(
|
|
23
|
+
<NsDropdown
|
|
24
|
+
placeholder="Select an option"
|
|
25
|
+
selectedValue={selectedValue}
|
|
26
|
+
selectedLabel={selectedLabel}
|
|
27
|
+
onSelect={onSelect}
|
|
28
|
+
>
|
|
29
|
+
{createDropdownOptions(options)}
|
|
30
|
+
</NsDropdown>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByText('Select an option')).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders the selected value label', () => {
|
|
37
|
+
const selectedLabel = 'Option 1';
|
|
38
|
+
render(
|
|
39
|
+
<NsDropdown selectedValue="Option 1" selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
40
|
+
{createDropdownOptions(options)}
|
|
41
|
+
</NsDropdown>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(screen.getByText(selectedLabel)).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('dropdown opens and closes on click', () => {
|
|
48
|
+
render(
|
|
49
|
+
<NsDropdown selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect}>
|
|
50
|
+
{createDropdownOptions(options)}
|
|
51
|
+
</NsDropdown>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const dropdownHead = screen.getByTestId('dropdown-head');
|
|
55
|
+
fireEvent.click(dropdownHead);
|
|
56
|
+
|
|
57
|
+
expect(screen.getByTestId('option-Option 1')).toBeInTheDocument();
|
|
58
|
+
expect(screen.getByTestId('option-Option 2')).toBeInTheDocument();
|
|
59
|
+
expect(screen.getByTestId('option-Option 3')).toBeInTheDocument();
|
|
60
|
+
|
|
61
|
+
fireEvent.click(dropdownHead);
|
|
62
|
+
expect(screen.queryByTestId('option-Option 1')).not.toBeInTheDocument();
|
|
63
|
+
expect(screen.queryByTestId('option-Option 2')).not.toBeInTheDocument();
|
|
64
|
+
expect(screen.queryByTestId('option-Option 3')).not.toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('dropdown closes on mouse leave when closeOnMouseLeave is set to true', async () => {
|
|
68
|
+
render(
|
|
69
|
+
<NsDropdown
|
|
70
|
+
closeOnMouseLeave
|
|
71
|
+
selectedValue={selectedValue}
|
|
72
|
+
selectedLabel={selectedLabel}
|
|
73
|
+
onSelect={onSelect}
|
|
74
|
+
>
|
|
75
|
+
{createDropdownOptions(options)}
|
|
76
|
+
</NsDropdown>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const dropdownHead = screen.getByTestId('dropdown-head');
|
|
80
|
+
fireEvent.click(dropdownHead);
|
|
81
|
+
|
|
82
|
+
const dropdownList = screen.getByTestId('dropdown-list');
|
|
83
|
+
fireEvent.mouseLeave(dropdownList);
|
|
84
|
+
|
|
85
|
+
await waitFor(() => {
|
|
86
|
+
expect(screen.queryByTestId('option-Option 1')).not.toBeInTheDocument();
|
|
87
|
+
expect(screen.queryByTestId('option-Option 2')).not.toBeInTheDocument();
|
|
88
|
+
expect(screen.queryByTestId('option-Option 3')).not.toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it('dropdown does not close on mouse leave when closeOnMouseLeave is set to false', () => {
|
|
92
|
+
render(
|
|
93
|
+
<NsDropdown
|
|
94
|
+
selectedValue={selectedValue}
|
|
95
|
+
selectedLabel={selectedLabel}
|
|
96
|
+
onSelect={onSelect}
|
|
97
|
+
closeOnMouseLeave={false}
|
|
98
|
+
>
|
|
99
|
+
{createDropdownOptions(options)}
|
|
100
|
+
</NsDropdown>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const dropdownHead = screen.getByTestId('dropdown-head');
|
|
104
|
+
fireEvent.click(dropdownHead);
|
|
105
|
+
|
|
106
|
+
const dropdownList = screen.getByTestId('dropdown-list');
|
|
107
|
+
fireEvent.mouseLeave(dropdownList);
|
|
108
|
+
|
|
109
|
+
expect(screen.getByTestId('option-Option 1')).toBeInTheDocument();
|
|
110
|
+
expect(screen.getByTestId('option-Option 2')).toBeInTheDocument();
|
|
111
|
+
expect(screen.getByTestId('option-Option 3')).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
it('dropdown does not open when disabled', () => {
|
|
114
|
+
render(
|
|
115
|
+
<NsDropdown selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={onSelect} disabled>
|
|
116
|
+
{createDropdownOptions(options)}
|
|
117
|
+
</NsDropdown>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const dropdownHead = screen.getByTestId('dropdown-head');
|
|
121
|
+
fireEvent.click(dropdownHead);
|
|
122
|
+
|
|
123
|
+
expect(screen.queryByTestId('dropdown-list')).not.toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { styled, ColorUtils, FoundationTheme } from '@nuskin/foundation-theme';
|
|
2
|
+
const { getPaletteColor, getAppColor } = ColorUtils;
|
|
3
|
+
|
|
4
|
+
const isLight = (theme: FoundationTheme.Theme) => {
|
|
5
|
+
const { palette = { primary: { main: '', contrastText: '' } } } = theme;
|
|
6
|
+
return palette.primary.main === getAppColor(theme, 'primary', 'black');
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const DropdownContainer = styled.div<{ disabled: boolean }>`
|
|
10
|
+
position: relative;
|
|
11
|
+
display: inline-block;
|
|
12
|
+
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const DropdownHead = styled.div<{ type: string; disabled: boolean }>`
|
|
16
|
+
cursor: ${({ disabled }) => (disabled ? 'inherit' : 'pointer')};
|
|
17
|
+
padding: 5px 10px;
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
align-items: center;
|
|
21
|
+
border: ${({ type, theme }) =>
|
|
22
|
+
type === 'outlined' ? `1px solid ${getAppColor(theme, 'primary', 'black')}` : 'none'};
|
|
23
|
+
color: ${({ theme }) => getPaletteColor(theme, 'primary')};
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
export const DropdownButton = styled.div`
|
|
27
|
+
padding-right: 10px;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export const DropdownList = styled.div<{ borderColor: FoundationTheme.PaletteColors }>`
|
|
31
|
+
position: absolute;
|
|
32
|
+
z-index: 100;
|
|
33
|
+
border: 1px solid ${({ borderColor, theme }) => getPaletteColor(theme, borderColor)};
|
|
34
|
+
padding: 0;
|
|
35
|
+
margin: 0;
|
|
36
|
+
width: 100%;
|
|
37
|
+
color: ${({ theme }) => getPaletteColor(theme, 'primary')};
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const ListItemContainer = styled.div<{ showListDivider: boolean }>`
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
padding: 5px 0;
|
|
43
|
+
border-bottom: 1px solid
|
|
44
|
+
${({ showListDivider, theme }) =>
|
|
45
|
+
showListDivider
|
|
46
|
+
? isLight(theme)
|
|
47
|
+
? getAppColor(theme, 'neutral', '30')
|
|
48
|
+
: getAppColor(theme, 'neutral', '20')
|
|
49
|
+
: 'none'};
|
|
50
|
+
&:hover {
|
|
51
|
+
background-color: ${({ theme }) =>
|
|
52
|
+
isLight(theme) ? getAppColor(theme, 'accentColorB', '10') : getAppColor(theme, 'accentColorB', '50')};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&:last-child {
|
|
56
|
+
border-bottom: none;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
export const ListItem = styled.span<{ isSelected: boolean }>`
|
|
61
|
+
padding: 5px 10px;
|
|
62
|
+
border-left: ${({ isSelected, theme }) =>
|
|
63
|
+
isSelected ? `2px solid ${getPaletteColor(theme, 'primary')}` : 'none'};
|
|
64
|
+
`;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { DropdownContainer, DropdownButton, DropdownList, DropdownHead } from './NsDropdown.styled';
|
|
3
|
+
import { NsIcon } from '../icon';
|
|
4
|
+
import { DropdownProps } from './types';
|
|
5
|
+
|
|
6
|
+
const NsDropdown: React.FC<DropdownProps> = ({
|
|
7
|
+
selectedValue,
|
|
8
|
+
selectedLabel,
|
|
9
|
+
onSelect,
|
|
10
|
+
children,
|
|
11
|
+
disabled = false,
|
|
12
|
+
type = 'outlined',
|
|
13
|
+
placeholder = 'Select',
|
|
14
|
+
dropdownBorderColor = 'accentColorB',
|
|
15
|
+
closeOnMouseLeave = false,
|
|
16
|
+
showListDivider = true,
|
|
17
|
+
}) => {
|
|
18
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
19
|
+
const [value, setValue] = useState(selectedValue || '');
|
|
20
|
+
|
|
21
|
+
const handleToggle = () => {
|
|
22
|
+
if (disabled) return;
|
|
23
|
+
setIsOpen(!isOpen);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const closeDropdown = () => {
|
|
27
|
+
if (closeOnMouseLeave) {
|
|
28
|
+
setIsOpen(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleSelect = (input: string) => {
|
|
33
|
+
onSelect(input);
|
|
34
|
+
setIsOpen(false);
|
|
35
|
+
setValue(input);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<DropdownContainer disabled={disabled}>
|
|
40
|
+
<DropdownHead data-testid="dropdown-head" disabled={disabled} type={type} onClick={handleToggle}>
|
|
41
|
+
<DropdownButton>{selectedLabel || placeholder}</DropdownButton>
|
|
42
|
+
<NsIcon testId="chevron-icon" name={isOpen ? 'arrowDown' : 'arrowUp'} color="primary" />
|
|
43
|
+
</DropdownHead>
|
|
44
|
+
{isOpen ? (
|
|
45
|
+
<DropdownList
|
|
46
|
+
data-testid="dropdown-list"
|
|
47
|
+
onMouseLeave={closeDropdown}
|
|
48
|
+
borderColor={dropdownBorderColor}
|
|
49
|
+
>
|
|
50
|
+
{React.Children.map(children, (child) => {
|
|
51
|
+
if (React.isValidElement(child)) {
|
|
52
|
+
return React.cloneElement(child, {
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
onSelect: handleSelect,
|
|
55
|
+
selectedValue: value,
|
|
56
|
+
showListDivider,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
})}
|
|
61
|
+
</DropdownList>
|
|
62
|
+
) : null}
|
|
63
|
+
</DropdownContainer>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default NsDropdown;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ListItem, ListItemContainer } from './NsDropdown.styled';
|
|
3
|
+
import { DropdownOptionProps } from './types';
|
|
4
|
+
|
|
5
|
+
export const NsDropdownOption: React.FC<DropdownOptionProps> = ({
|
|
6
|
+
value,
|
|
7
|
+
label,
|
|
8
|
+
onSelect,
|
|
9
|
+
selectedValue,
|
|
10
|
+
showListDivider = true,
|
|
11
|
+
}) => {
|
|
12
|
+
const handleClick = () => {
|
|
13
|
+
if (onSelect && typeof onSelect === 'function') {
|
|
14
|
+
onSelect(value);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<ListItemContainer data-testid={`option-${value}`} showListDivider={showListDivider} onClick={handleClick}>
|
|
20
|
+
<ListItem isSelected={value === selectedValue}>{label}</ListItem>
|
|
21
|
+
</ListItemContainer>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
export default NsDropdownOption;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
3
|
+
import NsDropdown from '../NsDropdown';
|
|
4
|
+
import NsDropdownOption from '../NsDropdownOption';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: 'Components/NsDropdown',
|
|
8
|
+
decorators: [
|
|
9
|
+
(story) => <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>{story()}</div>,
|
|
10
|
+
],
|
|
11
|
+
component: NsDropdown,
|
|
12
|
+
argTypes: {
|
|
13
|
+
type: { control: 'select', options: ['unfilled', 'outlined'] },
|
|
14
|
+
disabled: { control: 'boolean' },
|
|
15
|
+
dropdownBorderColor: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['primary', 'secondary', 'accentColorA', 'accentColorB', 'accentColorC'],
|
|
18
|
+
},
|
|
19
|
+
placeholder: { control: 'text' },
|
|
20
|
+
closeOnMouseLeave: { control: 'boolean' },
|
|
21
|
+
showListDivider: { control: 'boolean' },
|
|
22
|
+
},
|
|
23
|
+
parameters: {
|
|
24
|
+
options: [
|
|
25
|
+
{ label: 'Option 1', value: 'Option 1' },
|
|
26
|
+
{ label: 'Option 2', value: 'Option 2' },
|
|
27
|
+
{ label: 'Option 3', value: 'Option 3' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
type: 'outlined',
|
|
32
|
+
disabled: false,
|
|
33
|
+
dropdownBorderColor: 'primary',
|
|
34
|
+
closeOnMouseLeave: false,
|
|
35
|
+
showListDivider: true,
|
|
36
|
+
selectedValue: '',
|
|
37
|
+
selectedLabel: '',
|
|
38
|
+
},
|
|
39
|
+
} as ComponentMeta<typeof NsDropdown>;
|
|
40
|
+
|
|
41
|
+
export const Dropdown: ComponentStory<typeof NsDropdown> = (args, context) => {
|
|
42
|
+
const [selectedLabel, setSelectedLabel] = useState(args.selectedLabel);
|
|
43
|
+
const [selectedValue, setSelectedValue] = useState(args.selectedValue);
|
|
44
|
+
|
|
45
|
+
const options = context.parameters.options;
|
|
46
|
+
const handleSelect = (value: string) => {
|
|
47
|
+
setSelectedValue(value);
|
|
48
|
+
options.find((option: any) => {
|
|
49
|
+
if (option.value === value) {
|
|
50
|
+
setSelectedLabel(option.label);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
console.log('Selected:', value);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<NsDropdown {...args} selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={handleSelect}>
|
|
58
|
+
{options.map((option: any) => (
|
|
59
|
+
<NsDropdownOption key={option.value} value={option.value} label={option.label} />
|
|
60
|
+
))}
|
|
61
|
+
</NsDropdown>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
3
|
+
import NsDropdown from '../NsDropdown';
|
|
4
|
+
import NsDropdownOption from '../NsDropdownOption';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: 'Components/NsDropdown',
|
|
8
|
+
decorators: [
|
|
9
|
+
(story) => <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>{story()}</div>,
|
|
10
|
+
],
|
|
11
|
+
component: NsDropdown,
|
|
12
|
+
argTypes: {
|
|
13
|
+
type: { control: 'select', options: ['unfilled', 'outlined'] },
|
|
14
|
+
disabled: { control: 'boolean' },
|
|
15
|
+
dropdownBorderColor: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['primary', 'secondary', 'accentColorA', 'accentColorB', 'accentColorC'],
|
|
18
|
+
},
|
|
19
|
+
placeholder: { control: 'text' },
|
|
20
|
+
closeOnMouseLeave: { control: 'boolean' },
|
|
21
|
+
showListDivider: { control: 'boolean' },
|
|
22
|
+
},
|
|
23
|
+
parameters: {
|
|
24
|
+
options: [
|
|
25
|
+
{ label: 'English', value: 'english', country: 'United States (US)' },
|
|
26
|
+
{ label: 'French', value: 'french', country: 'France (FR)' },
|
|
27
|
+
{ label: 'German', value: 'german', country: 'Germany (DE)' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
selectedValue: 'english',
|
|
32
|
+
selectedLabel: 'United States (US) | English',
|
|
33
|
+
type: 'unfilled',
|
|
34
|
+
disabled: false,
|
|
35
|
+
dropdownBorderColor: 'accentColorB',
|
|
36
|
+
closeOnMouseLeave: true,
|
|
37
|
+
showListDivider: true,
|
|
38
|
+
},
|
|
39
|
+
} as ComponentMeta<typeof NsDropdown>;
|
|
40
|
+
|
|
41
|
+
export const DropdownCountry: ComponentStory<typeof NsDropdown> = (args, context) => {
|
|
42
|
+
const [selectedLabel, setSelectedLabel] = useState(args.selectedLabel);
|
|
43
|
+
const [selectedValue, setSelectedValue] = useState(args.selectedValue);
|
|
44
|
+
|
|
45
|
+
const options = context.parameters.options;
|
|
46
|
+
const handleSelect = (value: string) => {
|
|
47
|
+
setSelectedValue(value);
|
|
48
|
+
options.find((option: any) => {
|
|
49
|
+
if (option.value === value) {
|
|
50
|
+
setSelectedLabel(`${option.country} | ${option.label}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
console.log('Selected:', value);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<NsDropdown {...args} selectedValue={selectedValue} selectedLabel={selectedLabel} onSelect={handleSelect}>
|
|
58
|
+
{options.map((option: any) => (
|
|
59
|
+
<NsDropdownOption key={option.value} value={option.value} label={option.label} />
|
|
60
|
+
))}
|
|
61
|
+
</NsDropdown>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { FoundationTheme } from '@nuskin/foundation-theme';
|
|
3
|
+
|
|
4
|
+
export interface DropdownProps {
|
|
5
|
+
selectedValue: string;
|
|
6
|
+
selectedLabel: string;
|
|
7
|
+
onSelect: (arg: string) => void; // eslint-disable-line
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
type?: 'outlined' | 'unfilled';
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
dropdownBorderColor?: FoundationTheme.PaletteColors;
|
|
13
|
+
closeOnMouseLeave?: boolean;
|
|
14
|
+
showListDivider?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DropdownOptionProps {
|
|
18
|
+
value: string;
|
|
19
|
+
label: string;
|
|
20
|
+
selectedValue?: string;
|
|
21
|
+
onSelect?: (arg: string) => void; // eslint-disable-line
|
|
22
|
+
showListDivider?: boolean;
|
|
23
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ArrowDown from './icons/ArrowDown';
|
|
2
|
+
import ArrowUp from './icons/ArrowUp';
|
|
1
3
|
import Cart from './icons/Cart';
|
|
2
4
|
import CartBadge from './icons/CartBadge';
|
|
3
5
|
import InfoFilled from './icons/InfoFilled';
|
|
@@ -7,8 +9,12 @@ import CheckVerified from './icons/CheckVerified';
|
|
|
7
9
|
import SuccessIcon from './icons/SuccessIcon';
|
|
8
10
|
import ErrorIcon from './icons/ErrorIcon';
|
|
9
11
|
import TrashIcon from './icons/TrashIcon';
|
|
12
|
+
import SlashIcon from './icons/SlashIcon';
|
|
13
|
+
import Home from './icons/home';
|
|
10
14
|
|
|
11
15
|
const IconVariants = {
|
|
16
|
+
arrowDown: ArrowDown,
|
|
17
|
+
arrowUp: ArrowUp,
|
|
12
18
|
cart: Cart,
|
|
13
19
|
cartBadge: CartBadge,
|
|
14
20
|
checkShield: CheckShield,
|
|
@@ -18,6 +24,8 @@ const IconVariants = {
|
|
|
18
24
|
infoOutlined: InfoOutlined,
|
|
19
25
|
success: SuccessIcon,
|
|
20
26
|
trash: TrashIcon,
|
|
27
|
+
slash: SlashIcon,
|
|
28
|
+
home: Home,
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
export default IconVariants;
|
|
@@ -13,7 +13,7 @@ export const IconContainer = styled.div<{ size: NsIconSizeType }>`
|
|
|
13
13
|
place-content: center;
|
|
14
14
|
place-items: center;
|
|
15
15
|
svg {
|
|
16
|
-
|
|
17
|
-
height:
|
|
16
|
+
width: ${({ size }) => iconSize[size]};
|
|
17
|
+
height: ${({ size }) => iconSize[size]};
|
|
18
18
|
}
|
|
19
19
|
`;
|
|
@@ -2,7 +2,14 @@ import { IconContainer } from './NsIcon.styled';
|
|
|
2
2
|
import IconVariants from './IconMap';
|
|
3
3
|
import { NsIconProps } from './types';
|
|
4
4
|
|
|
5
|
-
const NsIcon = ({
|
|
5
|
+
const NsIcon = ({
|
|
6
|
+
name,
|
|
7
|
+
size = 'large',
|
|
8
|
+
color = 'primary',
|
|
9
|
+
testId = 'icon',
|
|
10
|
+
className,
|
|
11
|
+
colorOverride,
|
|
12
|
+
}: NsIconProps) => {
|
|
6
13
|
const Icon = IconVariants[name];
|
|
7
14
|
|
|
8
15
|
if (!Icon) {
|
|
@@ -10,8 +17,8 @@ const NsIcon = ({ name, size = 'large', color = 'primary', testId = 'icon' }: Ns
|
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
return (
|
|
13
|
-
<IconContainer data-testid={testId} size={size}>
|
|
14
|
-
<Icon color={color as any} />
|
|
20
|
+
<IconContainer data-testid={testId} size={size} className={className}>
|
|
21
|
+
<Icon color={colorOverride ? colorOverride : (color as any)} />
|
|
15
22
|
</IconContainer>
|
|
16
23
|
);
|
|
17
24
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
|
|
3
|
+
import { SvgIconProps, SvgIcon } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
const ArrowDown = (props: SvgIconProps) => {
|
|
6
|
+
return (
|
|
7
|
+
<SvgIcon viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
8
|
+
<path
|
|
9
|
+
fillRule="evenodd"
|
|
10
|
+
clipRule="evenodd"
|
|
11
|
+
d="M12.7071 16.2071C12.3166 16.5976 11.6834 16.5976 11.2929 16.2071L4.29289 9.20711C3.90237 8.81658 3.90237 8.18342 4.29289 7.79289C4.68342 7.40237 5.31658 7.40237 5.70711 7.79289L12 14.0858L18.2929 7.79289C18.6834 7.40237 19.3166 7.40237 19.7071 7.79289C20.0976 8.18342 20.0976 8.81658 19.7071 9.20711L12.7071 16.2071Z"
|
|
12
|
+
fill={props.color}
|
|
13
|
+
/>
|
|
14
|
+
</SvgIcon>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default ArrowDown;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
|
|
3
|
+
import { SvgIconProps, SvgIcon } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
const ArrowUp = (props: SvgIconProps) => {
|
|
6
|
+
return (
|
|
7
|
+
<SvgIcon viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
8
|
+
<path
|
|
9
|
+
fillRule="evenodd"
|
|
10
|
+
clipRule="evenodd"
|
|
11
|
+
d="M11.2929 7.79289C11.6834 7.40237 12.3166 7.40237 12.7071 7.79289L19.7071 14.7929C20.0976 15.1834 20.0976 15.8166 19.7071 16.2071C19.3166 16.5976 18.6834 16.5976 18.2929 16.2071L12 9.91421L5.70711 16.2071C5.31658 16.5976 4.68342 16.5976 4.29289 16.2071C3.90237 15.8166 3.90237 15.1834 4.29289 14.7929L11.2929 7.79289Z"
|
|
12
|
+
fill={props.color}
|
|
13
|
+
/>
|
|
14
|
+
</SvgIcon>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default ArrowUp;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SvgIconProps, SvgIcon } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
const SlashIcon = (props: SvgIconProps) => {
|
|
4
|
+
return (
|
|
5
|
+
<SvgIcon viewBox="0 0 2 22" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
6
|
+
<path
|
|
7
|
+
d="M1 1L1 11L0.999999 21"
|
|
8
|
+
stroke={props.color}
|
|
9
|
+
stroke-width="2"
|
|
10
|
+
stroke-linecap="round"
|
|
11
|
+
stroke-linejoin="round"
|
|
12
|
+
/>
|
|
13
|
+
</SvgIcon>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default SlashIcon;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SvgIconProps, SvgIcon } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
const Home = (props: SvgIconProps) => {
|
|
4
|
+
return (
|
|
5
|
+
<SvgIcon viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
6
|
+
<path
|
|
7
|
+
fill-rule="evenodd"
|
|
8
|
+
clip-rule="evenodd"
|
|
9
|
+
d="M23.77 12.4971C23.8226 12.577 23.8694 12.6606 23.91 12.7471C23.9626 12.8911 23.9864 13.044 23.98 13.1971C23.9839 13.3413 23.9566 13.4845 23.9 13.6171C23.8531 13.7514 23.7781 13.8741 23.68 13.9771C23.5815 14.0833 23.4624 14.1683 23.33 14.2271C23.206 14.2775 23.0738 14.3046 22.94 14.3071C22.7944 14.3048 22.651 14.2706 22.52 14.2071C22.3902 14.1445 22.2745 14.056 22.18 13.9471L12.28 3.33713C12.2277 3.27493 12.1581 3.22968 12.08 3.20713C12.0087 3.18275 11.9313 3.18275 11.86 3.20713C11.803 3.22861 11.7517 3.26281 11.71 3.30713L1.8 13.9271C1.70627 14.0302 1.59203 14.1126 1.46459 14.169C1.33716 14.2254 1.19935 14.2545 1.06 14.2545C0.920651 14.2545 0.78284 14.2254 0.655406 14.169C0.527971 14.1126 0.413727 14.0302 0.32 13.9271C0.217842 13.8273 0.136296 13.7084 0.08 13.5771C0.0284697 13.4396 0.00139518 13.294 0 13.1471C0.0013101 13.0035 0.0284068 12.8612 0.08 12.7271C0.136065 12.5928 0.217559 12.4705 0.32 12.3671L10.26 1.77713C10.6011 1.40957 11.0475 1.15652 11.538 1.05268C12.0286 0.94884 12.5393 0.999295 13 1.19713C13.2985 1.33692 13.5643 1.53794 13.78 1.78713L15.33 3.48713V3.10713C15.3302 2.82197 15.4372 2.54724 15.63 2.33713C15.7244 2.23535 15.8389 2.15434 15.9663 2.09925C16.0937 2.04415 16.2312 2.01619 16.37 2.01713H20C20.1486 2.0007 20.2989 2.01779 20.44 2.06713C20.5669 2.12598 20.6821 2.20728 20.78 2.30713C20.8776 2.41428 20.9525 2.54016 21 2.67713C21.0449 2.80898 21.0652 2.94794 21.06 3.08713V9.56713L23.77 12.4971ZM19 7.33713V4.17713H17.42V5.67713L19 7.33713ZM19.28 13.94C19.4773 13.7598 19.7329 13.6569 20 13.65C20.2667 13.6586 20.5217 13.7613 20.72 13.94C20.8169 14.0298 20.8951 14.1378 20.95 14.258C21.0049 14.3781 21.0355 14.508 21.04 14.64V22.02C21.0355 22.152 21.0049 22.2819 20.95 22.402C20.8951 22.5222 20.8169 22.6302 20.72 22.72C20.5232 22.8997 20.2665 22.9996 20 23H14.22C13.9504 23.0001 13.6903 22.9004 13.49 22.72C13.3949 22.6294 13.3185 22.5209 13.2653 22.4008C13.2121 22.2807 13.1831 22.1513 13.18 22.02V16.66C13.18 16.6134 13.1691 16.5675 13.1483 16.5258C13.1275 16.4842 13.0972 16.4479 13.06 16.42C13.0207 16.3807 12.9739 16.3498 12.9223 16.3292C12.8707 16.3086 12.8155 16.2986 12.76 16.3H11.3C11.2443 16.2974 11.1887 16.3068 11.137 16.3275C11.0852 16.3482 11.0385 16.3797 11 16.42C10.9597 16.4426 10.9252 16.4743 10.8992 16.5124C10.8732 16.5506 10.8564 16.5943 10.85 16.64V22.04C10.851 22.173 10.8229 22.3046 10.7676 22.4255C10.7123 22.5464 10.6312 22.6538 10.53 22.74C10.4345 22.8321 10.3217 22.9043 10.1981 22.9523C10.0745 23.0004 9.94256 23.0234 9.80997 23.02H3.99997C3.85815 23.0235 3.71703 22.9988 3.58484 22.9473C3.45265 22.8958 3.33202 22.8186 3.22997 22.72C3.13304 22.6302 3.05485 22.5222 2.99993 22.402C2.945 22.2819 2.91443 22.152 2.90997 22.02V14.64C2.91311 14.5063 2.94305 14.3745 2.99802 14.2526C3.05298 14.1306 3.13186 14.0209 3.22997 13.93C3.43312 13.7554 3.69211 13.6594 3.95997 13.6594C4.22783 13.6594 4.48683 13.7554 4.68997 13.93C4.78627 14.0218 4.86329 14.1318 4.91652 14.2538C4.96975 14.3757 4.99812 14.507 4.99997 14.64V21.09H8.76997V16.68C8.7711 16.3671 8.83749 16.0578 8.9649 15.772C9.09232 15.4861 9.27797 15.23 9.50997 15.02C9.74648 14.8043 10.0212 14.6348 10.32 14.52C10.622 14.4001 10.9451 14.3423 11.27 14.35H12.73C13.3796 14.3465 14.0071 14.5854 14.49 15.02C14.7181 15.2289 14.9014 15.482 15.0286 15.7639C15.1558 16.0459 15.2243 16.3507 15.23 16.66V21.07H18.96V14.64C18.9609 14.5073 18.9899 14.3764 19.045 14.2557C19.1002 14.1351 19.1803 14.0275 19.28 13.94Z"
|
|
10
|
+
fill={props?.color}
|
|
11
|
+
/>
|
|
12
|
+
</SvgIcon>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default Home;
|
|
@@ -3,6 +3,8 @@ import { FoundationTheme } from '@nuskin/foundation-theme';
|
|
|
3
3
|
export type NsIconSizeType = 'small' | 'medium' | 'large' | 'xLarge';
|
|
4
4
|
|
|
5
5
|
export type NsIconNameType =
|
|
6
|
+
| 'arrowDown'
|
|
7
|
+
| 'arrowUp'
|
|
6
8
|
| 'cart'
|
|
7
9
|
| 'cartBadge'
|
|
8
10
|
| 'checkShield'
|
|
@@ -11,10 +13,14 @@ export type NsIconNameType =
|
|
|
11
13
|
| 'infoFilled'
|
|
12
14
|
| 'infoOutlined'
|
|
13
15
|
| 'success'
|
|
14
|
-
| 'trash'
|
|
16
|
+
| 'trash'
|
|
17
|
+
| 'slash'
|
|
18
|
+
| 'home';
|
|
15
19
|
export type NsIconProps = {
|
|
16
20
|
name: NsIconNameType;
|
|
17
21
|
size?: NsIconSizeType;
|
|
18
22
|
color?: FoundationTheme.PaletteColors;
|
|
19
23
|
testId?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
colorOverride?: string;
|
|
20
26
|
};
|
package/src/common/index.ts
CHANGED
|
@@ -19,6 +19,12 @@ export * from './nav-tab';
|
|
|
19
19
|
export { default as NsNavProductCard } from './nav-product-card';
|
|
20
20
|
export * from './nav-product-card';
|
|
21
21
|
|
|
22
|
+
export { default as NsBanner } from './banner';
|
|
23
|
+
export * from './banner';
|
|
24
|
+
|
|
25
|
+
export { default as NsBreadcrumb } from './breadcrumb';
|
|
26
|
+
export * from './breadcrumb';
|
|
27
|
+
|
|
22
28
|
export { default as Typography } from './typography';
|
|
23
29
|
export * from './typography';
|
|
24
30
|
|
|
@@ -29,3 +35,5 @@ export * from './carousal';
|
|
|
29
35
|
export * from './icon';
|
|
30
36
|
|
|
31
37
|
export * from './cart-icon';
|
|
38
|
+
|
|
39
|
+
export * from './dropdown';
|
|
@@ -20,14 +20,14 @@ export const NsNavTabComponent = (props: PropsWithChildren<NSNavTabComponentProp
|
|
|
20
20
|
<div onMouseLeave={handleLeave} data-testid="nav-container-draw" className="p-0 m-0">
|
|
21
21
|
<div className="pb-0.5">
|
|
22
22
|
<div className="flex">
|
|
23
|
-
{tabs?.map((tab
|
|
23
|
+
{tabs?.map((tab) => {
|
|
24
24
|
const handleMouseEnter = () => {
|
|
25
25
|
setActive(tab.tabName);
|
|
26
26
|
};
|
|
27
27
|
return (
|
|
28
28
|
<div
|
|
29
|
-
key={
|
|
30
|
-
data-testid={tab
|
|
29
|
+
key={tab?.tabName}
|
|
30
|
+
data-testid={tab?.tabName}
|
|
31
31
|
className="relative cursor-pointer"
|
|
32
32
|
onMouseEnter={handleMouseEnter}
|
|
33
33
|
>
|
|
@@ -11,6 +11,7 @@ const NsTypography = ({
|
|
|
11
11
|
decoration = 'none',
|
|
12
12
|
color = 'primary',
|
|
13
13
|
additionalProps = {},
|
|
14
|
+
colorOverride = '',
|
|
14
15
|
}: PropsWithChildren<NsTypographyProps>) => {
|
|
15
16
|
return (
|
|
16
17
|
<StyledTypography
|
|
@@ -19,6 +20,7 @@ const NsTypography = ({
|
|
|
19
20
|
weight={weight}
|
|
20
21
|
variant={variant}
|
|
21
22
|
decoration={decoration}
|
|
23
|
+
colorOverride={colorOverride}
|
|
22
24
|
color={color}
|
|
23
25
|
{...additionalProps}
|
|
24
26
|
>
|
|
@@ -2,9 +2,7 @@ import { css } from '@emotion/react';
|
|
|
2
2
|
import { styled, GlobalStyles, ColorUtils } from '@nuskin/foundation-theme';
|
|
3
3
|
import { NsTypographyProps } from './types';
|
|
4
4
|
|
|
5
|
-
const {
|
|
6
|
-
getPaletteColor,
|
|
7
|
-
} = ColorUtils;
|
|
5
|
+
const { getPaletteColor } = ColorUtils;
|
|
8
6
|
|
|
9
7
|
const variantStyles = {
|
|
10
8
|
h1: 'font-size: 36px; line-height: 44px;',
|
|
@@ -32,7 +30,8 @@ const fontWeightMap = {
|
|
|
32
30
|
|
|
33
31
|
export const StyledTypography = styled.div<NsTypographyProps>`
|
|
34
32
|
font-family: Inter;
|
|
35
|
-
color: ${({ color, theme }) =>
|
|
33
|
+
color: ${({ color, theme, colorOverride }) =>
|
|
34
|
+
colorOverride?.startsWith('#') ? colorOverride : getPaletteColor(theme, color)};
|
|
36
35
|
font-weight: ${({ weight = 'normal' }) => fontWeightMap[weight]};
|
|
37
36
|
font-style: ${({ fontStyle }) => fontStyle};
|
|
38
37
|
${({ variant = 'h1' }) => variantStyles[variant]};
|