@nuskin/foundation-ui-components 1.0.0-cft-35.12 → 1.0.0-develop.1
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/package.json +21 -16
- package/src/common/alert/NsAlert.spec.tsx +134 -0
- package/src/common/alert/NsAlert.stories.tsx +42 -0
- package/src/common/alert/NsAlert.styled.ts +124 -0
- package/src/common/alert/NsAlert.tsx +155 -0
- package/src/common/alert/index.ts +2 -0
- package/src/common/alert/types.d.ts +45 -0
- package/src/common/auth-dropdown/NsAuthDropdown.stories.tsx +9 -5
- package/src/common/auth-dropdown/NsAuthDropdown.tsx +1 -1
- package/src/common/autocomplete/NsAutocomplete.spec.tsx +96 -0
- package/src/common/autocomplete/NsAutocomplete.stories.tsx +51 -0
- package/src/common/autocomplete/NsAutocomplete.tsx +110 -0
- package/src/common/autocomplete/index.ts +1 -0
- package/src/common/autocomplete/mock/Mock.styled.tsx +23 -0
- package/src/common/autocomplete/mock/MockData.ts +46 -0
- package/src/common/autocomplete/mock/MockUI.tsx +24 -0
- package/src/common/autocomplete/types.ts +39 -0
- package/src/common/banner/NsBanner.spec.tsx +2 -2
- package/src/common/banner/NsBanner.stories.tsx +2 -2
- package/src/common/banner/NsBanner.styled.tsx +1 -0
- package/src/common/banner/NsBanner.tsx +12 -3
- package/src/common/brand-components/NsBrand.stories.tsx +2 -2
- package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +12 -4
- package/src/common/breadcrumb/NsBreadcrumb.stories.tsx +19 -8
- package/src/common/breadcrumb/NsBreadcrumb.styled.tsx +1 -1
- package/src/common/breadcrumb/NsBreadcrumb.tsx +2 -4
- package/src/common/button/NsButton.stories.tsx +2 -2
- package/src/common/card/NsFfgCard.tsx +18 -0
- package/src/common/card/index.ts +1 -0
- package/src/common/card/stories/NsBgCard.stories.tsx +2 -2
- package/src/common/card/stories/NsBrandProductCard.stories.tsx +2 -2
- package/src/common/card/stories/NsFfgCard.stories.tsx +40 -0
- package/src/common/card/styled/NsFfgStyled.tsx +44 -0
- package/src/common/card/tests/NsFfgCard.spec.tsx +23 -0
- package/src/common/card/types.d.ts +9 -0
- package/src/common/carousal/CarousalControl.tsx +3 -5
- package/src/common/carousal/IconWrapper.tsx +4 -4
- package/src/common/carousal/tests/NsCarousal.spec.tsx +4 -4
- package/src/common/cart-icon/NsCartIcon.spec.tsx +10 -4
- package/src/common/cart-icon/NsCartIcon.stories.tsx +2 -2
- package/src/common/checkbox/NsCheckbox.stories.tsx +12 -16
- package/src/common/column-control/NDivs.story.tsx +35 -0
- package/src/common/column-control/NsColumnControl.spec.tsx +24 -0
- package/src/common/column-control/NsColumnControl.stories.tsx +69 -0
- package/src/common/column-control/NsColumnControl.tsx +59 -0
- package/src/common/column-control/index.ts +2 -0
- package/src/common/column-control/types.d.ts +47 -0
- package/src/common/column-control/utils.ts +66 -0
- package/src/common/dropdown/NsDropdown.tsx +1 -1
- package/src/common/dropdown/stories/NsDropdown.stories.tsx +13 -9
- package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +13 -9
- package/src/common/flag/NsFlag.stories.tsx +2 -2
- package/src/common/icon/NsIcon.spec.tsx +9 -2
- package/src/common/icon/NsIcon.stories.tsx +4 -17
- package/src/common/icon/NsIcon.tsx +1 -1
- package/src/common/icon/{IconMap.ts → NsIconVariants.ts} +10 -0
- package/src/common/icon/icons/AlertErrorIcon.tsx +36 -0
- package/src/common/icon/icons/ArrowDown.tsx +1 -1
- package/src/common/{carousal/LeftChevron.tsx → icon/icons/ArrowLeft.tsx} +7 -5
- package/src/common/{carousal/RightChevron.tsx → icon/icons/ArrowRight.tsx} +3 -3
- package/src/common/icon/icons/ArrowUp.tsx +1 -1
- package/src/common/icon/icons/Cart.tsx +7 -1
- package/src/common/icon/icons/CartBadge.tsx +7 -1
- package/src/common/icon/icons/CheckShield.tsx +7 -1
- package/src/common/icon/icons/CheckVerified.tsx +7 -1
- package/src/common/icon/icons/CloseIcon.tsx +22 -0
- package/src/common/icon/icons/ErrorIcon.tsx +1 -1
- package/src/common/icon/icons/InfoFilled.tsx +1 -1
- package/src/common/icon/icons/InfoOutlined.tsx +7 -1
- package/src/common/icon/icons/SearchIcon.tsx +1 -1
- package/src/common/icon/icons/SlashIcon.tsx +4 -4
- package/src/common/icon/icons/SuccessIcon.tsx +1 -1
- package/src/common/icon/icons/TrashIcon.tsx +1 -2
- package/src/common/icon/icons/WarningIcon.tsx +36 -0
- package/src/common/icon/icons/home.tsx +4 -3
- package/src/common/icon/index.ts +1 -0
- package/src/common/icon/types.d.ts +2 -15
- package/src/common/image/NsImage.stories.tsx +2 -2
- package/src/common/image/NsImage.tsx +3 -2
- package/src/common/image/NsNextImage.stories.tsx +2 -2
- package/src/common/image/NsNextImage.tsx +3 -7
- package/src/common/image/types.d.ts +6 -4
- package/src/common/index.ts +15 -8
- package/src/common/input/InputWrapper.tsx +3 -2
- package/src/common/input/NsInputField.spec.tsx +17 -2
- package/src/common/link/NsLink.spec.tsx +15 -8
- package/src/common/link/NsLink.stories.tsx +10 -2
- package/src/common/link/NsLink.styled.tsx +1 -1
- package/src/common/link/NsLink.tsx +26 -4
- package/src/common/link/types.d.ts +4 -2
- package/src/common/nav-product-card/NsNavProductCard.stories.tsx +2 -2
- package/src/common/nav-product-card/NsNavProductCard.tsx +3 -2
- package/src/common/nav-tab/NsNavTab.stories.tsx +2 -2
- package/src/common/typography/NsTypography.stories.tsx +2 -2
- package/src/common/videos/NsVideo.spec.tsx +26 -0
- package/src/common/videos/NsVideo.stories.tsx +32 -0
- package/src/common/videos/NsVideo.tsx +25 -0
- package/src/common/videos/index.ts +2 -0
- package/src/common/videos/types.d.ts +9 -0
- package/CHANGELOG.md +0 -194
- /package/src/common/breadcrumb/{types.ts → types.d.ts} +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { within, userEvent } from '@storybook/testing-library';
|
|
3
|
+
import { expect } from '@storybook/jest';
|
|
4
|
+
import { withCenterDecorator } from '../../../stories/decorators/withCenterDecorator';
|
|
5
|
+
import { NsFfgCard } from '../';
|
|
6
|
+
|
|
7
|
+
type ComponentType = typeof NsFfgCard;
|
|
8
|
+
type Story = StoryObj<ComponentType>;
|
|
9
|
+
type MetaStory = Meta<ComponentType>;
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
component: NsFfgCard,
|
|
13
|
+
title: 'Components/NsCard/NsFfgCard',
|
|
14
|
+
argTypes: {},
|
|
15
|
+
decorators: [withCenterDecorator],
|
|
16
|
+
} as MetaStory;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
actionText: 'LEARN MORE',
|
|
21
|
+
actionLink: 'https://www.forceforgood.org/',
|
|
22
|
+
text: 'Every child deserves a chance to shine. Through our Nu Skin Force for Good Foundation, we’re empowering children around the world with hope for a brighter future.',
|
|
23
|
+
topImageUrl:
|
|
24
|
+
'https://images.contentstack.io/v3/assets/blt594f30e2dc643eae/blt34cdb300dc9b93ff/645ccaa7520ba1a1af13a08a/nsffg-foundation.webp',
|
|
25
|
+
topImageAlt: 'Force for Good Foundation',
|
|
26
|
+
additionalProps: {
|
|
27
|
+
testid: 'ffg-card',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
play: async ({ canvasElement }) => {
|
|
31
|
+
const canvas = within(canvasElement);
|
|
32
|
+
await userEvent.hover(canvas.getByTestId('ffg-card'));
|
|
33
|
+
await userEvent.hover(canvas.getByRole('link'));
|
|
34
|
+
await expect(
|
|
35
|
+
canvas.getByText(
|
|
36
|
+
'Every child deserves a chance to shine. Through our Nu Skin Force for Good Foundation, we’re empowering children around the world with hope for a brighter future.'
|
|
37
|
+
)
|
|
38
|
+
).toBeInTheDocument();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Box } from '@mui/material';
|
|
2
|
+
import { styled } from '@nuskin/foundation-theme';
|
|
3
|
+
|
|
4
|
+
const options = {};
|
|
5
|
+
|
|
6
|
+
const Component = () => {
|
|
7
|
+
return {
|
|
8
|
+
backgroundClip: 'border-box',
|
|
9
|
+
backgroundColor: '#fff',
|
|
10
|
+
border: '1px solid #eff2f7',
|
|
11
|
+
borderRadius: '1rem',
|
|
12
|
+
boxShadow: '0 0.75rem 1.5rem rgba(18, 38, 63, .03)',
|
|
13
|
+
display: 'flex',
|
|
14
|
+
flexDirection: 'column' as 'column', // https://github.com/cssinjs/jss/issues/1344
|
|
15
|
+
marginBottom: '2rem',
|
|
16
|
+
transition: 'all .2s ease',
|
|
17
|
+
textAlign: 'center' as 'center',
|
|
18
|
+
wordWrap: 'break-word' as 'break-word',
|
|
19
|
+
':hover': {
|
|
20
|
+
transform: 'translateY(-10px)',
|
|
21
|
+
boxShadow: '0 1rem 3rem rgba(31, 45, 61, .125) !important',
|
|
22
|
+
'.action-link': {
|
|
23
|
+
color: '#2CD5C4',
|
|
24
|
+
textDecoration: 'none',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
'.body-text': {
|
|
28
|
+
padding: '0 1.5rem 3rem',
|
|
29
|
+
fontSize: '1rem',
|
|
30
|
+
color: '#8492a6',
|
|
31
|
+
},
|
|
32
|
+
'.top-image': {
|
|
33
|
+
padding: '1.5rem 3rem',
|
|
34
|
+
},
|
|
35
|
+
'.action-link': {
|
|
36
|
+
color: '#3c4858',
|
|
37
|
+
fontSize: '1.25rem',
|
|
38
|
+
padding: '0 1.5rem 3rem',
|
|
39
|
+
fontWeight: 600,
|
|
40
|
+
marginBottom: '0.67rem',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export const NsFfgStyled = styled(Box, options)<{}>(Component);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import NsFfgCard from '../NsFfgCard';
|
|
3
|
+
|
|
4
|
+
describe('NsFfgCard component', () => {
|
|
5
|
+
const props = {
|
|
6
|
+
topImageUrl: 'https://media.nuskin.com/m/6245528b8f76ad32/webimage-brand-ageLOC.png',
|
|
7
|
+
topImageAlt: 'alt-text',
|
|
8
|
+
text: 'card text',
|
|
9
|
+
actionLink: 'https://www.google.com',
|
|
10
|
+
};
|
|
11
|
+
it('renders with background image', () => {
|
|
12
|
+
render(<NsFfgCard {...props} additionalProps={{ testid: 'ffg-card' }} />);
|
|
13
|
+
const card = screen.getByTestId('ffg-card');
|
|
14
|
+
expect(card).toHaveStyle(`
|
|
15
|
+
background-color: #fff;
|
|
16
|
+
`);
|
|
17
|
+
});
|
|
18
|
+
it('renders with cover size', () => {
|
|
19
|
+
render(<NsFfgCard {...props} additionalProps={{ testid: 'ffg-card-1' }} actionText="Details" />);
|
|
20
|
+
const card = screen.getByText('Details');
|
|
21
|
+
expect(card).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { styled } from '@nuskin/foundation-theme';
|
|
2
|
-
import LeftChevron from './LeftChevron';
|
|
3
2
|
import IconWrapper from './IconWrapper';
|
|
4
|
-
import
|
|
5
|
-
import { NsTypography, NsLink } from '..';
|
|
3
|
+
import { NsTypography, NsLink, NsIcon } from '..';
|
|
6
4
|
|
|
7
5
|
interface CarousalControlProps {
|
|
8
6
|
next(): void;
|
|
@@ -32,10 +30,10 @@ const CarousalControl = ({ next, prev, viewAllUrl }: CarousalControlProps) => {
|
|
|
32
30
|
<NsTypography variant={'h5'}>View All</NsTypography>
|
|
33
31
|
</NsLink>
|
|
34
32
|
<IconWrapper onClick={prev} className="carousal-prev-button">
|
|
35
|
-
<
|
|
33
|
+
<NsIcon name="arrowLeft" />
|
|
36
34
|
</IconWrapper>
|
|
37
35
|
<IconWrapper onClick={next} className="carousal-next-button">
|
|
38
|
-
<
|
|
36
|
+
<NsIcon name="arrowRight" />
|
|
39
37
|
</IconWrapper>
|
|
40
38
|
</InnerWrapper>
|
|
41
39
|
</Wrapper>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
2
|
import { styled } from '@nuskin/foundation-theme';
|
|
3
3
|
import { Box } from '@mui/material';
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
type IconWrapperProps = PropsWithChildren<{
|
|
6
6
|
onClick(): void;
|
|
7
7
|
className?: string;
|
|
@@ -14,12 +14,12 @@ const Wrapper = styled(
|
|
|
14
14
|
const strokeColor = theme.palette?.primary?.main ?? '#252525';
|
|
15
15
|
return {
|
|
16
16
|
display: 'flex',
|
|
17
|
-
|
|
18
|
-
width: '20px',
|
|
17
|
+
width: 'auto',
|
|
19
18
|
border: `2px solid ${strokeColor}`,
|
|
20
19
|
borderRadius: '3px',
|
|
21
20
|
cursor: 'pointer',
|
|
22
|
-
padding: '10px',
|
|
21
|
+
padding: '6px 10px',
|
|
22
|
+
boxSizing: 'content-box',
|
|
23
23
|
};
|
|
24
24
|
});
|
|
25
25
|
const IconWrapper = ({ children, onClick, className }: IconWrapperProps) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render, screen, fireEvent } from '
|
|
1
|
+
import { render, screen, fireEvent } from '../../../../test-utils';
|
|
2
2
|
import NsCarousal from '../NsCarousal';
|
|
3
3
|
|
|
4
4
|
const mockEntries = [
|
|
@@ -19,7 +19,7 @@ const mockSettings = {
|
|
|
19
19
|
};
|
|
20
20
|
describe('NsCarousal', () => {
|
|
21
21
|
it('should have prev and next chevron', () => {
|
|
22
|
-
|
|
22
|
+
render(
|
|
23
23
|
<NsCarousal
|
|
24
24
|
entries={[]}
|
|
25
25
|
renderEntry={mockRenderEntry}
|
|
@@ -27,8 +27,8 @@ describe('NsCarousal', () => {
|
|
|
27
27
|
settings={mockSettings}
|
|
28
28
|
/>
|
|
29
29
|
);
|
|
30
|
-
const prevSvg = getByTestId('
|
|
31
|
-
const nextSvg = getByTestId('
|
|
30
|
+
const prevSvg = screen.getByTestId('arrowLeft');
|
|
31
|
+
const nextSvg = screen.getByTestId('arrowRight');
|
|
32
32
|
expect(prevSvg).toBeInTheDocument();
|
|
33
33
|
expect(nextSvg).toBeInTheDocument();
|
|
34
34
|
});
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { NsCartIcon } from './';
|
|
2
|
-
import { render } from '../../../test-utils';
|
|
2
|
+
import { render, screen } from '../../../test-utils';
|
|
3
3
|
|
|
4
|
+
const { getByTestId } = screen;
|
|
4
5
|
describe('NsCartIcon', () => {
|
|
5
6
|
it('renders Cart icon when item count is 0', () => {
|
|
6
|
-
|
|
7
|
+
render(<NsCartIcon itemCount={0} color="primary" size="large" />);
|
|
8
|
+
const cartIcon = getByTestId('cart-icon');
|
|
9
|
+
expect(cartIcon).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
it('renders Cart icon with optional values', () => {
|
|
12
|
+
render(<NsCartIcon itemCount={0} />);
|
|
7
13
|
const cartIcon = getByTestId('cart-icon');
|
|
8
14
|
expect(cartIcon).toBeInTheDocument();
|
|
9
15
|
});
|
|
10
16
|
|
|
11
17
|
it('renders Cart Badge icon when item count is greater than 0', () => {
|
|
12
|
-
|
|
18
|
+
render(<NsCartIcon itemCount={5} color="primary" size="large" />);
|
|
13
19
|
const cartBadgeIcon = getByTestId('cart-badge-icon');
|
|
14
20
|
expect(cartBadgeIcon).toBeInTheDocument();
|
|
15
21
|
});
|
|
16
22
|
|
|
17
23
|
it('renders item count badge when item count is greater than 0', () => {
|
|
18
|
-
|
|
24
|
+
render(<NsCartIcon itemCount={5} color="primary" size="large" />);
|
|
19
25
|
const itemCountBadge = getByTestId('item-count-badge');
|
|
20
26
|
expect(itemCountBadge).toBeInTheDocument();
|
|
21
27
|
expect(itemCountBadge.textContent).toBe('5');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
-
import {
|
|
2
|
+
import { withCenterDecorator } from '../../stories/decorators/withCenterDecorator';
|
|
3
3
|
import { NsCartIcon } from './';
|
|
4
4
|
|
|
5
5
|
type ComponentType = typeof NsCartIcon;
|
|
@@ -9,7 +9,7 @@ type MetaStory = Meta<ComponentType>;
|
|
|
9
9
|
export default {
|
|
10
10
|
title: 'Components/NsCartIcon',
|
|
11
11
|
component: NsCartIcon,
|
|
12
|
-
decorators: [
|
|
12
|
+
decorators: [withCenterDecorator],
|
|
13
13
|
argTypes: {
|
|
14
14
|
itemCount: { control: 'number', defaultValue: 1 },
|
|
15
15
|
size: { control: 'select', options: ['small', 'medium', 'large', 'xLarge'] },
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author [vijayaprathap Kumar] (vpkumar@nuskin.com)
|
|
3
|
-
* NsCheckbox.tsx
|
|
4
|
-
* @description Story for NsCheckbox component.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import React, { useState } from 'react';
|
|
8
|
-
import {
|
|
2
|
+
import { within } from '@testing-library/react';
|
|
3
|
+
import { withCenterDecorator } from '../../stories/decorators/withCenterDecorator';
|
|
9
4
|
import type { StoryObj, Meta } from '@storybook/react';
|
|
5
|
+
import { expect } from '@storybook/jest';
|
|
10
6
|
|
|
11
7
|
import { NsCheckbox } from './';
|
|
12
8
|
|
|
@@ -14,11 +10,12 @@ type ComponentType = typeof NsCheckbox;
|
|
|
14
10
|
type Story = StoryObj<ComponentType>;
|
|
15
11
|
type MetaStory = Meta<ComponentType>;
|
|
16
12
|
|
|
13
|
+
const labelText = 'Checkbox Label';
|
|
17
14
|
// In this case, we're only passing the label prop.
|
|
18
15
|
export default {
|
|
19
16
|
title: 'Components/NsCheckbox',
|
|
20
17
|
component: NsCheckbox,
|
|
21
|
-
decorators: [
|
|
18
|
+
decorators: [withCenterDecorator],
|
|
22
19
|
argTypes: {
|
|
23
20
|
disabled: { control: 'boolean' },
|
|
24
21
|
flag: {
|
|
@@ -27,10 +24,10 @@ export default {
|
|
|
27
24
|
},
|
|
28
25
|
},
|
|
29
26
|
args: {
|
|
30
|
-
label:
|
|
27
|
+
label: labelText,
|
|
31
28
|
disabled: false,
|
|
32
29
|
ariaLabel: 'primary checkbox',
|
|
33
|
-
size: '
|
|
30
|
+
size: 'medium',
|
|
34
31
|
flag: {
|
|
35
32
|
message: 'New',
|
|
36
33
|
color: 'accentColorA',
|
|
@@ -40,12 +37,6 @@ export default {
|
|
|
40
37
|
|
|
41
38
|
const RenderCheckbox = (args: any) => {
|
|
42
39
|
const [isChecked, setIsChecked] = useState(false);
|
|
43
|
-
|
|
44
|
-
/*
|
|
45
|
-
When the checkbox changes, we're calling handleChange,
|
|
46
|
-
which updates the isChecked state and calls the onChange function,
|
|
47
|
-
passing in the new isChecked value.
|
|
48
|
-
*/
|
|
49
40
|
const handleChange = (event: { target: { checked: boolean } }) => {
|
|
50
41
|
const isChecked = event.target.checked;
|
|
51
42
|
setIsChecked(isChecked);
|
|
@@ -55,4 +46,9 @@ const RenderCheckbox = (args: any) => {
|
|
|
55
46
|
};
|
|
56
47
|
export const Checkbox: Story = {
|
|
57
48
|
render: RenderCheckbox,
|
|
49
|
+
play: ({ canvasElement }) => {
|
|
50
|
+
const canvas = within(canvasElement);
|
|
51
|
+
const el = canvas.getByText(labelText);
|
|
52
|
+
expect(el).toBeInTheDocument();
|
|
53
|
+
},
|
|
58
54
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { withCenterDecorator } from '../../stories/decorators/withCenterDecorator';
|
|
3
|
+
|
|
4
|
+
type DivArrayProps = {
|
|
5
|
+
elementsCount: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* this component return a fragment containing n number of div, with each div numbered as per its index
|
|
10
|
+
* @param n: number of div to render
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export const DivArray = ({ elementsCount }: DivArrayProps) => (
|
|
14
|
+
<>
|
|
15
|
+
{Array(elementsCount)
|
|
16
|
+
.fill(0)
|
|
17
|
+
.map((e, i) => (
|
|
18
|
+
<div key={i}>{i + 1}</div>
|
|
19
|
+
))}
|
|
20
|
+
</>
|
|
21
|
+
);
|
|
22
|
+
type ComponentType = typeof DivArray;
|
|
23
|
+
type Story = StoryObj<ComponentType>;
|
|
24
|
+
type MetaStory = Meta<ComponentType>;
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
title: 'Components/NsColumnControl/NDivs',
|
|
28
|
+
component: DivArray,
|
|
29
|
+
decorators: [withCenterDecorator],
|
|
30
|
+
args: {
|
|
31
|
+
n: 2,
|
|
32
|
+
},
|
|
33
|
+
} as MetaStory;
|
|
34
|
+
|
|
35
|
+
export const Basic: Story = {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { render, screen } from '../../../test-utils';
|
|
2
|
+
import { NsColumnControl } from './';
|
|
3
|
+
import { DivArray } from './NDivs.story';
|
|
4
|
+
|
|
5
|
+
describe('NsColumnControl component', () => {
|
|
6
|
+
it('renders an column with proper grid value for tablet', () => {
|
|
7
|
+
render(
|
|
8
|
+
<NsColumnControl columnClass="ns-col-lg-1-1-1 ns-col-md-1-1 ns-col-sm-1">
|
|
9
|
+
<DivArray elementsCount={4} />
|
|
10
|
+
</NsColumnControl>
|
|
11
|
+
);
|
|
12
|
+
const element = screen.getByTestId('ns-column-control');
|
|
13
|
+
expect(element).toHaveStyle('grid-template-columns: 1fr 1fr 1fr;}');
|
|
14
|
+
});
|
|
15
|
+
it('renders an column with proper grid value', () => {
|
|
16
|
+
console.warn = jest.fn();
|
|
17
|
+
render(
|
|
18
|
+
<NsColumnControl columnClass="">
|
|
19
|
+
<DivArray elementsCount={4} />
|
|
20
|
+
</NsColumnControl>
|
|
21
|
+
);
|
|
22
|
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Received invalid className'));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { withCenterDecorator } from '../../stories/decorators/withCenterDecorator';
|
|
3
|
+
import { NsColumnControl } from '.';
|
|
4
|
+
import { DivArray } from './NDivs.story';
|
|
5
|
+
|
|
6
|
+
type ComponentType = typeof NsColumnControl;
|
|
7
|
+
type Story = StoryObj<ComponentType>;
|
|
8
|
+
type MetaStory = Meta<ComponentType>;
|
|
9
|
+
|
|
10
|
+
/* an array from 1 to n */
|
|
11
|
+
const selectDivOptions = Array(16)
|
|
12
|
+
.fill(0)
|
|
13
|
+
.map((e, i) => i + 1);
|
|
14
|
+
const childrenMapping = selectDivOptions.reduce((a, c) => {
|
|
15
|
+
return {
|
|
16
|
+
...a,
|
|
17
|
+
[c]: <DivArray elementsCount={c} />,
|
|
18
|
+
};
|
|
19
|
+
}, {});
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
title: 'Components/NsColumnControl',
|
|
23
|
+
component: NsColumnControl,
|
|
24
|
+
decorators: [withCenterDecorator],
|
|
25
|
+
argTypes: {
|
|
26
|
+
columnClass: {
|
|
27
|
+
control: 'text',
|
|
28
|
+
defaultValue: 'ns-col-lg-1-1-1 ns-col-md-1-1 ns-col-sm-1',
|
|
29
|
+
},
|
|
30
|
+
children: {
|
|
31
|
+
control: {
|
|
32
|
+
type: 'select',
|
|
33
|
+
options: selectDivOptions,
|
|
34
|
+
},
|
|
35
|
+
defaultValue: <DivArray elementsCount={8} />,
|
|
36
|
+
mapping: childrenMapping,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
args: {
|
|
40
|
+
columnClass: 'ns-col-lg-1-1-1 ns-col-md-1-1 ns-col-sm-1',
|
|
41
|
+
children: <DivArray elementsCount={12} />,
|
|
42
|
+
},
|
|
43
|
+
} as MetaStory;
|
|
44
|
+
|
|
45
|
+
export const SingleColumnControl: Story = {};
|
|
46
|
+
export const TwoColumn11: Story = {
|
|
47
|
+
args: { columnClass: 'ns-col-lg-1-1' },
|
|
48
|
+
};
|
|
49
|
+
export const TwoColumn21: Story = {
|
|
50
|
+
args: { columnClass: 'ns-col-lg-2-1' },
|
|
51
|
+
};
|
|
52
|
+
export const ThreeColumn111: Story = {
|
|
53
|
+
args: { columnClass: 'ns-col-lg-1-1-1' },
|
|
54
|
+
};
|
|
55
|
+
export const ThreeColumn211: Story = {
|
|
56
|
+
args: { columnClass: 'ns-col-lg-2-1-1' },
|
|
57
|
+
};
|
|
58
|
+
export const ThreeColumn121: Story = {
|
|
59
|
+
args: { columnClass: 'ns-col-lg-1-2-1' },
|
|
60
|
+
};
|
|
61
|
+
export const ThreeColumn11Responsive: Story = {
|
|
62
|
+
args: { columnClass: 'ns-col-lg-1-1 ns-col-md-1' },
|
|
63
|
+
};
|
|
64
|
+
export const FourColumn1111: Story = {
|
|
65
|
+
args: { columnClass: 'ns-col-lg-1-1-1-1' },
|
|
66
|
+
};
|
|
67
|
+
export const FourColumn1111Responsive: Story = {
|
|
68
|
+
args: { columnClass: 'ns-col-lg-1-1-1-1 ns-col-md-1-1 ns-col-sm-1' },
|
|
69
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import { styled } from '@nuskin/foundation-theme';
|
|
4
|
+
import { NsColumnControlProps, sizeObjectType } from './types';
|
|
5
|
+
import { getColumnSizes } from './utils';
|
|
6
|
+
|
|
7
|
+
const options = {
|
|
8
|
+
shouldForwardProp: (propKey: string) => propKey !== 'sizes',
|
|
9
|
+
};
|
|
10
|
+
type StyledDivProps = {
|
|
11
|
+
sizes: sizeObjectType;
|
|
12
|
+
gap?: number;
|
|
13
|
+
};
|
|
14
|
+
const descMaxWidthBreakpoints = [960, 600];
|
|
15
|
+
|
|
16
|
+
/* media queries */
|
|
17
|
+
const mediaQuery = descMaxWidthBreakpoints.map((bp) => `@media (max-width: ${bp}px)`);
|
|
18
|
+
|
|
19
|
+
const StyledDiv = styled(
|
|
20
|
+
Box as any,
|
|
21
|
+
options
|
|
22
|
+
)<StyledDivProps>(({ sizes, gap }: StyledDivProps) => {
|
|
23
|
+
const gridTemplateColumns = sizes['fractions']?.map((e) => `${e}fr`).join(' ');
|
|
24
|
+
const tabletTemplateColumns = sizes['tabletFractions']?.map((e) => `${e}fr`).join(' ');
|
|
25
|
+
const mobileTemplateColumns = sizes['mobileFractions']?.map((e) => `${e}fr`).join(' ');
|
|
26
|
+
return {
|
|
27
|
+
display: 'grid',
|
|
28
|
+
maxWidth: Array.isArray(sizes?.fractions) && sizes.fractions?.length <= 2 ? '1200px' : '2400px',
|
|
29
|
+
margin: '32px auto',
|
|
30
|
+
gap: gap ?? '2rem',
|
|
31
|
+
overflow: 'scroll',
|
|
32
|
+
gridTemplateColumns,
|
|
33
|
+
[mediaQuery[0]]: {
|
|
34
|
+
gridTemplateColumns: tabletTemplateColumns,
|
|
35
|
+
},
|
|
36
|
+
[mediaQuery[1]]: {
|
|
37
|
+
gridTemplateColumns: mobileTemplateColumns,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const NsColumnControl = ({ columnClass, children, gap, additionalProps }: NsColumnControlProps) => {
|
|
43
|
+
const sizes = useMemo(() => {
|
|
44
|
+
return getColumnSizes(columnClass);
|
|
45
|
+
}, [columnClass]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<StyledDiv
|
|
49
|
+
sizes={sizes}
|
|
50
|
+
gap={gap}
|
|
51
|
+
data-testid={additionalProps?.testid ?? 'ns-column-control'}
|
|
52
|
+
className={columnClass}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</StyledDiv>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default NsColumnControl;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { AdditionalProps } from '../common.types';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
options to be shown on content stack
|
|
6
|
+
1 Column (100%)
|
|
7
|
+
2 Columns (50%, 50%)
|
|
8
|
+
2 Columns (33%, 67%)
|
|
9
|
+
2 Columns (67%, 33%)
|
|
10
|
+
3 Columns (33%, 33%, 33%)
|
|
11
|
+
3 Columns (50%, 25%, 25%)
|
|
12
|
+
3 Columns (25%, 25%, 50%)
|
|
13
|
+
4 Columns (25%, 25%, 25%, 25%)
|
|
14
|
+
*/
|
|
15
|
+
export type NsColumnControlProps = PropsWithChildren<{
|
|
16
|
+
/**
|
|
17
|
+
* follows the convention of ns-col-lg-(column-ratio) (for large screens >= 960px),
|
|
18
|
+
* ns-col-md-(column-ratio) (for tablet screens >= 600px),
|
|
19
|
+
* and ns-col-sm-(column-ratio) (for mobile screens i.e < 600px)
|
|
20
|
+
* sample values
|
|
21
|
+
* 'ns-col-lg-2-1',
|
|
22
|
+
'ns-col-lg-1-2',
|
|
23
|
+
'ns-col-lg-1-1',
|
|
24
|
+
'ns-col-lg-1-1-1',
|
|
25
|
+
'ns-col-lg-2-1-1',
|
|
26
|
+
'ns-col-lg-1-1-1-1',
|
|
27
|
+
'ns-col-md-1',
|
|
28
|
+
'ns-col-md-1-1',
|
|
29
|
+
'ns-col-md-1-2',
|
|
30
|
+
'ns-col-md-2-1',
|
|
31
|
+
'ns-col-md-1-1-1',
|
|
32
|
+
'ns-col-md-2-1-1',
|
|
33
|
+
'ns-col-sm-1',
|
|
34
|
+
'ns-col-sm-1-1',
|
|
35
|
+
'ns-col-sm-1-2',
|
|
36
|
+
'ns-col-sm-2-1',
|
|
37
|
+
*/
|
|
38
|
+
columnClass: string;
|
|
39
|
+
gap?: number;
|
|
40
|
+
}> &
|
|
41
|
+
AdditionalProps;
|
|
42
|
+
|
|
43
|
+
export type sizeObjectType = {
|
|
44
|
+
fractions?: number[];
|
|
45
|
+
tabletFractions?: number[];
|
|
46
|
+
mobileFractions?: number[];
|
|
47
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ValueOf } from 'next/dist/shared/lib/constants';
|
|
2
|
+
import { NsColumnControlProps, sizeObjectType } from './types';
|
|
3
|
+
|
|
4
|
+
export const columnClasses: NsColumnControlProps['columnClass'][] = [
|
|
5
|
+
'ns-col-lg-2-1',
|
|
6
|
+
'ns-col-lg-1-2',
|
|
7
|
+
'ns-col-lg-1-1',
|
|
8
|
+
'ns-col-lg-1-1-1',
|
|
9
|
+
'ns-col-lg-2-1-1',
|
|
10
|
+
'ns-col-lg-1-1-1-1',
|
|
11
|
+
'ns-col-md-1',
|
|
12
|
+
'ns-col-md-1-1',
|
|
13
|
+
'ns-col-md-1-2',
|
|
14
|
+
'ns-col-md-2-1',
|
|
15
|
+
'ns-col-md-1-1-1',
|
|
16
|
+
'ns-col-md-2-1-1',
|
|
17
|
+
'ns-col-sm-1',
|
|
18
|
+
'ns-col-sm-1-1',
|
|
19
|
+
'ns-col-sm-1-2',
|
|
20
|
+
'ns-col-sm-2-1',
|
|
21
|
+
];
|
|
22
|
+
const sizeKeyPrefix = {
|
|
23
|
+
desktop: 'ns-col-lg-',
|
|
24
|
+
tablet: 'ns-col-md-',
|
|
25
|
+
mobile: 'ns-col-sm-',
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
const sizeKeyMap: Record<ValueOf<typeof sizeKeyPrefix>, string> = {
|
|
29
|
+
[sizeKeyPrefix.desktop]: 'fractions',
|
|
30
|
+
[sizeKeyPrefix.tablet]: 'tabletFractions',
|
|
31
|
+
[sizeKeyPrefix.mobile]: 'mobileFractions',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* getColumnSizes function, we parse the input class and
|
|
36
|
+
* return an object containing optionally containing 3 keys namely [fractions, tabletFractions, mobileFractions]
|
|
37
|
+
* value of each of the key is an array of fractions example ['1','1']
|
|
38
|
+
* meaning that the column should be divided into two fractions each of 1fr
|
|
39
|
+
* @param inputColumnClass = a string that is mix of ns-col-lg-1-1-1 ns-col-md-1-1 ns-col-sm-1 etc
|
|
40
|
+
* @returns {fractions:['1','1','1'], tabletFractions:['1','1'], mobileFractions:['1'] }
|
|
41
|
+
*/
|
|
42
|
+
export const getColumnSizes = (inputColumnClass: string): sizeObjectType => {
|
|
43
|
+
let columnClass = inputColumnClass.trim() as NsColumnControlProps['columnClass'];
|
|
44
|
+
const validClasses = columnClass
|
|
45
|
+
.split(' ')
|
|
46
|
+
.filter((e) => Object.values(sizeKeyPrefix).some((prefix) => e.startsWith(prefix)));
|
|
47
|
+
const sizeObject: sizeObjectType = validClasses.reduce((acc: any, current: string) => {
|
|
48
|
+
for (let key of Object.values(sizeKeyPrefix)) {
|
|
49
|
+
if (current.startsWith(key)) {
|
|
50
|
+
let interClass = current.replace(key, '');
|
|
51
|
+
let sizes = interClass.split('-').filter((e) => /^[1-9]$/.test(e));
|
|
52
|
+
acc[sizeKeyMap[key]] = sizes;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return acc;
|
|
57
|
+
}, {});
|
|
58
|
+
if (Object.keys(sizeObject).length < 1) {
|
|
59
|
+
/* based on string passed as className, no valid size array could be extracted */
|
|
60
|
+
console.warn(
|
|
61
|
+
'Received invalid className, valid column-control className starts with ' +
|
|
62
|
+
Object.values(sizeKeyPrefix).join(', ')
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return sizeObject;
|
|
66
|
+
};
|
|
@@ -39,7 +39,7 @@ const NsDropdown: React.FC<DropdownProps> = ({
|
|
|
39
39
|
<DropdownContainer disabled={disabled}>
|
|
40
40
|
<DropdownHead data-testid="dropdown-head" disabled={disabled} type={type} onClick={handleToggle}>
|
|
41
41
|
<DropdownButton>{selectedLabel || placeholder}</DropdownButton>
|
|
42
|
-
<NsIcon testId="chevron-icon" name={isOpen ? '
|
|
42
|
+
<NsIcon testId="chevron-icon" name={isOpen ? 'arrowUp' : 'arrowDown'} color="primary" />
|
|
43
43
|
</DropdownHead>
|
|
44
44
|
{isOpen ? (
|
|
45
45
|
<DropdownList
|