@hubspot/cms-component-library 0.3.5 → 0.3.6

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.
@@ -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`, `Heading`) over raw HTML elements for consistent theming and styling
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
- - **Heading**: Commonly used for card titles
185
+ - **Text**: Use with `textFeatureSet="heading"` for editable card titles
@@ -10,7 +10,7 @@ const ContentFields = ({
10
10
  <FormField
11
11
  label={formIdLabel}
12
12
  name={formIdName}
13
- embedVersions={['v4']}
13
+ embedVersions={['v4', 'v3', 'v2']}
14
14
  default={formIdDefault}
15
15
  />
16
16
  );
@@ -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 = (props: FormProps) => {
8
- return <Island module={FormIsland} {...props} />;
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, InternalFormProps } from '../types.js';
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, env }: FormProps & InternalFormProps) => {
10
+ const FormIsland = ({ formId }: FormProps) => {
11
11
  const portalId = getHubID();
12
- const resolvedEnv = env === 'qa' ? env : 'prod';
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
- export type InternalFormProps = {
3
- env?: 'qa' | 'prod';
4
- };
5
- export type FormProps = {
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, Heading, custom components). GridItem handles grid placement while passing through all other props to the underlying component.
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, Heading, CustomComponent
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={Heading} headingLevel="h2">Title</GridItem>
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
- {/* Heading-specific props are passed through */}
162
- <GridItem as={Heading} headingLevel="h2" displayAs="h1">
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 Heading from '@hubspot/cms-component-library/Heading';
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 Heading */}
215
- <GridItem
216
- as={Heading}
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
- <Heading headingLevel="h1">Dashboard</Heading>
267
+ <Text fieldPath="dashboardTitle" />
271
268
  </GridItem>
272
269
 
273
270
  {/* Sidebar - hidden on mobile, shown on tablet+ */}
@@ -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
- - [ ] Heading level vs display separated for proper semantics
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.5",
3
+ "version": "0.3.6",
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.17",
24
+ "@hubspot/cms-components": "1.2.19",
25
25
  "sass-embedded": "^1.97.3"
26
26
  },
27
27
  "peerDependencies": {