@hubspot/cms-component-library 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/components/componentLibrary/Accordion/AccordionTitle/AccordionTitleBase.tsx +45 -0
  2. package/components/componentLibrary/Accordion/AccordionTitle/index.tsx +17 -30
  3. package/components/componentLibrary/Accordion/AccordionTitle/islands/AccordionTitleIsland.tsx +29 -0
  4. package/components/componentLibrary/Button/StyleFields.tsx +8 -8
  5. package/components/componentLibrary/Button/index.module.scss +24 -27
  6. package/components/componentLibrary/Button/index.tsx +4 -4
  7. package/components/componentLibrary/Button/llm.txt +51 -64
  8. package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +2 -2
  9. package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +2 -2
  10. package/components/componentLibrary/Button/stories/ButtonDecorator.module.scss +19 -23
  11. package/components/componentLibrary/Button/types.ts +2 -2
  12. package/components/componentLibrary/Card/StyleFields.tsx +9 -14
  13. package/components/componentLibrary/Card/index.module.scss +7 -7
  14. package/components/componentLibrary/Card/index.tsx +8 -13
  15. package/components/componentLibrary/Card/llm.txt +22 -43
  16. package/components/componentLibrary/Card/stories/Card.stories.tsx +28 -20
  17. package/components/componentLibrary/Card/stories/CardDecorator.module.scss +28 -5
  18. package/components/componentLibrary/Card/types.ts +8 -5
  19. package/components/componentLibrary/Form/StyleFields.tsx +19 -0
  20. package/components/componentLibrary/Form/index.tsx +7 -1
  21. package/components/componentLibrary/Form/islands/FormIsland.tsx +3 -1
  22. package/components/componentLibrary/Form/islands/LegacyFormIsland.tsx +2 -1
  23. package/components/componentLibrary/Form/islands/legacyForm.module.css +251 -0
  24. package/components/componentLibrary/Form/islands/v4Form.module.css +95 -0
  25. package/components/componentLibrary/Form/llm.txt +184 -0
  26. package/components/componentLibrary/Form/types.ts +6 -0
  27. package/components/componentLibrary/Link/ContentFields.tsx +2 -2
  28. package/components/componentLibrary/Link/types.ts +5 -4
  29. package/components/componentLibrary/Video/ContentFields.tsx +112 -0
  30. package/components/componentLibrary/Video/StyleFields.tsx +19 -0
  31. package/components/componentLibrary/Video/index.tsx +47 -0
  32. package/components/componentLibrary/Video/islands/HSVideoIsland.tsx +53 -0
  33. package/components/componentLibrary/Video/serverUtils.ts +41 -0
  34. package/components/componentLibrary/Video/types.ts +74 -0
  35. package/components/componentLibrary/_patterns/README.md +11 -7
  36. package/components/componentLibrary/_patterns/checklist-and-examples.md +8 -0
  37. package/components/componentLibrary/_patterns/component-structure.md +5 -1
  38. package/components/componentLibrary/_patterns/field-patterns.md +46 -0
  39. package/components/componentLibrary/_patterns/island-patterns.md +136 -0
  40. package/components/componentLibrary/utils/index.ts +1 -0
  41. package/package.json +4 -3
@@ -1,30 +1,25 @@
1
1
  import styles from './index.module.scss';
2
2
  import cx from '../utils/classname.js';
3
- import type { CSSVariables } from '../utils/types.js';
4
3
  import { CardProps } from './types.js';
5
4
  import StyleFields from './StyleFields.js';
6
5
 
7
6
  const CardComponent = ({
8
- variant = 'elevated',
7
+ variant = 'cardStyle1',
9
8
  as: Component = 'div',
10
- borderRadius = 8,
11
9
  className = '',
12
10
  style = {},
13
11
  children,
14
12
  ...rest
15
13
  }: CardProps) => {
16
- const variantClass = styles[`hscl-card-${variant}`]; // !todo: not used atm but keeping for when we need to add variant system.
17
-
18
- const defaultClasses = cx(styles.card, variantClass);
19
- const combinedClasses = cx(defaultClasses, className);
20
-
21
- const cssVariables: CSSVariables = {
22
- '--hscl-card-borderRadius': `${borderRadius}px`,
23
- ...style,
24
- };
14
+ const combinedClasses = cx(styles.card, className);
25
15
 
26
16
  return (
27
- <Component className={combinedClasses} style={cssVariables} {...rest}>
17
+ <Component
18
+ className={combinedClasses}
19
+ style={style}
20
+ data-card-variant={variant}
21
+ {...rest}
22
+ >
28
23
  {children}
29
24
  </Component>
30
25
  );
@@ -1,6 +1,6 @@
1
1
  # Card Component
2
2
 
3
- A flexible, polymorphic container component that provides a consistent visual structure for grouping related content with customizable variants and styling.
3
+ A flexible, polymorphic container component that provides a consistent visual structure for grouping related content with theme variant styling.
4
4
 
5
5
  ## Import path
6
6
  ```tsx
@@ -9,7 +9,7 @@ import Card from '@hubspot/cms-component-library/Card';
9
9
 
10
10
  ## Purpose
11
11
 
12
- The Card component provides a reusable container for grouping related content in HubSpot CMS projects. It solves the challenge of maintaining visual consistency across content sections while supporting different elevation styles (shadows, borders, or filled backgrounds). Use it when you need to visually separate and organize content into distinct sections with flexible styling.
12
+ The Card component provides a reusable container for grouping related content in HubSpot CMS projects. It uses the theme variant system (via `data-card-variant` attribute) to apply visual styles like background color, text color, border radius, and borders. Use it when you need to visually separate and organize content into distinct sections with theme-consistent styling.
13
13
 
14
14
  ## Component Structure
15
15
 
@@ -17,29 +17,28 @@ The Card component provides a reusable container for grouping related content in
17
17
  Card/
18
18
  ├── index.tsx # Main component with render logic
19
19
  ├── types.ts # TypeScript type definitions
20
- ├── StyleFields.tsx # HubSpot field definitions for styling
21
- ├── index.module.scss # CSS module with design tokens
20
+ ├── StyleFields.tsx # HubSpot field definitions for styling (VariantSelectionField)
21
+ ├── index.module.scss # CSS module consuming theme variant CSS variables
22
22
  └── stories/
23
23
  ├── Card.stories.tsx # Storybook examples
24
24
  ├── CardDecorator.tsx # Storybook decorator
25
- └── CardDecorator.module.css # Decorator styles
25
+ └── CardDecorator.module.scss # Decorator styles with variant variable definitions
26
26
  ```
27
27
 
28
28
  ## Components
29
29
 
30
30
  ### Card (Main Component)
31
31
 
32
- **Purpose:** Polymorphic container component that renders as a `<div>`, `<article>`, or `<section>` element with configurable visual variants.
32
+ **Purpose:** Polymorphic container component that renders as a `<div>`, `<article>`, or `<section>` element with theme variant styling applied via the `data-card-variant` attribute.
33
33
 
34
34
  **Props:**
35
35
  ```tsx
36
36
  {
37
- variant?: 'elevated' | 'outlined' | 'filled'; // Visual style variant (default: 'elevated')
37
+ variant?: 'cardStyle1' | 'cardStyle2' | 'cardStyle3' | 'cardStyle4'; // Theme variant (default: 'cardStyle1')
38
38
  as?: 'div' | 'article' | 'section'; // HTML element to render (default: 'div')
39
- borderRadius?: number; // Border radius in pixels (default: 8)
40
39
  children?: React.ReactNode; // Card content
41
40
  className?: string; // Additional CSS classes
42
- style?: React.CSSProperties; // Inline styles with CSS variables
41
+ style?: React.CSSProperties; // Inline styles
43
42
  }
44
43
  ```
45
44
 
@@ -59,7 +58,7 @@ import Card from '@hubspot/cms-component-library/Card';
59
58
  ### Semantic Article Card
60
59
 
61
60
  ```tsx
62
- <Card as="article" variant="filled">
61
+ <Card as="article" variant="cardStyle3">
63
62
  <h3>Blog Post Title</h3>
64
63
  <p>Article content goes here...</p>
65
64
  </Card>
@@ -84,25 +83,11 @@ const features = [
84
83
  </div>
85
84
  ```
86
85
 
87
- ### Card with Rich Content
88
-
89
- ```tsx
90
- <Card variant="outlined">
91
- <h3>Product Features</h3>
92
- <ul>
93
- <li>Real-time collaboration</li>
94
- <li>Advanced security</li>
95
- <li>Custom integrations</li>
96
- </ul>
97
- <button>Learn More</button>
98
- </Card>
99
- ```
100
-
101
86
  ## HubSpot CMS Integration
102
87
 
103
88
  ### Field Definitions
104
89
 
105
- The Card component provides field definitions for styling configuration in HubSpot CMS modules.
90
+ The Card component uses `VariantSelectionField` from `@hubspot/cms-components/fields` to allow theme variant selection in the CMS editor.
106
91
 
107
92
  #### StyleFields.tsx
108
93
 
@@ -110,14 +95,14 @@ Configurable props for customizing field labels, names, and defaults:
110
95
 
111
96
  ```tsx
112
97
  <Card.StyleFields
113
- variantLabel="Card variant"
114
- variantName="variant"
115
- variantDefault="elevated"
98
+ cardVariantLabel="Card variant"
99
+ cardVariantName="cardVariant"
100
+ cardVariantDefault={{ variant_name: 'cardStyle1' }}
116
101
  />
117
102
  ```
118
103
 
119
104
  **Fields:**
120
- - `variant`: ChoiceField for selecting visual style (elevated, outlined, filled)
105
+ - `cardVariant`: VariantSelectionField for selecting theme variant (variantDefinitionName: "card")
121
106
 
122
107
  ### Module Usage Example
123
108
 
@@ -127,7 +112,7 @@ import Card from '@hubspot/cms-component-library/Card';
127
112
  export default function FeatureCardModule({ fieldValues }) {
128
113
  return (
129
114
  <Card
130
- variant={fieldValues.variant}
115
+ variant={fieldValues.cardVariant.variant_name}
131
116
  as="article"
132
117
  >
133
118
  <h3>{fieldValues.title}</h3>
@@ -147,19 +132,13 @@ FeatureCardModule.fields = (
147
132
 
148
133
  ## Styling
149
134
 
150
- ### CSS Variables
151
-
152
- The Card component uses CSS variables for theming and customization:
135
+ ### Theme Variant CSS Variables (provided by the variant system via `data-card-variant`)
153
136
 
154
- **Base Styles:**
155
- - `--hscl-card-padding`: Internal padding (default: 24px)
156
- - `--hscl-card-borderRadius`: Border radius (default: 8px)
157
- - `--hscl-card-backgroundColor`: Background color (default: #ffffff)
158
- - `--hscl-card-boxShadow`: Box shadow (default: none)
159
- - `--hscl-card-border`: Border style (default: none)
137
+ - `--hs-card-borderRadius`: Border radius
138
+ - `--hs-card-backgroundColor`: Background color
139
+ - `--hs-card-color`: Text color
140
+ - `--hs-card-border`: Border style
160
141
 
161
- **Hover States:**
162
- - `--hscl-card-boxShadow-hover`: Hover box shadow (inherits from base if not set)
163
142
 
164
143
  ## Accessibility
165
144
 
@@ -167,13 +146,13 @@ The Card component follows accessibility best practices:
167
146
 
168
147
  - **Semantic HTML**: Use the `as` prop to render appropriate semantic elements (`article` for blog posts, `section` for page sections, `div` for generic containers)
169
148
  - **Content Structure**: Ensure proper heading hierarchy within card content (e.g., if cards are in an h2 section, use h3 for card titles)
170
- - **Color Contrast**: Default styles meet WCAG AA contrast requirements; verify custom colors maintain sufficient contrast
149
+ - **Color Contrast**: Theme variants should meet WCAG AA contrast requirements; verify custom colors maintain sufficient contrast
171
150
  - **Focus Management**: Cards themselves are not interactive, but ensure interactive elements within cards (buttons, links) have proper focus styles
172
151
 
173
152
  ## Best Practices
174
153
 
175
154
  - **Choose semantic elements**: Use `as="article"` for self-contained content, `as="section"` for thematic groupings, and `as="div"` (default) for generic containers
176
- - **Border radius via theme settings**: Border radius is controlled by card shape settings in theme settings/variants, not an element-level field. Use the `borderRadius` prop only for programmatic overrides.
155
+ - **Border radius via theme variants**: Border radius is controlled by the variant system, not a direct prop.
177
156
  - **Override CSS variables**: Customize appearance using CSS variables rather than overriding class styles
178
157
  - **Grid layouts**: Use CSS Grid or Flexbox for card layouts rather than relying on card margins
179
158
  - **Content flexibility**: Cards are content-agnostic containers; structure internal content using appropriate semantic elements
@@ -43,8 +43,7 @@ type Story = StoryObj<typeof meta>;
43
43
 
44
44
  export const Default: Story = {
45
45
  args: {
46
- variant: 'elevated',
47
- borderRadius: 8,
46
+ variant: 'cardStyle1',
48
47
  },
49
48
  render: args => (
50
49
  <Card {...args}>
@@ -55,34 +54,43 @@ export const Default: Story = {
55
54
  ),
56
55
  };
57
56
 
58
- export const BorderRadius: Story = {
59
- name: 'Border Radius Options',
57
+ export const Variants: Story = {
58
+ name: 'Card Variants',
60
59
  render: () => (
61
60
  <SBContainer flex direction="column" gap="large">
62
61
  <SBContainer addBackground>
63
- <h4>No Radius (0px)</h4>
64
- <Card borderRadius={0}>
65
- <CardHeading>Sharp Corners</CardHeading>
62
+ <h4>Card Style 1 (Light)</h4>
63
+ <Card variant="cardStyle1">
64
+ <CardHeading>Card Style 1</CardHeading>
66
65
  <CardDivider />
67
- <CardContent>This card has no border radius.</CardContent>
66
+ <CardContent>Light background with dark text.</CardContent>
68
67
  </Card>
69
68
  </SBContainer>
70
69
 
71
70
  <SBContainer addBackground>
72
- <h4>Default Radius (8px)</h4>
73
- <Card borderRadius={8}>
74
- <CardHeading>Default Corners</CardHeading>
71
+ <h4>Card Style 2 (Subtle)</h4>
72
+ <Card variant="cardStyle2">
73
+ <CardHeading>Card Style 2</CardHeading>
75
74
  <CardDivider />
76
- <CardContent>This card has the default border radius.</CardContent>
75
+ <CardContent>Subtle background with dark text.</CardContent>
77
76
  </Card>
78
77
  </SBContainer>
79
78
 
80
79
  <SBContainer addBackground>
81
- <h4>Large Radius (16px)</h4>
82
- <Card borderRadius={16}>
83
- <CardHeading>Large Corners</CardHeading>
80
+ <h4>Card Style 3 (Dark)</h4>
81
+ <Card variant="cardStyle3">
82
+ <CardHeading>Card Style 3</CardHeading>
84
83
  <CardDivider />
85
- <CardContent>This card has large rounded corners.</CardContent>
84
+ <CardContent>Dark background with light text.</CardContent>
85
+ </Card>
86
+ </SBContainer>
87
+
88
+ <SBContainer addBackground>
89
+ <h4>Card Style 4 (Darker)</h4>
90
+ <Card variant="cardStyle4">
91
+ <CardHeading>Card Style 4</CardHeading>
92
+ <CardDivider />
93
+ <CardContent>Darker background with light text.</CardContent>
86
94
  </Card>
87
95
  </SBContainer>
88
96
  </SBContainer>
@@ -141,7 +149,7 @@ export const ComplexContent: Story = {
141
149
  <SBContainer flex direction="column" gap="large">
142
150
  <SBContainer addBackground>
143
151
  <h4>Card with Rich Content</h4>
144
- <Card borderRadius={12}>
152
+ <Card>
145
153
  <CardHeading>Product Launch Announcement</CardHeading>
146
154
  <CardDivider />
147
155
  <div style={{ margin: 0 }}>
@@ -163,7 +171,7 @@ export const ComplexContent: Story = {
163
171
 
164
172
  <SBContainer addBackground>
165
173
  <h4>Card with Multiple Elements</h4>
166
- <Card borderRadius={8}>
174
+ <Card>
167
175
  <CardHeading>Multi-Section Card</CardHeading>
168
176
  <CardDivider />
169
177
  <div style={{ margin: 0 }}>
@@ -223,7 +231,7 @@ export const InteractionStates: Story = {
223
231
  Card wrapped in a link becomes clickable and shows hover/focus states
224
232
  </p>
225
233
  <a href="https://www.hubspot.com" style={{ textDecoration: 'none' }}>
226
- <Card borderRadius={8}>
234
+ <Card>
227
235
  <CardHeading>Clickable Feature Card</CardHeading>
228
236
  <CardDivider />
229
237
  <CardContent>
@@ -243,7 +251,7 @@ export const InteractionStates: Story = {
243
251
  </p>
244
252
  <SBFocusWrapper>
245
253
  <a href="https://www.hubspot.com" style={{ textDecoration: 'none' }}>
246
- <Card borderRadius={8}>
254
+ <Card>
247
255
  <CardHeading>Focused Card</CardHeading>
248
256
  <CardDivider />
249
257
  <CardContent>
@@ -1,7 +1,30 @@
1
1
  .cardContainer {
2
- /* Base container for stories */
3
- /* Default elevated variant always applied */
4
- --hscl-card-backgroundColor: #ffffff;
5
- --hscl-card-boxShadow: 0 2px 8px rgba(0, 0, 0, 0.1);
6
- --hscl-card-boxShadow-hover: 0 4px 16px rgba(0, 0, 0, 0.15);
2
+ /* card Variant Styles */
3
+ :global([data-card-variant='cardStyle1']) {
4
+ --hs-card-borderRadius: 10px;
5
+ --hs-card-backgroundColor: rgb(255, 255, 255);
6
+ --hs-card-color: rgb(28, 28, 31);
7
+ --hs-card-border: 1px solid #e0e0e0;
8
+ }
9
+
10
+ :global([data-card-variant='cardStyle2']) {
11
+ --hs-card-borderRadius: 10px;
12
+ --hs-card-backgroundColor: rgb(249, 249, 249);
13
+ --hs-card-color: rgb(28, 28, 31);
14
+ --hs-card-border: 1px solid #e0e0e0;
15
+ }
16
+
17
+ :global([data-card-variant='cardStyle3']) {
18
+ --hs-card-borderRadius: 10px;
19
+ --hs-card-backgroundColor: rgb(28, 28, 31);
20
+ --hs-card-color: rgb(255,255,255);
21
+ --hs-card-border: 1px solid #e0e0e0;
22
+ }
23
+
24
+ :global([data-card-variant='cardStyle4']) {
25
+ --hs-card-borderRadius: 10px;
26
+ --hs-card-backgroundColor: rgb(36, 36, 36);
27
+ --hs-card-color: rgb(255,255,255);
28
+ --hs-card-border: 1px solid #e0e0e0;
29
+ }
7
30
  }
@@ -1,18 +1,21 @@
1
- export type CardVariant = 'elevated' | 'outlined' | 'filled';
1
+ export type CardVariant =
2
+ | 'cardStyle1'
3
+ | 'cardStyle2'
4
+ | 'cardStyle3'
5
+ | 'cardStyle4';
2
6
 
3
7
  export type CardAs = 'div' | 'article' | 'section';
4
8
 
5
9
  export type CardProps = {
6
10
  variant?: CardVariant;
7
11
  as?: CardAs;
8
- borderRadius?: number;
9
12
  children?: React.ReactNode;
10
13
  className?: string;
11
14
  style?: React.CSSProperties;
12
15
  };
13
16
 
14
17
  export type StyleFieldsProps = {
15
- variantLabel?: string;
16
- variantName?: string;
17
- variantDefault?: CardVariant;
18
+ cardVariantLabel?: string;
19
+ cardVariantName?: string;
20
+ cardVariantDefault?: { variantName?: string, variant_name?: string };
18
21
  };
@@ -0,0 +1,19 @@
1
+ import { VariantSelectionField } from '@hubspot/cms-components/fields';
2
+ import { StyleFieldsProps } from './types.js';
3
+
4
+ const StyleFields = ({
5
+ formVariantLabel = 'Form variant',
6
+ formVariantName = 'formVariant',
7
+ formVariantDefault = { variant_name: 'primaryForm' },
8
+ }: StyleFieldsProps) => {
9
+ return (
10
+ <VariantSelectionField
11
+ label={formVariantLabel}
12
+ name={formVariantName}
13
+ variantDefinitionName="form"
14
+ default={formVariantDefault}
15
+ />
16
+ );
17
+ };
18
+
19
+ export default StyleFields;
@@ -5,6 +5,7 @@ import LegacyFormIsland from './islands/LegacyFormIsland.js?island';
5
5
  import { FormProps } from './types.js';
6
6
  import { Island } from '@hubspot/cms-components';
7
7
  import ContentFields from './ContentFields.js';
8
+ import StyleFields from './StyleFields.js';
8
9
 
9
10
  const FormComponent = ({
10
11
  formField,
@@ -13,16 +14,21 @@ const FormComponent = ({
13
14
  ...rest
14
15
  }: FormProps) => {
15
16
  const resolvedFormId = formField != null ? formField.form_id : formId;
16
- const FormModule = formVersion === 'v4' ? FormIsland : LegacyFormIsland;
17
+ const resolvedVersion =
18
+ formField != null ? formField.embed_version : formVersion;
19
+ const isV4 = resolvedVersion === 'v4';
20
+ const FormModule = isV4 ? FormIsland : LegacyFormIsland;
17
21
 
18
22
  return <Island module={FormModule} formId={resolvedFormId} {...rest} />;
19
23
  };
20
24
 
21
25
  type FormComponentType = typeof FormComponent & {
22
26
  ContentFields: typeof ContentFields;
27
+ StyleFields: typeof StyleFields;
23
28
  };
24
29
 
25
30
  const Form = FormComponent as FormComponentType;
26
31
  Form.ContentFields = ContentFields;
32
+ Form.StyleFields = StyleFields;
27
33
 
28
34
  export default Form;
@@ -1,6 +1,8 @@
1
1
  import { useEffect } from 'react';
2
2
  import { FormProps } from '../types.js';
3
3
  import { getHubID, getHSEnv } from '@hubspot/cms-components';
4
+ import cx from '../../utils/classname.js';
5
+ import styles from './v4Form.module.css';
4
6
 
5
7
  const getScriptSrc = (portalId: number, env: string) => {
6
8
  const host = env === 'qa' ? 'js.hsformsqa.net' : 'js.hsforms.net';
@@ -43,7 +45,7 @@ const FormIsland = ({ formId }: FormProps) => {
43
45
 
44
46
  return (
45
47
  <div
46
- className="hs-form-frame"
48
+ className={cx('hs-form-html', styles.v4FormStyles)}
47
49
  data-form-id={formId}
48
50
  data-portal-id={portalId}
49
51
  data-env={resolvedEnv}
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useId } from 'react';
2
2
  import { FormProps } from '../types.js';
3
3
  import { getHubID, getHSEnv } from '@hubspot/cms-components';
4
+ import styles from './legacyForm.module.css';
4
5
 
5
6
  declare global {
6
7
  interface Window {
@@ -71,7 +72,7 @@ const LegacyFormIsland = ({ formId }: FormProps) => {
71
72
  return null;
72
73
  }
73
74
 
74
- return <div id={containerId} />;
75
+ return <div id={containerId} className={styles.legacyFormStyles} />;
75
76
  };
76
77
 
77
78
  export default LegacyFormIsland;