@hubspot/cms-component-library 0.3.5 → 0.3.7
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/llm.txt +1 -1
- package/components/componentLibrary/Card/llm.txt +1 -1
- package/components/componentLibrary/Form/ContentFields.tsx +1 -1
- package/components/componentLibrary/Form/index.tsx +12 -2
- package/components/componentLibrary/Form/islands/FormIsland.tsx +5 -5
- package/components/componentLibrary/Form/islands/LegacyFormIsland.tsx +77 -0
- package/components/componentLibrary/Form/types.ts +12 -4
- package/components/componentLibrary/Grid/llm.txt +13 -16
- 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/_patterns/checklist-and-examples.md +1 -1
- package/package.json +2 -2
|
@@ -439,7 +439,7 @@ The Accordion component follows accessibility best practices:
|
|
|
439
439
|
- **Gap selection**: Use any valid CSS length value (e.g., '8px', '16px', '24px', '48px') for spacing between items
|
|
440
440
|
- **Dynamic rendering**: Always provide unique `key` props when mapping arrays to AccordionItems
|
|
441
441
|
- **Rich content**: AccordionContent supports any HTML content including lists, paragraphs, images, and nested components
|
|
442
|
-
- **Prefer library components**: When adding content inside AccordionContent, prefer using library components (e.g., `List`, `
|
|
442
|
+
- **Prefer library components**: When adding content inside AccordionContent, prefer using library components (e.g., `List`, `Text` with `textFeatureSet="heading"` for headings) over raw HTML elements for consistent theming and styling
|
|
443
443
|
- **CSS Variables**: Override design tokens using CSS variables rather than hardcoding values
|
|
444
444
|
- **Single responsibility**: Keep each accordion item focused on one topic for better UX
|
|
445
445
|
|
|
@@ -182,4 +182,4 @@ The Card component follows accessibility best practices:
|
|
|
182
182
|
|
|
183
183
|
- **Flex**: Use for arranging cards in flexible layouts
|
|
184
184
|
- **Grid**: Use for creating responsive card grids
|
|
185
|
-
- **
|
|
185
|
+
- **Text**: Use with `textFeatureSet="heading"` for editable card titles
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
// @ts-expect-error -- ?island not typed
|
|
2
2
|
import FormIsland from './islands/FormIsland.js?island';
|
|
3
|
+
// @ts-expect-error -- ?island not typed
|
|
4
|
+
import LegacyFormIsland from './islands/LegacyFormIsland.js?island';
|
|
3
5
|
import { FormProps } from './types.js';
|
|
4
6
|
import { Island } from '@hubspot/cms-components';
|
|
5
7
|
import ContentFields from './ContentFields.js';
|
|
6
8
|
|
|
7
|
-
const FormComponent = (
|
|
8
|
-
|
|
9
|
+
const FormComponent = ({
|
|
10
|
+
formField,
|
|
11
|
+
formId,
|
|
12
|
+
formVersion,
|
|
13
|
+
...rest
|
|
14
|
+
}: FormProps) => {
|
|
15
|
+
const resolvedFormId = formField != null ? formField.form_id : formId;
|
|
16
|
+
const FormModule = formVersion === 'v4' ? FormIsland : LegacyFormIsland;
|
|
17
|
+
|
|
18
|
+
return <Island module={FormModule} formId={resolvedFormId} {...rest} />;
|
|
9
19
|
};
|
|
10
20
|
|
|
11
21
|
type FormComponentType = typeof FormComponent & {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import { FormProps
|
|
3
|
-
import { getHubID } from '@hubspot/cms-components';
|
|
2
|
+
import { FormProps } from '../types.js';
|
|
3
|
+
import { getHubID, getHSEnv } from '@hubspot/cms-components';
|
|
4
4
|
|
|
5
5
|
const getScriptSrc = (portalId: number, env: string) => {
|
|
6
6
|
const host = env === 'qa' ? 'js.hsformsqa.net' : 'js.hsforms.net';
|
|
7
|
-
return `https://${host}/forms/embed/${portalId}.js`;
|
|
7
|
+
return `https://${host}/forms/embed/developer/${portalId}.js`;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const FormIsland = ({ formId
|
|
10
|
+
const FormIsland = ({ formId }: FormProps) => {
|
|
11
11
|
const portalId = getHubID();
|
|
12
|
-
const resolvedEnv =
|
|
12
|
+
const resolvedEnv = getHSEnv();
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (!formId || !portalId) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useEffect, useId } from 'react';
|
|
2
|
+
import { FormProps } from '../types.js';
|
|
3
|
+
import { getHubID, getHSEnv } from '@hubspot/cms-components';
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
hbspt?: {
|
|
8
|
+
forms: {
|
|
9
|
+
create: (options: {
|
|
10
|
+
portalId: number;
|
|
11
|
+
formId: string;
|
|
12
|
+
env: 'qa' | 'prod';
|
|
13
|
+
target: string;
|
|
14
|
+
}) => void;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getScriptSrc = (env: string) => {
|
|
21
|
+
const host = env === 'qa' ? 'js.hsformsqa.net' : 'js.hsforms.net';
|
|
22
|
+
return `//${host}/forms/embed/v2.js`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const LegacyFormIsland = ({ formId }: FormProps) => {
|
|
26
|
+
const portalId = getHubID();
|
|
27
|
+
const resolvedEnv = getHSEnv();
|
|
28
|
+
const rawId = useId();
|
|
29
|
+
const containerId = `hs-legacy-form-${rawId.replace(/:/g, '')}`;
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!formId || !portalId) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const scriptSrc = getScriptSrc(resolvedEnv);
|
|
37
|
+
|
|
38
|
+
const createForm = () => {
|
|
39
|
+
window.hbspt?.forms.create({
|
|
40
|
+
portalId,
|
|
41
|
+
formId,
|
|
42
|
+
env: resolvedEnv,
|
|
43
|
+
target: `#${containerId}`,
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
|
|
48
|
+
|
|
49
|
+
if (existingScript) {
|
|
50
|
+
if (window.hbspt) {
|
|
51
|
+
createForm();
|
|
52
|
+
} else {
|
|
53
|
+
existingScript.addEventListener('load', createForm);
|
|
54
|
+
return () => existingScript.removeEventListener('load', createForm);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const script = document.createElement('script');
|
|
60
|
+
script.src = scriptSrc;
|
|
61
|
+
script.addEventListener('load', createForm);
|
|
62
|
+
document.head.appendChild(script);
|
|
63
|
+
|
|
64
|
+
return () => {
|
|
65
|
+
script.removeEventListener('load', createForm);
|
|
66
|
+
script.remove();
|
|
67
|
+
};
|
|
68
|
+
}, [formId, portalId, resolvedEnv, containerId]);
|
|
69
|
+
|
|
70
|
+
if (!formId || !portalId) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return <div id={containerId} />;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default LegacyFormIsland;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { FormFieldDefaults } from '@hubspot/cms-components/fields';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type
|
|
2
|
+
|
|
3
|
+
export type FormProps = FormPropsWithField | FormPropsWithoutField;
|
|
4
|
+
|
|
5
|
+
export type FormPropsWithoutField = {
|
|
6
|
+
formField?: never;
|
|
6
7
|
formId: string;
|
|
8
|
+
formVersion: 'v4' | 'v3' | 'v2' | '';
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type FormPropsWithField = {
|
|
12
|
+
formField: typeof FormFieldDefaults;
|
|
13
|
+
formId?: never;
|
|
14
|
+
formVersion?: never;
|
|
7
15
|
};
|
|
8
16
|
|
|
9
17
|
export type ContentFieldsProps = {
|
|
@@ -109,13 +109,13 @@ Grid/
|
|
|
109
109
|
|
|
110
110
|
**Purpose:** Wraps grid children to control their precise placement, spanning, and alignment within the Grid container. Can render as any HTML element or custom React component while maintaining grid positioning control.
|
|
111
111
|
|
|
112
|
-
**Key Feature:** The `as` prop is polymorphic - it accepts both HTML element strings ('div', 'section', etc.) and React component references (Button,
|
|
112
|
+
**Key Feature:** The `as` prop is polymorphic - it accepts both HTML element strings ('div', 'section', etc.) and React component references (Button, Text, custom components). GridItem handles grid placement while passing through all other props to the underlying component.
|
|
113
113
|
|
|
114
114
|
**Props:**
|
|
115
115
|
```tsx
|
|
116
116
|
{
|
|
117
117
|
as?: React.ElementType; // Any HTML element or React component (default: 'div')
|
|
118
|
-
// Examples: 'div', 'article', Button,
|
|
118
|
+
// Examples: 'div', 'article', Button, Text, CustomComponent
|
|
119
119
|
gridColumn?: string; // Grid column placement (e.g., '1 / 3', 'span 2', '2')
|
|
120
120
|
gridColumnMd?: string; // Grid column placement at tablet breakpoint (768px+)
|
|
121
121
|
gridColumnLg?: string; // Grid column placement at desktop breakpoint (1024px+)
|
|
@@ -148,7 +148,7 @@ GridItem's `as` prop provides flexibility in rendering while maintaining grid co
|
|
|
148
148
|
2. **React Components**: Pass component references directly
|
|
149
149
|
```tsx
|
|
150
150
|
<GridItem as={Button} buttonType="primary">Click me</GridItem>
|
|
151
|
-
<GridItem as={
|
|
151
|
+
<GridItem as={Text} fieldPath="title" />
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
3. **Prop Pass-Through**: All props beyond GridItem's own props are forwarded to the underlying component
|
|
@@ -158,10 +158,8 @@ GridItem's `as` prop provides flexibility in rendering while maintaining grid co
|
|
|
158
158
|
Link Button
|
|
159
159
|
</GridItem>
|
|
160
160
|
|
|
161
|
-
{/*
|
|
162
|
-
<GridItem as={
|
|
163
|
-
Styled Heading
|
|
164
|
-
</GridItem>
|
|
161
|
+
{/* Text-specific props are passed through */}
|
|
162
|
+
<GridItem as={Text} fieldPath="sectionTitle" className="custom-text" />
|
|
165
163
|
```
|
|
166
164
|
|
|
167
165
|
4. **Grid Positioning**: GridItem handles all grid-specific positioning regardless of the underlying component
|
|
@@ -199,7 +197,7 @@ import Grid, { GridItem } from '@hubspot/cms-component-library/Grid';
|
|
|
199
197
|
```tsx
|
|
200
198
|
import Grid, { GridItem } from '@hubspot/cms-component-library/Grid';
|
|
201
199
|
import Button from '@hubspot/cms-component-library/Button';
|
|
202
|
-
import
|
|
200
|
+
import Text from '@hubspot/cms-component-library/Text';
|
|
203
201
|
|
|
204
202
|
<Grid templateColumns="repeat(2, 1fr)" gap="16px">
|
|
205
203
|
{/* GridItem rendering as Button */}
|
|
@@ -211,13 +209,9 @@ import Heading from '@hubspot/cms-component-library/Heading';
|
|
|
211
209
|
Click Me
|
|
212
210
|
</GridItem>
|
|
213
211
|
|
|
214
|
-
{/* GridItem rendering as
|
|
215
|
-
<GridItem
|
|
216
|
-
|
|
217
|
-
headingLevel="h2"
|
|
218
|
-
displayAs="h3"
|
|
219
|
-
>
|
|
220
|
-
Section Title
|
|
212
|
+
{/* GridItem rendering as Text (heading feature set) */}
|
|
213
|
+
<GridItem>
|
|
214
|
+
<Text fieldPath="sectionTitle" />
|
|
221
215
|
</GridItem>
|
|
222
216
|
|
|
223
217
|
{/* Regular content */}
|
|
@@ -255,6 +249,9 @@ import Heading from '@hubspot/cms-component-library/Heading';
|
|
|
255
249
|
### Complex Layout Using Props (NO custom CSS)
|
|
256
250
|
|
|
257
251
|
```tsx
|
|
252
|
+
import Grid, { GridItem } from '@hubspot/cms-component-library/Grid';
|
|
253
|
+
import Text from '@hubspot/cms-component-library/Text';
|
|
254
|
+
|
|
258
255
|
{/* Dashboard layout - all positioning via props */}
|
|
259
256
|
<Grid
|
|
260
257
|
templateColumns="1fr"
|
|
@@ -267,7 +264,7 @@ import Heading from '@hubspot/cms-component-library/Heading';
|
|
|
267
264
|
gridColumn="1"
|
|
268
265
|
gridColumnMd="1 / -1"
|
|
269
266
|
>
|
|
270
|
-
<
|
|
267
|
+
<Text fieldPath="dashboardTitle" />
|
|
271
268
|
</GridItem>
|
|
272
269
|
|
|
273
270
|
{/* Sidebar - hidden on mobile, shown on tablet+ */}
|
|
@@ -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;
|
|
@@ -83,7 +83,7 @@ When creating a new component in componentLibrary, ensure:
|
|
|
83
83
|
- [ ] Semantic HTML elements used appropriately
|
|
84
84
|
- [ ] ARIA roles added when needed (e.g., `role="separator"`)
|
|
85
85
|
- [ ] Icon purpose properly set (SEMANTIC vs DECORATIVE)
|
|
86
|
-
- [ ]
|
|
86
|
+
- [ ] Use `Text` with `textFeatureSet="heading"` for editable heading fields rather than raw HTML heading elements
|
|
87
87
|
|
|
88
88
|
## Reference Examples
|
|
89
89
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cms-component-library",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "HubSpot CMS React component library for building CMS modules",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@hubspot/cms-components": "1.2.
|
|
24
|
+
"@hubspot/cms-components": "1.2.19",
|
|
25
25
|
"sass-embedded": "^1.97.3"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|