@hubspot/cms-component-library 0.3.3 → 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.
@@ -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,10 +2,10 @@
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
9
  }
10
10
 
11
11
  .iconBackground {
@@ -6,10 +6,22 @@ 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',
@@ -22,8 +34,16 @@ const IconComponent = ({
22
34
  }: IconProps) => {
23
35
  if (!showIcon) return null;
24
36
 
37
+ const resolvedSize =
38
+ typeof size === 'string' ? ICON_SIZE_MAP[size] : undefined;
39
+ const resolvedShape = iconBackgroundShape
40
+ ? ICON_SHAPE_MAP[iconBackgroundShape] ?? iconBackgroundShape
41
+ : undefined;
42
+
25
43
  const cssVariables: CSSVariables = {
26
- '--hscl-icon-size': `${size}px`,
44
+ ...(resolvedSize !== undefined && {
45
+ '--hscl-icon-size': `${resolvedSize}px`,
46
+ }),
27
47
  ...(fill && { '--hscl-icon-fill': fill }),
28
48
  };
29
49
 
@@ -33,7 +53,7 @@ const IconComponent = ({
33
53
  const icon = (
34
54
  <CMSIcon
35
55
  fieldPath={fieldPath}
36
- height={size}
56
+ height={resolvedSize}
37
57
  className={combinedClasses}
38
58
  style={{ ...cssVariables, ...style }}
39
59
  purpose={purpose}
@@ -50,8 +70,8 @@ const IconComponent = ({
50
70
  ...(iconBackgroundPadding && {
51
71
  '--hscl-iconBackground-padding': `${iconBackgroundPadding}px`,
52
72
  }),
53
- ...(iconBackgroundShape && {
54
- '--hscl-iconBackground-shape': iconBackgroundShape,
73
+ ...(resolvedShape && {
74
+ '--hscl-iconBackground-shape': resolvedShape,
55
75
  }),
56
76
  };
57
77
 
@@ -36,7 +36,7 @@ Icon/
36
36
  ```tsx
37
37
  {
38
38
  fieldPath?: string; // Path to icon in HubSpot fields
39
- 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)
40
40
  fill?: string; // Icon fill color (overrides CSS variable)
41
41
  className?: string; // Additional CSS classes
42
42
  style?: React.CSSProperties; // Inline styles (including CSS variables)
@@ -46,7 +46,7 @@ Icon/
46
46
  showIconBackground?: boolean; // Renders a centered background div behind the icon (default: false)
47
47
  iconBackgroundColor?: string; // Background div color (used when showIconBackground is true)
48
48
  iconBackgroundPadding?: number; // Padding around the icon inside the background div in px (default: 12)
49
- iconBackgroundShape?: string; // Border-radius of the background div, e.g. '0px', '8px', '100%' (default: 0px)
49
+ iconBackgroundShape?: 'square' | 'rounded' | 'circle' | string; // Background shape natural language or raw CSS border-radius value
50
50
  }
51
51
  ```
52
52
 
@@ -103,7 +103,7 @@ import Icon from '@hubspot/cms-component-library/Icon';
103
103
  showIconBackground={true}
104
104
  iconBackgroundColor="#2C2C2C"
105
105
  iconBackgroundPadding={16}
106
- iconBackgroundShape="100%"
106
+ iconBackgroundShape="circle"
107
107
  />
108
108
  ```
109
109
 
@@ -144,8 +144,8 @@ Configurable props for icon appearance:
144
144
  **Fields:**
145
145
  - `iconColor`: ColorField for the icon fill color
146
146
  - `iconBackgroundColor`: ColorField for the background div color
147
- - `iconShape`: ChoiceField for the background border-radius (`'0px'` Square, `'8px'` Rounded, `'100%'` Circle)
148
- - `iconSize`: ChoiceField for icon size (16px Small, 24px Medium, 32px Large)
147
+ - `iconShape`: ChoiceField for the background border-radius (`'square'`, `'rounded'`, `'circle'`)
148
+ - `iconSize`: ChoiceField for icon size (`'small'` 16px, `'medium'` 24px, `'large'` 32px)
149
149
 
150
150
  All fields support `label`, `name`, and `default` customization props, and can be hidden via `hideFields`.
151
151
 
@@ -158,7 +158,7 @@ export default function IconModule({ fieldValues }) {
158
158
  return (
159
159
  <Icon
160
160
  fieldPath="icon"
161
- size={Number(fieldValues.style?.iconSize) || 24}
161
+ size={fieldValues.style?.iconSize}
162
162
  showIcon={fieldValues.showIcon}
163
163
  fill={fieldValues.style?.iconColor?.color}
164
164
  showIconBackground={fieldValues.style?.showIconBackground}
@@ -193,8 +193,8 @@ export const fields = (
193
193
  The Icon component uses CSS variables for theming and customization:
194
194
 
195
195
  **Base Styles:**
196
- - `--hscl-icon-fill`: Icon fill color (default: currentColor)
197
- - `--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
198
 
199
199
  **Background Styles (applied when `showIconBackground` is true):**
200
200
  - `--hscl-iconBackground-backgroundColor`: Background div color
@@ -217,6 +217,6 @@ The Icon component follows accessibility best practices:
217
217
  - **Choose the right purpose**: Use `SEMANTIC` when the icon conveys important meaning, `DECORATIVE` when it's purely visual
218
218
  - **Always provide title with SEMANTIC**: When using `purpose="SEMANTIC"`, always include a meaningful `title` for screen readers
219
219
  - **Use CSS variables for theming**: Override `--hscl-icon-fill` for consistent color theming across your application
220
- - **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`.
221
221
  - **Field path**: The `fieldPath` prop should match the field name defined in ContentFields
222
- - **Background shape**: The `iconShape` StyleField value maps directly to `iconBackgroundShape` on the component pass it through as-is
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,9 +8,12 @@ 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;
@@ -20,7 +23,7 @@ export type IconProps = {
20
23
  showIconBackground?: boolean;
21
24
  iconBackgroundColor?: string;
22
25
  iconBackgroundPadding?: number;
23
- iconBackgroundShape?: string;
26
+ iconBackgroundShape?: IconShape | string;
24
27
  };
25
28
 
26
29
  type IconToggleProps = {
@@ -60,8 +63,8 @@ export type StyleFieldsProps = {
60
63
  iconBackgroundColorDefault?: ColorFieldValue;
61
64
  iconShapeLabel?: string;
62
65
  iconShapeName?: string;
63
- iconShapeDefault?: string;
66
+ iconShapeDefault?: IconShape;
64
67
  iconSizeLabel?: string;
65
68
  iconSizeName?: string;
66
- iconSizeDefault?: string;
69
+ iconSizeDefault?: IconSize;
67
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.3",
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": {