@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
|
@@ -18,22 +18,7 @@ export type NsColumnControlProps = PropsWithChildren<{
|
|
|
18
18
|
* ns-col-md-(column-ratio) (for tablet screens >= 600px),
|
|
19
19
|
* and ns-col-sm-(column-ratio) (for mobile screens i.e < 600px)
|
|
20
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',
|
|
21
|
+
* 'ns-col-lg-2-1', 'ns-col-lg-1-2', 'ns-col-lg-1-1',
|
|
37
22
|
*/
|
|
38
23
|
columnClass: string;
|
|
39
24
|
gap?: number;
|
|
@@ -45,3 +30,17 @@ export type sizeObjectType = {
|
|
|
45
30
|
tabletFractions?: number[];
|
|
46
31
|
mobileFractions?: number[];
|
|
47
32
|
};
|
|
33
|
+
|
|
34
|
+
export type NsFlexColumnsProps = PropsWithChildren<{
|
|
35
|
+
/**
|
|
36
|
+
* follows the convention of ns-col-lg-{column-ratio}
|
|
37
|
+
* sample value for column-ratio = 2-1 ==>ns-col-lg-2-1
|
|
38
|
+
*/
|
|
39
|
+
columnClass: string;
|
|
40
|
+
gap: number;
|
|
41
|
+
/* when this boolean is true, column control takes full width of page instead of being restricted to 1200px */
|
|
42
|
+
isFullWidth?: boolean;
|
|
43
|
+
}> &
|
|
44
|
+
AdditionalProps;
|
|
45
|
+
|
|
46
|
+
export type NsGridColumnsProps = NsFlexColumnsProps;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { NsDropdown, NsDropdownOption } from '../';
|
|
4
|
-
import { withCenterDecorator } from '
|
|
4
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
5
5
|
|
|
6
6
|
type ComponentType = typeof NsDropdown;
|
|
7
7
|
type Story = StoryObj<ComponentType>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { NsDropdown, NsDropdownOption } from '../';
|
|
4
|
-
import { withCenterDecorator } from '
|
|
4
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
5
5
|
|
|
6
6
|
type ComponentType = typeof NsDropdown;
|
|
7
7
|
type Story = StoryObj<ComponentType>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, act } from '@testing-library/react';
|
|
3
|
+
import NsEmailSimple from './NsEmailSimple';
|
|
4
|
+
import { userEvent } from '@storybook/testing-library';
|
|
5
|
+
import { emailPattern } from '../validations/regex';
|
|
6
|
+
|
|
7
|
+
describe('NsEmailSimple', () => {
|
|
8
|
+
let mockCheckEmail = jest.fn().mockResolvedValue([true, false]);
|
|
9
|
+
let propsData = {
|
|
10
|
+
disabled: false,
|
|
11
|
+
label: 'Email',
|
|
12
|
+
required: true,
|
|
13
|
+
maxLength: 50,
|
|
14
|
+
invalidEmailMessage: 'Please enter a valid email',
|
|
15
|
+
dupedEmailMessage: 'Email ID already exists',
|
|
16
|
+
requiredErrorMessage: 'Email field is required',
|
|
17
|
+
checkEmail: mockCheckEmail,
|
|
18
|
+
};
|
|
19
|
+
it('renders email field with invalid regex error', async () => {
|
|
20
|
+
render(<NsEmailSimple {...propsData} />);
|
|
21
|
+
const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
|
|
22
|
+
expect(emailField).toBeInTheDocument;
|
|
23
|
+
const inputElement = emailField.querySelector('input');
|
|
24
|
+
if (!inputElement) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
act(() => {
|
|
28
|
+
userEvent.type(inputElement, 'test');
|
|
29
|
+
userEvent.tab();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(inputElement).toHaveValue('test');
|
|
33
|
+
|
|
34
|
+
act(() => {
|
|
35
|
+
userEvent.clear(inputElement);
|
|
36
|
+
userEvent.type(inputElement, 'test@gm');
|
|
37
|
+
userEvent.tab();
|
|
38
|
+
});
|
|
39
|
+
expect(inputElement).toHaveValue('test@gm');
|
|
40
|
+
|
|
41
|
+
mockCheckEmail = jest.fn().mockResolvedValue([true, true]);
|
|
42
|
+
render(<NsEmailSimple {...propsData} />);
|
|
43
|
+
act(() => {
|
|
44
|
+
userEvent.clear(inputElement);
|
|
45
|
+
userEvent.type(inputElement, 'test!gmail');
|
|
46
|
+
userEvent.tab();
|
|
47
|
+
});
|
|
48
|
+
expect(inputElement).toHaveValue('test!gmail');
|
|
49
|
+
act(() => {
|
|
50
|
+
userEvent.clear(inputElement);
|
|
51
|
+
userEvent.tab();
|
|
52
|
+
});
|
|
53
|
+
expect(inputElement).toHaveValue('');
|
|
54
|
+
|
|
55
|
+
mockCheckEmail = jest.fn().mockResolvedValue([true, false]);
|
|
56
|
+
render(<NsEmailSimple {...propsData} />);
|
|
57
|
+
act(() => {
|
|
58
|
+
userEvent.clear(inputElement);
|
|
59
|
+
userEvent.type(inputElement, 'test@gmail.co');
|
|
60
|
+
userEvent.tab();
|
|
61
|
+
});
|
|
62
|
+
expect(inputElement).toHaveValue('test@gmail.co');
|
|
63
|
+
});
|
|
64
|
+
it('renders email field with invalid error', async () => {
|
|
65
|
+
let mockCheckEmail1 = jest.fn().mockResolvedValue([false, false]);
|
|
66
|
+
let propsData1 = {
|
|
67
|
+
disabled: false,
|
|
68
|
+
label: 'Email',
|
|
69
|
+
required: true,
|
|
70
|
+
maxLength: 50,
|
|
71
|
+
invalidEmailMessage: 'Please enter a valid email',
|
|
72
|
+
dupedEmailMessage: 'Email ID already exists',
|
|
73
|
+
requiredErrorMessage: 'Email field is required',
|
|
74
|
+
checkEmail: mockCheckEmail1,
|
|
75
|
+
};
|
|
76
|
+
render(<NsEmailSimple {...propsData1} />);
|
|
77
|
+
const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
|
|
78
|
+
expect(emailField).toBeInTheDocument;
|
|
79
|
+
const inputElement = emailField.querySelector('input');
|
|
80
|
+
if (!inputElement) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
act(() => {
|
|
84
|
+
userEvent.clear(inputElement);
|
|
85
|
+
userEvent.type(inputElement, 'test@gmail.com');
|
|
86
|
+
userEvent.tab();
|
|
87
|
+
});
|
|
88
|
+
expect(inputElement).toHaveValue('test@gmail.com');
|
|
89
|
+
expect(inputElement.value).toMatch(emailPattern);
|
|
90
|
+
});
|
|
91
|
+
it('renders email field without error', async () => {
|
|
92
|
+
let mockCheckEmail1 = jest.fn().mockResolvedValue([true, true]);
|
|
93
|
+
let propsData1 = {
|
|
94
|
+
disabled: false,
|
|
95
|
+
label: 'Email',
|
|
96
|
+
required: true,
|
|
97
|
+
maxLength: 50,
|
|
98
|
+
invalidEmailMessage: 'Please enter a valid email',
|
|
99
|
+
dupedEmailMessage: 'Email ID already exists',
|
|
100
|
+
requiredErrorMessage: 'Email field is required',
|
|
101
|
+
checkEmail: mockCheckEmail1,
|
|
102
|
+
};
|
|
103
|
+
render(<NsEmailSimple {...propsData1} />);
|
|
104
|
+
const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
|
|
105
|
+
expect(emailField).toBeInTheDocument;
|
|
106
|
+
const inputElement = emailField.querySelector('input');
|
|
107
|
+
if (!inputElement) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
act(() => {
|
|
111
|
+
userEvent.clear(inputElement);
|
|
112
|
+
userEvent.type(inputElement, 'test@gmail.com');
|
|
113
|
+
userEvent.tab();
|
|
114
|
+
});
|
|
115
|
+
expect(inputElement).toHaveValue('test@gmail.com');
|
|
116
|
+
expect(inputElement.value).toMatch(emailPattern);
|
|
117
|
+
});
|
|
118
|
+
it('renders email field with API error', async () => {
|
|
119
|
+
let mockCheckEmail1 = jest.fn().mockResolvedValue(undefined);
|
|
120
|
+
let propsData1 = {
|
|
121
|
+
disabled: false,
|
|
122
|
+
label: 'Email',
|
|
123
|
+
required: true,
|
|
124
|
+
maxLength: 50,
|
|
125
|
+
invalidEmailMessage: 'Please enter a valid email',
|
|
126
|
+
dupedEmailMessage: 'Email ID already exists',
|
|
127
|
+
requiredErrorMessage: 'Email field is required',
|
|
128
|
+
checkEmail: mockCheckEmail1,
|
|
129
|
+
};
|
|
130
|
+
render(<NsEmailSimple {...propsData1} />);
|
|
131
|
+
const emailField = (await screen.findByTestId('email-field-test')) as HTMLInputElement;
|
|
132
|
+
expect(emailField).toBeInTheDocument;
|
|
133
|
+
const inputElement = emailField.querySelector('input');
|
|
134
|
+
if (!inputElement) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
act(() => {
|
|
138
|
+
userEvent.clear(inputElement);
|
|
139
|
+
userEvent.type(inputElement, 'test@gmail.com');
|
|
140
|
+
userEvent.tab();
|
|
141
|
+
});
|
|
142
|
+
expect(inputElement).toHaveValue('test@gmail.com');
|
|
143
|
+
expect(inputElement.value).toMatch(emailPattern);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import NsEmailSimple from './NsEmailSimple';
|
|
3
|
+
import { within } from '@storybook/testing-library';
|
|
4
|
+
import { expect } from '@storybook/jest';
|
|
5
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
6
|
+
|
|
7
|
+
async function checkEmail() {
|
|
8
|
+
const isServiceValid = Promise.resolve(true);
|
|
9
|
+
const isEmailAvailable = Promise.resolve(true);
|
|
10
|
+
return [isServiceValid, isEmailAvailable];
|
|
11
|
+
}
|
|
12
|
+
type ComponentType = typeof NsEmailSimple;
|
|
13
|
+
type Story = StoryObj<ComponentType>;
|
|
14
|
+
type MetaStory = Meta<ComponentType>;
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
component: NsEmailSimple,
|
|
18
|
+
title: 'Components/Inputs/NsEmailSimple',
|
|
19
|
+
decorators: [withCenterDecorator],
|
|
20
|
+
play: ({ canvasElement }) => {
|
|
21
|
+
const canvas = within(canvasElement);
|
|
22
|
+
const el = canvas.getByTestId('email-field-test');
|
|
23
|
+
expect(el).toBeInTheDocument();
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
argTypes: {
|
|
27
|
+
disabled: {
|
|
28
|
+
description: 'Indicates whether the email field is enabled/disabled',
|
|
29
|
+
},
|
|
30
|
+
label: {
|
|
31
|
+
description: 'Label of the email component',
|
|
32
|
+
},
|
|
33
|
+
required: {
|
|
34
|
+
description: 'Indicates whether the email field is mandatory or not',
|
|
35
|
+
},
|
|
36
|
+
maxLength: {
|
|
37
|
+
description: 'Maximum number of characters allowed',
|
|
38
|
+
},
|
|
39
|
+
invalidEmailMessage: {
|
|
40
|
+
description: 'Displayed when an invalid email is entered',
|
|
41
|
+
},
|
|
42
|
+
dupedEmailMessage: {
|
|
43
|
+
description: 'Displayed when a duplicate email is entered',
|
|
44
|
+
},
|
|
45
|
+
requiredErrorMessage: {
|
|
46
|
+
description: 'Displayed when email is not entered',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
args: {
|
|
50
|
+
disabled: false,
|
|
51
|
+
label: 'Email',
|
|
52
|
+
required: true,
|
|
53
|
+
maxLength: 50,
|
|
54
|
+
invalidEmailMessage: 'Please enter a valid email',
|
|
55
|
+
dupedEmailMessage: 'Email ID already exists',
|
|
56
|
+
requiredErrorMessage: 'Email field is required',
|
|
57
|
+
checkEmail: checkEmail,
|
|
58
|
+
},
|
|
59
|
+
} as MetaStory;
|
|
60
|
+
|
|
61
|
+
export const NsEmail: Story = {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { NsEmailSimpleProps } from './types';
|
|
3
|
+
import { NsInputField } from '../input';
|
|
4
|
+
import { fieldStateType } from '../input/types';
|
|
5
|
+
import { emailPattern } from '../validations/regex';
|
|
6
|
+
|
|
7
|
+
const NsEmailSimple = (props: NsEmailSimpleProps) => {
|
|
8
|
+
const {
|
|
9
|
+
disabled,
|
|
10
|
+
label,
|
|
11
|
+
required,
|
|
12
|
+
maxLength,
|
|
13
|
+
invalidEmailMessage,
|
|
14
|
+
dupedEmailMessage,
|
|
15
|
+
requiredErrorMessage,
|
|
16
|
+
checkEmail,
|
|
17
|
+
additionalProps,
|
|
18
|
+
} = props;
|
|
19
|
+
const [fieldState, setFieldState] = useState<fieldStateType>();
|
|
20
|
+
const [errorMsg, setErrorMessage] = useState('');
|
|
21
|
+
|
|
22
|
+
const handleBlur = async (e: React.FocusEvent<HTMLInputElement>) => {
|
|
23
|
+
const emailInput = e.target.value.toLowerCase().trim();
|
|
24
|
+
let fieldStateValue: fieldStateType;
|
|
25
|
+
let errorMessageValue = '';
|
|
26
|
+
if (required && !emailInput) {
|
|
27
|
+
fieldStateValue = 'error';
|
|
28
|
+
errorMessageValue = requiredErrorMessage;
|
|
29
|
+
} else if (!emailPattern.test(emailInput)) {
|
|
30
|
+
fieldStateValue = 'error';
|
|
31
|
+
errorMessageValue = invalidEmailMessage;
|
|
32
|
+
} else if (typeof checkEmail === 'function') {
|
|
33
|
+
try {
|
|
34
|
+
const [isValid, isAvailable] = await checkEmail();
|
|
35
|
+
if (!isValid) {
|
|
36
|
+
fieldStateValue = 'error';
|
|
37
|
+
errorMessageValue = invalidEmailMessage;
|
|
38
|
+
} else if (!isAvailable) {
|
|
39
|
+
fieldStateValue = 'error';
|
|
40
|
+
errorMessageValue = dupedEmailMessage;
|
|
41
|
+
} else {
|
|
42
|
+
fieldStateValue = undefined;
|
|
43
|
+
errorMessageValue = '';
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.log('API error', error);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
//TODO What happens when user does not pass valid checkEmail function
|
|
50
|
+
}
|
|
51
|
+
setFieldState(fieldStateValue);
|
|
52
|
+
setErrorMessage(errorMessageValue);
|
|
53
|
+
};
|
|
54
|
+
return (
|
|
55
|
+
<NsInputField
|
|
56
|
+
required={required}
|
|
57
|
+
disabled={disabled}
|
|
58
|
+
label={label}
|
|
59
|
+
onBlur={handleBlur}
|
|
60
|
+
inputProps={{ maxLength }}
|
|
61
|
+
additionalProps={{ testid: additionalProps?.testid ?? 'email-field-test' }}
|
|
62
|
+
helperText={errorMsg}
|
|
63
|
+
fieldState={fieldState}
|
|
64
|
+
InputProps={{
|
|
65
|
+
endAdornment: null,
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default NsEmailSimple;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TextFieldProps } from '@mui/material';
|
|
2
|
+
import { AdditionalProps } from '../common.types';
|
|
3
|
+
|
|
4
|
+
export type NsEmailSimpleProps = Pick<TextFieldProps> &
|
|
5
|
+
AdditionalProps & {
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
maxLength?: number;
|
|
10
|
+
invalidEmailText?: string;
|
|
11
|
+
dupedEmailText?: string;
|
|
12
|
+
requiredErrorMessage?: string;
|
|
13
|
+
checkEmail?: Function;
|
|
14
|
+
};
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author [vijayaprathap Kumar] (vpkumar@nuskin.com)
|
|
3
|
-
* Flag.spec.tsx
|
|
4
|
-
* @description In this test case,
|
|
5
|
-
* we use the render function to render the MyComponent with the desired background color and text.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import React from 'react';
|
|
9
|
-
import nuskinTheme from '@nuskin/foundation-theme/src/themes/genome-foundation';
|
|
10
|
-
|
|
11
2
|
import { render } from '@/test-utils/index';
|
|
12
|
-
import NsFlag from '
|
|
13
|
-
|
|
14
|
-
const { palette } = nuskinTheme;
|
|
3
|
+
import { NsFlag } from '../';
|
|
15
4
|
|
|
16
|
-
// Test suite for FlagComponent
|
|
17
5
|
describe('FlagComponent', () => {
|
|
18
|
-
// Test case for rendering a div element with background color and text.
|
|
19
6
|
it('renders a div element with background color and text', () => {
|
|
20
|
-
|
|
7
|
+
const text = 'NU SKIN';
|
|
21
8
|
const { getByText } = render(<NsFlag message="NU SKIN" color="accentColorA" />);
|
|
9
|
+
const divElement = getByText(text);
|
|
22
10
|
|
|
23
|
-
// Get the rendered div element using its text
|
|
24
|
-
const divElement = getByText('NU SKIN');
|
|
25
|
-
|
|
26
|
-
// Assert that the div element is in the document
|
|
27
11
|
expect(divElement).toBeInTheDocument();
|
|
28
|
-
|
|
29
|
-
// Assert that the div element has the expected background color
|
|
30
|
-
expect(divElement).toHaveStyle(`color: ${palette?.primary?.contrastText}`);
|
|
31
|
-
|
|
32
|
-
// Assert that the div element has the expected text content
|
|
33
|
-
expect(divElement).toHaveTextContent('NU SKIN');
|
|
12
|
+
expect(divElement).toHaveTextContent(text);
|
|
34
13
|
});
|
|
35
14
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
-
import { withCenterDecorator } from '
|
|
2
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
3
|
import { NsFlag } from './';
|
|
4
4
|
|
|
5
5
|
type ComponentType = typeof NsFlag;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
-
import { withCenterDecorator } from '
|
|
2
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
3
|
import { NsIcon, NsIconVariants } from './';
|
|
4
4
|
|
|
5
5
|
type ComponentType = typeof NsIcon;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
-
import { withCenterDecorator } from '
|
|
2
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
3
|
import { NsImage } from './';
|
|
4
4
|
|
|
5
5
|
type ComponentType = typeof NsImage;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NsNextImage } from '.';
|
|
2
|
-
import { withCenterDecorator } from '
|
|
2
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
3
|
import type { StoryObj, Meta } from '@storybook/react';
|
|
4
4
|
|
|
5
5
|
type ComponentType = typeof NsNextImage;
|
package/src/common/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './accordion';
|
|
1
2
|
export * from './alert';
|
|
2
3
|
export * from './auth-dropdown';
|
|
3
4
|
export * from './autocomplete';
|
|
@@ -23,3 +24,5 @@ export * from './nav-product-card';
|
|
|
23
24
|
export * from './text-strip';
|
|
24
25
|
export * from './table';
|
|
25
26
|
export * from './typography';
|
|
27
|
+
export * from './email-simple';
|
|
28
|
+
export * from './radiogroup';
|
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { NsInputField } from '.';
|
|
3
3
|
import { NsIcon } from '../icon';
|
|
4
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
4
5
|
|
|
5
6
|
type ComponentType = typeof NsInputField;
|
|
6
7
|
type Story = StoryObj<ComponentType>;
|
|
7
8
|
type MetaStory = Meta<ComponentType>;
|
|
8
9
|
|
|
9
10
|
export default {
|
|
10
|
-
title: 'Components/NsInputField',
|
|
11
|
+
title: 'Components/Inputs/NsInputField',
|
|
11
12
|
component: NsInputField,
|
|
12
|
-
decorators: [
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
decorators: [withCenterDecorator],
|
|
14
|
+
args: {
|
|
15
|
+
helperText: '',
|
|
16
|
+
iconSide: <NsIcon name="trash" />,
|
|
17
|
+
focused: false,
|
|
18
|
+
placeholder: 'enter text',
|
|
19
|
+
disabled: false,
|
|
20
|
+
fullWidth: false,
|
|
21
|
+
},
|
|
15
22
|
argTypes: {
|
|
16
23
|
fieldState: { control: 'select', options: ['default', 'success', 'error'] },
|
|
17
24
|
value: { control: 'text' },
|
|
25
|
+
helperText: { control: 'text', description: 'text shown below input' },
|
|
18
26
|
required: { control: 'boolean' },
|
|
19
27
|
disabled: { control: 'boolean' },
|
|
20
28
|
fullWidth: { control: 'boolean' },
|
|
21
|
-
showOptional: { control: 'boolean' },
|
|
29
|
+
showOptional: { control: 'boolean', description: 'if true, an optional text is shown along with label' },
|
|
22
30
|
focused: { control: 'boolean' },
|
|
23
31
|
},
|
|
24
32
|
} as MetaStory;
|
|
25
33
|
|
|
26
34
|
export const Default: Story = {
|
|
27
35
|
args: {
|
|
28
|
-
focused: false,
|
|
29
|
-
placeholder: 'enter text',
|
|
30
|
-
disabled: false,
|
|
31
|
-
fullWidth: false,
|
|
32
36
|
value: 'Input value',
|
|
33
37
|
fieldState: 'success',
|
|
34
|
-
iconSide: <NsIcon name="trash" />,
|
|
35
38
|
},
|
|
36
39
|
};
|
|
37
40
|
|
|
@@ -3,7 +3,7 @@ import InputWrapper from './InputWrapper';
|
|
|
3
3
|
import { NsInputFieldProps } from './types.d';
|
|
4
4
|
import { NsLabel } from '../label';
|
|
5
5
|
import InputFieldWrapper from './InputFieldWrapper';
|
|
6
|
-
import { getAdornmentProps } from './getAdornmentProps';
|
|
6
|
+
import { AdornmentPropsType, getAdornmentProps } from './getAdornmentProps';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* we are using fieldState in place of error prop
|
|
@@ -14,6 +14,7 @@ const NsInputField = (props: NsInputFieldProps) => {
|
|
|
14
14
|
const {
|
|
15
15
|
autoComplete,
|
|
16
16
|
autoFocus,
|
|
17
|
+
additionalProps,
|
|
17
18
|
defaultValue,
|
|
18
19
|
disabled,
|
|
19
20
|
fullWidth,
|
|
@@ -54,16 +55,22 @@ const NsInputField = (props: NsInputFieldProps) => {
|
|
|
54
55
|
} = props;
|
|
55
56
|
/** EndIcon is created so that we can automatically add success and error icon based of fieldState,
|
|
56
57
|
* it will override iconEnd. Also to note, InputProps is given to override both iconStart, iconEnd and EndIcon */
|
|
57
|
-
let EndIcon:
|
|
58
|
+
let EndIcon: AdornmentPropsType = getAdornmentProps({ disabled, fieldState, iconEnd, iconStart });
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
61
|
<InputFieldWrapper>
|
|
61
62
|
{label ? (
|
|
62
|
-
<NsLabel
|
|
63
|
+
<NsLabel
|
|
64
|
+
{...InputLabelProps}
|
|
65
|
+
showOptional={showOptional}
|
|
66
|
+
required={required}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
error={fieldState === 'error' && !disabled}
|
|
69
|
+
>
|
|
63
70
|
{label}
|
|
64
71
|
</NsLabel>
|
|
65
72
|
) : null}
|
|
66
|
-
<InputWrapper fieldState={fieldState} data-testid=
|
|
73
|
+
<InputWrapper fieldState={fieldState} data-testid={additionalProps?.testid ?? 'ns-input-field'}>
|
|
67
74
|
<TextField
|
|
68
75
|
{...rest}
|
|
69
76
|
InputProps={{
|
|
@@ -97,7 +104,7 @@ const NsInputField = (props: NsInputFieldProps) => {
|
|
|
97
104
|
/>
|
|
98
105
|
{iconSide}
|
|
99
106
|
</InputWrapper>
|
|
100
|
-
{helperText ? (
|
|
107
|
+
{helperText && !disabled ? (
|
|
101
108
|
<FormHelperText {...FormHelperTextProps} className={fieldState}>
|
|
102
109
|
{helperText}
|
|
103
110
|
</FormHelperText>
|
|
@@ -22,6 +22,10 @@ type props = {
|
|
|
22
22
|
* @param param0
|
|
23
23
|
* @returns
|
|
24
24
|
*/
|
|
25
|
+
export type AdornmentPropsType = {
|
|
26
|
+
startAdornment?: ReactNode;
|
|
27
|
+
endAdornment?: ReactNode;
|
|
28
|
+
}
|
|
25
29
|
export const getAdornmentProps = ({ disabled, fieldState, iconStart, iconEnd }: props) => {
|
|
26
30
|
const icons = {
|
|
27
31
|
startAdornment: iconStart ? <InputAdornment position="start">{iconStart}</InputAdornment> : null,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { TextFieldProps } from '@mui/material';
|
|
3
|
+
import { AdditionalProps } from '../common.types';
|
|
3
4
|
|
|
5
|
+
// TODO: check if fieldStateType could be converted to enum, since we also have to account for undefined
|
|
4
6
|
export type fieldStateType = undefined | 'error' | 'success';
|
|
5
7
|
export type NsInputFieldProps = Omit<
|
|
6
8
|
TextFieldProps,
|
|
@@ -17,11 +19,26 @@ export type NsInputFieldProps = Omit<
|
|
|
17
19
|
| 'select'
|
|
18
20
|
| 'SelectProps'
|
|
19
21
|
> & {
|
|
22
|
+
/**
|
|
23
|
+
* has default undefined value, and we can update the value to success
|
|
24
|
+
* on successful validation and update value to 'error' in case of error
|
|
25
|
+
*/
|
|
26
|
+
|
|
20
27
|
fieldState?: fieldStateType;
|
|
21
28
|
iconStart?: ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* assign react component for end icon
|
|
31
|
+
*/
|
|
22
32
|
iconEnd?: ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* assign react component for side icon
|
|
35
|
+
*/
|
|
36
|
+
|
|
23
37
|
iconSide?: ReactNode;
|
|
24
38
|
/** purpose of showOptional props is to show additional text 'optional' in the label,
|
|
25
39
|
* if required = false and this flag is true */
|
|
26
40
|
showOptional?: boolean;
|
|
27
|
-
|
|
41
|
+
defaultValue?: string;
|
|
42
|
+
rows?: number;
|
|
43
|
+
id?: string;
|
|
44
|
+
} & AdditionalProps;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { NsLabel } from '.';
|
|
3
|
+
import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
|
|
3
4
|
|
|
4
5
|
type ComponentType = typeof NsLabel;
|
|
5
6
|
type Story = StoryObj<ComponentType>;
|
|
@@ -8,12 +9,14 @@ type MetaStory = Meta<ComponentType>;
|
|
|
8
9
|
export default {
|
|
9
10
|
component: NsLabel,
|
|
10
11
|
title: 'Components/NsLabel',
|
|
12
|
+
decorators: [withCenterDecorator],
|
|
11
13
|
} as MetaStory;
|
|
12
14
|
|
|
13
15
|
export const Default: Story = {
|
|
14
16
|
args: {
|
|
15
17
|
children: 'Label',
|
|
16
18
|
required: false,
|
|
19
|
+
size: 16,
|
|
17
20
|
},
|
|
18
21
|
};
|
|
19
22
|
|
|
@@ -21,6 +24,7 @@ export const Optional: Story = {
|
|
|
21
24
|
args: {
|
|
22
25
|
children: 'Label',
|
|
23
26
|
showOptional: true,
|
|
27
|
+
size: 16,
|
|
24
28
|
},
|
|
25
29
|
};
|
|
26
30
|
|
|
@@ -29,5 +33,6 @@ export const Required: Story = {
|
|
|
29
33
|
children: 'Label',
|
|
30
34
|
showOptional: true,
|
|
31
35
|
required: true,
|
|
36
|
+
size: 16,
|
|
32
37
|
},
|
|
33
38
|
};
|