@nuskin/foundation-ui-components 1.30.2 → 1.32.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/foundation-ui-components",
3
- "version": "1.30.2",
3
+ "version": "1.32.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": {
@@ -57,6 +57,7 @@
57
57
  "react-dom": "^18.2.0",
58
58
  "react-material-ui-carousel": "^3.4.2",
59
59
  "react-slick": "^0.29.0",
60
+ "react-window": "^1.8.10",
60
61
  "slick-carousel": "^1.8.1",
61
62
  "typescript": "^5.4.2"
62
63
  },
@@ -86,6 +87,7 @@
86
87
  "@types/ramda": "^0.29.7",
87
88
  "@types/react-slick": "^0.23.11",
88
89
  "@types/react-test-renderer": "^18.0.5",
90
+ "@types/react-window": "^1.8.8",
89
91
  "babel-jest": "^29.7.0",
90
92
  "babel-loader": "^9.1.3",
91
93
  "babel-plugin-emotion": "^11.0.0",
@@ -1,6 +1,6 @@
1
1
  import { styled, ColorUtils } from '@nuskin/foundation-theme';
2
2
  import { NsInputField } from '../input';
3
- import { Popper } from '@mui/material';
3
+ import { Popper, autocompleteClasses } from '@mui/material';
4
4
  const { getGenomeColor } = ColorUtils;
5
5
 
6
6
  export const StyledTextField = styled(NsInputField)`
@@ -13,9 +13,12 @@ export const StyledTextField = styled(NsInputField)`
13
13
  }
14
14
  `;
15
15
 
16
- export const StyledPopper = styled(Popper)`
17
- width: 100% !important;
18
- #custom_autocomplete_dropdown-listbox{
19
- overflow-x:hidden;
20
- }
21
- `
16
+ export const StyledPopper = styled(Popper)({
17
+ [`& .${autocompleteClasses.listbox}`]: {
18
+ boxSizing: 'border-box',
19
+ '& ul': {
20
+ padding: 0,
21
+ margin: 0,
22
+ },
23
+ },
24
+ });
@@ -1,36 +1,26 @@
1
- import React, {
2
- PropsWithChildren,
3
- ReactElement,
4
- useEffect,
5
- useRef,
6
- useState,
7
- } from 'react';
1
+ import React, { PropsWithChildren, ReactElement, useEffect, useRef, useState } from 'react';
8
2
 
9
3
  import {
10
4
  Autocomplete,
11
5
  AutocompleteRenderInputParams,
6
+ ListSubheader,
12
7
  Popper,
8
+ useMediaQuery,
9
+ useTheme,
13
10
  } from '@mui/material';
14
11
 
15
12
  import { LineLoader } from './helper/LineLoader';
16
13
  import { RecentClearAll } from './helper/RecentClearAll';
17
- import {
18
- renderGroupBy,
19
- renderGroupName,
20
- renderOptionLabel,
21
- } from './helper/renderGroupName';
14
+ import { renderGroupBy, renderOptionLabel } from './helper/renderGroupName';
22
15
  import { SeeMoreButton } from './helper/SeeMoreButton';
23
- import {
24
- StyledPopper,
25
- StyledTextField,
26
- } from './NsAutocomplete.styled';
27
- import {
28
- ComponentBoxProps,
29
- NsAutocompleteProps,
30
- OptionsProps,
31
- } from './types.d';
16
+ import { StyledPopper, StyledTextField } from './NsAutocomplete.styled';
17
+ import { ComponentBoxProps, NsAutocompleteProps, OptionsProps } from './types.d';
18
+ import { VariableSizeList, ListChildComponentProps } from 'react-window';
32
19
 
20
+ const LISTBOX_PADDING = 8;
33
21
  const noop = () => {};
22
+ const OuterElementContext = React.createContext({});
23
+
34
24
  const NsAutocomplete = ({
35
25
  id = 'custom_autocomplete_dropdown',
36
26
  options,
@@ -50,9 +40,10 @@ const NsAutocomplete = ({
50
40
  value,
51
41
  isFilterEnabled = true,
52
42
  fullWidth,
53
- }: PropsWithChildren<NsAutocompleteProps>):ReactElement => {
43
+ onKeypress,
44
+ }: PropsWithChildren<NsAutocompleteProps>): ReactElement => {
45
+ const listboxRef = useRef<any | null>(null);
54
46
  const [scrollTopPosition, setScrollTopPosition] = useState(0);
55
- const listboxRef = useRef<HTMLUListElement | null>(null);
56
47
 
57
48
  useEffect(() => {
58
49
  if (listboxRef.current && options.length > 0) {
@@ -60,6 +51,13 @@ const NsAutocomplete = ({
60
51
  }
61
52
  }, [options, scrollTopPosition]);
62
53
 
54
+ const OuterElementType = React.forwardRef((props: any, ref) => {
55
+ const outerProps = React.useContext(OuterElementContext);
56
+ return <ul ref={ref} {...props} {...outerProps} />;
57
+ });
58
+
59
+ OuterElementType.displayName = 'OuterElementType';
60
+
63
61
  const renderInput = (params: AutocompleteRenderInputParams) => (
64
62
  <StyledTextField
65
63
  {...(params as any)}
@@ -82,54 +80,132 @@ const NsAutocomplete = ({
82
80
  </li>
83
81
  );
84
82
 
85
- const renderListBoxComponent = ({ children, ...rest }: ComponentBoxProps): React.JSX.Element => {
83
+ const renderRow = (props: ListChildComponentProps) => {
84
+ const { data, index, style } = props;
85
+ const dataSet = data[index];
86
+ const option = dataSet?.[1];
87
+ const inlineStyle = {
88
+ top: (style.top as number) + LISTBOX_PADDING,
89
+ };
90
+
91
+ if (Object.hasOwn(dataSet, 'group')) {
92
+ return (
93
+ <ListSubheader key={dataSet.key} component="div" style={inlineStyle}>
94
+ {dataSet.group}
95
+ </ListSubheader>
96
+ );
97
+ }
98
+
86
99
  return (
87
- <ul
88
- aria-labelledby={rest['aria-labelledby']}
89
- onMouseDown={rest.onMouseDown}
90
- role={rest.role}
91
- id={rest.id}
92
- className={rest.className}
93
- ref={listboxRef}
94
- >
95
- <RecentClearAll
96
- show={clear?.show ?? false}
97
- text={clear?.text ?? 'Clear All'}
98
- onClick={clear?.onClear ?? noop}
99
- testId="autocomplete-clear-id"
100
- recentSearchText={clear?.recentSearchText ?? 'Your Recent Searches'}
101
- />
102
- {children}
103
- <SeeMoreButton
104
- onSeeMore={seeMore?.onSeeMore}
105
- show={seeMore?.show}
106
- text={seeMore?.text}
107
- options={options}
108
- setScrollTopPosition={setScrollTopPosition}
109
- listboxRef={listboxRef}
110
- />
111
- </ul>
100
+ <div component="div" {...dataSet[0]} style={inlineStyle}>
101
+ {renderOption(props, option)}
102
+ </div>
112
103
  );
113
104
  };
105
+
106
+ const useResetCache = (data: any) => {
107
+ const ref = React.useRef<VariableSizeList>(null);
108
+ React.useEffect(() => {
109
+ if (ref.current != null) {
110
+ ref.current.resetAfterIndex(0, true);
111
+ }
112
+ }, [data]);
113
+ return ref;
114
+ };
115
+
116
+ const ListboxComponent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLElement>>(
117
+ function ListboxComponent(props, ref) {
118
+ const { children, ...other } = props;
119
+ const itemData: React.ReactElement[] = [];
120
+ (children as React.ReactElement[]).forEach(
121
+ (item: React.ReactElement & { children?: React.ReactElement[] }) => {
122
+ itemData.push(item);
123
+ itemData.push(...(item.children || []));
124
+ }
125
+ );
126
+
127
+ const theme = useTheme();
128
+ const smUp = useMediaQuery(theme.breakpoints.up('sm'), {
129
+ noSsr: true,
130
+ });
131
+ const itemCount = itemData.length;
132
+ const itemSize = smUp ? 36 : 48;
133
+
134
+ const getChildSize = (child: React.ReactElement) => {
135
+ if (Object.hasOwn(child, 'group')) {
136
+ return 48;
137
+ }
138
+
139
+ return itemSize;
140
+ };
141
+
142
+ const getHeight = () => {
143
+ if (itemCount > 8) {
144
+ return 8 * itemSize;
145
+ }
146
+ return itemData.map(getChildSize).reduce((a, b) => a + b, 0);
147
+ };
148
+
149
+ const gridRef = useResetCache(itemCount);
150
+
151
+ return (
152
+ <>
153
+ <RecentClearAll
154
+ show={clear?.show ?? false}
155
+ text={clear?.text ?? 'Clear All'}
156
+ onClick={clear?.onClear ?? noop}
157
+ testId="autocomplete-clear-id"
158
+ recentSearchText={clear?.recentSearchText ?? 'Your Recent Searches'}
159
+ />
160
+ <div ref={ref}>
161
+ <OuterElementContext.Provider value={other}>
162
+ <VariableSizeList
163
+ itemData={itemData}
164
+ height={getHeight() + 2 * LISTBOX_PADDING}
165
+ width="100%"
166
+ ref={gridRef}
167
+ outerElementType={OuterElementType}
168
+ innerElementType="ul"
169
+ itemSize={(index) => getChildSize(itemData[index])}
170
+ overscanCount={5}
171
+ itemCount={itemCount}
172
+ >
173
+ {renderRow}
174
+ </VariableSizeList>
175
+ </OuterElementContext.Provider>
176
+ </div>
177
+ <SeeMoreButton
178
+ onSeeMore={seeMore?.onSeeMore}
179
+ show={seeMore?.show}
180
+ text={seeMore?.text}
181
+ options={options}
182
+ setScrollTopPosition={setScrollTopPosition}
183
+ listboxRef={listboxRef}
184
+ />
185
+ </>
186
+ );
187
+ }
188
+ );
189
+
114
190
  return (
115
191
  <>
116
192
  <LineLoader show={loading ?? false} />
117
193
  <Autocomplete
118
- openOnFocus
194
+ open
119
195
  id={id}
120
- disablePortal
196
+ disableListWrap
121
197
  className={className}
122
- options={options}
123
198
  loading={loading}
124
199
  noOptionsText={noOptionsText}
200
+ PopperComponent={fullWidth ? StyledPopper : Popper}
201
+ ListboxComponent={ListboxComponent}
202
+ options={options}
125
203
  groupBy={renderGroupBy}
126
204
  getOptionLabel={renderOptionLabel}
127
- renderOption={renderOption as any}
128
- renderInput={renderInput}
129
- PopperComponent={fullWidth ? StyledPopper : Popper} // TODO:https://code.tls.nuskin.io/ns-am/content-foundation/nuskin-app/-/jobs/1355817
130
- ListboxComponent={renderListBoxComponent}
131
- renderGroup={renderGroupName}
205
+ renderOption={(props, option, state) => [props, option, state.index] as React.ReactNode}
206
+ renderGroup={(params) => params as any}
132
207
  onChange={onChange}
208
+ renderInput={renderInput}
133
209
  onInputChange={onInputChange}
134
210
  value={value}
135
211
  onFocus={onFocus ?? noop}
@@ -140,6 +216,7 @@ const NsAutocomplete = ({
140
216
  : (options) => options
141
217
  }
142
218
  freeSolo
219
+ onKeyDown={onKeypress}
143
220
  />
144
221
  </>
145
222
  );
@@ -1,4 +1,4 @@
1
- import React, { ReactElement } from 'react';
1
+ import React, { ReactElement, SyntheticEvent } from 'react';
2
2
 
3
3
  import { withCenterDecorator } from '@/stories/decorators/withCenterDecorator';
4
4
  import type { Meta, StoryObj } from '@storybook/react';
@@ -65,6 +65,7 @@ export default {
65
65
  iconEnd: (<NsIcon name="roundedClose" size="small" colorOverride="#F5F5F5" />) as ReactElement,
66
66
  placeholder: 'Search',
67
67
  fullWidth: false,
68
+ onKeypress: (e: SyntheticEvent) => console.log(e),
68
69
  },
69
70
  } as MetaStory;
70
71
 
@@ -17,6 +17,7 @@ export type NsAutocompleteProps = {
17
17
  reason Can be: "input" (user input), "reset" (programmatic change), "clear".
18
18
  */
19
19
  onInputChange(event: SyntheticEvent, value: string, reason: string): void;
20
+ onKeypress?:(event:SyntheticEvent) => void;
20
21
  /*
21
22
  event The event source of the callback.
22
23
  value The new value of the component.
@@ -13,6 +13,7 @@ type NsAwardCardProps = {
13
13
  mediaOutlet: string;
14
14
  backgroundImageDetail: BynderImageDataType;
15
15
  className?: string;
16
+ fontColor?: 'primary' | 'secondary';
16
17
  };
17
18
 
18
19
  const CardContainer = styled(Box)`
@@ -49,18 +50,19 @@ export default function NsAwardCard({
49
50
  mediaOutlet,
50
51
  backgroundImageDetail,
51
52
  className,
53
+ fontColor,
52
54
  }: NsAwardCardProps): ReactElement {
53
55
  return (
54
56
  <CardContainer className={className} role="award-card" component="div" data-testid="award-card">
55
57
  <BynderMedia className="image" imageDetail={backgroundImageDetail} />
56
58
  <div className="content">
57
- <NsTypography weight="normal" className="title" variant="h5">
59
+ <NsTypography weight="normal" className="title" variant="h5" color={fontColor}>
58
60
  {title}
59
61
  </NsTypography>
60
- <NsTypography className="content-text" variant="body-l">
62
+ <NsTypography className="content-text" variant="body-l" color={fontColor}>
61
63
  {awardName}
62
64
  </NsTypography>
63
- <NsTypography className="content-text" variant="body-l">
65
+ <NsTypography className="content-text" variant="body-l" color={fontColor}>
64
66
  {mediaOutlet}
65
67
  </NsTypography>
66
68
  </div>
@@ -18,6 +18,7 @@ type NsBrandCardProps = {
18
18
  size?: 'small' | 'large';
19
19
  button?: NsCardButton;
20
20
  className?: string;
21
+ fontColor?: 'primary' | 'secondary';
21
22
  };
22
23
 
23
24
  const CardContainer = styled((props: any) => <Box {...props} />)(({ size }) => {
@@ -67,6 +68,7 @@ export default function NsBrandCard({
67
68
  button,
68
69
  size,
69
70
  className,
71
+ fontColor,
70
72
  }: NsBrandCardProps): ReactElement {
71
73
  return (
72
74
  <CardContainer className={className} role="brand-card" component="div" data-testid="brand-card" size={size}>
@@ -76,7 +78,7 @@ export default function NsBrandCard({
76
78
  </NsBrandWrapper>
77
79
  </NsBgCard>
78
80
  {title ? (
79
- <NsTypography variant="h3" className="brand-title">
81
+ <NsTypography variant="h3" className="brand-title" color={fontColor}>
80
82
  {title}
81
83
  </NsTypography>
82
84
  ) : null}
@@ -11,6 +11,7 @@ type NsCheckoutCardProps = {
11
11
  linkText: string;
12
12
  url: string;
13
13
  className?: string;
14
+ fontColor?: 'primary' | 'secondary';
14
15
  };
15
16
 
16
17
  const CardContainer = styled(Box)`
@@ -18,12 +19,12 @@ const CardContainer = styled(Box)`
18
19
  display: flex;
19
20
  max-width: 345px;
20
21
  height: auto;
21
- border: 2px solid #B2B2B2;
22
+ border: 2px solid #b2b2b2;
22
23
  border-radius: 2px;
23
24
  .left-bar {
24
25
  width: 4px;
25
26
  height: auto;
26
- background-color: #22D55C;
27
+ background-color: #22d55c;
27
28
  }
28
29
  .content {
29
30
  display: flex;
@@ -44,7 +45,7 @@ const CardContainer = styled(Box)`
44
45
  font-weight: 400;
45
46
  }
46
47
  .action-link {
47
- color: #7123D9;
48
+ color: #7123d9;
48
49
  font-size: 14px;
49
50
  font-weight: 700;
50
51
  letter-spacing: 0.7;
@@ -57,9 +58,22 @@ const CardContainer = styled(Box)`
57
58
  cursor: pointer;
58
59
  }
59
60
  }
61
+ .primary {
62
+ color: #fff;
63
+ }
64
+ .secondary {
65
+ color: #000;
66
+ }
60
67
  `;
61
-
62
- export default function NsCheckoutCard({ title, icon, linkText, url, className }: NsCheckoutCardProps): ReactElement {
68
+
69
+ export default function NsCheckoutCard({
70
+ title,
71
+ icon,
72
+ linkText,
73
+ url,
74
+ className,
75
+ fontColor,
76
+ }: NsCheckoutCardProps): ReactElement {
63
77
  const [dropdownIcon, setDropdownIcon] = useState(false);
64
78
  const handleClick = () => {
65
79
  setDropdownIcon(!dropdownIcon);
@@ -73,9 +87,9 @@ export default function NsCheckoutCard({ title, icon, linkText, url, className }
73
87
  <NsIcon name={icon} />
74
88
  </span>
75
89
  <div className="text-content">
76
- <article className="title">{title}</article>
77
- <article className="address">Address</article>
78
- <NsLink className="action-link" href={url}>
90
+ <article className={`title ${fontColor}`}>{title}</article>
91
+ <article className={`address ${fontColor}`}>Address</article>
92
+ <NsLink className={`action-link ${fontColor}`} href={url}>
79
93
  {linkText}
80
94
  </NsLink>
81
95
  </div>
@@ -15,6 +15,7 @@ type NsHeroCardLevel1Props = {
15
15
  button?: NsCardButton;
16
16
  className?: string;
17
17
  imagePosition?: 'top' | 'bottom';
18
+ fontColor?: 'primary' | 'secondary';
18
19
  };
19
20
 
20
21
  const CardContainer = styled((props: any) => <Box {...props} />)(({ imagePosition }) => {
@@ -54,6 +55,12 @@ const CardContainer = styled((props: any) => <Box {...props} />)(({ imagePositio
54
55
  fontSize: '18px',
55
56
  marginBottom: '20px',
56
57
  },
58
+ '.primary': {
59
+ color: '#fff',
60
+ },
61
+ '.secondary': {
62
+ color: '#000',
63
+ },
57
64
  '.action-link': {
58
65
  marginTop: '10px',
59
66
  },
@@ -83,13 +90,14 @@ export default function NsHeroCardLevel1({
83
90
  button,
84
91
  className,
85
92
  imagePosition = 'top',
93
+ fontColor,
86
94
  }: NsHeroCardLevel1Props): ReactElement {
87
95
  return (
88
96
  <CardContainer className={className} data-testid="hero-card-level-1" imagePosition={imagePosition}>
89
97
  <BynderMedia className="bg-image" imageDetail={backgroundImageDetail} />
90
98
  <div className="content">
91
- <article className="body-title">{cardTitle}</article>
92
- <article className="body-text">{cardDescription}</article>
99
+ <article className={`body-title ${fontColor}`}>{cardTitle}</article>
100
+ <article className={`body-text ${fontColor}`}>{cardDescription}</article>
93
101
  {button ? <CardButton {...button} /> : null}
94
102
  </div>
95
103
  </CardContainer>
@@ -15,6 +15,7 @@ type NsHeroCardLevel2Props = {
15
15
  backgroundImageDetail: BynderImageDataType;
16
16
  button?: NsCardButton;
17
17
  className?: string;
18
+ fontColor?: 'primary' | 'secondary';
18
19
  };
19
20
 
20
21
  const CardContainer = styled(Box)`
@@ -55,12 +56,18 @@ const CardContainer = styled(Box)`
55
56
  font-weight: 700;
56
57
  margin-bottom: 5px;
57
58
  }
58
- @media (max-width:768px) {
59
+ .primary {
60
+ color: #fff;
61
+ }
62
+ .secondary {
63
+ color: #000;
64
+ }
65
+ @media (max-width: 768px) {
59
66
  .content {
60
67
  padding: 5%;
61
68
  }
62
69
  }
63
- `
70
+ `;
64
71
 
65
72
  const NsHeroCardLevel2 = ({
66
73
  brandName,
@@ -69,16 +76,17 @@ const NsHeroCardLevel2 = ({
69
76
  backgroundImageDetail,
70
77
  button,
71
78
  className,
79
+ fontColor,
72
80
  }: NsHeroCardLevel2Props): ReactElement => {
73
81
  return (
74
82
  <CardContainer className={className} data-testid="hero-card-level-2">
75
83
  <BynderMedia className="bg-image" imageDetail={backgroundImageDetail} />
76
84
  <div className="content">
77
85
  <div className="brand-container">
78
- <article>{brandName}</article>
86
+ <article className={fontColor}>{brandName}</article>
79
87
  </div>
80
- <article className="body-title">{cardTitle}</article>
81
- <article className="body-text">{cardDescription}</article>
88
+ <article className={`body-title ${fontColor}`}>{cardTitle}</article>
89
+ <article className={`body-text ${fontColor}`}>{cardDescription}</article>
82
90
  <div>{button ? <CardButton {...button} /> : null}</div>
83
91
  </div>
84
92
  </CardContainer>
@@ -7,6 +7,7 @@ type NsIconTextProps = {
7
7
  text: string;
8
8
  body: string;
9
9
  className?: string;
10
+ fontColor?: 'primary' | 'secondary';
10
11
  };
11
12
 
12
13
  const CardContainer = styled(Box)`
@@ -18,7 +19,7 @@ const CardContainer = styled(Box)`
18
19
  overflow: hidden;
19
20
  height: auto;
20
21
  max-width: 222px;
21
- background-color: #F5F5F5;
22
+ background-color: #f5f5f5;
22
23
  padding: 20px;
23
24
  justify-content: center;
24
25
  gap: 30px;
@@ -29,7 +30,7 @@ const CardContainer = styled(Box)`
29
30
  .divider {
30
31
  width: 182px;
31
32
  height: 1px;
32
- background: #B2B2B2;
33
+ background: #b2b2b2;
33
34
  }
34
35
  .title {
35
36
  font-size: 26px;
@@ -40,6 +41,12 @@ const CardContainer = styled(Box)`
40
41
  font-weight: 400;
41
42
  font-size: 14px;
42
43
  }
44
+ .primary {
45
+ color: #fff;
46
+ }
47
+ .secondary {
48
+ color: #000;
49
+ }
43
50
  `;
44
51
 
45
52
  const Icon = (): ReactElement => {
@@ -145,13 +152,13 @@ const Icon = (): ReactElement => {
145
152
  </div>
146
153
  );
147
154
  };
148
- const NsIconText = ({ text, body, className }: NsIconTextProps) => {
155
+ const NsIconText = ({ text, body, className, fontColor }: NsIconTextProps) => {
149
156
  return (
150
157
  <CardContainer className={className} data-testid="icon-text-card">
151
158
  <Icon />
152
- <article className="title">{text}</article>
159
+ <article className={`title ${fontColor}`}>{text}</article>
153
160
  <div className="divider"></div>
154
- <article className="text">{body}</article>
161
+ <article className={`text ${fontColor}`}>{body}</article>
155
162
  </CardContainer>
156
163
  );
157
164
  };
@@ -16,6 +16,7 @@ type NsLargeCardProps = {
16
16
  isHorizontal?: boolean;
17
17
  button?: NsCardButton;
18
18
  className?: string;
19
+ fontColor?: 'primary' | 'secondary';
19
20
  };
20
21
 
21
22
  const CardContainer = styled((props: any) => <Box {...props} />)(({ isHorizontal, matches }) => {
@@ -62,6 +63,12 @@ const CardContainer = styled((props: any) => <Box {...props} />)(({ isHorizontal
62
63
  fontWeight: '400',
63
64
  marginTop: '10px',
64
65
  },
66
+ '.primary': {
67
+ color: '#fff',
68
+ },
69
+ '.secondary': {
70
+ color: '#000',
71
+ },
65
72
  '.cta': {
66
73
  marginTop: '10px',
67
74
  },
@@ -79,6 +86,7 @@ export default function NsLargeCard({
79
86
  isHorizontal,
80
87
  button,
81
88
  className,
89
+ fontColor,
82
90
  }: NsLargeCardProps): ReactElement {
83
91
  return (
84
92
  <CardContainer
@@ -90,9 +98,9 @@ export default function NsLargeCard({
90
98
  >
91
99
  <BynderMedia className="bg-image" imageDetail={backgroundImageDetail} />
92
100
  <div className="content">
93
- <article className="subtitle">{subtitle}</article>
94
- <article className="title">{title}</article>
95
- <article className="subtitle">{intro}</article>
101
+ <article className={`subtitle ${fontColor}`}>{subtitle}</article>
102
+ <article className={`title ${fontColor}`}>{title}</article>
103
+ <article className={`subtitle ${fontColor}`}>{intro}</article>
96
104
  {button ? <CardButton {...button} /> : null}
97
105
  </div>
98
106
  </CardContainer>
@@ -18,6 +18,7 @@ type NsMediumCardProps = {
18
18
  className?: string;
19
19
  fluid?: boolean;
20
20
  showArrowWithButtonText?: boolean;
21
+ fontColor?: 'primary' | 'secondary';
21
22
  };
22
23
  const textVariantButton = {
23
24
  padding: '0 0 4px',
@@ -63,6 +64,12 @@ const CardContainer = styled(Box)<{ fluid: boolean; variant: string }>(({ fluid,
63
64
  fontSize: '20px',
64
65
  marginTop: '10px',
65
66
  },
67
+ '.primary': {
68
+ color: '#fff',
69
+ },
70
+ '.secondary': {
71
+ color: '#000',
72
+ },
66
73
  '.cta': {
67
74
  marginTop: 'auto',
68
75
  ...textVariantCssModification,
@@ -79,6 +86,7 @@ export default function NsMediumCard({
79
86
  className,
80
87
  fluid,
81
88
  showArrowWithButtonText = false,
89
+ fontColor,
82
90
  }: NsMediumCardProps): ReactElement {
83
91
  return (
84
92
  <CardContainer
@@ -91,10 +99,10 @@ export default function NsMediumCard({
91
99
  >
92
100
  <BynderMedia className="image" imageDetail={backgroundImageDetail} />
93
101
  <div className="content">
94
- <NsTypography variant="h5" color="primary" className="title" weight={titleFontWeight}>
102
+ <NsTypography variant="h5" color={fontColor} className="title" weight={titleFontWeight}>
95
103
  {title}
96
104
  </NsTypography>
97
- <article className="subtitle mt-3 mb-5">{subtitle}</article>
105
+ <article className={`subtitle mt-3 mb-5 ${fontColor}`}>{subtitle}</article>
98
106
  {button ? <CardButton {...button} showArrowWithText={showArrowWithButtonText} /> : null}
99
107
  </div>
100
108
  </CardContainer>
@@ -11,6 +11,7 @@ type NsPostProps = {
11
11
  body: string;
12
12
  backgroundImageDetail: BynderImageDataType;
13
13
  className?: string;
14
+ fontColor?: 'primary' | 'secondary';
14
15
  };
15
16
 
16
17
  const CardContainer = styled(Box)`
@@ -26,7 +27,7 @@ const CardContainer = styled(Box)`
26
27
  .image {
27
28
  height: 180px;
28
29
  overflow: hidden;
29
- object-fit: contain
30
+ object-fit: contain;
30
31
  }
31
32
  .content {
32
33
  padding: 20px;
@@ -34,7 +35,7 @@ const CardContainer = styled(Box)`
34
35
  flex-direction: column;
35
36
  min-height: 92px;
36
37
  max-height: 92px;
37
- justify-content: space-around
38
+ justify-content: space-around;
38
39
  }
39
40
  .body-title {
40
41
  font-size: 18px;
@@ -44,7 +45,7 @@ const CardContainer = styled(Box)`
44
45
  -webkit-line-clamp: 1;
45
46
  overflow: hidden;
46
47
  text-overflow: ellipsis;
47
- margin: 0
48
+ margin: 0;
48
49
  }
49
50
  .body-text {
50
51
  font-size: 12px;
@@ -54,20 +55,20 @@ const CardContainer = styled(Box)`
54
55
  -webkit-line-clamp: 1;
55
56
  overflow: hidden;
56
57
  text-overflow: ellipsis;
57
- color: #8C8C8C;
58
- margin: 0
58
+ color: #8c8c8c;
59
+ margin: 0;
59
60
  }
60
61
  `;
61
62
 
62
- const NsPost = ({ text, body, backgroundImageDetail, className }: NsPostProps): ReactElement => {
63
+ const NsPost = ({ text, body, backgroundImageDetail, className, fontColor }: NsPostProps): ReactElement => {
63
64
  return (
64
65
  <CardContainer className={className} data-testid="post-card">
65
66
  <BynderMedia className="image" imageDetail={backgroundImageDetail} />
66
67
  <article className="content">
67
- <h2 className="body-title" title={text}>
68
+ <h2 className={`body-title ${fontColor}`} title={text}>
68
69
  {text}
69
70
  </h2>
70
- <p className="body-text" title={body?.substring(0, 80)}>
71
+ <p className={`body-text ${fontColor}`} title={body?.substring(0, 80)}>
71
72
  {body}
72
73
  </p>
73
74
  </article>
@@ -14,6 +14,7 @@ type NsSmallFullCardProps = {
14
14
  backgroundImageDetail: BynderImageDataType;
15
15
  button?: NsCardButton;
16
16
  className?: string;
17
+ fontColor?: 'primary' | 'secondary';
17
18
  };
18
19
 
19
20
  const CardContainer = styled(Box)`
@@ -26,7 +27,7 @@ const CardContainer = styled(Box)`
26
27
  .image {
27
28
  width: 100%;
28
29
  height: 100%;
29
- object-fit: cover
30
+ object-fit: cover;
30
31
  }
31
32
  .gradientOverlay {
32
33
  position: absolute;
@@ -34,7 +35,7 @@ const CardContainer = styled(Box)`
34
35
  left: 0;
35
36
  width: 100%;
36
37
  height: 100%;
37
- background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 50%);
38
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, transparent 50%);
38
39
  }
39
40
  .content {
40
41
  position: absolute;
@@ -42,27 +43,33 @@ const CardContainer = styled(Box)`
42
43
  width: 100%;
43
44
  padding: 40px 40px 25px 30px;
44
45
  display: flex;
45
- flex-direction: column
46
+ flex-direction: column;
46
47
  }
47
48
  .subtitle {
48
49
  color: #fff;
49
50
  font-size: 11px;
50
51
  font-weight: 400;
51
- margin-top: auto
52
+ margin-top: auto;
52
53
  }
53
54
  .title {
54
55
  color: #fff;
55
56
  font-size: 26px;
56
57
  font-weight: 400;
57
- margin-top: 20px
58
+ margin-top: 20px;
58
59
  }
59
60
  .cta {
60
61
  margin-top: 20px;
61
62
  max-width: 208px;
62
63
  border-radius: 4px;
63
- overflow: hidden
64
+ overflow: hidden;
65
+ }
66
+ .primary {
67
+ color: #fff;
68
+ }
69
+ .secondary {
70
+ color: #000;
64
71
  }
65
- `
72
+ `;
66
73
 
67
74
  export default function NsSmallFullCard({
68
75
  title,
@@ -70,14 +77,15 @@ export default function NsSmallFullCard({
70
77
  backgroundImageDetail,
71
78
  button,
72
79
  className,
80
+ fontColor,
73
81
  }: NsSmallFullCardProps): ReactElement {
74
82
  return (
75
83
  <CardContainer className={className} role="small-full-card" component="div" data-testid="small-full-card">
76
84
  <BynderMedia className="image" imageDetail={backgroundImageDetail} />
77
85
  <div className="gradientOverlay"></div>
78
86
  <div className="content">
79
- <article className="subtitle">{subtitle}</article>
80
- <article className="title">{title}</article>
87
+ <article className={`subtitle ${fontColor}`}>{subtitle}</article>
88
+ <article className={`title ${fontColor}`}>{title}</article>
81
89
  {button ? <CardButton {...button} /> : null}
82
90
  </div>
83
91
  </CardContainer>
@@ -14,6 +14,7 @@ type NsXLargeCardProps = {
14
14
  backgroundImageDetail: BynderImageDataType;
15
15
  button?: NsCardButton;
16
16
  className?: string;
17
+ fontColor?: 'primary' | 'secondary';
17
18
  };
18
19
 
19
20
  const CardContainer = styled(Box)`
@@ -33,7 +34,7 @@ const CardContainer = styled(Box)`
33
34
  left: 0;
34
35
  width: 100%;
35
36
  height: 100%;
36
- background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 50%);
37
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, transparent 50%);
37
38
  }
38
39
  .content {
39
40
  position: absolute;
@@ -55,10 +56,16 @@ const CardContainer = styled(Box)`
55
56
  font-weight: 400;
56
57
  margin-top: 20px;
57
58
  }
59
+ .primary {
60
+ color: #fff;
61
+ }
62
+ .secondary {
63
+ color: #000;
64
+ }
58
65
  .cta {
59
66
  margin-top: 20px;
60
67
  }
61
- `
68
+ `;
62
69
 
63
70
  export default function NsXLargeCard({
64
71
  title,
@@ -66,14 +73,15 @@ export default function NsXLargeCard({
66
73
  backgroundImageDetail,
67
74
  button,
68
75
  className,
76
+ fontColor,
69
77
  }: NsXLargeCardProps): ReactElement {
70
78
  return (
71
79
  <CardContainer className={className} role="x-large-card" component="div" data-testid="x-large-card">
72
80
  <BynderMedia className="image" imageDetail={backgroundImageDetail} />
73
81
  <div className="gradientOverlay"></div>
74
82
  <div className="content">
75
- <article className="subtitle">{subtitle}</article>
76
- <article className="title">{title}</article>
83
+ <article className={`subtitle ${fontColor}`}>{subtitle}</article>
84
+ <article className={`title ${fontColor}`}>{title}</article>
77
85
  {button ? <CardButton {...button} /> : null}
78
86
  </div>
79
87
  </CardContainer>