@hubspot/cms-component-library 0.3.2 → 0.3.4

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 (23) hide show
  1. package/components/componentLibrary/Accordion/AccordionItem/index.module.scss +4 -0
  2. package/components/componentLibrary/Accordion/AccordionTitle/ContentFields.tsx +1 -0
  3. package/components/componentLibrary/Accordion/AccordionTitle/icons.tsx +19 -0
  4. package/components/componentLibrary/Accordion/AccordionTitle/index.module.scss +4 -0
  5. package/components/componentLibrary/Accordion/AccordionTitle/index.tsx +3 -1
  6. package/components/componentLibrary/Accordion/AccordionTitle/types.ts +1 -1
  7. package/components/componentLibrary/Accordion/llm.txt +7 -6
  8. package/components/componentLibrary/Accordion/stories/Accordion.stories.tsx +21 -0
  9. package/components/componentLibrary/Card/StyleFields.tsx +12 -24
  10. package/components/componentLibrary/Card/llm.txt +2 -16
  11. package/components/componentLibrary/Card/types.ts +0 -3
  12. package/components/componentLibrary/Divider/index.tsx +1 -3
  13. package/components/componentLibrary/Divider/llm.txt +0 -1
  14. package/components/componentLibrary/Divider/stories/Divider.stories.tsx +0 -1
  15. package/components/componentLibrary/Divider/types.ts +0 -1
  16. package/components/componentLibrary/Icon/StyleFields.tsx +10 -8
  17. package/components/componentLibrary/Icon/index.module.scss +14 -3
  18. package/components/componentLibrary/Icon/index.tsx +50 -6
  19. package/components/componentLibrary/Icon/llm.txt +54 -14
  20. package/components/componentLibrary/Icon/stories/Icon.stories.tsx +66 -4
  21. package/components/componentLibrary/Icon/types.ts +10 -3
  22. package/components/componentLibrary/Text/ContentFields.tsx +0 -7
  23. package/package.json +1 -1
@@ -18,3 +18,7 @@
18
18
  .accordionItem[open] :global(.accordionIconMinus) {
19
19
  display: block;
20
20
  }
21
+
22
+ .accordionItem[open] :global(.accordionIconCaret) {
23
+ transform: rotate(180deg);
24
+ }
@@ -18,6 +18,7 @@ const ContentFields = ({
18
18
  choices={[
19
19
  ['chevron', 'Chevron'],
20
20
  ['plus', 'Plus'],
21
+ ['caret', 'Caret'],
21
22
  ]}
22
23
  preset="expand_icon"
23
24
  required={false}
@@ -57,3 +57,22 @@ export const MinusIcon = () => {
57
57
  </svg>
58
58
  );
59
59
  };
60
+
61
+ export const CaretIcon = () => {
62
+ return (
63
+ <svg
64
+ className={cx(
65
+ styles.accordionIcon,
66
+ styles.accordionIconCaret,
67
+ 'accordionIconCaret'
68
+ )}
69
+ viewBox="0 0 24 24"
70
+ fill="currentColor"
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ aria-hidden="true"
73
+ role="img"
74
+ >
75
+ <path d="M11.2445 16.5296C11.717 16.9121 12.4107 16.8859 12.8495 16.4471L17.6495 11.6471C17.9945 11.3021 18.0957 10.7884 17.9082 10.3384C17.7207 9.88836 17.2857 9.59961 16.802 9.59961H7.20195C6.7182 9.59961 6.27945 9.89211 6.09195 10.3421C5.90445 10.7921 6.00945 11.3059 6.35445 11.6471L11.1545 16.4471L11.2445 16.5296Z" />
76
+ </svg>
77
+ );
78
+ };
@@ -44,3 +44,7 @@
44
44
  height: 24px;
45
45
  }
46
46
 
47
+ .accordionIconCaret {
48
+ width: 24px;
49
+ height: 24px;
50
+ }
@@ -2,7 +2,7 @@ import styles from './index.module.scss';
2
2
  import ContentFields from './ContentFields.js';
3
3
  import cx from '../../utils/classname.js';
4
4
  import { AccordionTitleProps } from './types.js';
5
- import { ChevronIcon, PlusIcon, MinusIcon } from './icons.js';
5
+ import { ChevronIcon, PlusIcon, MinusIcon, CaretIcon } from './icons.js';
6
6
 
7
7
  const AccordionTitleComponent = ({
8
8
  icon = 'chevron',
@@ -24,6 +24,8 @@ const AccordionTitleComponent = ({
24
24
  );
25
25
  }
26
26
 
27
+ if (icon === 'caret') return <CaretIcon />;
28
+
27
29
  return null;
28
30
  };
29
31
 
@@ -1,4 +1,4 @@
1
- export type AccordionIconType = 'chevron' | 'plus';
1
+ export type AccordionIconType = 'chevron' | 'plus' | 'caret';
2
2
 
3
3
  export type AccordionTitleProps = {
4
4
  icon?: AccordionIconType;
@@ -104,10 +104,10 @@ Accordion/
104
104
  **Props:**
105
105
  ```tsx
106
106
  {
107
- icon?: 'chevron' | 'plus'; // Icon type (default: 'chevron')
108
- className?: string; // Additional CSS classes
109
- style?: React.CSSProperties; // Inline styles
110
- children?: React.ReactNode; // Title text content
107
+ icon?: 'chevron' | 'plus' | 'caret'; // Icon type (default: 'chevron')
108
+ className?: string; // Additional CSS classes
109
+ style?: React.CSSProperties; // Inline styles
110
+ children?: React.ReactNode; // Title text content
111
111
  }
112
112
  ```
113
113
 
@@ -243,7 +243,7 @@ Configurable props for title text and icon:
243
243
 
244
244
  **Fields:**
245
245
  - `title`: TextField for accordion title text
246
- - `icon`: ChoiceField for selecting icon type (chevron, plus)
246
+ - `icon`: ChoiceField for selecting icon type (caret, chevron, plus)
247
247
 
248
248
  #### AccordionContent.ContentFields
249
249
 
@@ -397,6 +397,7 @@ The Accordion component uses CSS variables for theming and customization. Variab
397
397
  - `.accordionIconChevron`: Chevron-specific sizing (rotates 180° when open)
398
398
  - `.accordionIconPlus`: Plus icon (hidden when open)
399
399
  - `.accordionIconMinus`: Minus icon (shown when open)
400
+ - `.accordionIconCaret`: Caret icon (rotates 180° when open)
400
401
 
401
402
  **AccordionContent (`AccordionContent/index.module.scss`):**
402
403
  - `.accordionContent`: Content area with padding and typography
@@ -420,7 +421,7 @@ The Accordion component follows accessibility best practices:
420
421
 
421
422
  ## Best Practices
422
423
 
423
- - **Choose appropriate icons**: Use `chevron` for subtle indication, `plus` for clearer expand/collapse semantics (common in FAQs)
424
+ - **Choose appropriate icons**: Use `caret` for a compact filled triangle indicator, `chevron` for subtle indication, `plus` for clearer expand/collapse semantics (common in FAQs)
424
425
  - **Consistent variants**: Apply the same variant to all items within an accordion for visual consistency
425
426
  - **Gap selection**: Use any valid CSS length value (e.g., '8px', '16px', '24px', '48px') for spacing between items
426
427
  - **Dynamic rendering**: Always provide unique `key` props when mapping arrays to AccordionItems
@@ -106,6 +106,27 @@ export const IconOptions: Story = {
106
106
  </AccordionItem>
107
107
  </Accordion>
108
108
  </SBContainer>
109
+
110
+ <SBContainer addBackground>
111
+ <h4>Caret Icon</h4>
112
+ <Accordion gap="16px">
113
+ <AccordionItem>
114
+ <AccordionTitle icon="caret">Caret Icon</AccordionTitle>
115
+ <AccordionContent>
116
+ <p>
117
+ The caret icon provides a simple visual cue for the expanded
118
+ state.
119
+ </p>
120
+ </AccordionContent>
121
+ </AccordionItem>
122
+ <AccordionItem>
123
+ <AccordionTitle icon="caret">Another caret item</AccordionTitle>
124
+ <AccordionContent>
125
+ <p>This is just some caret cake.</p>
126
+ </AccordionContent>
127
+ </AccordionItem>
128
+ </Accordion>
129
+ </SBContainer>
109
130
  </SBContainer>
110
131
  ),
111
132
  };
@@ -1,35 +1,23 @@
1
- import { ChoiceField, NumberField } from '@hubspot/cms-components/fields';
1
+ import { ChoiceField } from '@hubspot/cms-components/fields';
2
2
  import { StyleFieldsProps } from './types.js';
3
3
 
4
4
  const StyleFields = ({
5
5
  variantLabel = 'Card Variant',
6
6
  variantName = 'variant',
7
7
  variantDefault = 'elevated',
8
- borderRadiusLabel = 'Border Radius',
9
- borderRadiusName = 'borderRadius',
10
- borderRadiusDefault = 8,
11
8
  }: StyleFieldsProps) => {
12
9
  return (
13
- <>
14
- <ChoiceField
15
- label={variantLabel}
16
- name={variantName}
17
- required={true}
18
- choices={[
19
- ['elevated', 'Elevated (with shadow)'],
20
- ['outlined', 'Outlined (with border)'],
21
- ['filled', 'Filled (solid background)'],
22
- ]}
23
- default={variantDefault}
24
- />
25
- <NumberField
26
- label={borderRadiusLabel}
27
- name={borderRadiusName}
28
- suffix="px"
29
- min={0}
30
- default={borderRadiusDefault}
31
- />
32
- </>
10
+ <ChoiceField
11
+ label={variantLabel}
12
+ name={variantName}
13
+ required={true}
14
+ choices={[
15
+ ['elevated', 'Elevated (with shadow)'],
16
+ ['outlined', 'Outlined (with border)'],
17
+ ['filled', 'Filled (solid background)'],
18
+ ]}
19
+ default={variantDefault}
20
+ />
33
21
  );
34
22
  };
35
23
 
@@ -56,15 +56,6 @@ import Card from '@hubspot/cms-component-library/Card';
56
56
  </Card>
57
57
  ```
58
58
 
59
- ### Card with Custom Border Radius
60
-
61
- ```tsx
62
- <Card variant="outlined" borderRadius={16}>
63
- <h3>Rounded Card</h3>
64
- <p>This card has a larger border radius for a softer appearance.</p>
65
- </Card>
66
- ```
67
-
68
59
  ### Semantic Article Card
69
60
 
70
61
  ```tsx
@@ -96,7 +87,7 @@ const features = [
96
87
  ### Card with Rich Content
97
88
 
98
89
  ```tsx
99
- <Card variant="outlined" borderRadius={12}>
90
+ <Card variant="outlined">
100
91
  <h3>Product Features</h3>
101
92
  <ul>
102
93
  <li>Real-time collaboration</li>
@@ -122,15 +113,11 @@ Configurable props for customizing field labels, names, and defaults:
122
113
  variantLabel="Card variant"
123
114
  variantName="variant"
124
115
  variantDefault="elevated"
125
- borderRadiusLabel="Border radius"
126
- borderRadiusName="borderRadius"
127
- borderRadiusDefault={8}
128
116
  />
129
117
  ```
130
118
 
131
119
  **Fields:**
132
120
  - `variant`: ChoiceField for selecting visual style (elevated, outlined, filled)
133
- - `borderRadius`: NumberField for border radius in pixels (min: 0)
134
121
 
135
122
  ### Module Usage Example
136
123
 
@@ -141,7 +128,6 @@ export default function FeatureCardModule({ fieldValues }) {
141
128
  return (
142
129
  <Card
143
130
  variant={fieldValues.variant}
144
- borderRadius={fieldValues.borderRadius}
145
131
  as="article"
146
132
  >
147
133
  <h3>{fieldValues.title}</h3>
@@ -187,7 +173,7 @@ The Card component follows accessibility best practices:
187
173
  ## Best Practices
188
174
 
189
175
  - **Choose semantic elements**: Use `as="article"` for self-contained content, `as="section"` for thematic groupings, and `as="div"` (default) for generic containers
190
- - **Border radius consistency**: Maintain consistent border radius values across your design system (typically 4px, 8px, 12px, or 16px)
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.
191
177
  - **Override CSS variables**: Customize appearance using CSS variables rather than overriding class styles
192
178
  - **Grid layouts**: Use CSS Grid or Flexbox for card layouts rather than relying on card margins
193
179
  - **Content flexibility**: Cards are content-agnostic containers; structure internal content using appropriate semantic elements
@@ -15,7 +15,4 @@ export type StyleFieldsProps = {
15
15
  variantLabel?: string;
16
16
  variantName?: string;
17
17
  variantDefault?: CardVariant;
18
- borderRadiusLabel?: string;
19
- borderRadiusName?: string;
20
- borderRadiusDefault?: number;
21
18
  };
@@ -41,15 +41,13 @@ const DividerComponent = ({
41
41
  length = 100,
42
42
  thickness = 1,
43
43
  color,
44
- variant = 'primary', // !todo: not used atm but keeping for when we need to add variant system.
45
44
  className = '',
46
45
  style = {},
47
46
  }: DividerProps) => {
48
- const variantClass = styles[`hscl-divider-${variant}`];
49
47
  const orientationClass =
50
48
  orientation === 'horizontal' ? styles.horizontal : styles.vertical;
51
49
 
52
- const defaultClasses = cx(styles.divider, variantClass, orientationClass);
50
+ const defaultClasses = cx(styles.divider, orientationClass);
53
51
  const combinedClasses = cx(defaultClasses, className);
54
52
 
55
53
  const cssVariables: CSSVariables = {
@@ -44,7 +44,6 @@ Divider/
44
44
  length?: number; // Length as percentage (1-100) of available space (default: 100)
45
45
  thickness?: number; // Border thickness in pixels (default: 1)
46
46
  color?: { rgba?: string; color?: string; opacity?: number }; // Line color (from ColorField or rgba value)
47
- variant?: 'primary' | 'secondary' | 'tertiary'; // Visual style variant (default: 'primary')
48
47
  className?: string; // Additional CSS classes
49
48
  style?: React.CSSProperties; // Inline styles (including CSS variables)
50
49
  }
@@ -30,7 +30,6 @@ export const Default: Story = {
30
30
  length: 100,
31
31
  thickness: 1,
32
32
  spacing: '16px',
33
- variant: 'primary',
34
33
  },
35
34
  render: args => (
36
35
  <SBContainer
@@ -23,7 +23,6 @@ export type DividerProps = {
23
23
  length?: number;
24
24
  thickness?: number;
25
25
  color?: ColorValue;
26
- variant?: 'primary' | 'secondary' | 'tertiary';
27
26
  className?: string;
28
27
  style?: CSSVariables;
29
28
  };
@@ -11,10 +11,10 @@ const StyleFields = ({
11
11
  iconBackgroundColorDefault = { color: '#2C2C2C', opacity: 100 },
12
12
  iconShapeLabel = 'Icon shape',
13
13
  iconShapeName = 'iconShape',
14
- iconShapeDefault = '0px',
14
+ iconShapeDefault = 'square',
15
15
  iconSizeLabel = 'Icon size',
16
16
  iconSizeName = 'iconSize',
17
- iconSizeDefault = '16',
17
+ iconSizeDefault = 'small',
18
18
  }: StyleFieldsProps) => {
19
19
  return (
20
20
  <>
@@ -23,6 +23,7 @@ const StyleFields = ({
23
23
  label={iconColorLabel}
24
24
  name={iconColorName}
25
25
  default={iconColorDefault}
26
+ required
26
27
  showOpacity={false}
27
28
  />
28
29
  )}
@@ -31,6 +32,7 @@ const StyleFields = ({
31
32
  label={iconBackgroundColorLabel}
32
33
  name={iconBackgroundColorName}
33
34
  default={iconBackgroundColorDefault}
35
+ required
34
36
  showOpacity={false}
35
37
  />
36
38
  )}
@@ -41,9 +43,9 @@ const StyleFields = ({
41
43
  display="buttons"
42
44
  preset="icon_shape"
43
45
  choices={[
44
- ['0px', 'Square'],
45
- ['8px', 'Rounded'],
46
- ['100%', 'Circle'],
46
+ ['square', 'Square'],
47
+ ['rounded', 'Rounded'],
48
+ ['circle', 'Circle'],
47
49
  ]}
48
50
  default={iconShapeDefault}
49
51
  required={true}
@@ -54,9 +56,9 @@ const StyleFields = ({
54
56
  label={iconSizeLabel}
55
57
  name={iconSizeName}
56
58
  choices={[
57
- ['16', 'Small (16x16 px)'],
58
- ['24', 'Medium (24x24 px)'],
59
- ['32', 'Large (32x32 px)'],
59
+ ['small', 'Small (16x16 px)'],
60
+ ['medium', 'Medium (24x24 px)'],
61
+ ['large', 'Large (32x32 px)'],
60
62
  ]}
61
63
  default={iconSizeDefault}
62
64
  required={true}
@@ -2,8 +2,19 @@
2
2
  display: inline-flex;
3
3
  align-items: center;
4
4
  justify-content: center;
5
- fill: var(--hscl-icon-fill);
5
+ fill: var(--hscl-icon-fill, transparent);
6
6
  stroke: var(--hscl-icon-stroke);
7
- width: var(--hscl-icon-size);
8
- height: var(--hscl-icon-size);
7
+ width: var(--hscl-icon-size, 16px);
8
+ height: var(--hscl-icon-size, 16px);
9
+ }
10
+
11
+ .iconBackground {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ justify-self: flex-start;
16
+ align-self: flex-start;
17
+ background-color: var(--hscl-iconBackground-backgroundColor, transparent);
18
+ padding: var(--hscl-iconBackground-padding, 12px);
19
+ border-radius: var(--hscl-iconBackground-shape, 0px);
9
20
  }
@@ -6,36 +6,80 @@ import cx from '../utils/classname.js';
6
6
  import type { CSSVariables } from '../utils/types.js';
7
7
  import { IconProps } from './types.js';
8
8
 
9
+ const ICON_SIZE_MAP: Record<string, number> = {
10
+ small: 16,
11
+ medium: 24,
12
+ large: 32,
13
+ };
14
+
15
+ const ICON_SHAPE_MAP: Record<string, string> = {
16
+ square: '0px',
17
+ rounded: '8px',
18
+ circle: '100%',
19
+ };
20
+
9
21
  const IconComponent = ({
10
22
  fieldPath,
11
23
  showIcon = true,
12
- size = 12,
24
+ size,
13
25
  className = '',
14
26
  style = {},
15
27
  purpose = 'DECORATIVE',
16
28
  title = '',
17
29
  fill,
30
+ showIconBackground = false,
31
+ iconBackgroundColor,
32
+ iconBackgroundPadding,
33
+ iconBackgroundShape,
18
34
  }: IconProps) => {
19
35
  if (!showIcon) return null;
20
36
 
21
- const defaultClasses = styles.icon;
22
- const combinedClasses = cx(defaultClasses, className);
37
+ const resolvedSize =
38
+ typeof size === 'string' ? ICON_SIZE_MAP[size] : undefined;
39
+ const resolvedShape = iconBackgroundShape
40
+ ? ICON_SHAPE_MAP[iconBackgroundShape] ?? iconBackgroundShape
41
+ : undefined;
23
42
 
24
43
  const cssVariables: CSSVariables = {
25
- '--hscl-icon-size': `${size}px`,
44
+ ...(resolvedSize !== undefined && {
45
+ '--hscl-icon-size': `${resolvedSize}px`,
46
+ }),
26
47
  ...(fill && { '--hscl-icon-fill': fill }),
27
48
  };
28
49
 
29
- return (
50
+ const defaultClasses = styles.icon;
51
+ const combinedClasses = cx(defaultClasses, className);
52
+
53
+ const icon = (
30
54
  <CMSIcon
31
55
  fieldPath={fieldPath}
32
- height={size}
56
+ height={resolvedSize}
33
57
  className={combinedClasses}
34
58
  style={{ ...cssVariables, ...style }}
35
59
  purpose={purpose}
36
60
  title={title}
37
61
  />
38
62
  );
63
+
64
+ if (!showIconBackground) return icon;
65
+
66
+ const backgroundCssVariables: CSSVariables = {
67
+ ...(iconBackgroundColor && {
68
+ '--hscl-iconBackground-backgroundColor': iconBackgroundColor,
69
+ }),
70
+ ...(iconBackgroundPadding && {
71
+ '--hscl-iconBackground-padding': `${iconBackgroundPadding}px`,
72
+ }),
73
+ ...(resolvedShape && {
74
+ '--hscl-iconBackground-shape': resolvedShape,
75
+ }),
76
+ };
77
+
78
+ return (
79
+ <div className={styles.iconBackground} style={backgroundCssVariables}>
80
+ {icon}
81
+ </div>
82
+ );
39
83
  };
40
84
 
41
85
  type IconComponentType = typeof IconComponent & {
@@ -9,7 +9,7 @@ import Icon from '@hubspot/cms-component-library/Icon';
9
9
 
10
10
  ## Purpose
11
11
 
12
- The Icon component wraps the native HubSpot CMS Icon component to provide a consistent interface for rendering icons with customizable styling and accessibility options. It simplifies icon usage in HubSpot CMS projects by handling common configuration patterns like sizing, color theming through CSS variables, and proper accessibility attributes (SEMANTIC vs DECORATIVE purpose).
12
+ The Icon component wraps the native HubSpot CMS Icon component to provide a consistent interface for rendering icons with customizable styling and accessibility options. It simplifies icon usage in HubSpot CMS projects by handling common configuration patterns like sizing, color theming through CSS variables, optional background containers, and proper accessibility attributes (SEMANTIC vs DECORATIVE purpose).
13
13
 
14
14
  ## Component Structure
15
15
 
@@ -18,6 +18,7 @@ Icon/
18
18
  ├── index.tsx # Main component with render logic
19
19
  ├── types.ts # TypeScript type definitions
20
20
  ├── ContentFields.tsx # HubSpot field definitions for icon content
21
+ ├── StyleFields.tsx # HubSpot field definitions for icon styles
21
22
  ├── index.module.scss # CSS module with design tokens
22
23
  └── stories/
23
24
  ├── Icon.stories.tsx # Component usage examples
@@ -29,19 +30,23 @@ Icon/
29
30
 
30
31
  ### Icon (Main Component)
31
32
 
32
- **Purpose:** Renders an icon with configurable size, color, and accessibility properties.
33
+ **Purpose:** Renders an icon with configurable size, color, background, and accessibility properties.
33
34
 
34
35
  **Props:**
35
36
  ```tsx
36
37
  {
37
38
  fieldPath?: string; // Path to icon in HubSpot fields
38
- size?: number; // Icon size in pixels, sets both width and height (default: 12)
39
+ size?: number | 'small' | 'medium' | 'large'; // Icon size natural language or explicit px value. Omit to use CSS default (16px)
39
40
  fill?: string; // Icon fill color (overrides CSS variable)
40
41
  className?: string; // Additional CSS classes
41
42
  style?: React.CSSProperties; // Inline styles (including CSS variables)
42
43
  showIcon?: boolean; // Toggle icon visibility (default: true)
43
44
  purpose?: 'SEMANTIC' | 'DECORATIVE'; // Icon accessibility role (default: 'DECORATIVE')
44
45
  title?: string; // Icon title for screen readers (use with SEMANTIC)
46
+ showIconBackground?: boolean; // Renders a centered background div behind the icon (default: false)
47
+ iconBackgroundColor?: string; // Background div color (used when showIconBackground is true)
48
+ iconBackgroundPadding?: number; // Padding around the icon inside the background div in px (default: 12)
49
+ iconBackgroundShape?: 'square' | 'rounded' | 'circle' | string; // Background shape — natural language or raw CSS border-radius value
45
50
  }
46
51
  ```
47
52
 
@@ -89,6 +94,19 @@ import Icon from '@hubspot/cms-component-library/Icon';
89
94
  />
90
95
  ```
91
96
 
97
+ ### Icon with Background
98
+
99
+ ```tsx
100
+ <Icon
101
+ fieldPath="icon"
102
+ size={24}
103
+ showIconBackground={true}
104
+ iconBackgroundColor="#2C2C2C"
105
+ iconBackgroundPadding={16}
106
+ iconBackgroundShape="circle"
107
+ />
108
+ ```
109
+
92
110
  ## HubSpot CMS Integration
93
111
 
94
112
  ### Field Definitions
@@ -115,9 +133,21 @@ Configurable props for icon selection and visibility:
115
133
  - `icon`: IconField for selecting the icon
116
134
  - `showIcon`: BooleanField toggle for icon visibility (optional, enabled with `addIconToggle={true}`)
117
135
 
118
- #### Style Fields
136
+ #### StyleFields
119
137
 
120
- **Note:** There are no style fields included with this component. Do not try to import them.
138
+ Configurable props for icon appearance:
139
+
140
+ ```tsx
141
+ <Icon.StyleFields />
142
+ ```
143
+
144
+ **Fields:**
145
+ - `iconColor`: ColorField for the icon fill color
146
+ - `iconBackgroundColor`: ColorField for the background div color
147
+ - `iconShape`: ChoiceField for the background border-radius (`'square'`, `'rounded'`, `'circle'`)
148
+ - `iconSize`: ChoiceField for icon size (`'small'` 16px, `'medium'` 24px, `'large'` 32px)
149
+
150
+ All fields support `label`, `name`, and `default` customization props, and can be hidden via `hideFields`.
121
151
 
122
152
  ### Module Usage Example
123
153
 
@@ -128,8 +158,12 @@ export default function IconModule({ fieldValues }) {
128
158
  return (
129
159
  <Icon
130
160
  fieldPath="icon"
131
- size={fieldValues.iconSize || 24}
161
+ size={fieldValues.style?.iconSize}
132
162
  showIcon={fieldValues.showIcon}
163
+ fill={fieldValues.style?.iconColor?.color}
164
+ showIconBackground={fieldValues.style?.showIconBackground}
165
+ iconBackgroundColor={fieldValues.style?.iconBackgroundColor?.color}
166
+ iconBackgroundShape={fieldValues.style?.iconShape}
133
167
  purpose="DECORATIVE"
134
168
  />
135
169
  );
@@ -139,14 +173,15 @@ export default function IconModule({ fieldValues }) {
139
173
  ### Module Fields Example
140
174
 
141
175
  ```tsx
142
- import { ModuleFields } from '@hubspot/cms-components/fields';
176
+ import { ModuleFields, FieldGroup } from '@hubspot/cms-components/fields';
143
177
  import Icon from '@hubspot/cms-component-library/Icon';
144
178
 
145
179
  export const fields = (
146
180
  <ModuleFields>
147
- <Icon.ContentFields
148
- addIconToggle={true}
149
- />
181
+ <Icon.ContentFields addIconToggle={true} />
182
+ <FieldGroup name="style" label="Style" tab="STYLE">
183
+ <Icon.StyleFields />
184
+ </FieldGroup>
150
185
  </ModuleFields>
151
186
  );
152
187
  ```
@@ -158,8 +193,13 @@ export const fields = (
158
193
  The Icon component uses CSS variables for theming and customization:
159
194
 
160
195
  **Base Styles:**
161
- - `--hscl-icon-fill`: Icon fill color (default: currentColor)
162
- - `--hscl-icon-size`: Icon width and height (set automatically from the `size` prop)
196
+ - `--hscl-icon-fill`: Icon fill color (default: `transparent`)
197
+ - `--hscl-icon-size`: Icon width and height (default: `16px`). Only set when `size` prop is provided — omitting `size` lets the CSS default apply.
198
+
199
+ **Background Styles (applied when `showIconBackground` is true):**
200
+ - `--hscl-iconBackground-backgroundColor`: Background div color
201
+ - `--hscl-iconBackground-padding`: Padding around the icon inside the background div (default: 12px)
202
+ - `--hscl-iconBackground-shape`: Border-radius of the background div (default: 0px)
163
203
 
164
204
 
165
205
  ## Accessibility
@@ -177,6 +217,6 @@ The Icon component follows accessibility best practices:
177
217
  - **Choose the right purpose**: Use `SEMANTIC` when the icon conveys important meaning, `DECORATIVE` when it's purely visual
178
218
  - **Always provide title with SEMANTIC**: When using `purpose="SEMANTIC"`, always include a meaningful `title` for screen readers
179
219
  - **Use CSS variables for theming**: Override `--hscl-icon-fill` for consistent color theming across your application
180
- - **Size appropriately**: Common sizes are 12px (small), 16px (default inline), 24px (medium), 32px (large), 48px (extra large)
220
+ - **Use natural language sizes**: Pass `'small'` (16px), `'medium'` (24px), or `'large'` (32px) to `size`, or an explicit `number` in pixels. Omit `size` entirely to use the CSS default of `16px`.
181
221
  - **Field path**: The `fieldPath` prop should match the field name defined in ContentFields
182
- - **Style fields**: There are no style fields in this component. Do not try to import them.
222
+ - **Background shape**: Pass `iconShape` field values (`'square'`, `'rounded'`, `'circle'`) directly to `iconBackgroundShape` — the component maps them to CSS values internally. Raw CSS border-radius strings are also accepted.
@@ -25,18 +25,40 @@ type Story = StoryObj<typeof meta>;
25
25
  export const Default: Story = {
26
26
  args: {
27
27
  fieldPath: 'icon',
28
- size: 24,
28
+ size: 'medium',
29
29
  showIcon: true,
30
30
  purpose: 'DECORATIVE',
31
31
  },
32
32
  };
33
33
 
34
+ export const NamedSizes: Story = {
35
+ name: 'Named Sizes',
36
+ render: () => (
37
+ <SBContainer flex direction="row" gap="large" alignItems="center">
38
+ <SBContainer addBackground flex alignItems="center">
39
+ <h4>small (16px)</h4>
40
+ <Icon fieldPath="icon" size="small" />
41
+ </SBContainer>
42
+
43
+ <SBContainer addBackground flex alignItems="center">
44
+ <h4>medium (24px)</h4>
45
+ <Icon fieldPath="icon" size="medium" />
46
+ </SBContainer>
47
+
48
+ <SBContainer addBackground flex alignItems="center">
49
+ <h4>large (32px)</h4>
50
+ <Icon fieldPath="icon" size="large" />
51
+ </SBContainer>
52
+ </SBContainer>
53
+ ),
54
+ };
55
+
34
56
  export const IconSizes: Story = {
35
- name: 'Icon Sizes',
57
+ name: 'Icon Sizes (Explicit px)',
36
58
  render: () => (
37
59
  <SBContainer flex direction="row" gap="large" alignItems="center">
38
60
  <SBContainer addBackground flex alignItems="center">
39
- <h4>12px (default)</h4>
61
+ <h4>12px</h4>
40
62
  <Icon fieldPath="icon" size={12} />
41
63
  </SBContainer>
42
64
 
@@ -68,7 +90,7 @@ export const Colors: Story = {
68
90
  render: () => (
69
91
  <SBContainer flex direction="row" gap="large" alignItems="center">
70
92
  <SBContainer addBackground flex alignItems="center">
71
- <h4>Default (currentColor)</h4>
93
+ <h4>Default (transparent)</h4>
72
94
  <Icon fieldPath="icon" size={32} />
73
95
  </SBContainer>
74
96
 
@@ -102,3 +124,43 @@ export const Colors: Story = {
102
124
  </SBContainer>
103
125
  ),
104
126
  };
127
+
128
+ export const BackgroundShapes: Story = {
129
+ name: 'Background Shapes',
130
+ render: () => (
131
+ <SBContainer flex direction="row" gap="large" alignItems="center">
132
+ <SBContainer addBackground flex alignItems="center">
133
+ <h4>square</h4>
134
+ <Icon
135
+ fieldPath="icon"
136
+ size="medium"
137
+ showIconBackground
138
+ iconBackgroundColor="#2C2C2C"
139
+ iconBackgroundShape="square"
140
+ />
141
+ </SBContainer>
142
+
143
+ <SBContainer addBackground flex alignItems="center">
144
+ <h4>rounded</h4>
145
+ <Icon
146
+ fieldPath="icon"
147
+ size="medium"
148
+ showIconBackground
149
+ iconBackgroundColor="#2C2C2C"
150
+ iconBackgroundShape="rounded"
151
+ />
152
+ </SBContainer>
153
+
154
+ <SBContainer addBackground flex alignItems="center">
155
+ <h4>circle</h4>
156
+ <Icon
157
+ fieldPath="icon"
158
+ size="medium"
159
+ showIconBackground
160
+ iconBackgroundColor="#2C2C2C"
161
+ iconBackgroundShape="circle"
162
+ />
163
+ </SBContainer>
164
+ </SBContainer>
165
+ ),
166
+ };
@@ -8,15 +8,22 @@ type ColorFieldValue = typeof ColorFieldDefaults;
8
8
 
9
9
  export type IconPurpose = 'SEMANTIC' | 'DECORATIVE';
10
10
 
11
+ export type IconSize = 'small' | 'medium' | 'large';
12
+ export type IconShape = 'square' | 'rounded' | 'circle';
13
+
11
14
  export type IconProps = {
12
15
  fieldPath?: string;
13
- size?: number;
16
+ size?: number | IconSize;
14
17
  fill?: string;
15
18
  className?: string;
16
19
  style?: React.CSSProperties;
17
20
  showIcon?: boolean;
18
21
  purpose?: IconPurpose;
19
22
  title?: string;
23
+ showIconBackground?: boolean;
24
+ iconBackgroundColor?: string;
25
+ iconBackgroundPadding?: number;
26
+ iconBackgroundShape?: IconShape | string;
20
27
  };
21
28
 
22
29
  type IconToggleProps = {
@@ -56,8 +63,8 @@ export type StyleFieldsProps = {
56
63
  iconBackgroundColorDefault?: ColorFieldValue;
57
64
  iconShapeLabel?: string;
58
65
  iconShapeName?: string;
59
- iconShapeDefault?: string;
66
+ iconShapeDefault?: IconShape;
60
67
  iconSizeLabel?: string;
61
68
  iconSizeName?: string;
62
- iconSizeDefault?: string;
69
+ iconSizeDefault?: IconSize;
63
70
  };
@@ -7,17 +7,10 @@ import type { ContentFieldsProps } from './types.js';
7
7
  // To do: These are placeholders - we can refine and add more after some UX feedback and further discussion
8
8
 
9
9
  const headingEnabledFeatures: RichTextFieldType['enabledFeatures'] = [
10
- 'block',
11
10
  'alignment',
12
- 'indents',
13
- 'lists',
14
11
  'standard_emphasis',
15
- 'advanced_emphasis',
16
12
  'link',
17
13
  'personalize',
18
- 'nonbreaking_space',
19
- 'source_code',
20
- 'visual_blocks',
21
14
  ];
22
15
 
23
16
  const bodyContentEnabledFeatures: RichTextFieldType['enabledFeatures'] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cms-component-library",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "HubSpot CMS React component library for building CMS modules",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {