@hubspot/cms-component-library 0.2.0 → 0.3.1

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 (24) hide show
  1. package/components/componentLibrary/Button/StyleFields.tsx +21 -1
  2. package/components/componentLibrary/Button/index.tsx +1 -1
  3. package/components/componentLibrary/Button/llm.txt +5 -1
  4. package/components/componentLibrary/Button/types.ts +6 -1
  5. package/components/componentLibrary/Flex/index.module.scss +4 -6
  6. package/components/componentLibrary/Flex/llm.txt +5 -7
  7. package/components/componentLibrary/Flex/types.ts +0 -4
  8. package/components/componentLibrary/Icon/index.module.scss +2 -2
  9. package/components/componentLibrary/Icon/index.tsx +3 -2
  10. package/components/componentLibrary/Icon/llm.txt +7 -6
  11. package/components/componentLibrary/Icon/stories/Icon.stories.tsx +11 -11
  12. package/components/componentLibrary/Icon/stories/IconDecorator.module.scss +1 -2
  13. package/components/componentLibrary/Icon/types.ts +1 -1
  14. package/components/componentLibrary/_patterns/css-patterns.md +7 -18
  15. package/package.json +3 -2
  16. package/components/componentLibrary/Heading/ContentFields.tsx +0 -37
  17. package/components/componentLibrary/Heading/StyleFields.tsx +0 -40
  18. package/components/componentLibrary/Heading/index.module.scss +0 -11
  19. package/components/componentLibrary/Heading/index.tsx +0 -52
  20. package/components/componentLibrary/Heading/llm.txt +0 -175
  21. package/components/componentLibrary/Heading/stories/Heading.stories.tsx +0 -88
  22. package/components/componentLibrary/Heading/stories/HeadingDecorator.module.scss +0 -47
  23. package/components/componentLibrary/Heading/stories/HeadingDecorator.tsx +0 -8
  24. package/components/componentLibrary/Heading/types.ts +0 -35
@@ -1,11 +1,21 @@
1
- import { ChoiceField } from '@hubspot/cms-components/fields';
1
+ import { ChoiceField, Visibility } from '@hubspot/cms-components/fields';
2
2
  import { StyleFieldsProps } from './types.js';
3
3
 
4
4
  const StyleFields = ({
5
5
  buttonVariantLabel = 'Button variant',
6
6
  buttonVariantName = 'buttonVariant',
7
7
  buttonVariantDefault = 'primary',
8
+ buttonIconPositionLabel = 'Icon position',
9
+ buttonIconPositionName = 'buttonIconPosition',
10
+ buttonIconPositionDefault = 'right',
8
11
  }: StyleFieldsProps) => {
12
+ // iconComponentShowIcon is the hardcoded id in @components/componentLibrary/Icon/ContentFields.tsx
13
+ const iconPositionVisibility: Visibility = {
14
+ operator: 'EQUAL',
15
+ controlling_field: 'iconComponentShowIcon',
16
+ controlling_value_regex: 'true',
17
+ };
18
+
9
19
  return (
10
20
  <>
11
21
  <ChoiceField
@@ -18,6 +28,16 @@ const StyleFields = ({
18
28
  ]}
19
29
  default={buttonVariantDefault}
20
30
  />
31
+ <ChoiceField
32
+ label={buttonIconPositionLabel}
33
+ name={buttonIconPositionName}
34
+ choices={[
35
+ ['left', 'Left'],
36
+ ['right', 'Right'],
37
+ ]}
38
+ default={buttonIconPositionDefault}
39
+ visibility={iconPositionVisibility}
40
+ />
21
41
  </>
22
42
  );
23
43
  };
@@ -39,7 +39,7 @@ const ButtonComponent = ({
39
39
  const icon = (
40
40
  <Icon
41
41
  fieldPath={iconFieldPath}
42
- height={iconSize}
42
+ size={iconSize}
43
43
  className={styles.icon}
44
44
  showIcon={showIcon}
45
45
  purpose={iconPurpose}
@@ -234,18 +234,22 @@ Configurable props for customizing field labels, names, and defaults:
234
234
 
235
235
  #### StyleFields.tsx
236
236
 
237
- Configurable props for variant selection:
237
+ Configurable props for variant and icon position:
238
238
 
239
239
  ```tsx
240
240
  <Button.StyleFields
241
241
  buttonVariantLabel="Button variant"
242
242
  buttonVariantName="buttonVariant"
243
243
  buttonVariantDefault="primary"
244
+ buttonIconPositionLabel="Icon position"
245
+ buttonIconPositionName="buttonIconPosition"
246
+ buttonIconPositionDefault="right"
244
247
  />
245
248
  ```
246
249
 
247
250
  **Fields:**
248
251
  - `buttonVariant`: ChoiceField for selecting visual style (primary, secondary, tertiary)
252
+ - `buttonIconPosition`: ChoiceField for selecting icon placement (left, right). This field has a hardcoded visibility rule — it is only shown when the icon toggle is enabled (`iconComponentShowIcon === true`), which corresponds to the `BooleanField` id hardcoded in `Icon.ContentFields`.
249
253
 
250
254
  ### Module Usage Example
251
255
 
@@ -4,6 +4,8 @@ import {
4
4
  } from '@hubspot/cms-components/fields';
5
5
  import { IconContentFieldsWithToggleProps } from '../Icon/types.js';
6
6
 
7
+ export type IconPositionHorizontal = 'left' | 'right';
8
+
7
9
  // Base props shared by all variants
8
10
  export type BaseButtonProps = {
9
11
  variant?: 'primary' | 'secondary' | 'tertiary'; // !todo: not used atm but keeping for when we need to add variant system.
@@ -11,7 +13,7 @@ export type BaseButtonProps = {
11
13
  style?: React.CSSProperties;
12
14
  children?: React.ReactNode;
13
15
  iconFieldPath?: string;
14
- iconPosition?: 'left' | 'right';
16
+ iconPosition?: IconPositionHorizontal;
15
17
  iconSize?: number;
16
18
  showIcon?: boolean;
17
19
  iconPurpose?: 'SEMANTIC' | 'DECORATIVE';
@@ -56,6 +58,9 @@ export type StyleFieldsProps = {
56
58
  buttonVariantLabel?: string;
57
59
  buttonVariantName?: string;
58
60
  buttonVariantDefault?: string;
61
+ buttonIconPositionLabel?: string;
62
+ buttonIconPositionName?: string;
63
+ buttonIconPositionDefault?: IconPositionHorizontal;
59
64
  };
60
65
 
61
66
  export type RenderWithIconProps = Pick<
@@ -9,12 +9,10 @@
9
9
  align-items: var(--hscl-flex-alignItems, stretch);
10
10
  align-content: var(--hscl-flex-alignContent, stretch);
11
11
  gap: var(--hscl-flex-gap, 0);
12
- padding: var(--hscl-flex-padding, 0);
13
- padding-inline: var(--hscl-flex-paddingInline);
14
- padding-block: var(--hscl-flex-paddingBlock);
15
- margin: var(--hscl-flex-margin, 0);
16
- margin-inline: var(--hscl-flex-marginInline);
17
- margin-block: var(--hscl-flex-marginBlock);
12
+ padding-inline: var(--hscl-flex-paddingInline, 0);
13
+ padding-block: var(--hscl-flex-paddingBlock, 0);
14
+ margin-inline: var(--hscl-flex-marginInline, 0);
15
+ margin-block: var(--hscl-flex-marginBlock, 0);
18
16
  max-width: var(--hscl-flex-maxWidth, 100%);
19
17
  max-height: var(--hscl-flex-maxHeight, auto);
20
18
  }
@@ -47,7 +47,7 @@ Flex/
47
47
  maxHeight?: string; // Maximum height constraint
48
48
  inline?: boolean; // Use inline-flex instead of flex (default: false)
49
49
  className?: string; // Additional CSS classes
50
- style?: React.CSSProperties; // Inline styles
50
+ style?: CSSVariables; // Inline styles and CSS variable overrides
51
51
  children?: React.ReactNode; // Child elements
52
52
  }
53
53
  ```
@@ -169,12 +169,10 @@ The Flex component uses CSS variables for all styling properties:
169
169
  - `--hscl-flex-alignItems`: Cross axis alignment (default: 'stretch')
170
170
  - `--hscl-flex-alignContent`: Multi-line alignment (default: 'stretch')
171
171
  - `--hscl-flex-gap`: Gap between items
172
- - `--hscl-flex-padding`: Padding (default: 0)
173
- - `--hscl-flex-paddingInline`: Horizontal padding
174
- - `--hscl-flex-paddingBlock`: Vertical padding
175
- - `--hscl-flex-margin`: Margin (default: 0)
176
- - `--hscl-flex-marginInline`: Horizontal margin
177
- - `--hscl-flex-marginBlock`: Vertical margin
172
+ - `--hscl-flex-paddingInline`: Horizontal padding (default: 0)
173
+ - `--hscl-flex-paddingBlock`: Vertical padding (default: 0)
174
+ - `--hscl-flex-marginInline`: Horizontal margin (default: 0)
175
+ - `--hscl-flex-marginBlock`: Vertical margin (default: 0)
178
176
  - `--hscl-flex-maxWidth`: Maximum width (default: 100%)
179
177
  - `--hscl-flex-maxHeight`: Maximum height (default: auto)
180
178
 
@@ -48,12 +48,8 @@ export type FlexProps = {
48
48
  alignItems?: FlexAlignItems;
49
49
  alignContent?: FlexAlignContent;
50
50
  gap?: string;
51
- rowGap?: string;
52
- columnGap?: string;
53
- padding?: string;
54
51
  paddingInline?: string;
55
52
  paddingBlock?: string;
56
- margin?: string;
57
53
  marginInline?: string;
58
54
  marginBlock?: string;
59
55
  inline?: boolean;
@@ -4,6 +4,6 @@
4
4
  justify-content: center;
5
5
  fill: var(--hscl-icon-fill);
6
6
  stroke: var(--hscl-icon-stroke);
7
- width: var(--hscl-icon-width); // !todo: not sure if we want this or not
8
- height: var(--hscl-icon-height); // !todo: not sure if we want this or not
7
+ width: var(--hscl-icon-size);
8
+ height: var(--hscl-icon-size);
9
9
  }
@@ -8,7 +8,7 @@ import { IconProps } from './types.js';
8
8
  const IconComponent = ({
9
9
  fieldPath,
10
10
  showIcon = true,
11
- height = 12,
11
+ size = 12,
12
12
  className = '',
13
13
  style = {},
14
14
  purpose = 'DECORATIVE',
@@ -21,13 +21,14 @@ const IconComponent = ({
21
21
  const combinedClasses = cx(defaultClasses, className);
22
22
 
23
23
  const cssVariables: CSSVariables = {
24
+ '--hscl-icon-size': `${size}px`,
24
25
  ...(fill && { '--hscl-icon-fill': fill }),
25
26
  };
26
27
 
27
28
  return (
28
29
  <CMSIcon
29
30
  fieldPath={fieldPath}
30
- height={height}
31
+ height={size}
31
32
  className={combinedClasses}
32
33
  style={{ ...cssVariables, ...style }}
33
34
  purpose={purpose}
@@ -35,7 +35,7 @@ Icon/
35
35
  ```tsx
36
36
  {
37
37
  fieldPath?: string; // Path to icon in HubSpot fields
38
- height?: number; // Icon height in pixels (default: 12)
38
+ size?: number; // Icon size in pixels, sets both width and height (default: 12)
39
39
  fill?: string; // Icon fill color (overrides CSS variable)
40
40
  className?: string; // Additional CSS classes
41
41
  style?: React.CSSProperties; // Inline styles (including CSS variables)
@@ -54,7 +54,7 @@ import Icon from '@hubspot/cms-component-library/Icon';
54
54
 
55
55
  <Icon
56
56
  fieldPath="icon"
57
- height={24}
57
+ size={24}
58
58
  />
59
59
  ```
60
60
 
@@ -63,7 +63,7 @@ import Icon from '@hubspot/cms-component-library/Icon';
63
63
  ```tsx
64
64
  <Icon
65
65
  fieldPath="icon"
66
- height={24}
66
+ size={24}
67
67
  purpose="SEMANTIC"
68
68
  title="Download file"
69
69
  />
@@ -74,7 +74,7 @@ import Icon from '@hubspot/cms-component-library/Icon';
74
74
  ```tsx
75
75
  <Icon
76
76
  fieldPath="icon"
77
- height={32}
77
+ size={32}
78
78
  fill="#FF7A59"
79
79
  />
80
80
  ```
@@ -84,7 +84,7 @@ import Icon from '@hubspot/cms-component-library/Icon';
84
84
  ```tsx
85
85
  <Icon
86
86
  fieldPath="icon"
87
- height={20}
87
+ size={20}
88
88
  showIcon={shouldShowIcon}
89
89
  />
90
90
  ```
@@ -128,7 +128,7 @@ export default function IconModule({ fieldValues }) {
128
128
  return (
129
129
  <Icon
130
130
  fieldPath="icon"
131
- height={fieldValues.iconSize || 24}
131
+ size={fieldValues.iconSize || 24}
132
132
  showIcon={fieldValues.showIcon}
133
133
  purpose="DECORATIVE"
134
134
  />
@@ -159,6 +159,7 @@ The Icon component uses CSS variables for theming and customization:
159
159
 
160
160
  **Base Styles:**
161
161
  - `--hscl-icon-fill`: Icon fill color (default: currentColor)
162
+ - `--hscl-icon-size`: Icon width and height (set automatically from the `size` prop)
162
163
 
163
164
 
164
165
  ## Accessibility
@@ -25,7 +25,7 @@ type Story = StoryObj<typeof meta>;
25
25
  export const Default: Story = {
26
26
  args: {
27
27
  fieldPath: 'icon',
28
- height: 24,
28
+ size: 24,
29
29
  showIcon: true,
30
30
  purpose: 'DECORATIVE',
31
31
  },
@@ -37,27 +37,27 @@ export const IconSizes: Story = {
37
37
  <SBContainer flex direction="row" gap="large" alignItems="center">
38
38
  <SBContainer addBackground flex alignItems="center">
39
39
  <h4>12px (default)</h4>
40
- <Icon fieldPath="icon" height={12} />
40
+ <Icon fieldPath="icon" size={12} />
41
41
  </SBContainer>
42
42
 
43
43
  <SBContainer addBackground flex alignItems="center">
44
44
  <h4>16px</h4>
45
- <Icon fieldPath="icon" height={16} />
45
+ <Icon fieldPath="icon" size={16} />
46
46
  </SBContainer>
47
47
 
48
48
  <SBContainer addBackground flex alignItems="center">
49
49
  <h4>24px</h4>
50
- <Icon fieldPath="icon" height={24} />
50
+ <Icon fieldPath="icon" size={24} />
51
51
  </SBContainer>
52
52
 
53
53
  <SBContainer addBackground flex alignItems="center">
54
54
  <h4>32px</h4>
55
- <Icon fieldPath="icon" height={32} />
55
+ <Icon fieldPath="icon" size={32} />
56
56
  </SBContainer>
57
57
 
58
58
  <SBContainer addBackground flex alignItems="center">
59
59
  <h4>48px</h4>
60
- <Icon fieldPath="icon" height={48} />
60
+ <Icon fieldPath="icon" size={48} />
61
61
  </SBContainer>
62
62
  </SBContainer>
63
63
  ),
@@ -69,34 +69,34 @@ export const Colors: Story = {
69
69
  <SBContainer flex direction="row" gap="large" alignItems="center">
70
70
  <SBContainer addBackground flex alignItems="center">
71
71
  <h4>Default (currentColor)</h4>
72
- <Icon fieldPath="icon" height={32} />
72
+ <Icon fieldPath="icon" size={32} />
73
73
  </SBContainer>
74
74
 
75
75
  <SBContainer addBackground flex alignItems="center">
76
76
  <h4>Blue Fill</h4>
77
77
  <div className={styles.blueFill}>
78
- <Icon fieldPath="icon" height={32} />
78
+ <Icon fieldPath="icon" size={32} />
79
79
  </div>
80
80
  </SBContainer>
81
81
 
82
82
  <SBContainer addBackground flex alignItems="center">
83
83
  <h4>Red Fill</h4>
84
84
  <div className={styles.redFill}>
85
- <Icon fieldPath="icon" height={32} />
85
+ <Icon fieldPath="icon" size={32} />
86
86
  </div>
87
87
  </SBContainer>
88
88
 
89
89
  <SBContainer addBackground flex alignItems="center">
90
90
  <h4>Green Fill</h4>
91
91
  <div className={styles.greenFill}>
92
- <Icon fieldPath="icon" height={32} />
92
+ <Icon fieldPath="icon" size={32} />
93
93
  </div>
94
94
  </SBContainer>
95
95
 
96
96
  <SBContainer addBackground flex alignItems="center">
97
97
  <h4>Custom Stroke</h4>
98
98
  <div className={styles.customStroke}>
99
- <Icon fieldPath="icon" height={32} />
99
+ <Icon fieldPath="icon" size={32} />
100
100
  </div>
101
101
  </SBContainer>
102
102
  </SBContainer>
@@ -2,8 +2,7 @@
2
2
  padding: 20px;
3
3
  --hscl-icon-fill: currentColor;
4
4
  --hscl-icon-stroke: none;
5
- --hscl-icon-width: auto;
6
- --hscl-icon-height: auto;
5
+ --hscl-icon-size: auto;
7
6
  }
8
7
 
9
8
  .blueFill {
@@ -7,7 +7,7 @@ export type IconPurpose = 'SEMANTIC' | 'DECORATIVE';
7
7
 
8
8
  export type IconProps = {
9
9
  fieldPath?: string;
10
- height?: number;
10
+ size?: number;
11
11
  fill?: string;
12
12
  className?: string;
13
13
  style?: React.CSSProperties;
@@ -7,7 +7,7 @@ This document outlines CSS variable naming conventions, styling patterns, and CS
7
7
  **Pattern:** `--hscl-componentName-[elementName]-cssProperty-[state]`
8
8
 
9
9
  - **Prefix:** Always start with `--hscl-` (HubSpot Component Library)
10
- - **Component name:** Lowercase component name in camelCase, with name taken from the index.tsx file (e.g., `button`, `heading`, `icon`, `divider`, `image`, `accordion`)
10
+ - **Component name:** Lowercase component name in camelCase, with name taken from the index.tsx file (e.g., `button`, `icon`, `divider`, `image`, `accordion`, `card`)
11
11
  - **Element name (optional):** For sub-elements within a component, specify the element name in camelCase (e.g., `icon`, `body`, `title`, `header`, `overlay`, `submenu`)
12
12
  - Omit for properties that apply to the main component element itself
13
13
  - Include for properties that apply to specific sub-elements
@@ -19,8 +19,6 @@ This document outlines CSS variable naming conventions, styling patterns, and CS
19
19
  --hscl-button-backgroundColor
20
20
  --hscl-button-backgroundColor-hover
21
21
  --hscl-button-backgroundColor-focus
22
- --hscl-heading-fontSize
23
- --hscl-heading-textAlign
24
22
  --hscl-divider-borderColor
25
23
  --hscl-icon-fill
26
24
  --hscl-card-padding
@@ -47,12 +45,12 @@ This document outlines CSS variable naming conventions, styling patterns, and CS
47
45
 
48
46
  **Nested/Dynamic Variables:**
49
47
  ```typescript
50
- // Example from Heading component - uses template variables
51
- '--hscl-heading-font': `var(--hscl-heading-${displayAsValue}-font)`
52
- '--hscl-heading-fontSize': `var(--hscl-heading-${displayAsValue}-fontSize)`
48
+ // Components can use template variables to dynamically reference nested variables
49
+ '--hscl-component-font': `var(--hscl-component-${variantValue}-font)`
50
+ '--hscl-component-fontSize': `var(--hscl-component-${variantValue}-fontSize)`
53
51
  ```
54
52
 
55
- **Note:** When using dynamic variants (like h1, h2, display1), use numbers without underscores (e.g., `display1` not `display_1`).
53
+ **Note:** When using dynamic variants, use numbers without underscores (e.g., `variant1` not `variant_1`).
56
54
 
57
55
  ## CSS Variables Application
58
56
 
@@ -105,15 +103,6 @@ const elementStyle: CSSVariables = {
105
103
  ```typescript
106
104
  import type { CSSVariables } from '../utils/types.js';
107
105
 
108
- // Heading - dynamic variable references
109
- const cssVariables: CSSVariables = {
110
- '--hscl-heading-font': `var(--hscl-heading-${displayAsValue}-font)`,
111
- '--hscl-heading-fontSize': `var(--hscl-heading-${displayAsValue}-fontSize)`,
112
- ...(alignment && {
113
- '--hscl-heading-textAlign': alignment.toLowerCase(),
114
- }),
115
- };
116
-
117
106
  // Divider - direct values with units
118
107
  const cssVariables: CSSVariables = {
119
108
  '--hscl-divider-alignment': getAlignmentCSSVar(alignment),
@@ -235,8 +224,8 @@ Variables can reference other variables for dynamic behavior:
235
224
 
236
225
  ```typescript
237
226
  const cssVariables = {
238
- '--hscl-heading-font': `var(--hscl-heading-${displayAsValue}-font)`,
239
- '--hscl-heading-fontSize': `var(--hscl-heading-${displayAsValue}-fontSize)`,
227
+ '--hscl-component-font': `var(--hscl-component-${variantValue}-font)`,
228
+ '--hscl-component-fontSize': `var(--hscl-component-${variantValue}-fontSize)`,
240
229
  };
241
230
  ```
242
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cms-component-library",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "HubSpot CMS React component library for building CMS modules",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
@@ -16,7 +16,8 @@
16
16
  "url": "git@git.hubteam.com:HubSpot/cms-component-library.git"
17
17
  },
18
18
  "publishConfig": {
19
- "access": "public"
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org"
20
21
  },
21
22
  "type": "module",
22
23
  "dependencies": {
@@ -1,37 +0,0 @@
1
- import { ChoiceField, TextField } from '@hubspot/cms-components/fields';
2
- import type { ContentFieldsProps } from './types.js';
3
-
4
- const ContentFields = ({
5
- headingTextLabel = 'Heading text',
6
- headingTextName = 'headingText',
7
- headingTextDefault = 'Heading Text',
8
- headingLevelLabel = 'Heading level',
9
- headingLevelName = 'headingLevel',
10
- headingLevelDefault = 'h1',
11
- }: ContentFieldsProps) => {
12
- return (
13
- <>
14
- <TextField
15
- label={headingTextLabel}
16
- name={headingTextName}
17
- default={headingTextDefault}
18
- />
19
- <ChoiceField
20
- label={headingLevelLabel}
21
- name={headingLevelName}
22
- choices={[
23
- ['h1', 'h1'],
24
- ['h2', 'h2'],
25
- ['h3', 'h3'],
26
- ['h4', 'h4'],
27
- ['h5', 'h5'],
28
- ['h6', 'h6'],
29
- ]}
30
- required={true}
31
- default={headingLevelDefault}
32
- />
33
- </>
34
- );
35
- };
36
-
37
- export default ContentFields;
@@ -1,40 +0,0 @@
1
- import { AlignmentField, ChoiceField } from '@hubspot/cms-components/fields';
2
- import type { StyleFieldsProps } from './types.js';
3
-
4
- const StyleFields = ({
5
- textAlignLabel = 'Heading alignment',
6
- textAlignName = 'headingTextAlign',
7
- textAlignDefault = 'LEFT',
8
- displayAsLabel = 'Display as',
9
- displayAsName = 'headingDisplayAs',
10
- displayAsDefault = 'h1',
11
- }: StyleFieldsProps) => {
12
- return (
13
- <>
14
- <AlignmentField
15
- label={textAlignLabel}
16
- name={textAlignName}
17
- alignmentDirection="HORIZONTAL"
18
- default={{ horizontal_align: textAlignDefault }}
19
- />
20
- <ChoiceField
21
- label={displayAsLabel}
22
- name={displayAsName}
23
- choices={[
24
- ['h1', 'h1'],
25
- ['h2', 'h2'],
26
- ['h3', 'h3'],
27
- ['h4', 'h4'],
28
- ['h5', 'h5'],
29
- ['h6', 'h6'],
30
- ['display_1', 'Display 1'],
31
- ['display_2', 'Display 2'],
32
- ]}
33
- required={true}
34
- default={displayAsDefault}
35
- />
36
- </>
37
- );
38
- };
39
-
40
- export default StyleFields;
@@ -1,11 +0,0 @@
1
- .heading {
2
- font-family: var(--hscl-heading-font);
3
- font-size: var(--hscl-heading-fontSize);
4
- font-style: var(--hscl-heading-fontStyle);
5
- font-weight: var(--hscl-heading-fontWeight);
6
- line-height: var(--hscl-heading-lineHeight);
7
- margin-block: var(--hscl-heading-margin);
8
- color: var(--hscl-heading-color);
9
- text-align: var(--hscl-heading-textAlign);
10
- }
11
-
@@ -1,52 +0,0 @@
1
- import styles from './index.module.scss';
2
- import ContentFields from './ContentFields.js';
3
- import StyleFields from './StyleFields.js';
4
- import cx from '../utils/classname.js';
5
- import type { CSSVariables } from '../utils/types.js';
6
- import { HeadingProps } from './types.js';
7
-
8
- const HeadingComponent = ({
9
- headingLevel,
10
- displayAs,
11
- textAlign,
12
- className = '',
13
- style = {},
14
- children = 'A clear and bold heading',
15
- ...rest
16
- }: HeadingProps) => {
17
- const HeadingLevel = headingLevel;
18
- const displayAsValue = displayAs || headingLevel;
19
-
20
- const cssVariables: CSSVariables = {
21
- '--hscl-heading-font': `var(--hscl-heading-${displayAsValue}-font)`,
22
- '--hscl-heading-fontSize': `var(--hscl-heading-${displayAsValue}-fontSize)`,
23
- '--hscl-heading-fontStyle': `var(--hscl-heading-${displayAsValue}-fontStyle)`,
24
- '--hscl-heading-fontWeight': `var(--hscl-heading-${displayAsValue}-fontWeight)`,
25
- ...(textAlign && {
26
- '--hscl-heading-textAlign': textAlign.toLowerCase(),
27
- }),
28
- };
29
-
30
- const defaultClasses = cx(styles.heading, className);
31
-
32
- return (
33
- <HeadingLevel
34
- className={defaultClasses}
35
- style={{ ...cssVariables, ...style }}
36
- {...rest}
37
- >
38
- {children}
39
- </HeadingLevel>
40
- );
41
- };
42
-
43
- type HeadingComponentType = typeof HeadingComponent & {
44
- ContentFields: typeof ContentFields;
45
- StyleFields: typeof StyleFields;
46
- };
47
-
48
- const Heading = HeadingComponent as HeadingComponentType;
49
- Heading.ContentFields = ContentFields;
50
- Heading.StyleFields = StyleFields;
51
-
52
- export default Heading;
@@ -1,175 +0,0 @@
1
- # Heading Component
2
-
3
- A semantic heading component that renders HTML heading elements (h1-h6) with flexible visual styling and alignment controls, allowing the semantic level to differ from the visual appearance.
4
-
5
- ## Import path
6
- ```tsx
7
- import Heading from '@hubspot/cms-component-library/Heading';
8
- ```
9
-
10
- ## Purpose
11
-
12
- The Heading component provides proper semantic structure while maintaining visual design flexibility. It solves the common challenge where semantic HTML hierarchy (h1-h6) needs to differ from visual styling - for example, an h3 element styled to look like an h1. This separation of semantic meaning from visual presentation ensures both accessibility and design consistency.
13
-
14
- ## Component Structure
15
-
16
- ```
17
- Heading/
18
- ├── index.tsx # Main component with render logic
19
- ├── types.ts # TypeScript type definitions
20
- ├── ContentFields.tsx # HubSpot field definitions for content
21
- ├── StyleFields.tsx # HubSpot field definitions for styling
22
- ├── index.module.scss # CSS module with design tokens
23
- └── stories/
24
- ├── Heading.stories.tsx # Component usage examples
25
- ├── HeadingDecorator.tsx # Storybook decorator
26
- └── HeadingDecorator.module.css # Decorator styles
27
- ```
28
-
29
- ## Props
30
-
31
- ```tsx
32
- {
33
- headingLevel: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; // Semantic HTML level (required)
34
- displayAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'display_1' | 'display_2'; // Visual style (defaults to headingLevel)
35
- textAlign?: 'LEFT' | 'CENTER' | 'RIGHT'; // Text alignment
36
- className?: string; // Additional CSS classes
37
- style?: React.CSSProperties; // Inline styles (including CSS variables)
38
- children?: React.ReactNode; // Heading text content
39
- }
40
- ```
41
-
42
- ## Usage Examples
43
-
44
- ### Basic Heading
45
-
46
- ```tsx
47
- import Heading from '@hubspot/cms-component-library/Heading';
48
-
49
- <Heading headingLevel="h1">
50
- Welcome to Our Site
51
- </Heading>
52
- ```
53
-
54
- ### Semantic vs Visual Styling
55
-
56
- When you need a lower-level heading to look like a higher-level one:
57
-
58
- ```tsx
59
- <Heading
60
- headingLevel="h3" // Semantic: third-level heading
61
- displayAs="h1" // Visual: styled like an h1
62
- >
63
- Section Title
64
- </Heading>
65
- ```
66
-
67
- ### With Text Alignment
68
-
69
- ```tsx
70
- <Heading
71
- headingLevel="h2"
72
- textAlign="CENTER"
73
- >
74
- Centered Heading
75
- </Heading>
76
- ```
77
-
78
-
79
- ## HubSpot CMS Integration
80
-
81
- ### Field Definitions
82
-
83
- #### ContentFields.tsx
84
-
85
- ```tsx
86
- <Heading.ContentFields
87
- headingTextLabel="Heading text"
88
- headingTextName="headingText"
89
- headingTextDefault="Heading Text"
90
- headingLevelLabel="Heading Level"
91
- headingLevelName="headingLevel"
92
- headingLevelDefault="h1"
93
- />
94
- ```
95
-
96
- **Fields:**
97
- - `headingText`: TextField for heading content
98
- - `headingLevel`: ChoiceField for semantic level (h1-h6)
99
-
100
- #### StyleFields.tsx
101
-
102
- ```tsx
103
- <Heading.StyleFields
104
- textAlignLabel="Heading text align"
105
- textAlignName="headingTextAlign"
106
- textAlignDefault="LEFT"
107
- displayAsLabel="Display as"
108
- displayAsName="headingDisplayAs"
109
- displayAsDefault="h1"
110
- />
111
- ```
112
-
113
- **Fields:**
114
- - `headingTextAlign`: AlignmentField for text alignment
115
- - `headingDisplayAs`: ChoiceField for visual style (h1-h6, display_1, display_2)
116
-
117
- ### Module Usage Example
118
-
119
- ```tsx
120
- import Heading from '@hubspot/cms-component-library/Heading';
121
-
122
- export default function HeroModule({ fieldValues }) {
123
- return (
124
- <Heading
125
- headingLevel={fieldValues.headingLevel}
126
- displayAs={fieldValues.headingDisplayAs}
127
- textAlign={fieldValues.headingTextAlign?.horizontal_align}
128
- >
129
- {fieldValues.headingText}
130
- </Heading>
131
- );
132
- }
133
- ```
134
-
135
- ## Styling
136
-
137
- ### CSS Variables
138
-
139
- The Heading component uses CSS variables for theming and customization:
140
-
141
- **Base Styles:**
142
- - `--hscl-heading-font`: Font family
143
- - `--hscl-heading-fontSize`: Font size (set by displayAs level)
144
- - `--hscl-heading-fontStyle`: Font style (set by displayAs level)
145
- - `--hscl-heading-fontWeight`: Font weight (set by displayAs level)
146
- - `--hscl-heading-lineHeight`: Line height
147
- - `--hscl-heading-margin`: Vertical margin (margin-block)
148
- - `--hscl-heading-color`: Text color
149
- - `--hscl-heading-textAlign`: Text alignment
150
-
151
- **Level-specific Variables:**
152
- Each heading level has its own design tokens:
153
- - `--hscl-heading-h1-font`, `--hscl-heading-h1-fontSize`, etc.
154
- - `--hscl-heading-h2-font`, `--hscl-heading-h2-fontSize`, etc.
155
- - Through `--hscl-heading-h6-font`, `--hscl-heading-h6-fontSize`, etc.
156
- - `--hscl-heading-display_1-font`, `--hscl-heading-display_1-fontSize`, etc.
157
- - `--hscl-heading-display_2-font`, `--hscl-heading-display_2-fontSize`, etc.
158
-
159
- ## Accessibility
160
-
161
- The Heading component follows accessibility best practices:
162
-
163
- - **Semantic HTML**: Always renders the appropriate heading element (h1-h6) based on `headingLevel` for proper document structure
164
- - **Visual Hierarchy Independence**: The `displayAs` prop allows visual styling to differ from semantic structure, ensuring flexibility without breaking accessibility
165
- - **Proper Nesting**: Ensure headings follow logical order in your document (h1 → h2 → h3, etc.) regardless of visual styling
166
- - **Screen Readers**: Screen readers announce the semantic level (`headingLevel`), not the visual style
167
- - **Keyboard Navigation**: Native heading element support for keyboard navigation and focus management
168
-
169
- ## Best Practices
170
-
171
- - **Follow semantic order**: Use headings in logical order (h1 for main title, h2 for sections, h3 for subsections, etc.)
172
- - **One h1 per page**: Each page should have exactly one h1 element representing the main topic
173
- - **Use displayAs for visual flexibility**: When you need a different visual style than the semantic level requires, use the `displayAs` prop
174
- - **Don't skip levels**: Don't jump from h2 to h4; maintain proper hierarchy
175
-
@@ -1,88 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import Heading from '../index.js';
3
- import { HeadingProps } from '../types.js';
4
- import { withHeadingStyles } from './HeadingDecorator.js';
5
- import { SBContainer } from '@sb-utils/SBContainer.js';
6
-
7
- const meta: Meta<HeadingProps> = {
8
- title: 'Component Library/Heading',
9
- component: Heading,
10
- parameters: {
11
- layout: 'centered',
12
- docs: {
13
- description: {
14
- component: `The Heading component provides a flexible and accessible way to render semantic HTML headings (h1-h6) with customizable visual styles through the displayAs prop.`,
15
- },
16
- },
17
- },
18
- decorators: [withHeadingStyles],
19
- };
20
-
21
- export default meta;
22
- type Story = StoryObj<typeof meta>;
23
-
24
- export const Default: Story = {
25
- args: {
26
- headingLevel: 'h2',
27
- children: 'A clear and bold heading',
28
- },
29
- };
30
-
31
- export const AllHeadingLevels: Story = {
32
- name: 'All Heading Levels',
33
- render: () => (
34
- <SBContainer flex direction="column" gap="large">
35
- <SBContainer addBackground>
36
- <p>
37
- Each heading level has different semantic meaning and default styling
38
- </p>
39
- <Heading headingLevel="h1">Heading Level 1</Heading>
40
- <Heading headingLevel="h2">Heading Level 2</Heading>
41
- <Heading headingLevel="h3">Heading Level 3</Heading>
42
- <Heading headingLevel="h4">Heading Level 4</Heading>
43
- <Heading headingLevel="h5">Heading Level 5</Heading>
44
- <Heading headingLevel="h6">Heading Level 6</Heading>
45
- </SBContainer>
46
- </SBContainer>
47
- ),
48
- };
49
-
50
- export const TextAlignment: Story = {
51
- name: 'Text Alignment',
52
- render: () => (
53
- <SBContainer flex direction="column" gap="large" minWidth="500px">
54
- <SBContainer addBackground>
55
- <Heading headingLevel="h2" textAlign="LEFT">
56
- Left Aligned
57
- </Heading>
58
- </SBContainer>
59
- <SBContainer addBackground>
60
- <Heading headingLevel="h2" textAlign="CENTER">
61
- Center Aligned
62
- </Heading>
63
- </SBContainer>
64
- <SBContainer addBackground>
65
- <Heading headingLevel="h2" textAlign="RIGHT">
66
- Right Aligned
67
- </Heading>
68
- </SBContainer>
69
- </SBContainer>
70
- ),
71
- };
72
-
73
- export const LongText: Story = {
74
- name: 'Long Text',
75
- render: () => (
76
- <SBContainer flex direction="column" gap="large" maxWidth="500px">
77
- <SBContainer addBackground>
78
- <h4>Long Heading Text</h4>
79
- <p>Headings should handle long text gracefully</p>
80
- <Heading headingLevel="h2">
81
- This is a very long heading that demonstrates how the component
82
- handles extensive text content that may wrap across multiple lines in
83
- the layout
84
- </Heading>
85
- </SBContainer>
86
- </SBContainer>
87
- ),
88
- };
@@ -1,47 +0,0 @@
1
- .decoratorContainer {
2
- padding: 20px;
3
- --hscl-heading-h1-font: Arial, sans-serif;
4
- --hscl-heading-h1-fontSize: 48px;
5
- --hscl-heading-h1-fontStyle: normal;
6
- --hscl-heading-h1-fontWeight: 700;
7
- --hscl-heading-h2-font: Arial, sans-serif;
8
- --hscl-heading-h2-fontSize: 36px;
9
- --hscl-heading-h2-fontStyle: normal;
10
- --hscl-heading-h2-fontWeight: 600;
11
- --hscl-heading-h3-font: Arial, sans-serif;
12
- --hscl-heading-h3-fontSize: 28px;
13
- --hscl-heading-h3-fontStyle: normal;
14
- --hscl-heading-h3-fontWeight: 600;
15
- --hscl-heading-h4-font: Arial, sans-serif;
16
- --hscl-heading-h4-fontSize: 24px;
17
- --hscl-heading-h4-fontStyle: normal;
18
- --hscl-heading-h4-fontWeight: 500;
19
- --hscl-heading-h5-font: Arial, sans-serif;
20
- --hscl-heading-h5-fontSize: 20px;
21
- --hscl-heading-h5-fontStyle: normal;
22
- --hscl-heading-h5-fontWeight: 500;
23
- --hscl-heading-h6-font: Arial, sans-serif;
24
- --hscl-heading-h6-fontSize: 16px;
25
- --hscl-heading-h6-fontStyle: normal;
26
- --hscl-heading-h6-fontWeight: 500;
27
- --hscl-heading-display_1-font: Arial, sans-serif;
28
- --hscl-heading-display_1-fontSize: 72px;
29
- --hscl-heading-display_1-fontStyle: normal;
30
- --hscl-heading-display_1-fontWeight: 800;
31
- --hscl-heading-display_2-font: Arial, sans-serif;
32
- --hscl-heading-display_2-fontSize: 60px;
33
- --hscl-heading-display_2-fontStyle: normal;
34
- --hscl-heading-display_2-fontWeight: 800;
35
- --hscl-heading-lineHeight: 1.2;
36
- --hscl-heading-margin: 0;
37
- --hscl-heading-color: #2d3e50;
38
- }
39
-
40
- .decoratorContainer h1,
41
- .decoratorContainer h2,
42
- .decoratorContainer h3,
43
- .decoratorContainer h4,
44
- .decoratorContainer h5,
45
- .decoratorContainer h6 {
46
- margin-bottom: 1rem;
47
- }
@@ -1,8 +0,0 @@
1
- import type { Decorator } from '@storybook/react';
2
- import styles from './HeadingDecorator.module.scss';
3
-
4
- export const withHeadingStyles: Decorator = Story => (
5
- <div className={styles.decoratorContainer}>
6
- <Story />
7
- </div>
8
- );
@@ -1,35 +0,0 @@
1
- import {
2
- AlignmentFieldDefaults,
3
- TextFieldDefaults,
4
- } from '@hubspot/cms-components/fields';
5
-
6
- export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
7
-
8
- export type DisplayAs = HeadingLevel | 'display_1' | 'display_2';
9
-
10
- export type HeadingProps = {
11
- headingLevel: HeadingLevel;
12
- displayAs?: DisplayAs;
13
- textAlign?: (typeof AlignmentFieldDefaults)['horizontal_align'];
14
- className?: string;
15
- style?: React.CSSProperties;
16
- children?: React.ReactNode;
17
- };
18
-
19
- export type ContentFieldsProps = {
20
- headingTextLabel?: string;
21
- headingTextName?: string;
22
- headingTextDefault?: typeof TextFieldDefaults;
23
- headingLevelLabel?: string;
24
- headingLevelName?: string;
25
- headingLevelDefault?: HeadingLevel;
26
- };
27
-
28
- export type StyleFieldsProps = {
29
- textAlignLabel?: string;
30
- textAlignName?: string;
31
- textAlignDefault?: (typeof AlignmentFieldDefaults)['horizontal_align'];
32
- displayAsLabel?: string;
33
- displayAsName?: string;
34
- displayAsDefault?: DisplayAs;
35
- };