@hubspot/cms-component-library 0.3.6 → 0.3.8
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/components/componentLibrary/Accordion/AccordionContent/ContentFields.tsx +6 -6
- package/components/componentLibrary/Accordion/AccordionContent/index.tsx +3 -1
- package/components/componentLibrary/Accordion/AccordionContent/types.ts +2 -3
- package/components/componentLibrary/Accordion/AccordionTitle/ContentFields.tsx +8 -3
- package/components/componentLibrary/Accordion/AccordionTitle/index.tsx +7 -1
- package/components/componentLibrary/Accordion/AccordionTitle/types.ts +1 -0
- package/components/componentLibrary/Accordion/llm.txt +20 -13
- package/components/componentLibrary/LanguageSwitcher/index.tsx +12 -103
- package/components/componentLibrary/LanguageSwitcher/islands/LanguageSwitcherIsland.tsx +110 -0
- package/components/componentLibrary/LanguageSwitcher/llm.txt +35 -74
- package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcher.stories.tsx +19 -26
- package/components/componentLibrary/LanguageSwitcher/types.ts +12 -1
- package/components/componentLibrary/Link/ContentFields.tsx +9 -1
- package/components/componentLibrary/Link/llm.txt +13 -0
- package/components/componentLibrary/Link/types.ts +2 -1
- package/components/componentLibrary/Logo/ContentFields.tsx +25 -0
- package/components/componentLibrary/Logo/index.tsx +13 -4
- package/components/componentLibrary/Logo/types.tsx +15 -0
- package/components/componentLibrary/_patterns/field-patterns.md +19 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Text from '../../Text/index.js';
|
|
2
2
|
import type { ContentFieldsProps } from './types.js';
|
|
3
3
|
|
|
4
4
|
const ContentFields = ({
|
|
@@ -7,11 +7,11 @@ const ContentFields = ({
|
|
|
7
7
|
contentDefault = '<p>Accordion content goes here. You can add multiple lines of text.</p>',
|
|
8
8
|
}: ContentFieldsProps) => {
|
|
9
9
|
return (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
<Text.ContentFields
|
|
11
|
+
textLabel={contentLabel}
|
|
12
|
+
textName={contentName}
|
|
13
|
+
textDefault={contentDefault}
|
|
14
|
+
textFeatureSet="bodyContent"
|
|
15
15
|
/>
|
|
16
16
|
);
|
|
17
17
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import Text from '../../Text/index.js';
|
|
1
2
|
import styles from './index.module.scss';
|
|
2
3
|
import ContentFields from './ContentFields.js';
|
|
3
4
|
import cx from '../../utils/classname.js';
|
|
4
5
|
import { AccordionContentProps } from './types.js';
|
|
5
6
|
|
|
6
7
|
const AccordionContentComponent = ({
|
|
8
|
+
fieldPath,
|
|
7
9
|
className = '',
|
|
8
10
|
style = {},
|
|
9
11
|
children,
|
|
@@ -12,7 +14,7 @@ const AccordionContentComponent = ({
|
|
|
12
14
|
|
|
13
15
|
return (
|
|
14
16
|
<div className={combinedClasses} style={style}>
|
|
15
|
-
{children}
|
|
17
|
+
{fieldPath ? <Text fieldPath={fieldPath} /> : children}
|
|
16
18
|
</div>
|
|
17
19
|
);
|
|
18
20
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { RichTextFieldDefaults } from '@hubspot/cms-components/fields';
|
|
2
|
-
|
|
3
1
|
export type AccordionContentProps = {
|
|
2
|
+
fieldPath?: string;
|
|
4
3
|
className?: string;
|
|
5
4
|
style?: React.CSSProperties;
|
|
6
5
|
children?: React.ReactNode;
|
|
@@ -9,5 +8,5 @@ export type AccordionContentProps = {
|
|
|
9
8
|
export type ContentFieldsProps = {
|
|
10
9
|
contentLabel?: string;
|
|
11
10
|
contentName?: string;
|
|
12
|
-
contentDefault?:
|
|
11
|
+
contentDefault?: string;
|
|
13
12
|
};
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Text from '../../Text/index.js';
|
|
2
2
|
import type { ContentFieldsProps } from './types.js';
|
|
3
3
|
|
|
4
4
|
const ContentFields = ({
|
|
5
5
|
titleLabel = 'Title',
|
|
6
6
|
titleName = 'title',
|
|
7
|
-
titleDefault = 'Accordion Title',
|
|
7
|
+
titleDefault = '<p>Accordion Title</p>',
|
|
8
8
|
}: ContentFieldsProps) => {
|
|
9
9
|
return (
|
|
10
|
-
<
|
|
10
|
+
<Text.ContentFields
|
|
11
|
+
textLabel={titleLabel}
|
|
12
|
+
textName={titleName}
|
|
13
|
+
textDefault={titleDefault}
|
|
14
|
+
textFeatureSet="heading"
|
|
15
|
+
/>
|
|
11
16
|
);
|
|
12
17
|
};
|
|
13
18
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Text from '../../Text/index.js';
|
|
1
2
|
import styles from './index.module.scss';
|
|
2
3
|
import ContentFields from './ContentFields.js';
|
|
3
4
|
import StyleFields from './StyleFields.js';
|
|
@@ -7,6 +8,7 @@ import { ChevronIcon, PlusIcon, MinusIcon, CaretIcon } from './icons.js';
|
|
|
7
8
|
|
|
8
9
|
const AccordionTitleComponent = ({
|
|
9
10
|
icon = 'chevron',
|
|
11
|
+
fieldPath,
|
|
10
12
|
className = '',
|
|
11
13
|
style = {},
|
|
12
14
|
children,
|
|
@@ -32,7 +34,11 @@ const AccordionTitleComponent = ({
|
|
|
32
34
|
|
|
33
35
|
return (
|
|
34
36
|
<summary className={combinedClasses} style={style}>
|
|
35
|
-
|
|
37
|
+
{fieldPath ? (
|
|
38
|
+
<Text fieldPath={fieldPath} className={styles.accordionTitleText} />
|
|
39
|
+
) : (
|
|
40
|
+
<span className={styles.accordionTitleText}>{children}</span>
|
|
41
|
+
)}
|
|
36
42
|
{renderIcon()}
|
|
37
43
|
</summary>
|
|
38
44
|
);
|
|
@@ -100,28 +100,30 @@ Accordion/
|
|
|
100
100
|
|
|
101
101
|
### AccordionTitle (Trigger)
|
|
102
102
|
|
|
103
|
-
**Purpose:** Clickable trigger component that renders a `<summary>` element with text and an expand/collapse icon.
|
|
103
|
+
**Purpose:** Clickable trigger component that renders a `<summary>` element with rich text and an expand/collapse icon. Uses the `Text` component internally for CMS inline editing support.
|
|
104
104
|
|
|
105
105
|
**Props:**
|
|
106
106
|
```tsx
|
|
107
107
|
{
|
|
108
108
|
icon?: 'chevron' | 'plus' | 'caret'; // Icon type (default: 'chevron')
|
|
109
|
+
fieldPath?: string; // Path to the RichText field in HubSpot CMS (preferred for CMS modules)
|
|
109
110
|
className?: string; // Additional CSS classes
|
|
110
111
|
style?: React.CSSProperties; // Inline styles
|
|
111
|
-
children?: React.ReactNode; //
|
|
112
|
+
children?: React.ReactNode; // Fallback title content (used when fieldPath is not provided, e.g. in Storybook)
|
|
112
113
|
}
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
### AccordionContent (Panel)
|
|
116
117
|
|
|
117
|
-
**Purpose:** Content panel component that renders a `<div>` containing the expandable content.
|
|
118
|
+
**Purpose:** Content panel component that renders a `<div>` containing the expandable content. Uses the `Text` component internally when `fieldPath` is provided for CMS inline editing support.
|
|
118
119
|
|
|
119
120
|
**Props:**
|
|
120
121
|
```tsx
|
|
121
122
|
{
|
|
123
|
+
fieldPath?: string; // Path to the RichText field in HubSpot CMS (preferred for CMS modules)
|
|
122
124
|
className?: string; // Additional CSS classes
|
|
123
125
|
style?: React.CSSProperties; // Inline styles
|
|
124
|
-
children?: React.ReactNode; //
|
|
126
|
+
children?: React.ReactNode; // Fallback content (used when fieldPath is not provided, e.g. in Storybook)
|
|
125
127
|
}
|
|
126
128
|
```
|
|
127
129
|
|
|
@@ -229,18 +231,18 @@ Configurable props for variant selection:
|
|
|
229
231
|
|
|
230
232
|
#### AccordionTitle.ContentFields
|
|
231
233
|
|
|
232
|
-
Configurable props for title text:
|
|
234
|
+
Configurable props for title text. Uses `Text.ContentFields` internally with the `heading` feature set:
|
|
233
235
|
|
|
234
236
|
```tsx
|
|
235
237
|
<AccordionTitle.ContentFields
|
|
236
238
|
titleName="title"
|
|
237
239
|
titleLabel="Title"
|
|
238
|
-
titleDefault="Accordion Title"
|
|
240
|
+
titleDefault="<p>Accordion Title</p>"
|
|
239
241
|
/>
|
|
240
242
|
```
|
|
241
243
|
|
|
242
244
|
**Fields:**
|
|
243
|
-
- `title`:
|
|
245
|
+
- `title`: RichTextField for accordion title text (heading feature set)
|
|
244
246
|
|
|
245
247
|
#### AccordionTitle.StyleFields
|
|
246
248
|
|
|
@@ -259,7 +261,7 @@ Configurable props for icon style:
|
|
|
259
261
|
|
|
260
262
|
#### AccordionContent.ContentFields
|
|
261
263
|
|
|
262
|
-
Configurable props for content:
|
|
264
|
+
Configurable props for content. Uses `Text.ContentFields` internally with the `bodyContent` feature set:
|
|
263
265
|
|
|
264
266
|
```tsx
|
|
265
267
|
<AccordionContent.ContentFields
|
|
@@ -270,7 +272,7 @@ Configurable props for content:
|
|
|
270
272
|
```
|
|
271
273
|
|
|
272
274
|
**Fields:**
|
|
273
|
-
- `content`: RichTextField for accordion content
|
|
275
|
+
- `content`: RichTextField for accordion content (bodyContent feature set)
|
|
274
276
|
|
|
275
277
|
### Module Usage Example
|
|
276
278
|
|
|
@@ -304,10 +306,15 @@ export const Component = ({
|
|
|
304
306
|
|
|
305
307
|
return (
|
|
306
308
|
<Accordion>
|
|
307
|
-
{accordionItems.map((
|
|
309
|
+
{accordionItems.map((_item, index) => (
|
|
308
310
|
<AccordionItem key={index} variant={variant}>
|
|
309
|
-
<AccordionTitle
|
|
310
|
-
|
|
311
|
+
<AccordionTitle
|
|
312
|
+
icon={icon}
|
|
313
|
+
fieldPath={`accordionItems[${index}].title`}
|
|
314
|
+
/>
|
|
315
|
+
<AccordionContent
|
|
316
|
+
fieldPath={`accordionItems[${index}].content`}
|
|
317
|
+
/>
|
|
311
318
|
</AccordionItem>
|
|
312
319
|
))}
|
|
313
320
|
</Accordion>
|
|
@@ -337,7 +344,7 @@ import {
|
|
|
337
344
|
} from '@hubspot/cms-component-library/Accordion';
|
|
338
345
|
|
|
339
346
|
const defaultItem = {
|
|
340
|
-
title: 'Accordion Title',
|
|
347
|
+
title: '<p>Accordion Title</p>',
|
|
341
348
|
content: '<p>Accordion content goes here. You can add multiple lines of text.</p>',
|
|
342
349
|
};
|
|
343
350
|
|
|
@@ -1,114 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import { getLanguageDisplayName } from './utils.js';
|
|
4
|
-
import LanguageOptions from './LanguageOptions.js';
|
|
5
|
-
import GlobeIcon from './assets/Globe.js';
|
|
1
|
+
// @ts-expect-error -- ?island not typed
|
|
2
|
+
import LanguageSwitcherIsland from './islands/LanguageSwitcherIsland.js?island';
|
|
6
3
|
import type { LanguageSwitcherProps } from './types.js';
|
|
4
|
+
import { Island, useLanguageVariants } from '@hubspot/cms-components';
|
|
7
5
|
import ContentFields from './ContentFields.js';
|
|
8
6
|
import StyleFields from './StyleFields.js';
|
|
9
|
-
import Button from '../Button/index.js';
|
|
10
|
-
import { useLanguageVariants } from '@hubspot/cms-components';
|
|
11
|
-
import { createTranslationsArrayAsObject } from './utils.js';
|
|
12
|
-
import Drawer, { useDrawer } from '../Drawer/index.js';
|
|
13
|
-
import { dummyTranslationsAsObject, dummyTranslations } from './_dummyData.js';
|
|
14
|
-
import Divider from '../Divider/index.js';
|
|
15
|
-
import type { CSSVariables } from '../utils/types.js';
|
|
16
7
|
|
|
17
8
|
const LanguageSwitcherComponent = ({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
menuBackgroundColorHover,
|
|
21
|
-
color,
|
|
22
|
-
colorHover,
|
|
23
|
-
languageSwitcherSelectText = 'Select a language',
|
|
24
|
-
className = '',
|
|
25
|
-
style = {},
|
|
9
|
+
translations: translationsProp,
|
|
10
|
+
...rest
|
|
26
11
|
}: LanguageSwitcherProps) => {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
? dummyTranslationsAsObject
|
|
30
|
-
: createTranslationsArrayAsObject(translations); // !todo remove this in PROD
|
|
31
|
-
const activeTranslation = translations?.find(item => item.isActive);
|
|
32
|
-
const currentPageLanguage = activeTranslation?.languageCode ?? '';
|
|
33
|
-
const currentPageLanguageDisplayName = getLanguageDisplayName({
|
|
34
|
-
currentPageLanguage,
|
|
35
|
-
translationsArrayAsObject,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const { isOpen, toggle, close } = useDrawer();
|
|
39
|
-
|
|
40
|
-
const isInEditor = false; // !todo - update this to use useEditorVariableChecks() hook (is_in_editor property) once it's working
|
|
41
|
-
|
|
42
|
-
const colorCss = color?.rgba;
|
|
43
|
-
const menuBackgroundColorCss = menuBackgroundColor?.rgba;
|
|
44
|
-
|
|
45
|
-
const cssVariables = {
|
|
46
|
-
...(colorCss && { '--hscl-languageSwitcher-color': colorCss }),
|
|
47
|
-
...(menuBackgroundColorCss && {
|
|
48
|
-
'--hscl-languageSwitcher-backgroundColor': menuBackgroundColorCss,
|
|
49
|
-
}),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const dividerCssVariables: CSSVariables = {
|
|
53
|
-
'--hscl-divider-borderColor': colorCss,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const combinedClasses = cx(styles.languageSwitcher, className);
|
|
12
|
+
const languageVariants = useLanguageVariants();
|
|
13
|
+
const translations = translationsProp ?? languageVariants;
|
|
57
14
|
|
|
58
15
|
return (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
className={styles.button}
|
|
65
|
-
style={{ color: colorCss }}
|
|
66
|
-
>
|
|
67
|
-
<GlobeIcon />
|
|
68
|
-
{currentPageLanguageDisplayName}
|
|
69
|
-
</Button>
|
|
70
|
-
{!isInEditor && (
|
|
71
|
-
<Drawer
|
|
72
|
-
open={isOpen}
|
|
73
|
-
direction="right"
|
|
74
|
-
onOverlayClick={close}
|
|
75
|
-
aria-label="Language selector"
|
|
76
|
-
className={styles.drawer}
|
|
77
|
-
style={{
|
|
78
|
-
...cssVariables,
|
|
79
|
-
backgroundColor: menuBackgroundColorCss,
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<div className={styles.sidePanelHeader}>
|
|
83
|
-
<span className={styles.sidePanelTitle}>
|
|
84
|
-
{languageSwitcherSelectText}
|
|
85
|
-
</span>
|
|
86
|
-
<Button
|
|
87
|
-
buttonType="button"
|
|
88
|
-
onClick={close}
|
|
89
|
-
aria-label="Close language selector"
|
|
90
|
-
className={styles.sidePanelCloseButton}
|
|
91
|
-
>
|
|
92
|
-
✕
|
|
93
|
-
</Button>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
<Divider spacing="0px" style={dividerCssVariables} />
|
|
97
|
-
|
|
98
|
-
<div className={styles.sidePanelOptionsContainer}>
|
|
99
|
-
<LanguageOptions
|
|
100
|
-
{...{
|
|
101
|
-
translations,
|
|
102
|
-
menuBackgroundColor,
|
|
103
|
-
menuBackgroundColorHover,
|
|
104
|
-
color,
|
|
105
|
-
colorHover,
|
|
106
|
-
}}
|
|
107
|
-
/>
|
|
108
|
-
</div>
|
|
109
|
-
</Drawer>
|
|
110
|
-
)}
|
|
111
|
-
</div>
|
|
16
|
+
<Island
|
|
17
|
+
module={LanguageSwitcherIsland}
|
|
18
|
+
translations={translations}
|
|
19
|
+
{...rest}
|
|
20
|
+
/>
|
|
112
21
|
);
|
|
113
22
|
};
|
|
114
23
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import styles from '../index.module.scss';
|
|
2
|
+
import cx from '../../utils/classname.js';
|
|
3
|
+
import { getLanguageDisplayName } from '../utils.js';
|
|
4
|
+
import LanguageOptions from '../LanguageOptions.js';
|
|
5
|
+
import GlobeIcon from '../assets/Globe.js';
|
|
6
|
+
import type { LanguageSwitcherIslandProps } from '../types.js';
|
|
7
|
+
import Button from '../../Button/index.js';
|
|
8
|
+
import { createTranslationsArrayAsObject } from '../utils.js';
|
|
9
|
+
import Drawer, { useDrawer } from '../../Drawer/index.js';
|
|
10
|
+
import Divider from '../../Divider/index.js';
|
|
11
|
+
import type { CSSVariables } from '../../utils/types.js';
|
|
12
|
+
|
|
13
|
+
const LanguageSwitcherIsland = ({
|
|
14
|
+
translations,
|
|
15
|
+
menuBackgroundColor,
|
|
16
|
+
menuBackgroundColorHover,
|
|
17
|
+
color,
|
|
18
|
+
colorHover,
|
|
19
|
+
languageSwitcherSelectText = 'Select a language',
|
|
20
|
+
className = '',
|
|
21
|
+
style = {},
|
|
22
|
+
}: LanguageSwitcherIslandProps) => {
|
|
23
|
+
const translationsArrayAsObject = createTranslationsArrayAsObject(
|
|
24
|
+
translations
|
|
25
|
+
);
|
|
26
|
+
const activeTranslation = translations?.find(item => item.isActive);
|
|
27
|
+
const currentPageLanguage = activeTranslation?.languageCode ?? '';
|
|
28
|
+
const currentPageLanguageDisplayName = getLanguageDisplayName({
|
|
29
|
+
currentPageLanguage,
|
|
30
|
+
translationsArrayAsObject,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { isOpen, toggle, close } = useDrawer();
|
|
34
|
+
|
|
35
|
+
const isInEditor = false;
|
|
36
|
+
|
|
37
|
+
const colorCss = color?.rgba;
|
|
38
|
+
const menuBackgroundColorCss = menuBackgroundColor?.rgba;
|
|
39
|
+
|
|
40
|
+
const cssVariables = {
|
|
41
|
+
...(colorCss && { '--hscl-languageSwitcher-color': colorCss }),
|
|
42
|
+
...(menuBackgroundColorCss && {
|
|
43
|
+
'--hscl-languageSwitcher-backgroundColor': menuBackgroundColorCss,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const dividerCssVariables: CSSVariables = {
|
|
48
|
+
'--hscl-divider-borderColor': colorCss,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const combinedClasses = cx(styles.languageSwitcher, className);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div style={{ ...cssVariables, ...style }} className={combinedClasses}>
|
|
55
|
+
<Button
|
|
56
|
+
buttonType="button"
|
|
57
|
+
aria-expanded={isOpen}
|
|
58
|
+
onClick={toggle}
|
|
59
|
+
className={styles.button}
|
|
60
|
+
style={{ color: colorCss }}
|
|
61
|
+
>
|
|
62
|
+
<GlobeIcon />
|
|
63
|
+
{currentPageLanguageDisplayName}
|
|
64
|
+
</Button>
|
|
65
|
+
{!isInEditor && (
|
|
66
|
+
<Drawer
|
|
67
|
+
open={isOpen}
|
|
68
|
+
direction="right"
|
|
69
|
+
onOverlayClick={close}
|
|
70
|
+
aria-label="Language selector"
|
|
71
|
+
className={styles.drawer}
|
|
72
|
+
style={{
|
|
73
|
+
...cssVariables,
|
|
74
|
+
backgroundColor: menuBackgroundColorCss,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<div className={styles.sidePanelHeader}>
|
|
78
|
+
<span className={styles.sidePanelTitle}>
|
|
79
|
+
{languageSwitcherSelectText}
|
|
80
|
+
</span>
|
|
81
|
+
<Button
|
|
82
|
+
buttonType="button"
|
|
83
|
+
onClick={close}
|
|
84
|
+
aria-label="Close language selector"
|
|
85
|
+
className={styles.sidePanelCloseButton}
|
|
86
|
+
>
|
|
87
|
+
✕
|
|
88
|
+
</Button>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<Divider spacing="0px" style={dividerCssVariables} />
|
|
92
|
+
|
|
93
|
+
<div className={styles.sidePanelOptionsContainer}>
|
|
94
|
+
<LanguageOptions
|
|
95
|
+
{...{
|
|
96
|
+
translations,
|
|
97
|
+
menuBackgroundColor,
|
|
98
|
+
menuBackgroundColorHover,
|
|
99
|
+
color,
|
|
100
|
+
colorHover,
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
</Drawer>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default LanguageSwitcherIsland;
|
|
@@ -15,7 +15,7 @@ The LanguageSwitcher component provides an accessible interface for users to vie
|
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
LanguageSwitcher/
|
|
18
|
-
├── index.tsx # Main component
|
|
18
|
+
├── index.tsx # Main component (wrapper that delegates to Island)
|
|
19
19
|
├── types.ts # TypeScript type definitions
|
|
20
20
|
├── ContentFields.tsx # HubSpot field definitions for content
|
|
21
21
|
├── StyleFields.tsx # HubSpot field definitions for styling
|
|
@@ -26,6 +26,8 @@ LanguageSwitcher/
|
|
|
26
26
|
├── index.module.scss # CSS module with design tokens
|
|
27
27
|
├── assets/
|
|
28
28
|
│ └── Globe.tsx # Globe SVG icon component
|
|
29
|
+
├── islands/
|
|
30
|
+
│ └── LanguageSwitcherIsland.tsx # Client-side island with interactive UI
|
|
29
31
|
└── stories/
|
|
30
32
|
├── LanguageSwitcher.stories.tsx # Component usage examples
|
|
31
33
|
├── LanguageSwitcherDecorator.tsx # Storybook decorator
|
|
@@ -41,82 +43,43 @@ LanguageSwitcher/
|
|
|
41
43
|
**Props:**
|
|
42
44
|
```tsx
|
|
43
45
|
{
|
|
46
|
+
translations?: LanguageVariant[]; // Language variants (if not provided, uses useLanguageVariants hook)
|
|
44
47
|
className?: string; // Additional CSS classes
|
|
45
48
|
style?: React.CSSProperties; // Inline styles
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
color?: ColorFieldValue; // Text color for button and menu items
|
|
50
|
+
colorHover?: ColorFieldValue; // Text color for menu items on hover
|
|
48
51
|
menuBackgroundColor?: ColorFieldValue; // Background color for the drawer
|
|
49
52
|
menuBackgroundColorHover?: ColorFieldValue; // Background color for menu items on hover
|
|
50
53
|
languageSwitcherSelectText?: string; // Header text in drawer (default: 'Select a language')
|
|
51
|
-
useDummyData?: boolean; // Use dummy data for testing (development only)
|
|
52
54
|
}
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
## Usage Examples
|
|
56
58
|
|
|
57
|
-
###
|
|
59
|
+
### Basic Usage
|
|
58
60
|
|
|
59
|
-
The LanguageSwitcher component
|
|
60
|
-
|
|
61
|
-
#### Island Component
|
|
61
|
+
The LanguageSwitcher component automatically handles Island-ification internally. Simply import and use it with optional props:
|
|
62
62
|
|
|
63
63
|
```tsx
|
|
64
|
-
// islands/LanguageSwitcherIsland.tsx
|
|
65
64
|
import LanguageSwitcher from '@hubspot/cms-component-library/LanguageSwitcher';
|
|
66
65
|
|
|
67
|
-
export default function
|
|
68
|
-
|
|
69
|
-
textColorHover,
|
|
70
|
-
menuBackgroundColor,
|
|
71
|
-
menuBackgroundColorHover,
|
|
72
|
-
languageSwitcherSelectText,
|
|
73
|
-
}) {
|
|
74
|
-
return (
|
|
75
|
-
<LanguageSwitcher
|
|
76
|
-
textColor={textColor}
|
|
77
|
-
textColorHover={textColorHover}
|
|
78
|
-
menuBackgroundColor={menuBackgroundColor}
|
|
79
|
-
menuBackgroundColorHover={menuBackgroundColorHover}
|
|
80
|
-
languageSwitcherSelectText={languageSwitcherSelectText}
|
|
81
|
-
/>
|
|
82
|
-
);
|
|
66
|
+
export default function MyComponent() {
|
|
67
|
+
return <LanguageSwitcher />;
|
|
83
68
|
}
|
|
84
69
|
```
|
|
85
70
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```tsx
|
|
89
|
-
import { Island } from '@hubspot/cms-components';
|
|
90
|
-
import LanguageSwitcherIsland from './islands/LanguageSwitcherIsland?island';
|
|
91
|
-
|
|
92
|
-
export const Component = ({ fieldValues }) => {
|
|
93
|
-
return (
|
|
94
|
-
<Island
|
|
95
|
-
module={LanguageSwitcherIsland}
|
|
96
|
-
textColor={fieldValues.textColor}
|
|
97
|
-
textColorHover={fieldValues.textColorHover}
|
|
98
|
-
menuBackgroundColor={fieldValues.menuBackgroundColor}
|
|
99
|
-
menuBackgroundColorHover={fieldValues.menuBackgroundColorHover}
|
|
100
|
-
languageSwitcherSelectText={fieldValues.languageSwitcherSelectText}
|
|
101
|
-
/>
|
|
102
|
-
);
|
|
103
|
-
};
|
|
104
|
-
```
|
|
71
|
+
When used without a `translations` prop, it automatically calls `useLanguageVariants()` to fetch language data.
|
|
105
72
|
|
|
106
|
-
###
|
|
73
|
+
### With Custom Header Text
|
|
107
74
|
|
|
108
75
|
```tsx
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
export default function LanguageSwitcherIsland() {
|
|
112
|
-
return <LanguageSwitcher />;
|
|
113
|
-
}
|
|
76
|
+
<LanguageSwitcher languageSwitcherSelectText="Choose your language" />
|
|
114
77
|
```
|
|
115
78
|
|
|
116
|
-
### With Custom
|
|
79
|
+
### With Custom Translations
|
|
117
80
|
|
|
118
81
|
```tsx
|
|
119
|
-
<LanguageSwitcher
|
|
82
|
+
<LanguageSwitcher translations={customLanguageVariants} />
|
|
120
83
|
```
|
|
121
84
|
|
|
122
85
|
### With Custom Colors
|
|
@@ -125,8 +88,8 @@ If passing in colors directly (without using a ColorField), like the below examp
|
|
|
125
88
|
|
|
126
89
|
```tsx
|
|
127
90
|
<LanguageSwitcher
|
|
128
|
-
|
|
129
|
-
|
|
91
|
+
color={{ rgba: 'rgba(51, 71, 91, 1)' }}
|
|
92
|
+
colorHover={{ rgba: 'rgba(0, 102, 204, 1)' }}
|
|
130
93
|
menuBackgroundColor={{ rgba: 'rgba(255, 255, 255, 1)' }}
|
|
131
94
|
menuBackgroundColorHover={{ rgba: 'rgba(245, 245, 245, 1)' }}
|
|
132
95
|
/>
|
|
@@ -197,21 +160,19 @@ Configurable props for color customization:
|
|
|
197
160
|
|
|
198
161
|
### Module Usage Example
|
|
199
162
|
|
|
200
|
-
If passing in colors using a ColorField, like the below example, pass in the entire field value. HubSpot will transform the field value from a default of { color: hex, opacity: number } to {
|
|
163
|
+
If passing in colors using a ColorField, like the below example, pass in the entire field value. HubSpot will transform the field value from a default of { color: hex, opacity: number } to { rgba, rgb, opacity, color, hex, css }. The LanguageSwitcher component will pull the rgba value from this final output.
|
|
201
164
|
|
|
202
165
|
```tsx
|
|
203
|
-
import
|
|
204
|
-
import LanguageSwitcherIsland from './islands/LanguageSwitcherIsland?island';
|
|
166
|
+
import LanguageSwitcher from '@hubspot/cms-component-library/LanguageSwitcher';
|
|
205
167
|
|
|
206
168
|
export default function HeaderModule({ fieldValues }) {
|
|
207
169
|
return (
|
|
208
170
|
<header>
|
|
209
171
|
<nav>
|
|
210
172
|
{/* Other navigation items */}
|
|
211
|
-
<
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
textColorHover={fieldValues.textColorHover}
|
|
173
|
+
<LanguageSwitcher
|
|
174
|
+
color={fieldValues.color}
|
|
175
|
+
colorHover={fieldValues.colorHover}
|
|
215
176
|
menuBackgroundColor={fieldValues.menuBackgroundColor}
|
|
216
177
|
menuBackgroundColorHover={fieldValues.menuBackgroundColorHover}
|
|
217
178
|
languageSwitcherSelectText={fieldValues.languageSwitcherSelectText}
|
|
@@ -280,14 +241,14 @@ The LanguageSwitcher component follows accessibility best practices:
|
|
|
280
241
|
|
|
281
242
|
## Best Practices
|
|
282
243
|
|
|
283
|
-
- **
|
|
244
|
+
- **Island-ification is handled internally**: The LanguageSwitcher component automatically handles Island-ification for interactive state, so you don't need to manually wrap it
|
|
284
245
|
- **Position prominently**: Place in global navigation (header or footer) where users expect language controls
|
|
285
246
|
- **Use native language names**: The component automatically displays language names in their native script (e.g., "Español" for Spanish, "Français" for French)
|
|
286
247
|
- **Customize header text**: Set `languageSwitcherSelectText` to match your site's primary language for consistency
|
|
287
248
|
- **Color coordination**: Match `textColor` and `menuBackgroundColor` to your site's theme
|
|
288
249
|
- **Color setting**: If using ColorFields, pass in the entire field value, as HubSpot transforms the user's color choice into an object with an rgba key. Otherwise, ensure the color object has an rgba key and value. The component expects an object with an rgba key.
|
|
289
250
|
- **Responsive design**: The drawer automatically adjusts to full width on mobile devices (≤400px)
|
|
290
|
-
- **Test with real translations**:
|
|
251
|
+
- **Test with real translations**: Pass real HubSpot language variants via the `translations` prop, or rely on the default `useLanguageVariants()` hook for production
|
|
291
252
|
- **Provide clear visual feedback**: Use hover colors that provide sufficient contrast for user interaction
|
|
292
253
|
- **Consider placement**: Position near other utility navigation items (search, account, etc.)
|
|
293
254
|
- **Dark mode support**: Provide appropriate color values for dark backgrounds when needed
|
|
@@ -297,8 +258,7 @@ The LanguageSwitcher component follows accessibility best practices:
|
|
|
297
258
|
### Header Navigation Integration
|
|
298
259
|
|
|
299
260
|
```tsx
|
|
300
|
-
import
|
|
301
|
-
import LanguageSwitcherIsland from './islands/LanguageSwitcherIsland?island';
|
|
261
|
+
import LanguageSwitcher from '@hubspot/cms-component-library/LanguageSwitcher';
|
|
302
262
|
|
|
303
263
|
export default function SiteHeader({ fieldValues }) {
|
|
304
264
|
return (
|
|
@@ -315,9 +275,8 @@ export default function SiteHeader({ fieldValues }) {
|
|
|
315
275
|
<a href="/about">About</a>
|
|
316
276
|
<a href="/services">Services</a>
|
|
317
277
|
<a href="/contact">Contact</a>
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
textColor={fieldValues.textColor}
|
|
278
|
+
<LanguageSwitcher
|
|
279
|
+
color={fieldValues.color}
|
|
321
280
|
menuBackgroundColor={fieldValues.menuBackgroundColor}
|
|
322
281
|
languageSwitcherSelectText={fieldValues.languageSwitcherSelectText}
|
|
323
282
|
/>
|
|
@@ -330,13 +289,14 @@ export default function SiteHeader({ fieldValues }) {
|
|
|
330
289
|
### Footer Placement
|
|
331
290
|
|
|
332
291
|
```tsx
|
|
292
|
+
import LanguageSwitcher from '@hubspot/cms-component-library/LanguageSwitcher';
|
|
293
|
+
|
|
333
294
|
export default function SiteFooter({ fieldValues }) {
|
|
334
295
|
return (
|
|
335
296
|
<footer style={{ padding: '48px 24px', textAlign: 'center' }}>
|
|
336
297
|
<div style={{ marginBottom: '24px' }}>
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
textColor={fieldValues.footerTextColor}
|
|
298
|
+
<LanguageSwitcher
|
|
299
|
+
color={fieldValues.footerTextColor}
|
|
340
300
|
menuBackgroundColor={fieldValues.menuBackgroundColor}
|
|
341
301
|
languageSwitcherSelectText={fieldValues.languageSwitcherSelectText}
|
|
342
302
|
/>
|
|
@@ -350,6 +310,8 @@ export default function SiteFooter({ fieldValues }) {
|
|
|
350
310
|
### Mobile-First Navigation
|
|
351
311
|
|
|
352
312
|
```tsx
|
|
313
|
+
import LanguageSwitcher from '@hubspot/cms-component-library/LanguageSwitcher';
|
|
314
|
+
|
|
353
315
|
export default function MobileNav({ fieldValues }) {
|
|
354
316
|
return (
|
|
355
317
|
<nav style={{
|
|
@@ -362,9 +324,8 @@ export default function MobileNav({ fieldValues }) {
|
|
|
362
324
|
<a href="/about">About</a>
|
|
363
325
|
<a href="/contact">Contact</a>
|
|
364
326
|
<div style={{ borderTop: '1px solid #eee', paddingTop: '16px' }}>
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
textColor={fieldValues.textColor}
|
|
327
|
+
<LanguageSwitcher
|
|
328
|
+
color={fieldValues.color}
|
|
368
329
|
menuBackgroundColor={fieldValues.menuBackgroundColor}
|
|
369
330
|
/>
|
|
370
331
|
</div>
|
|
@@ -3,6 +3,7 @@ import LanguageSwitcher from '../index.js';
|
|
|
3
3
|
import { LanguageSwitcherProps } from '../types.js';
|
|
4
4
|
import { withLanguageSwitcherStyles } from './LanguageSwitcherDecorator.js';
|
|
5
5
|
import { SBContainer, SBFocusWrapper } from '@sb-utils';
|
|
6
|
+
import { dummyTranslations } from '../_dummyData.js';
|
|
6
7
|
|
|
7
8
|
const meta: Meta<LanguageSwitcherProps> = {
|
|
8
9
|
title: 'Component Library/LanguageSwitcher',
|
|
@@ -21,14 +22,6 @@ const meta: Meta<LanguageSwitcherProps> = {
|
|
|
21
22
|
},
|
|
22
23
|
decorators: [withLanguageSwitcherStyles],
|
|
23
24
|
argTypes: {
|
|
24
|
-
useDummyData: {
|
|
25
|
-
control: 'boolean',
|
|
26
|
-
description: 'Use dummy translation data for testing',
|
|
27
|
-
table: {
|
|
28
|
-
category: 'Development',
|
|
29
|
-
defaultValue: { summary: 'false' },
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
25
|
languageSwitcherSelectText: {
|
|
33
26
|
control: 'text',
|
|
34
27
|
description: 'Text displayed in the drawer header',
|
|
@@ -37,14 +30,14 @@ const meta: Meta<LanguageSwitcherProps> = {
|
|
|
37
30
|
defaultValue: { summary: 'Select a language' },
|
|
38
31
|
},
|
|
39
32
|
},
|
|
40
|
-
|
|
33
|
+
color: {
|
|
41
34
|
control: 'object',
|
|
42
35
|
description: 'Text color for button and menu items',
|
|
43
36
|
table: {
|
|
44
37
|
category: 'Visual',
|
|
45
38
|
},
|
|
46
39
|
},
|
|
47
|
-
|
|
40
|
+
colorHover: {
|
|
48
41
|
control: 'object',
|
|
49
42
|
description: 'Text color for menu items on hover',
|
|
50
43
|
table: {
|
|
@@ -90,7 +83,7 @@ export const Default: Story = {
|
|
|
90
83
|
return (
|
|
91
84
|
<SBContainer>
|
|
92
85
|
<LanguageSwitcher
|
|
93
|
-
|
|
86
|
+
translations={dummyTranslations}
|
|
94
87
|
languageSwitcherSelectText="Select a language"
|
|
95
88
|
/>
|
|
96
89
|
</SBContainer>
|
|
@@ -140,10 +133,10 @@ export const ColorThemes: Story = {
|
|
|
140
133
|
<p>Custom color theming with purple tones.</p>
|
|
141
134
|
<SBContainer>
|
|
142
135
|
<LanguageSwitcher
|
|
143
|
-
|
|
136
|
+
translations={dummyTranslations}
|
|
144
137
|
languageSwitcherSelectText="Wählen Sie eine Sprache"
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
color={purpleTextColor}
|
|
139
|
+
colorHover={purpleTextColorHover}
|
|
147
140
|
menuBackgroundColor={purpleMenuBgColor}
|
|
148
141
|
menuBackgroundColorHover={purpleMenuBgColorHover}
|
|
149
142
|
/>
|
|
@@ -162,10 +155,10 @@ export const ColorThemes: Story = {
|
|
|
162
155
|
</SBContainer>
|
|
163
156
|
<SBContainer>
|
|
164
157
|
<LanguageSwitcher
|
|
165
|
-
|
|
158
|
+
translations={dummyTranslations}
|
|
166
159
|
languageSwitcherSelectText="Select a language"
|
|
167
|
-
|
|
168
|
-
|
|
160
|
+
color={darkTextColor}
|
|
161
|
+
colorHover={darkTextColorHover}
|
|
169
162
|
menuBackgroundColor={darkMenuBgColor}
|
|
170
163
|
menuBackgroundColorHover={darkMenuBgColorHover}
|
|
171
164
|
/>
|
|
@@ -195,7 +188,7 @@ export const CustomText: Story = {
|
|
|
195
188
|
<strong>🇬🇧 English:</strong>
|
|
196
189
|
</p>
|
|
197
190
|
<LanguageSwitcher
|
|
198
|
-
|
|
191
|
+
translations={dummyTranslations}
|
|
199
192
|
languageSwitcherSelectText="Select a language"
|
|
200
193
|
/>
|
|
201
194
|
</SBContainer>
|
|
@@ -205,7 +198,7 @@ export const CustomText: Story = {
|
|
|
205
198
|
<strong>🇩🇪 German:</strong>
|
|
206
199
|
</p>
|
|
207
200
|
<LanguageSwitcher
|
|
208
|
-
|
|
201
|
+
translations={dummyTranslations}
|
|
209
202
|
languageSwitcherSelectText="Sprache wählen"
|
|
210
203
|
/>
|
|
211
204
|
</SBContainer>
|
|
@@ -215,7 +208,7 @@ export const CustomText: Story = {
|
|
|
215
208
|
<strong>🇪🇸 Spanish:</strong>
|
|
216
209
|
</p>
|
|
217
210
|
<LanguageSwitcher
|
|
218
|
-
|
|
211
|
+
translations={dummyTranslations}
|
|
219
212
|
languageSwitcherSelectText="Seleccionar idioma"
|
|
220
213
|
/>
|
|
221
214
|
</SBContainer>
|
|
@@ -225,7 +218,7 @@ export const CustomText: Story = {
|
|
|
225
218
|
<strong>🇫🇷 French:</strong>
|
|
226
219
|
</p>
|
|
227
220
|
<LanguageSwitcher
|
|
228
|
-
|
|
221
|
+
translations={dummyTranslations}
|
|
229
222
|
languageSwitcherSelectText="Choisir une langue"
|
|
230
223
|
/>
|
|
231
224
|
</SBContainer>
|
|
@@ -288,9 +281,9 @@ export const InContext: Story = {
|
|
|
288
281
|
|
|
289
282
|
<div style={{ paddingTop: '6px' }}>
|
|
290
283
|
<LanguageSwitcher
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
284
|
+
translations={dummyTranslations}
|
|
285
|
+
color={{ rgba: 'rgb(55, 245, 255)' }}
|
|
286
|
+
colorHover={{ rgba: 'rgb(255, 255, 255)' }}
|
|
294
287
|
menuBackgroundColor={{ rgba: 'rgba(100, 181, 246, 1)' }}
|
|
295
288
|
menuBackgroundColorHover={{ rgba: 'rgba(0, 160, 240, 1)' }}
|
|
296
289
|
/>
|
|
@@ -325,7 +318,7 @@ export const InteractionStates: Story = {
|
|
|
325
318
|
<h4>Hover State</h4>
|
|
326
319
|
<p>Hover over the language switcher button to see the hover effect</p>
|
|
327
320
|
<LanguageSwitcher
|
|
328
|
-
|
|
321
|
+
translations={dummyTranslations}
|
|
329
322
|
languageSwitcherSelectText="Select a language"
|
|
330
323
|
/>
|
|
331
324
|
</SBContainer>
|
|
@@ -339,7 +332,7 @@ export const InteractionStates: Story = {
|
|
|
339
332
|
</p>
|
|
340
333
|
<SBFocusWrapper>
|
|
341
334
|
<LanguageSwitcher
|
|
342
|
-
|
|
335
|
+
translations={dummyTranslations}
|
|
343
336
|
languageSwitcherSelectText="Select a language"
|
|
344
337
|
/>
|
|
345
338
|
</SBFocusWrapper>
|
|
@@ -7,8 +7,19 @@ import type { CSSVariables } from '../utils/types.js';
|
|
|
7
7
|
|
|
8
8
|
type ColorFieldValue = typeof ColorFieldDefaults;
|
|
9
9
|
|
|
10
|
+
export type LanguageSwitcherIslandProps = {
|
|
11
|
+
translations: LanguageVariant[];
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSVariables;
|
|
14
|
+
color?: ColorFieldValue;
|
|
15
|
+
colorHover?: ColorFieldValue;
|
|
16
|
+
menuBackgroundColorHover?: ColorFieldValue;
|
|
17
|
+
menuBackgroundColor?: ColorFieldValue;
|
|
18
|
+
languageSwitcherSelectText?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
10
21
|
export type LanguageSwitcherProps = {
|
|
11
|
-
|
|
22
|
+
translations?: LanguageVariant[];
|
|
12
23
|
className?: string;
|
|
13
24
|
style?: CSSVariables;
|
|
14
25
|
color?: ColorFieldValue;
|
|
@@ -11,8 +11,16 @@ const ContentFields = ({
|
|
|
11
11
|
href: 'https://www.hubspot.com',
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
|
+
fieldVisibility,
|
|
14
15
|
}: ContentFieldsProps) => {
|
|
15
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
<LinkField
|
|
18
|
+
label={linkLabel}
|
|
19
|
+
name={linkName}
|
|
20
|
+
visibility={fieldVisibility?.[linkName]}
|
|
21
|
+
default={linkDefault}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
16
24
|
};
|
|
17
25
|
|
|
18
26
|
export default ContentFields;
|
|
@@ -198,6 +198,19 @@ The Link component provides field definitions for easy integration with HubSpot
|
|
|
198
198
|
/>
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
**Props:**
|
|
202
|
+
- `linkLabel?: string` — label for the LinkField (default: `'Link'`)
|
|
203
|
+
- `linkName?: string` — field name (default: `'link'`)
|
|
204
|
+
- `linkDefault?` — default value for the LinkField
|
|
205
|
+
- `fieldVisibility?: Partial<Record<string, Visibility>>` — visibility options keyed by field name (keys must match the corresponding field name prop, e.g. `linkName`). Values match the cms-components Visibility schema (e.g. `hidden_subfields`, `controlling_field_path`, `operator`)
|
|
206
|
+
|
|
207
|
+
**Example — hide the nofollow option:**
|
|
208
|
+
```tsx
|
|
209
|
+
<Link.ContentFields
|
|
210
|
+
fieldVisibility={{ link: { hidden_subfields: { no_follow: true } } }}
|
|
211
|
+
/>
|
|
212
|
+
```
|
|
213
|
+
|
|
201
214
|
**Fields:**
|
|
202
215
|
- `link`: LinkField for href destination
|
|
203
216
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LinkFieldDefaults } from '@hubspot/cms-components/fields';
|
|
1
|
+
import { LinkFieldDefaults, Visibility } from '@hubspot/cms-components/fields';
|
|
2
2
|
import { LinkFieldValue } from '../utils/linkField.js';
|
|
3
3
|
|
|
4
4
|
export type BaseLinkProps = {
|
|
@@ -37,6 +37,7 @@ export type ContentFieldsProps = {
|
|
|
37
37
|
linkLabel?: string;
|
|
38
38
|
linkName?: string;
|
|
39
39
|
linkDefault?: typeof LinkFieldDefaults;
|
|
40
|
+
fieldVisibility?: Partial<Record<string, Visibility>>;
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
export type StyleFieldsProps = {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LogoField } from '@hubspot/cms-components/fields';
|
|
2
|
+
import { ContentFieldsProps } from './types.js';
|
|
3
|
+
|
|
4
|
+
const ContentFields = ({
|
|
5
|
+
logoLabel = 'Logo',
|
|
6
|
+
logoName = 'img',
|
|
7
|
+
logoDefault = {
|
|
8
|
+
override_inherited_src: false,
|
|
9
|
+
src: null,
|
|
10
|
+
alt: null,
|
|
11
|
+
loading: 'disabled',
|
|
12
|
+
},
|
|
13
|
+
showLoading = true,
|
|
14
|
+
}: ContentFieldsProps) => {
|
|
15
|
+
return (
|
|
16
|
+
<LogoField
|
|
17
|
+
label={logoLabel}
|
|
18
|
+
name={logoName}
|
|
19
|
+
showLoading={showLoading}
|
|
20
|
+
default={logoDefault}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default ContentFields;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useBrandSettings } from '@hubspot/cms-components';
|
|
3
3
|
import { dummyLogoData } from './_dummyLogoData.js';
|
|
4
|
+
import ContentFields from './ContentFields.js';
|
|
4
5
|
import { LogoData, LogoProps } from './types.js';
|
|
5
6
|
import styles from './index.module.scss';
|
|
6
7
|
import cx from '../utils/classname.js';
|
|
@@ -18,9 +19,10 @@ export const Logo = ({
|
|
|
18
19
|
}: LogoProps) => {
|
|
19
20
|
const { primaryLogo } = useBrandSettings() ?? {};
|
|
20
21
|
|
|
21
|
-
const logo: LogoData | undefined = useMemo(
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const logo: LogoData | undefined = useMemo(
|
|
23
|
+
() => (useDummyLogo ? dummyLogoData.logo : primaryLogo),
|
|
24
|
+
[useDummyLogo, primaryLogo]
|
|
25
|
+
);
|
|
24
26
|
|
|
25
27
|
if (!logo?.src) {
|
|
26
28
|
return null;
|
|
@@ -70,4 +72,11 @@ export const Logo = ({
|
|
|
70
72
|
);
|
|
71
73
|
};
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
type LogoComponentType = typeof Logo & {
|
|
76
|
+
ContentFields: typeof ContentFields;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const LogoWithFields = Logo as LogoComponentType;
|
|
80
|
+
LogoWithFields.ContentFields = ContentFields;
|
|
81
|
+
|
|
82
|
+
export default LogoWithFields;
|
|
@@ -6,6 +6,21 @@ export type LogoData = {
|
|
|
6
6
|
link?: string;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
export type ContentFieldsProps = {
|
|
10
|
+
logoLabel?: string;
|
|
11
|
+
logoName?: string;
|
|
12
|
+
logoDefault?: {
|
|
13
|
+
override_inherited_src?: boolean;
|
|
14
|
+
src?: string | null;
|
|
15
|
+
alt?: string | null;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
loading?: string;
|
|
19
|
+
suppress_company_name?: boolean;
|
|
20
|
+
};
|
|
21
|
+
showLoading?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
9
24
|
export type LogoProps = {
|
|
10
25
|
useDummyLogo?: boolean;
|
|
11
26
|
className?: string;
|
|
@@ -203,6 +203,25 @@ const SHOW_ICON_VISIBILITY_RULES: Visibility = {
|
|
|
203
203
|
/>
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
### Field Visibility Map Pattern
|
|
207
|
+
|
|
208
|
+
For components with configurable field names, use a `fieldVisibility` prop keyed by field name. Values match the cms-components `Visibility` schema (e.g. `hidden_subfields`, `controlling_field_path`, `operator`). Keys must match the field's name prop (e.g. `linkName`).
|
|
209
|
+
|
|
210
|
+
Link component example:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
<Link.ContentFields
|
|
214
|
+
fieldVisibility={{ link: { hidden_subfields: { no_follow: true } } }}
|
|
215
|
+
/>
|
|
216
|
+
|
|
217
|
+
<Link.ContentFields
|
|
218
|
+
linkName="footerLink"
|
|
219
|
+
fieldVisibility={{ footerLink: { hidden_subfields: { no_follow: true, open_in_new_tab: true } } }}
|
|
220
|
+
/>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Key Pattern:** Look up visibility by the configurable name prop: `visibility={fieldVisibility?.[linkName]}`
|
|
224
|
+
|
|
206
225
|
## Nested Component Fields Pattern
|
|
207
226
|
|
|
208
227
|
**Pattern for nested component fields:**
|