@nuskin/foundation-ui-components 1.0.0-develop.2 → 1.0.0-develop.4
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 +1 -1
- package/src/common/accordion/NsAccordion.tsx +77 -0
- package/src/common/accordion/NsAccordionHeader.tsx +56 -0
- package/src/common/accordion/NsAccordionItem.tsx +39 -0
- package/src/common/accordion/NsCollapse.tsx +56 -0
- package/src/common/accordion/NsToggleSection.tsx +30 -0
- package/src/common/accordion/ToggleSection.styled.tsx +28 -0
- package/src/common/accordion/index.ts +6 -0
- package/src/common/accordion/specs/NsAccordion.spec.tsx +91 -0
- package/src/common/accordion/specs/NsAccordionHeader.spec.tsx +47 -0
- package/src/common/accordion/specs/NsAccordionItem.spec.tsx +38 -0
- package/src/common/accordion/specs/NsCollapse.spec.tsx +37 -0
- package/src/common/accordion/specs/NsToggleSection.spec.tsx +33 -0
- package/src/common/accordion/stories/NsAccordion.stories.tsx +47 -0
- package/src/common/accordion/stories/NsAccordionHeader.stories.tsx +40 -0
- package/src/common/accordion/stories/NsAccordionItem.stories.tsx +40 -0
- package/src/common/accordion/stories/NsCollapse.stories.tsx +35 -0
- package/src/common/accordion/stories/NsToggleSection.stories.tsx +23 -0
- package/src/common/accordion/stories/data.tsx +22 -0
- package/src/common/accordion/types.d.ts +73 -0
- package/src/common/alert/NsAlert.stories.tsx +15 -14
- package/src/common/alert/types.d.ts +22 -22
- package/src/common/auth-dropdown/NsAuthDropdown.stories.tsx +1 -1
- package/src/common/autocomplete/NsAutocomplete.spec.tsx +20 -8
- package/src/common/autocomplete/NsAutocomplete.stories.tsx +4 -1
- package/src/common/autocomplete/NsAutocomplete.styled.tsx +12 -0
- package/src/common/autocomplete/NsAutocomplete.tsx +36 -18
- package/src/common/autocomplete/types.d.ts +7 -1
- package/src/common/banner/NsBanner.stories.tsx +1 -1
- package/src/common/brand-components/NsBrand.stories.tsx +1 -1
- package/src/common/breadcrumb/NsBreadcrumb.spec.tsx +1 -7
- package/src/common/breadcrumb/NsBreadcrumb.stories.tsx +2 -4
- package/src/common/breadcrumb/data.ts +7 -0
- package/src/common/button/NsButton.spec.tsx +10 -11
- package/src/common/button/NsButton.stories.tsx +1 -1
- package/src/common/card/stories/NsBgCard.stories.tsx +1 -3
- package/src/common/card/stories/NsBrandProductCard.stories.tsx +1 -1
- package/src/common/card/stories/NsFfgCard.stories.tsx +1 -1
- package/src/common/card/stories/{data.js → data.ts} +1 -25
- package/src/common/carousal/NsScrollingCarousal.tsx +26 -2
- package/src/common/carousal/stories/NsScrollingCarousal.stories.tsx +1 -1
- package/src/common/carousal/types.d.ts +2 -0
- package/src/common/cart-icon/NsCartIcon.stories.tsx +1 -1
- package/src/common/checkbox/NsCheckbox.stories.tsx +1 -1
- package/src/common/checkbox/NsCheckbox.styled.tsx +2 -2
- package/src/common/column-control/NsFlexColumns.tsx +5 -10
- package/src/common/column-control/NsGridColumns.tsx +5 -10
- package/src/common/column-control/specs/NsColumnControl.spec.tsx +1 -1
- package/src/common/column-control/specs/NsFlexColumns.spec.tsx +1 -1
- package/src/common/column-control/specs/NsGridColumns.spec.tsx +1 -1
- package/src/common/column-control/stories/DivArray.stories.tsx +18 -0
- package/src/common/column-control/stories/DivArray.tsx +63 -0
- package/src/common/column-control/stories/NsColumnControl.stories.tsx +6 -3
- package/src/common/column-control/stories/NsFlexColumns.stories.tsx +13 -9
- package/src/common/column-control/stories/NsGridColumns.stories.tsx +12 -8
- package/src/common/column-control/stories/common.tsx +1 -1
- package/src/common/column-control/styled/StyledFlex.tsx +7 -3
- package/src/common/column-control/styled/StyledGrid.tsx +9 -2
- package/src/common/column-control/styled/common.ts +2 -2
- package/src/common/column-control/types.d.ts +15 -16
- package/src/common/common.types.d.ts +1 -0
- package/src/common/dropdown/stories/NsDropdown.stories.tsx +1 -1
- package/src/common/dropdown/stories/NsDropdownCountry.stories.tsx +1 -1
- package/src/common/email-simple/NsEmailSimple.spec.tsx +145 -0
- package/src/common/email-simple/NsEmailSimple.stories.tsx +61 -0
- package/src/common/email-simple/NsEmailSimple.tsx +71 -0
- package/src/common/email-simple/index.ts +2 -0
- package/src/common/email-simple/types.d.ts +14 -0
- package/src/common/flag/NsFlag.spec.tsx +4 -25
- package/src/common/flag/NsFlag.stories.tsx +1 -1
- package/src/common/flag/NsFlag.tsx +0 -2
- package/src/common/icon/NsIcon.stories.tsx +1 -1
- package/src/common/image/NsImage.stories.tsx +1 -1
- package/src/common/image/NsNextImage.stories.tsx +1 -1
- package/src/common/image/types.d.ts +1 -0
- package/src/common/index.ts +3 -0
- package/src/common/input/NsInputField.stories.tsx +13 -10
- package/src/common/input/NsInputField.tsx +12 -5
- package/src/common/input/getAdornmentProps.tsx +4 -0
- package/src/common/input/types.d.ts +18 -1
- package/src/common/label/NsLabel.stories.tsx +5 -0
- package/src/common/label/NsLabel.tsx +16 -19
- package/src/common/label/types.d.ts +2 -1
- package/src/common/link/NsLink.stories.tsx +1 -1
- package/src/common/nav-product-card/NsNavProductCard.stories.tsx +1 -1
- package/src/common/nav-tab/NsNavTab.spec.tsx +0 -5
- package/src/common/nav-tab/NsNavTab.stories.tsx +1 -1
- package/src/common/radiogroup/NsRadioGroup.spec.tsx +63 -0
- package/src/common/radiogroup/NsRadioGroup.stories.tsx +101 -0
- package/src/common/radiogroup/NsRadioGroup.styled.tsx +56 -0
- package/src/common/radiogroup/NsRadioGroup.tsx +46 -0
- package/src/common/radiogroup/index.ts +2 -0
- package/src/common/radiogroup/types.d.ts +19 -0
- package/src/common/select/NsSelect.spec.tsx +200 -0
- package/src/common/select/NsSelect.styled.tsx +89 -0
- package/src/common/select/NsSelect.tsx +117 -0
- package/src/common/select/NsSelectOption.tsx +30 -0
- package/src/common/select/index.ts +3 -0
- package/src/common/select/stories/NsSelect.stories.tsx +134 -0
- package/src/common/select/types.d.ts +34 -0
- package/src/common/table/NsExpandableRow.tsx +68 -0
- package/src/common/table/NsTable.spec.tsx +69 -41
- package/src/common/table/NsTable.stories.tsx +48 -10
- package/src/common/table/NsTable.styled.ts +95 -4
- package/src/common/table/NsTable.tsx +78 -89
- package/src/common/table/NsTableHeader.tsx +52 -38
- package/src/common/table/NsTablePagination.tsx +21 -13
- package/src/common/table/types.d.ts +21 -8
- package/src/common/text-strip/NsTextStrip.stories.tsx +5 -4
- package/src/common/text-strip/NsTextStrip.tsx +5 -2
- package/src/common/text-strip/types.d.ts +7 -4
- package/src/common/typography/NsTypography.stories.tsx +1 -1
- package/src/common/validations/regex.ts +3 -0
- package/src/common/videos/NsVideo.stories.tsx +1 -1
- package/src/common/column-control/stories/NDivs.story.tsx +0 -35
- /package/src/common/card/{tests → specs}/BrandLogo.spec.tsx +0 -0
- /package/src/common/card/{tests → specs}/NsBgCard.spec.tsx +0 -0
- /package/src/common/card/{tests → specs}/NsBrandProductCard.spec.tsx +0 -0
- /package/src/common/card/{tests → specs}/NsFfgCard.spec.tsx +0 -0
- /package/src/common/carousal/{tests → specs}/Chevron.spec.tsx +0 -0
- /package/src/common/carousal/{tests → specs}/NsCarousal.spec.tsx +0 -0
- /package/src/common/carousal/{tests → specs}/NsScrollingCarousal.spec.tsx +0 -0
- /package/src/common/carousal/{tests → specs}/RenderBrand.spec.tsx +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/foundation-ui-components",
|
|
3
|
-
"version": "1.0.0-develop.
|
|
3
|
+
"version": "1.0.0-develop.4",
|
|
4
4
|
"description": "It is a foundation library written in react, that provides common components to all UI pages",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "src/common/index.ts",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import NsAccordionItem from './NsAccordionItem';
|
|
3
|
+
import { NsAccordionProps } from './types';
|
|
4
|
+
import { StyledAccordionWrapper } from './ToggleSection.styled';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NsAccordion can be used to show a list of items with title and body
|
|
8
|
+
* it accepts a `boolean` prop `allowMultipleExpanded` which when true,
|
|
9
|
+
* multiple sections of accordion can remain open at a time
|
|
10
|
+
*
|
|
11
|
+
* we can define certain section to remain open by default using `isDefaultOpen` in the
|
|
12
|
+
* contentArray item
|
|
13
|
+
* if there are multiple items available for default open, but `allowMultipleExpanded`
|
|
14
|
+
* is false then only first item of default open is made open
|
|
15
|
+
*/
|
|
16
|
+
const NsAccordion = ({
|
|
17
|
+
additionalProps,
|
|
18
|
+
allowMultipleExpanded,
|
|
19
|
+
contentArray,
|
|
20
|
+
variant,
|
|
21
|
+
headerTypographyVariant,
|
|
22
|
+
closedIconName,
|
|
23
|
+
openedIconName,
|
|
24
|
+
}: NsAccordionProps) => {
|
|
25
|
+
const defaultOpenIds = useMemo(() => {
|
|
26
|
+
return contentArray
|
|
27
|
+
.map((content, index) => (content?.isDefaultOpen ? index : null))
|
|
28
|
+
.filter((e) => typeof e === 'number') as number[];
|
|
29
|
+
}, [contentArray]);
|
|
30
|
+
const [expandedIds, setExpandedIds] = useState<Array<number>>(defaultOpenIds);
|
|
31
|
+
|
|
32
|
+
const toggleOpen = (sectionNumber: number, isExpanded: boolean) => {
|
|
33
|
+
if (allowMultipleExpanded) {
|
|
34
|
+
if (isExpanded) {
|
|
35
|
+
setExpandedIds(expandedIds.filter((existingNumber) => existingNumber !== sectionNumber));
|
|
36
|
+
} else {
|
|
37
|
+
setExpandedIds([...expandedIds, sectionNumber]);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
if (isExpanded) {
|
|
41
|
+
setExpandedIds([]);
|
|
42
|
+
} else {
|
|
43
|
+
setExpandedIds([sectionNumber]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
setExpandedIds(allowMultipleExpanded ? defaultOpenIds : [defaultOpenIds[0]]);
|
|
49
|
+
}, [allowMultipleExpanded, defaultOpenIds]);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<StyledAccordionWrapper
|
|
53
|
+
variant={variant}
|
|
54
|
+
className="ns-accordion"
|
|
55
|
+
data-testid={additionalProps?.testid ?? 'ns-accordion-testid'}
|
|
56
|
+
>
|
|
57
|
+
{contentArray.map((content, index) => {
|
|
58
|
+
const isOpen = expandedIds.includes(index);
|
|
59
|
+
return (
|
|
60
|
+
<NsAccordionItem
|
|
61
|
+
variant={variant}
|
|
62
|
+
headerTypographyVariant={headerTypographyVariant}
|
|
63
|
+
closedIconName={closedIconName}
|
|
64
|
+
openedIconName={openedIconName}
|
|
65
|
+
key={index}
|
|
66
|
+
isOpen={isOpen}
|
|
67
|
+
toggleOpen={() => toggleOpen(index, isOpen)}
|
|
68
|
+
content={content?.body}
|
|
69
|
+
title={content?.title}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
})}
|
|
73
|
+
</StyledAccordionWrapper>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default NsAccordion;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NsTypography, NsIcon, NsAccordionHeaderProps, AccordionVariant } from '..';
|
|
2
|
+
import { styled, ColorUtils } from '@nuskin/foundation-theme';
|
|
3
|
+
const { getPaletteColor } = ColorUtils;
|
|
4
|
+
|
|
5
|
+
const StyledHeader = styled('header')<{ variant: AccordionVariant }>(({ theme, variant }) => {
|
|
6
|
+
const backgroundColor = getPaletteColor(theme, 'primary', 'contrastText') as string;
|
|
7
|
+
let borderBottom = 'none';
|
|
8
|
+
if (variant === AccordionVariant.Compact) {
|
|
9
|
+
borderBottom = '1px solid rgb(0,0,0,0.125)';
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
backgroundColor,
|
|
13
|
+
cursor: 'pointer',
|
|
14
|
+
display: 'grid',
|
|
15
|
+
gridTemplateColumns: '1fr 24px',
|
|
16
|
+
padding: '9px 16px',
|
|
17
|
+
borderBottom,
|
|
18
|
+
} as any;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* NsAccordionHeader is primarily used by NsToggleSection and NsAccordionItem
|
|
23
|
+
* as a header, so it accepts `title` , `isOpen` and `onClick`
|
|
24
|
+
*/
|
|
25
|
+
const NsAccordionHeader = ({
|
|
26
|
+
title,
|
|
27
|
+
onClick,
|
|
28
|
+
isOpen,
|
|
29
|
+
additionalProps,
|
|
30
|
+
openedIconName,
|
|
31
|
+
closedIconName,
|
|
32
|
+
variant,
|
|
33
|
+
headerTypographyVariant,
|
|
34
|
+
}: NsAccordionHeaderProps) => {
|
|
35
|
+
return (
|
|
36
|
+
<StyledHeader
|
|
37
|
+
role="button"
|
|
38
|
+
aria-expanded={isOpen ? 'true' : 'false'}
|
|
39
|
+
className={`section-header ${isOpen ? 'expanded' : ''} ${additionalProps?.className ?? ''}`}
|
|
40
|
+
onClick={onClick}
|
|
41
|
+
variant={variant}
|
|
42
|
+
>
|
|
43
|
+
{typeof title === 'string' ? (
|
|
44
|
+
<NsTypography variant={headerTypographyVariant ?? 'h4'} weight="bold">
|
|
45
|
+
{title}
|
|
46
|
+
</NsTypography>
|
|
47
|
+
) : (
|
|
48
|
+
title
|
|
49
|
+
)}
|
|
50
|
+
<span className="header-icon">
|
|
51
|
+
<NsIcon name={isOpen ? openedIconName ?? 'arrowUp' : closedIconName ?? 'arrowDown'} />
|
|
52
|
+
</span>
|
|
53
|
+
</StyledHeader>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
export default NsAccordionHeader;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NsAccordionItemProps, NsCollapse } from '..';
|
|
3
|
+
import NsAccordionHeader from './NsAccordionHeader';
|
|
4
|
+
import { StyledToggleSection } from './ToggleSection.styled';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* this component is used by accordion to pass title
|
|
8
|
+
* and content
|
|
9
|
+
*/
|
|
10
|
+
const NsAccordionItem = ({
|
|
11
|
+
isOpen,
|
|
12
|
+
toggleOpen,
|
|
13
|
+
title,
|
|
14
|
+
content,
|
|
15
|
+
variant,
|
|
16
|
+
headerTypographyVariant,
|
|
17
|
+
closedIconName,
|
|
18
|
+
openedIconName,
|
|
19
|
+
}: NsAccordionItemProps) => {
|
|
20
|
+
return (
|
|
21
|
+
<StyledToggleSection variant={variant}>
|
|
22
|
+
<NsAccordionHeader
|
|
23
|
+
additionalProps={{ className: 'accordion-section' }}
|
|
24
|
+
closedIconName={closedIconName}
|
|
25
|
+
headerTypographyVariant={headerTypographyVariant}
|
|
26
|
+
isOpen={isOpen}
|
|
27
|
+
openedIconName={openedIconName}
|
|
28
|
+
onClick={toggleOpen}
|
|
29
|
+
title={title}
|
|
30
|
+
variant={variant}
|
|
31
|
+
/>
|
|
32
|
+
<NsCollapse isOpen={isOpen} variant={variant}>
|
|
33
|
+
{content}
|
|
34
|
+
</NsCollapse>
|
|
35
|
+
</StyledToggleSection>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default NsAccordionItem;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { styled, ColorUtils, FoundationTheme } from '@nuskin/foundation-theme';
|
|
2
|
+
import { AccordionVariant, NsCollapseProps } from './types.d';
|
|
3
|
+
const { getAppColor, getPaletteColor } = ColorUtils;
|
|
4
|
+
const getIsLight = (theme: FoundationTheme.Theme) => {
|
|
5
|
+
const { palette = { primary: { main: '', contrastText: '' } } } = theme;
|
|
6
|
+
return palette.primary.main === getAppColor(theme, 'primary', 'black');
|
|
7
|
+
};
|
|
8
|
+
const options = {
|
|
9
|
+
shouldForwardProp: (key: string) => key !== 'variant',
|
|
10
|
+
};
|
|
11
|
+
export const StyledCollapse = styled(
|
|
12
|
+
'section',
|
|
13
|
+
options
|
|
14
|
+
)<{ variant: AccordionVariant }>(({ theme, variant }) => {
|
|
15
|
+
let backgroundColor = getIsLight(theme)
|
|
16
|
+
? (getAppColor(theme, 'primary', 'gray') as string)
|
|
17
|
+
: (getAppColor(theme, 'primary', 'dark') as string);
|
|
18
|
+
let borderBottom = 'none';
|
|
19
|
+
if (variant === AccordionVariant.Compact) {
|
|
20
|
+
backgroundColor = getIsLight(theme)
|
|
21
|
+
? (getAppColor(theme, 'primary', 'white') as string)
|
|
22
|
+
: (getAppColor(theme, 'primary', 'dark') as string);
|
|
23
|
+
borderBottom = '1px solid rgb(0,0,0,0.125)';
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
backgroundColor,
|
|
27
|
+
color: getPaletteColor(theme, 'primary', 'main') as string,
|
|
28
|
+
opacity: 0,
|
|
29
|
+
padding: '0 16px',
|
|
30
|
+
height: 0,
|
|
31
|
+
overflow: 'hidden',
|
|
32
|
+
transition: '0.3s',
|
|
33
|
+
'&.expanded': {
|
|
34
|
+
padding: '9px 16px',
|
|
35
|
+
height: 'auto',
|
|
36
|
+
opacity: 1,
|
|
37
|
+
borderBottom,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* NsCollapse component adds css animation while showing children items if prop `isOpen` is true,
|
|
44
|
+
* and while removing children if prop `isOpen` is false
|
|
45
|
+
*/
|
|
46
|
+
const NsCollapse = ({ isOpen, children, additionalProps, variant }: NsCollapseProps) => {
|
|
47
|
+
return (
|
|
48
|
+
<StyledCollapse
|
|
49
|
+
className={`ns-collapse ${isOpen ? 'expanded' : ''} ${additionalProps?.className ?? ''}`}
|
|
50
|
+
variant={variant}
|
|
51
|
+
>
|
|
52
|
+
{isOpen ? children : null}
|
|
53
|
+
</StyledCollapse>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
export default NsCollapse;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { NsToggleSectionProps, NsAccordionItem } from '../';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* NsToggleSection takes prop `title` and prop `children` and toggles
|
|
6
|
+
* children with animation when clicked on title
|
|
7
|
+
*
|
|
8
|
+
* it also has a `defaultOpen` prop, which when true, children is visible
|
|
9
|
+
* by default, otherwise children is hidden by default and toggles between visible
|
|
10
|
+
* and hidden on clicking title
|
|
11
|
+
*/
|
|
12
|
+
const NsToggleSection = ({ title, children, additionalProps, defaultOpen, variant }: NsToggleSectionProps) => {
|
|
13
|
+
const [isExpanded, setIsExpanded] = useState(defaultOpen ?? false);
|
|
14
|
+
|
|
15
|
+
const toggleAccordion = () => {
|
|
16
|
+
setIsExpanded(!isExpanded);
|
|
17
|
+
};
|
|
18
|
+
return (
|
|
19
|
+
<NsAccordionItem
|
|
20
|
+
variant={variant}
|
|
21
|
+
additionalProps={{ ...additionalProps, className: 'toggle-section ' + additionalProps?.className ?? '' }}
|
|
22
|
+
isOpen={isExpanded}
|
|
23
|
+
title={title}
|
|
24
|
+
content={children}
|
|
25
|
+
toggleOpen={toggleAccordion}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default NsToggleSection;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { styled } from '@nuskin/foundation-theme';
|
|
2
|
+
import { AccordionVariant } from './types.d';
|
|
3
|
+
|
|
4
|
+
export const StyledToggleSection = styled('section')<{ variant: AccordionVariant }>(({ variant }) => {
|
|
5
|
+
if (variant === AccordionVariant.Compact) {
|
|
6
|
+
return { borderRight: '1px solid rgb(0,0,0,0.125)' };
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
margin: '5px',
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const StyledAccordionWrapper = styled('div')<{ variant: AccordionVariant }>(({ variant }) => {
|
|
14
|
+
if (variant === AccordionVariant.Compact) {
|
|
15
|
+
return {
|
|
16
|
+
display: 'flex',
|
|
17
|
+
flexDirection: 'column',
|
|
18
|
+
gap: '0',
|
|
19
|
+
borderTop: '1px solid rgb(0,0,0,0.125)',
|
|
20
|
+
borderLeft: '1px solid rgb(0,0,0,0.125)',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
flexDirection: 'column',
|
|
26
|
+
gap: '10px',
|
|
27
|
+
};
|
|
28
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as NsCollapse } from './NsCollapse';
|
|
2
|
+
export { default as NsAccordionHeader } from './NsAccordionHeader';
|
|
3
|
+
export { default as NsToggleSection } from './NsToggleSection';
|
|
4
|
+
export { default as NsAccordionItem } from './NsAccordionItem';
|
|
5
|
+
export { default as NsAccordion } from './NsAccordion';
|
|
6
|
+
export * from './types.d';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen } from '@/test-utils/index';
|
|
3
|
+
import { NsAccordion, NsAccordionProps } from '@/common/index';
|
|
4
|
+
|
|
5
|
+
const generateContentArray = (count: number) => {
|
|
6
|
+
const arr = [] as NsAccordionProps['contentArray'];
|
|
7
|
+
for (let i = 1; i <= count; i++) {
|
|
8
|
+
arr.push({
|
|
9
|
+
title: `Title ${i}`,
|
|
10
|
+
body: `Content ${i}`,
|
|
11
|
+
isDefaultOpen: true,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return arr;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('NsAccordion', () => {
|
|
18
|
+
const contentArray = generateContentArray(3);
|
|
19
|
+
const defaultProps = {
|
|
20
|
+
additionalProps: {
|
|
21
|
+
testid: 'test-accordion',
|
|
22
|
+
},
|
|
23
|
+
allowMultipleExpanded: false,
|
|
24
|
+
contentArray,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
it('renders without error', () => {
|
|
28
|
+
const { getByTestId } = render(<NsAccordion {...defaultProps} />);
|
|
29
|
+
const accordion = getByTestId(defaultProps.additionalProps.testid);
|
|
30
|
+
expect(accordion).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it(`only first section should be visible when allowMultipleExpanded prop is false and multiple items have
|
|
34
|
+
isDefaultOpen true`, () => {
|
|
35
|
+
const { getAllByText } = render(<NsAccordion {...defaultProps} />);
|
|
36
|
+
const [title1, title2, title3] = getAllByText(/Title /i);
|
|
37
|
+
|
|
38
|
+
expect(title1.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
39
|
+
expect(title2.closest('header')).toHaveAttribute('aria-expanded', 'false');
|
|
40
|
+
expect(title3.closest('header')).toHaveAttribute('aria-expanded', 'false');
|
|
41
|
+
expect(screen.queryByText(/Content 1/i)).not.toBeNull();
|
|
42
|
+
expect(screen.queryByText(/Content 2/i)).toBeNull();
|
|
43
|
+
expect(screen.queryByText(/Content 3/i)).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('expands second section only when allowMultipleExpanded prop is false and second title is clicked', async () => {
|
|
47
|
+
const { getAllByText } = render(<NsAccordion {...defaultProps} />);
|
|
48
|
+
const [title1, title2, title3] = getAllByText(/Title /i);
|
|
49
|
+
const header = title2.closest('header');
|
|
50
|
+
expect(header).toHaveAttribute('aria-expanded', 'false');
|
|
51
|
+
if (header) {
|
|
52
|
+
fireEvent.click(header);
|
|
53
|
+
expect(title1.closest('header')).toHaveAttribute('aria-expanded', 'false');
|
|
54
|
+
expect(title2.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
55
|
+
expect(title3.closest('header')).toHaveAttribute('aria-expanded', 'false');
|
|
56
|
+
expect(screen.queryByText(/Content 1/i)).toBeNull();
|
|
57
|
+
expect(screen.queryByText(/Content 2/i)).not.toBeNull();
|
|
58
|
+
expect(screen.queryByText(/Content 3/i)).toBeNull();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('expands multiple sections by default when allowMultipleExpanded prop is true', () => {
|
|
63
|
+
const contentArray = generateContentArray(3);
|
|
64
|
+
contentArray[2].isDefaultOpen = false;
|
|
65
|
+
const defaultProps = {
|
|
66
|
+
additionalProps: {
|
|
67
|
+
testid: 'test-accordion',
|
|
68
|
+
},
|
|
69
|
+
allowMultipleExpanded: false,
|
|
70
|
+
contentArray,
|
|
71
|
+
};
|
|
72
|
+
const { getAllByText } = render(<NsAccordion {...defaultProps} allowMultipleExpanded />);
|
|
73
|
+
const [title1, title2, title3] = getAllByText(/Title /i);
|
|
74
|
+
|
|
75
|
+
expect(title1.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
76
|
+
expect(title2.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
77
|
+
expect(title3.closest('header')).toHaveAttribute('aria-expanded', 'false');
|
|
78
|
+
expect(screen.queryByText(/Content 1/i)).not.toBeNull();
|
|
79
|
+
expect(screen.queryByText(/Content 2/i)).not.toBeNull();
|
|
80
|
+
expect(screen.queryByText(/Content 3/i)).toBeNull();
|
|
81
|
+
|
|
82
|
+
fireEvent.click(title3);
|
|
83
|
+
|
|
84
|
+
expect(title1.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
85
|
+
expect(title2.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
86
|
+
expect(title3.closest('header')).toHaveAttribute('aria-expanded', 'true');
|
|
87
|
+
expect(screen.queryByText(/Content 1/i)).not.toBeNull();
|
|
88
|
+
expect(screen.queryByText(/Content 2/i)).not.toBeNull();
|
|
89
|
+
expect(screen.queryByText(/Content 3/i)).not.toBeNull();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@/test-utils/index';
|
|
3
|
+
import { NsAccordionHeader } from '@/common/index';
|
|
4
|
+
|
|
5
|
+
describe('NsAccordionHeader', () => {
|
|
6
|
+
it('renders title correctly', () => {
|
|
7
|
+
const onClick = jest.fn();
|
|
8
|
+
const title = 'Section Header';
|
|
9
|
+
const { getByText } = render(<NsAccordionHeader title={title} onClick={onClick} />);
|
|
10
|
+
expect(getByText(title)).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('calls onClick handler when clicked', () => {
|
|
14
|
+
const onClick = jest.fn();
|
|
15
|
+
const { container } = render(<NsAccordionHeader title="Section Header" onClick={onClick} />);
|
|
16
|
+
const header = container.querySelector('header');
|
|
17
|
+
if (header) {
|
|
18
|
+
fireEvent.click(header);
|
|
19
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders with additional class name', () => {
|
|
24
|
+
const additionalClassName = 'custom-class';
|
|
25
|
+
const onClick = jest.fn();
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<NsAccordionHeader
|
|
28
|
+
title="Section Header"
|
|
29
|
+
additionalProps={{ className: additionalClassName }}
|
|
30
|
+
onClick={onClick}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
expect(container.querySelector('header')).toHaveClass(`section-header ${additionalClassName}`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders with expanded class when isOpen is true', () => {
|
|
37
|
+
const onClick = jest.fn();
|
|
38
|
+
const { container } = render(<NsAccordionHeader title="Section Header" isOpen onClick={onClick} />);
|
|
39
|
+
expect(container.querySelector('header')).toHaveClass('expanded');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders with header icon', () => {
|
|
43
|
+
const onClick = jest.fn();
|
|
44
|
+
const { getByTestId } = render(<NsAccordionHeader title="Section Header" onClick={onClick} />);
|
|
45
|
+
expect(getByTestId('icon')).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@/test-utils/index';
|
|
3
|
+
import { NsAccordionItem } from '@/common/index';
|
|
4
|
+
|
|
5
|
+
describe('NsAccordionItem', () => {
|
|
6
|
+
const title = 'Accordion Section Title';
|
|
7
|
+
const content = 'Accordion Section Content';
|
|
8
|
+
|
|
9
|
+
it('renders title correctly', () => {
|
|
10
|
+
const { getByText } = render(
|
|
11
|
+
<NsAccordionItem isOpen={false} toggleOpen={() => {}} title={title} content={content} />
|
|
12
|
+
);
|
|
13
|
+
expect(getByText(title)).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('calls toggleOpen function when clicked on the section header', () => {
|
|
17
|
+
const toggleOpen = jest.fn();
|
|
18
|
+
const { getByText } = render(
|
|
19
|
+
<NsAccordionItem isOpen={false} toggleOpen={toggleOpen} title={title} content={content} />
|
|
20
|
+
);
|
|
21
|
+
fireEvent.click(getByText(title));
|
|
22
|
+
expect(toggleOpen).toHaveBeenCalledTimes(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders content when isOpen is true', () => {
|
|
26
|
+
const { getByText } = render(
|
|
27
|
+
<NsAccordionItem isOpen={true} toggleOpen={() => {}} title={title} content={content} />
|
|
28
|
+
);
|
|
29
|
+
expect(getByText(content)).toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not render content when isOpen is false', () => {
|
|
33
|
+
const { queryByText } = render(
|
|
34
|
+
<NsAccordionItem isOpen={false} toggleOpen={() => {}} title={title} content={content} />
|
|
35
|
+
);
|
|
36
|
+
expect(queryByText(content)).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@/test-utils/index';
|
|
3
|
+
import { NsCollapse } from '@/common/index';
|
|
4
|
+
|
|
5
|
+
describe('NsCollapse', () => {
|
|
6
|
+
it('renders children when isOpen is true', () => {
|
|
7
|
+
const { getByText } = render(
|
|
8
|
+
<NsCollapse isOpen>
|
|
9
|
+
<div>Child 1</div>
|
|
10
|
+
<div>Child 2</div>
|
|
11
|
+
</NsCollapse>
|
|
12
|
+
);
|
|
13
|
+
expect(getByText('Child 1')).toBeInTheDocument();
|
|
14
|
+
expect(getByText('Child 2')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('does not render children when isOpen is false', () => {
|
|
18
|
+
render(
|
|
19
|
+
<NsCollapse isOpen={false}>
|
|
20
|
+
<div>Child 1</div>
|
|
21
|
+
<div>Child 2</div>
|
|
22
|
+
</NsCollapse>
|
|
23
|
+
);
|
|
24
|
+
expect(screen.queryByText('Child')).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders with additional class name', () => {
|
|
28
|
+
const additionalClassName = 'custom-class';
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<NsCollapse additionalProps={{ className: additionalClassName }} isOpen>
|
|
31
|
+
<div>Child 1</div>
|
|
32
|
+
<div>Child 2</div>
|
|
33
|
+
</NsCollapse>
|
|
34
|
+
);
|
|
35
|
+
expect(container.querySelector('section')).toHaveClass(`ns-collapse ${additionalClassName}`);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@/test-utils/index';
|
|
3
|
+
import { NsToggleSection } from '@/common/index';
|
|
4
|
+
|
|
5
|
+
describe('NsToggleSection', () => {
|
|
6
|
+
const title = 'Toggle Section Title';
|
|
7
|
+
const content = 'Toggle Section Content';
|
|
8
|
+
|
|
9
|
+
it('renders title correctly', () => {
|
|
10
|
+
const { getByText } = render(<NsToggleSection title={title}>{content}</NsToggleSection>);
|
|
11
|
+
expect(getByText(title)).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('toggles content visibility when clicked on the section header', () => {
|
|
15
|
+
const { getByText, queryByText } = render(<NsToggleSection title={title}>{content}</NsToggleSection>);
|
|
16
|
+
|
|
17
|
+
expect(queryByText(content)).toBeNull();
|
|
18
|
+
fireEvent.click(getByText(title));
|
|
19
|
+
expect(getByText(content)).toBeInTheDocument();
|
|
20
|
+
fireEvent.click(getByText(title));
|
|
21
|
+
expect(queryByText(content)).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('sets defaultOpen prop correctly', () => {
|
|
25
|
+
const { getByText } = render(
|
|
26
|
+
<NsToggleSection title={title} defaultOpen>
|
|
27
|
+
{content}
|
|
28
|
+
</NsToggleSection>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(getByText(content)).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
|
+
import NsAccordion from '../NsAccordion';
|
|
4
|
+
import { contentArray } from './data';
|
|
5
|
+
import { AccordionVariant } from '../types.d';
|
|
6
|
+
|
|
7
|
+
type ComponentType = typeof NsAccordion;
|
|
8
|
+
type Story = StoryObj<ComponentType>;
|
|
9
|
+
type MetaStory = Meta<ComponentType>;
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
title: 'Components/Accordion/NsAccordion',
|
|
13
|
+
component: NsAccordion,
|
|
14
|
+
decorators: [withCenterDecorator],
|
|
15
|
+
argTypes: {
|
|
16
|
+
allowMultipleExpanded: {
|
|
17
|
+
control: 'boolean',
|
|
18
|
+
description: `if true, then multiple sections can remain open at a same time, and if false,
|
|
19
|
+
opening a section will close other sections of the accordion`,
|
|
20
|
+
},
|
|
21
|
+
contentArray: {
|
|
22
|
+
description: ` this should be an array of objects, each object should have 3 properties,
|
|
23
|
+
title, body and isDefaultOpen`,
|
|
24
|
+
},
|
|
25
|
+
variant: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: [...Object.values(AccordionVariant)],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
variant: AccordionVariant.Basic,
|
|
32
|
+
},
|
|
33
|
+
} as MetaStory;
|
|
34
|
+
|
|
35
|
+
export const SingleOpen: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
allowMultipleExpanded: false,
|
|
38
|
+
contentArray,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const MultipleOpen: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
allowMultipleExpanded: true,
|
|
45
|
+
contentArray,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
|
+
import { NsAccordionHeader, NsIconVariants } from '../..';
|
|
4
|
+
|
|
5
|
+
type ComponentType = typeof NsAccordionHeader;
|
|
6
|
+
type Story = StoryObj<ComponentType>;
|
|
7
|
+
type MetaStory = Meta<ComponentType>;
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Components/Accordion/NsAccordionHeader',
|
|
11
|
+
component: NsAccordionHeader,
|
|
12
|
+
decorators: [withCenterDecorator],
|
|
13
|
+
argTypes: {
|
|
14
|
+
title: { control: 'string' },
|
|
15
|
+
openedIconName: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: Object.keys(NsIconVariants),
|
|
18
|
+
},
|
|
19
|
+
closedIconName: {
|
|
20
|
+
control: 'select',
|
|
21
|
+
options: Object.keys(NsIconVariants),
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
args: {
|
|
25
|
+
title: 'Nuskin',
|
|
26
|
+
onClick: () => {
|
|
27
|
+
console.log('accordion header clicked');
|
|
28
|
+
},
|
|
29
|
+
openedIconName: 'arrowUp',
|
|
30
|
+
closedIconName: 'arrowDown',
|
|
31
|
+
isOpen: false,
|
|
32
|
+
},
|
|
33
|
+
} as MetaStory;
|
|
34
|
+
|
|
35
|
+
export const ClosedHeader: Story = {};
|
|
36
|
+
export const OpenedHeader: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
isOpen: true,
|
|
39
|
+
},
|
|
40
|
+
};
|