@nuskin/foundation-ui-components 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/src/common/autocomplete/NsAutocomplete.styled.tsx +5 -1
  3. package/src/common/autocomplete/NsAutocomplete.tsx +19 -71
  4. package/src/common/autocomplete/helper/ClearButton.tsx +22 -0
  5. package/src/common/autocomplete/helper/SeeMoreButton.tsx +47 -0
  6. package/src/common/autocomplete/helper/renderGroupName.tsx +17 -0
  7. package/src/common/autocomplete/stories/ACWithoutClose.tsx +49 -0
  8. package/src/common/autocomplete/stories/NsAutocomplete.stories.tsx +76 -0
  9. package/src/common/autocomplete/stories/RenderAutoComplete.tsx +60 -0
  10. package/src/common/autocomplete/types.d.ts +1 -1
  11. package/src/common/card/NsBrandCard.tsx +94 -0
  12. package/src/common/card/NsCheckoutCard.tsx +92 -0
  13. package/src/common/card/NsHeroCardLevel1.tsx +106 -0
  14. package/src/common/card/NsHeroCardLevel2.tsx +126 -0
  15. package/src/common/card/NsIconText.tsx +153 -0
  16. package/src/common/card/NsLargeCard.tsx +120 -0
  17. package/src/common/card/NsMediumCard.tsx +64 -0
  18. package/src/common/card/NsPost.tsx +60 -0
  19. package/src/common/card/NsSmallFullCard.tsx +90 -0
  20. package/src/common/card/NsXLargeCard.tsx +80 -0
  21. package/src/common/card/cardSizes.ts +12 -0
  22. package/src/common/card/index.ts +10 -2
  23. package/src/common/card/specs/BrandLogo.spec.tsx +1 -1
  24. package/src/common/card/specs/NsBgCard.spec.tsx +2 -2
  25. package/src/common/card/specs/NsBrandCard.spec.tsx +97 -0
  26. package/src/common/card/specs/NsCheckoutCard.spec.tsx +50 -0
  27. package/src/common/card/specs/NsHeroCardLevel1.spec.tsx +48 -0
  28. package/src/common/card/specs/NsHeroCardLevel2.spec.tsx +51 -0
  29. package/src/common/card/specs/NsIconText.spec.tsx +28 -0
  30. package/src/common/card/specs/NsLargeCard.spec.tsx +41 -0
  31. package/src/common/card/specs/NsMediumCard.spec.tsx +35 -0
  32. package/src/common/card/specs/NsPost.spec.tsx +31 -0
  33. package/src/common/card/specs/NsSmallFullCard.spec.tsx +36 -0
  34. package/src/common/card/specs/NsXLargeCard.spec.tsx +35 -0
  35. package/src/common/card/stories/{NsBrandProductCard.stories.tsx → NsBrandCard.stories.tsx} +9 -4
  36. package/src/common/card/stories/NsCheckoutCard.stories.tsx +24 -0
  37. package/src/common/card/stories/NsHeroCardLevel1.stories.tsx +45 -0
  38. package/src/common/card/stories/NsHeroCardLevel2.stories.tsx +50 -0
  39. package/src/common/card/stories/NsIconText.stories.tsx +36 -0
  40. package/src/common/card/stories/NsLargeCard.stories.tsx +32 -0
  41. package/src/common/card/stories/NsMediumCard.stories.tsx +29 -0
  42. package/src/common/card/stories/NsPost.stories.tsx +39 -0
  43. package/src/common/card/stories/NsSmallFullCard.stories.tsx +29 -0
  44. package/src/common/card/stories/NsXLargeCard.stories.tsx +29 -0
  45. package/src/common/carousal/RenderBrand.tsx +3 -2
  46. package/src/common/input/InputWrapper.tsx +1 -1
  47. package/src/common/autocomplete/NsAutocomplete.stories.tsx +0 -108
  48. package/src/common/card/NsBrandProductCard.tsx +0 -47
  49. package/src/common/card/NsFfgCard.tsx +0 -24
  50. package/src/common/card/specs/NsBrandProductCard.spec.tsx +0 -32
  51. package/src/common/card/specs/NsFfgCard.spec.tsx +0 -33
  52. package/src/common/card/stories/NsBgCard.stories.tsx +0 -20
  53. package/src/common/card/stories/NsFfgCard.stories.tsx +0 -73
  54. package/src/common/card/styled/NsFfgStyled.tsx +0 -46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/foundation-ui-components",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "It is a foundation library written in react, that provides common components to all UI pages",
5
5
  "main": "src/common/index.ts",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  import { styled } from '@nuskin/foundation-theme';
2
-
3
2
  import { NsInputField } from '../input';
4
3
 
4
+ // TODO: why is .muiautocomplete-popupindicator added?
5
5
  export const StyledTextField = styled(NsInputField)`
6
6
  .MuiAutocomplete-inputRoot {
7
7
  padding-right: 10px !important;
@@ -9,4 +9,8 @@ export const StyledTextField = styled(NsInputField)`
9
9
  .muiautocomplete-popupindicator {
10
10
  display: none;
11
11
  }
12
+ .styled-text-field.MuiOutlinedInput-root input#custom_autocomplete_dropdown::placeholder {
13
+ opacity: 1;
14
+ color: #252525;
15
+ }
12
16
  `;
@@ -1,12 +1,11 @@
1
+ import { Autocomplete, AutocompleteRenderInputParams, List } from '@mui/material';
1
2
  import React, { HTMLAttributes, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
2
- import { Autocomplete, AutocompleteRenderGroupParams, List, AutocompleteRenderInputParams } from '@mui/material';
3
- import { NsTypography, NsButton } from '../';
4
- import { NsAutocompleteProps, OptionsProps, ComponentBoxProps } from './types.d';
5
- import { StyledTextField } from './NsAutocomplete.styled';
6
-
7
- const renderOptionLabel = (option: OptionsProps) => option.value;
8
3
 
9
- const renderGroupBy = (option: OptionsProps) => option.group as string;
4
+ import { ClearButton } from './helper/ClearButton';
5
+ import { renderGroupBy, renderGroupName, renderOptionLabel } from './helper/renderGroupName';
6
+ import { SeeMoreButton } from './helper/SeeMoreButton';
7
+ import { StyledTextField } from './NsAutocomplete.styled';
8
+ import { ComponentBoxProps, NsAutocompleteProps, OptionsProps } from './types.d';
10
9
 
11
10
  const NsAutocomplete = ({
12
11
  id = 'custom_autocomplete_dropdown',
@@ -30,7 +29,7 @@ const NsAutocomplete = ({
30
29
  const listboxRef = useRef<HTMLUListElement | null>(null);
31
30
 
32
31
  useEffect(() => {
33
- if (listboxRef?.current && options.length > 0) {
32
+ if (listboxRef.current && options.length > 0) {
34
33
  listboxRef.current.scrollTop = scrollTopPosition;
35
34
  }
36
35
  }, [options, scrollTopPosition]);
@@ -44,7 +43,7 @@ const NsAutocomplete = ({
44
43
  [onSelect]
45
44
  );
46
45
 
47
- const handleInputChange = useCallback(
46
+ const onInputChange = useCallback(
48
47
  (event: React.SyntheticEvent, newInputValue: string) => {
49
48
  if (typeof onChange === 'function') {
50
49
  onChange(newInputValue);
@@ -59,82 +58,31 @@ const NsAutocomplete = ({
59
58
  placeholder={placeholder}
60
59
  InputProps={{
61
60
  ...params.InputProps,
61
+ className: 'styled-text-field',
62
62
  startAdornment: iconStart,
63
63
  endAdornment: iconEnd,
64
64
  }}
65
65
  />
66
66
  );
67
67
 
68
- // TODO: @vijayprathap need to check if we can remove object spread of ...props and ...params in renderInput
69
68
  const renderOption = (props: ComponentBoxProps, option: OptionsProps) => (
70
69
  <li {...props} key={props.id}>
71
70
  {OptionsUI ? <OptionsUI {...props} option={option} /> : null}
72
71
  </li>
73
72
  );
74
73
 
75
- const renderGroupName = ({ key, group, children }: AutocompleteRenderGroupParams) => (
76
- <li key={key}>
77
- {group ? (
78
- <NsTypography variant="p-m" weight="bold" className="p-3">
79
- {group}
80
- </NsTypography>
81
- ) : null}
82
- <ul className="p-0">{children}</ul>
83
- </li>
84
- );
85
-
86
- const renderClearButton = useCallback(() => {
87
- const { show, onClear, text = 'Clear Search' } = clear || {};
88
- if (!show) {
89
- return null;
90
- }
91
- return (
92
- <li className="text-right p-3">
93
- <NsButton onClick={onClear} data-testid="autocomplete-clear-id">
94
- <NsTypography variant="p-xs" weight="bold">
95
- {text}
96
- </NsTypography>
97
- </NsButton>
98
- </li>
99
- );
100
- }, [clear]);
101
-
102
- const handleSeeMore = useCallback(() => {
103
- const { onSeeMore } = seeMore || {};
104
- // Remember the current scroll position
105
- if (options?.length > 0 && listboxRef?.current) {
106
- const lastScrollPosition = listboxRef.current?.scrollTop;
107
- if (lastScrollPosition) {
108
- setScrollTopPosition(lastScrollPosition);
109
- }
110
- }
111
- if (onSeeMore && typeof onSeeMore === 'function') {
112
- onSeeMore();
113
- }
114
- }, [seeMore, options]);
115
-
116
- const renderSeeMoreButton = useCallback(() => {
117
- const { show, text = 'See More Results' } = seeMore || {};
118
- if (!show) {
119
- return null;
120
- }
121
-
122
- return (
123
- <li className="text-center p-3">
124
- <NsButton onClick={handleSeeMore} data-testid="autocomplete-loadmore-id">
125
- <NsTypography variant="p-xs" weight="bold">
126
- {text}
127
- </NsTypography>
128
- </NsButton>
129
- </li>
130
- );
131
- }, [seeMore, handleSeeMore]);
132
-
133
74
  const renderListBoxComponent = ({ children, ...otherProps }: ComponentBoxProps) => (
134
75
  <List {...otherProps} ref={listboxRef}>
135
- {renderClearButton()}
76
+ <ClearButton show={clear?.show} text={clear?.text} onClear={clear?.onClear} />
136
77
  {children}
137
- {renderSeeMoreButton()}
78
+ <SeeMoreButton
79
+ onSeeMore={seeMore?.onSeeMore}
80
+ show={seeMore?.show}
81
+ text={seeMore?.text}
82
+ options={options}
83
+ setScrollTopPosition={setScrollTopPosition}
84
+ listboxRef={listboxRef}
85
+ />
138
86
  </List>
139
87
  );
140
88
 
@@ -154,7 +102,7 @@ const NsAutocomplete = ({
154
102
  ListboxProps={{ scrollTopPosition } as HTMLAttributes<HTMLUListElement>} // Pass the current scroll position
155
103
  renderGroup={renderGroupName}
156
104
  onChange={handleSelect}
157
- onInputChange={handleInputChange}
105
+ onInputChange={onInputChange}
158
106
  value={value}
159
107
  inputValue={inputValue}
160
108
  filterOptions={
@@ -0,0 +1,22 @@
1
+ import { NsButton, NsTypography } from '../..';
2
+
3
+ type ClearButtonProps = {
4
+ text?: string;
5
+ show?: boolean;
6
+ onClear?(): void;
7
+ };
8
+ export const ClearButton = (props: ClearButtonProps) => {
9
+ const { show, onClear, text = 'Clear All' } = props ?? {};
10
+ if (!show) {
11
+ return null;
12
+ }
13
+ return (
14
+ <li className="text-right p-3">
15
+ <NsButton onClick={onClear} data-testid="autocomplete-clear-id">
16
+ <NsTypography variant="p-xs" weight="bold">
17
+ {text}
18
+ </NsTypography>
19
+ </NsButton>
20
+ </li>
21
+ );
22
+ };
@@ -0,0 +1,47 @@
1
+ import { Dispatch, MutableRefObject, SetStateAction, useCallback } from 'react';
2
+ import { NsButton, NsTypography } from '../..';
3
+
4
+ type SeeMoreButtonProps = {
5
+ text?: string;
6
+ show?: boolean;
7
+ onSeeMore?(): void;
8
+ options: any[];
9
+ setScrollTopPosition: Dispatch<SetStateAction<number>>;
10
+ listboxRef: MutableRefObject<HTMLUListElement | null>;
11
+ };
12
+
13
+ export const SeeMoreButton = (props: SeeMoreButtonProps) => {
14
+ const {
15
+ show = false,
16
+ text = 'See More Results',
17
+ onSeeMore,
18
+ options,
19
+ listboxRef,
20
+ setScrollTopPosition,
21
+ } = props || {};
22
+
23
+ const handleSeeMore = useCallback(() => {
24
+ /* Remember the current scroll position */
25
+ if (options?.length > 0 && listboxRef?.current) {
26
+ const lastScrollPosition = listboxRef.current?.scrollTop;
27
+ if (lastScrollPosition) {
28
+ setScrollTopPosition(lastScrollPosition);
29
+ }
30
+ }
31
+ if (onSeeMore && typeof onSeeMore === 'function') {
32
+ onSeeMore();
33
+ }
34
+ }, [listboxRef, onSeeMore, options?.length, setScrollTopPosition]);
35
+ if (!show) {
36
+ return null;
37
+ }
38
+ return (
39
+ <li className="text-center p-3">
40
+ <NsButton onClick={handleSeeMore} data-testid="autocomplete-loadmore-id">
41
+ <NsTypography variant="p-xs" weight="bold">
42
+ {text}
43
+ </NsTypography>
44
+ </NsButton>
45
+ </li>
46
+ );
47
+ };
@@ -0,0 +1,17 @@
1
+ import { AutocompleteRenderGroupParams } from '@mui/material';
2
+ import { NsTypography, OptionsProps } from '../..';
3
+
4
+ export const renderGroupName = ({ key, group, children }: AutocompleteRenderGroupParams) => (
5
+ <li key={key}>
6
+ {group ? (
7
+ <NsTypography variant="p-m" weight="bold" className="p-3">
8
+ {group}
9
+ </NsTypography>
10
+ ) : null}
11
+ <ul className="p-0">{children}</ul>
12
+ </li>
13
+ );
14
+
15
+ export const renderOptionLabel = (option: OptionsProps) => option.value;
16
+
17
+ export const renderGroupBy = (option: OptionsProps) => option.group as string;
@@ -0,0 +1,49 @@
1
+ import { NsAutocompleteProps, OptionsProps } from '../types';
2
+ import React, { useState } from 'react';
3
+
4
+ import { NsAutocomplete } from '..';
5
+ import { OptionUiComponent } from '../mock/MockUI';
6
+
7
+ const PAGE_SIZE = 6;
8
+
9
+ export const ACWithoutClose = (args: NsAutocompleteProps) => {
10
+ const [selectedOption, setSelectedOption] = useState<string | OptionsProps | null>(null);
11
+ const [searchTerms, setSearchTerms] = useState<string>('');
12
+ const [pageNo, setPageNo] = useState(PAGE_SIZE);
13
+
14
+ const onSeeMoreHandler = () => {
15
+ setPageNo(PAGE_SIZE + PAGE_SIZE);
16
+ };
17
+
18
+ const AutoCompleteProps = {
19
+ ...args,
20
+ seeMore: {
21
+ ...args.seeMore,
22
+ onSeeMore: onSeeMoreHandler,
23
+ },
24
+ } as NsAutocompleteProps;
25
+
26
+ const onSelect = (value: string | OptionsProps | null) => {
27
+ setSelectedOption(value);
28
+ setSearchTerms((value = ''));
29
+ };
30
+
31
+ const handleInputChange = (newValue: string) => {
32
+ setSearchTerms(newValue);
33
+ };
34
+
35
+ return (
36
+ <div>
37
+ <NsAutocomplete
38
+ {...AutoCompleteProps}
39
+ options={args.options.slice(0, pageNo)}
40
+ onChange={handleInputChange}
41
+ onSelect={onSelect}
42
+ OptionsUI={OptionUiComponent}
43
+ value={selectedOption}
44
+ inputValue={searchTerms}
45
+ iconEnd={undefined}
46
+ />
47
+ </div>
48
+ );
49
+ };
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+
3
+ import type { StoryObj, Meta } from '@storybook/react';
4
+
5
+ import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
6
+ import { NsAutocomplete } from '..';
7
+ import { AutocompleteMockList } from '../mock/MockData';
8
+ import { NsIcon } from '../../icon';
9
+ import { RenderAutoComplete } from './RenderAutoComplete';
10
+ import { ACWithoutClose } from './ACWithoutClose';
11
+
12
+ type ComponentType = typeof NsAutocomplete;
13
+ type Story = StoryObj<ComponentType>;
14
+ type MetaStory = Meta<ComponentType>;
15
+
16
+ export default {
17
+ title: 'Components/Inputs/NsAutocomplete',
18
+ component: NsAutocomplete,
19
+ decorators: [withCenterDecorator],
20
+ argTypes: {
21
+ children: {
22
+ control: 'text',
23
+ },
24
+ options: {
25
+ description: 'An array of objects representing the available options for the autocomplete.',
26
+ },
27
+ OptionsUI: {
28
+ description: '(optional): A custom component to render each option in the dropdown.',
29
+ },
30
+ loading: {
31
+ description: '(optional): A boolean indicating whether the autocomplete is in a loading state.',
32
+ },
33
+ noOptionsText: { description: '(optional): The text to display when there are no options available.' },
34
+ onSelect: { description: '(optional): A callback function to be called when an option is selected.' },
35
+ onChange: { description: '(optional): A callback function to be called when the input value changes.' },
36
+ clear: { description: '(optional): An object containing properties for the clear button.' },
37
+ seeMore: { description: '(optional): An object containing properties for the "see more" button.' },
38
+ placeholder: { description: '(optional): The placeholder text for the input field.' },
39
+ className: { description: '(optional): Additional CSS class name(s) for the autocomplete component.' },
40
+ iconStart: {
41
+ description: '(optional): An icon or React node to be displayed at the start of the input field.',
42
+ },
43
+ iconEnd: { description: '(optional): An icon or React node to be displayed at the end of the input field.' },
44
+ inputValue: { description: ' The current value of the input field.' },
45
+ value: { description: ': The currently selected option.' },
46
+ isFilterEnabled: { description: ' (optional): A boolean indicating whether filtering of options is enabled.' },
47
+ },
48
+ args: {
49
+ options: AutocompleteMockList,
50
+ seeMore: {
51
+ text: 'See More Results',
52
+ show: true,
53
+ onSeeMore: () => {
54
+ throw new Error('Function not implemented.');
55
+ },
56
+ },
57
+ clear: {
58
+ text: 'Clear Recent Searches',
59
+ show: true,
60
+ onClear: () => {
61
+ throw new Error('Function not implemented.');
62
+ },
63
+ },
64
+ iconStart: <NsIcon name="search" />,
65
+ iconEnd: <NsIcon name="roundedClose" size="small" colorOverride="#F5F5F5" />,
66
+ placeholder: 'search',
67
+ },
68
+ } as MetaStory;
69
+
70
+ export const Autocomplete: Story = {
71
+ render: RenderAutoComplete,
72
+ };
73
+
74
+ export const WithoutClose: Story = {
75
+ render: ACWithoutClose,
76
+ };
@@ -0,0 +1,60 @@
1
+ import { NsAutocompleteProps, OptionsProps } from '../types';
2
+ import { IconButton } from '@mui/material';
3
+ import React, { useState } from 'react';
4
+
5
+ import { NsAutocomplete } from '..';
6
+ import { NsIcon } from '../../icon';
7
+ import { OptionUiComponent } from '../mock/MockUI';
8
+
9
+ const PAGE_SIZE = 6;
10
+
11
+ export const RenderAutoComplete = (args: NsAutocompleteProps) => {
12
+ const [selectedOption, setSelectedOption] = useState<string | OptionsProps | null>(null);
13
+ const [searchTerms, setSearchTerms] = useState<string>('');
14
+ const [pageNo, setPageNo] = useState(PAGE_SIZE);
15
+
16
+ const onSeeMoreHandler = () => {
17
+ setPageNo(PAGE_SIZE + PAGE_SIZE);
18
+ };
19
+
20
+ const AutoCompleteProps = {
21
+ ...args,
22
+ seeMore: {
23
+ ...args.seeMore,
24
+ onSeeMore: onSeeMoreHandler,
25
+ },
26
+ } as NsAutocompleteProps;
27
+
28
+ const onSelect = (value: string | OptionsProps | null) => {
29
+ setSelectedOption(value);
30
+ setSearchTerms((value = ''));
31
+ };
32
+
33
+ const handleInputChange = (newValue: string) => {
34
+ setSearchTerms(newValue);
35
+ };
36
+
37
+ const onInputClear = () => {
38
+ setSelectedOption(null);
39
+ setSearchTerms('');
40
+ };
41
+
42
+ return (
43
+ <div>
44
+ <NsAutocomplete
45
+ {...AutoCompleteProps}
46
+ options={args.options.slice(0, pageNo)}
47
+ onChange={handleInputChange}
48
+ onSelect={onSelect}
49
+ OptionsUI={OptionUiComponent}
50
+ iconEnd={
51
+ <IconButton onClick={onInputClear}>
52
+ <NsIcon name="roundedClose" size="xLarge" colorOverride="#F5F5F5" />
53
+ </IconButton>
54
+ }
55
+ value={selectedOption}
56
+ inputValue={searchTerms}
57
+ />
58
+ </div>
59
+ );
60
+ };
@@ -31,7 +31,7 @@ export type NsAutocompleteProps = {
31
31
  iconEnd?: string | React.ReactNode;
32
32
  inputValue: string;
33
33
  value: string | OptionsProps | null;
34
- isFilterEnabled?:boolean
34
+ isFilterEnabled?: boolean;
35
35
  } & AdditionalProps;
36
36
 
37
37
  export type ListUiProps = {
@@ -0,0 +1,94 @@
1
+ import { ShoppingCart } from '@mui/icons-material';
2
+ import Box from '@mui/material/Box';
3
+ import { styled } from '@nuskin/foundation-theme';
4
+ import { NsTypography } from '../';
5
+ import NsBrandWrapper from './NsBrandWrapper';
6
+ import NsBrandLogo from './NsBrandLogo';
7
+ import NsBgCard from './NsBgCard';
8
+ import NsButton from '../button/NsButton';
9
+ import cardSizes from './cardSizes';
10
+
11
+ type NsBrandCardProps = {
12
+ title?: string;
13
+ brandImageUrl: string;
14
+ backgroundImageUrl: string;
15
+ size?: 'small' | 'large';
16
+ button?: boolean;
17
+ onClick?: () => void;
18
+ };
19
+
20
+ const CardContainer = styled((props: any) => <Box {...props} />)(({ size }) => {
21
+ const { small, large } = cardSizes;
22
+ let width, height;
23
+
24
+ if (size === 'small') {
25
+ width = small.width;
26
+ height = small.height;
27
+ } else {
28
+ width = large.width;
29
+ height = large.height;
30
+ }
31
+
32
+ return {
33
+ width,
34
+ display: 'flex',
35
+ alignItems: 'start',
36
+ justifyContent: 'center',
37
+ position: 'relative',
38
+ flexDirection: 'column',
39
+ '.brand-title': {
40
+ marginTop: '15px',
41
+ fontSize: '20px',
42
+ display: 'inline-block',
43
+ wordBreak: 'break-word',
44
+ },
45
+ ':hover': {
46
+ '.brand-title': {
47
+ opacity: '0.7',
48
+ },
49
+ },
50
+ '.cta': {
51
+ marginTop: '15px',
52
+ },
53
+ '.image': {
54
+ width,
55
+ height,
56
+ backgroundSize: 'cover',
57
+ backgroundPosition: 'center',
58
+ },
59
+ };
60
+ });
61
+ export default function NsBrandCard({
62
+ title,
63
+ brandImageUrl,
64
+ backgroundImageUrl,
65
+ button,
66
+ size,
67
+ onClick,
68
+ }: NsBrandCardProps) {
69
+ return (
70
+ <CardContainer role="brand-card" component="div" data-testid="brand-card" size={size} onClick={onClick}>
71
+ <NsBgCard backgroundImageUrl={backgroundImageUrl} className="image">
72
+ <NsBrandWrapper>
73
+ <NsBrandLogo component="img" image={brandImageUrl} />
74
+ </NsBrandWrapper>
75
+ </NsBgCard>
76
+ {title ? (
77
+ <NsTypography variant="h3" className="brand-title">
78
+ {title}
79
+ </NsTypography>
80
+ ) : null}
81
+ {button ? (
82
+ <NsButton
83
+ className="cta"
84
+ darkModeEnabled={false}
85
+ variant="outlined"
86
+ fullWidth={true}
87
+ endIcon={<ShoppingCart />}
88
+ >
89
+ Add to Cart
90
+ </NsButton>
91
+ ) : null}
92
+ </CardContainer>
93
+ );
94
+ }
@@ -0,0 +1,92 @@
1
+ import Box from '@mui/material/Box';
2
+ import { styled } from '@nuskin/foundation-theme';
3
+ import { NsIcon, NsIconNameType, NsLink } from '../';
4
+ import { useState } from 'react';
5
+
6
+ type NsCheckoutCardProps = {
7
+ icon: NsIconNameType;
8
+ title: string;
9
+ address: string;
10
+ onClick: () => void;
11
+ };
12
+
13
+ const CardContainer = styled(Box)(() => {
14
+ return {
15
+ backgroundColor: '#fff',
16
+ display: 'flex',
17
+ width: '345px',
18
+ height: 'auto',
19
+ border: '2px solid #B2B2B2',
20
+ borderRadius: '2px',
21
+ '.left-bar': {
22
+ width: '4px',
23
+ height: 'auto',
24
+ backgroundColor: '#22D55C',
25
+ },
26
+ '.content': {
27
+ display: 'flex',
28
+ flexDirection: 'row',
29
+ gap: '10px',
30
+ margin: '10px',
31
+ width: '100%',
32
+ },
33
+ '.text-content': {
34
+ width: '100%',
35
+ },
36
+ '.title': {
37
+ fontSize: '14px',
38
+ fontWeight: '700',
39
+ },
40
+ '.address': {
41
+ fontSize: '13px',
42
+ fontWeight: '400',
43
+ },
44
+ '.action-link': {
45
+ color: '#7123D9',
46
+ fontSize: '14px',
47
+ fontWeight: '700',
48
+ letterSpacing: '0.7',
49
+ },
50
+ '.icon-right': {
51
+ marginLeft: 'auto',
52
+ },
53
+ '.icon': {
54
+ ':hover': {
55
+ cursor: 'pointer',
56
+ },
57
+ },
58
+ };
59
+ });
60
+
61
+ export default function NsCheckoutCard({ title, address, icon, onClick }: NsCheckoutCardProps) {
62
+ const [dropdownIcon, setDropdownIcon] = useState(false);
63
+ const handleClick = () => {
64
+ setDropdownIcon(!dropdownIcon);
65
+ };
66
+
67
+ return (
68
+ <CardContainer role="checkout-card" component="div" data-testid="checkout-card" onClick={onClick}>
69
+ <div className="left-bar"></div>
70
+ <div className="content">
71
+ <span>
72
+ <NsIcon name={icon} />
73
+ </span>
74
+ <div className="text-content">
75
+ <article className="title">{title}</article>
76
+ <article className="address">{address}</article>
77
+ <NsLink className="action-link" href={''}>
78
+ CHANGE
79
+ </NsLink>
80
+ </div>
81
+ <span className="icon-right">
82
+ <NsIcon
83
+ name={dropdownIcon ? 'arrowUp' : 'arrowDown'}
84
+ className="icon"
85
+ onClick={handleClick}
86
+ testId="dropdown-icon"
87
+ />
88
+ </span>
89
+ </div>
90
+ </CardContainer>
91
+ );
92
+ }