@hubspot/cms-component-library 0.3.3 → 0.3.5
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.
- package/components/componentLibrary/Accordion/AccordionTitle/ContentFields.tsx +2 -20
- package/components/componentLibrary/Accordion/AccordionTitle/StyleFields.tsx +26 -0
- package/components/componentLibrary/Accordion/AccordionTitle/index.tsx +3 -0
- package/components/componentLibrary/Accordion/AccordionTitle/types.ts +3 -0
- package/components/componentLibrary/Accordion/llm.txt +19 -6
- package/components/componentLibrary/Card/StyleFields.tsx +12 -24
- package/components/componentLibrary/Card/llm.txt +2 -16
- package/components/componentLibrary/Card/types.ts +0 -3
- package/components/componentLibrary/Divider/index.tsx +1 -3
- package/components/componentLibrary/Divider/llm.txt +0 -1
- package/components/componentLibrary/Divider/stories/Divider.stories.tsx +0 -1
- package/components/componentLibrary/Divider/types.ts +0 -1
- package/components/componentLibrary/Form/ContentFields.tsx +19 -0
- package/components/componentLibrary/Form/index.tsx +18 -0
- package/components/componentLibrary/Form/islands/FormIsland.tsx +54 -0
- package/components/componentLibrary/Form/islands/index.module.scss +3 -0
- package/components/componentLibrary/Form/types.ts +13 -0
- package/components/componentLibrary/Icon/StyleFields.tsx +10 -8
- package/components/componentLibrary/Icon/index.module.scss +3 -3
- package/components/componentLibrary/Icon/index.tsx +25 -5
- package/components/componentLibrary/Icon/llm.txt +10 -10
- package/components/componentLibrary/Icon/stories/Icon.stories.tsx +66 -4
- package/components/componentLibrary/Icon/types.ts +7 -4
- package/components/componentLibrary/Text/ContentFields.tsx +0 -7
- package/package.json +1 -1
|
@@ -1,31 +1,13 @@
|
|
|
1
|
-
import { TextField
|
|
1
|
+
import { TextField } from '@hubspot/cms-components/fields';
|
|
2
2
|
import type { ContentFieldsProps } from './types.js';
|
|
3
3
|
|
|
4
4
|
const ContentFields = ({
|
|
5
5
|
titleLabel = 'Title',
|
|
6
6
|
titleName = 'title',
|
|
7
7
|
titleDefault = 'Accordion Title',
|
|
8
|
-
iconLabel = 'Icon',
|
|
9
|
-
iconName = 'icon',
|
|
10
|
-
iconDefault = 'chevron',
|
|
11
8
|
}: ContentFieldsProps) => {
|
|
12
9
|
return (
|
|
13
|
-
|
|
14
|
-
<ChoiceField
|
|
15
|
-
label={iconLabel}
|
|
16
|
-
name={iconName}
|
|
17
|
-
display="buttons"
|
|
18
|
-
choices={[
|
|
19
|
-
['chevron', 'Chevron'],
|
|
20
|
-
['plus', 'Plus'],
|
|
21
|
-
['caret', 'Caret'],
|
|
22
|
-
]}
|
|
23
|
-
preset="expand_icon"
|
|
24
|
-
required={false}
|
|
25
|
-
default={iconDefault}
|
|
26
|
-
/>
|
|
27
|
-
<TextField label={titleLabel} name={titleName} default={titleDefault} />
|
|
28
|
-
</>
|
|
10
|
+
<TextField label={titleLabel} name={titleName} default={titleDefault} />
|
|
29
11
|
);
|
|
30
12
|
};
|
|
31
13
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ChoiceField } from '@hubspot/cms-components/fields';
|
|
2
|
+
import type { StyleFieldsProps } from './types.js';
|
|
3
|
+
|
|
4
|
+
const StyleFields = ({
|
|
5
|
+
iconLabel = 'Icon',
|
|
6
|
+
iconName = 'icon',
|
|
7
|
+
iconDefault = 'chevron',
|
|
8
|
+
}: StyleFieldsProps) => {
|
|
9
|
+
return (
|
|
10
|
+
<ChoiceField
|
|
11
|
+
label={iconLabel}
|
|
12
|
+
name={iconName}
|
|
13
|
+
display="buttons"
|
|
14
|
+
choices={[
|
|
15
|
+
['caret', 'Caret'],
|
|
16
|
+
['chevron', 'Chevron'],
|
|
17
|
+
['plus', 'Plus'],
|
|
18
|
+
]}
|
|
19
|
+
preset="expand_icon"
|
|
20
|
+
required={false}
|
|
21
|
+
default={iconDefault}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default StyleFields;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import styles from './index.module.scss';
|
|
2
2
|
import ContentFields from './ContentFields.js';
|
|
3
|
+
import StyleFields from './StyleFields.js';
|
|
3
4
|
import cx from '../../utils/classname.js';
|
|
4
5
|
import { AccordionTitleProps } from './types.js';
|
|
5
6
|
import { ChevronIcon, PlusIcon, MinusIcon, CaretIcon } from './icons.js';
|
|
@@ -39,9 +40,11 @@ const AccordionTitleComponent = ({
|
|
|
39
40
|
|
|
40
41
|
type AccordionTitleComponentType = typeof AccordionTitleComponent & {
|
|
41
42
|
ContentFields: typeof ContentFields;
|
|
43
|
+
StyleFields: typeof StyleFields;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
const AccordionTitle = AccordionTitleComponent as AccordionTitleComponentType;
|
|
45
47
|
AccordionTitle.ContentFields = ContentFields;
|
|
48
|
+
AccordionTitle.StyleFields = StyleFields;
|
|
46
49
|
|
|
47
50
|
export default AccordionTitle;
|
|
@@ -52,7 +52,8 @@ Accordion/
|
|
|
52
52
|
├── AccordionTitle/
|
|
53
53
|
│ ├── index.tsx # AccordionTitle component
|
|
54
54
|
│ ├── types.ts # AccordionTitle TypeScript types
|
|
55
|
-
│ ├── ContentFields.tsx # HubSpot field definitions for title
|
|
55
|
+
│ ├── ContentFields.tsx # HubSpot field definitions for title text
|
|
56
|
+
│ ├── StyleFields.tsx # HubSpot field definitions for icon
|
|
56
57
|
│ ├── icons.tsx # SVG icon components (Chevron, Plus, Minus)
|
|
57
58
|
│ └── index.module.scss # CSS module for title styling
|
|
58
59
|
├── AccordionContent/
|
|
@@ -228,13 +229,25 @@ Configurable props for variant selection:
|
|
|
228
229
|
|
|
229
230
|
#### AccordionTitle.ContentFields
|
|
230
231
|
|
|
231
|
-
Configurable props for title text
|
|
232
|
+
Configurable props for title text:
|
|
232
233
|
|
|
233
234
|
```tsx
|
|
234
235
|
<AccordionTitle.ContentFields
|
|
235
236
|
titleName="title"
|
|
236
237
|
titleLabel="Title"
|
|
237
238
|
titleDefault="Accordion Title"
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Fields:**
|
|
243
|
+
- `title`: TextField for accordion title text
|
|
244
|
+
|
|
245
|
+
#### AccordionTitle.StyleFields
|
|
246
|
+
|
|
247
|
+
Configurable props for icon style:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
<AccordionTitle.StyleFields
|
|
238
251
|
iconName="icon"
|
|
239
252
|
iconLabel="Icon"
|
|
240
253
|
iconDefault="chevron"
|
|
@@ -242,7 +255,6 @@ Configurable props for title text and icon:
|
|
|
242
255
|
```
|
|
243
256
|
|
|
244
257
|
**Fields:**
|
|
245
|
-
- `title`: TextField for accordion title text
|
|
246
258
|
- `icon`: ChoiceField for selecting icon type (caret, chevron, plus)
|
|
247
259
|
|
|
248
260
|
#### AccordionContent.ContentFields
|
|
@@ -275,11 +287,11 @@ import { AccordionIconType } from '@hubspot/cms-component-library/Accordion/Acco
|
|
|
275
287
|
type FAQModuleProps = {
|
|
276
288
|
style?: {
|
|
277
289
|
variant?: AccordionVariant;
|
|
290
|
+
icon?: AccordionIconType;
|
|
278
291
|
};
|
|
279
292
|
accordionItems?: Array<{
|
|
280
293
|
title?: string;
|
|
281
294
|
content?: string;
|
|
282
|
-
icon?: AccordionIconType;
|
|
283
295
|
}>;
|
|
284
296
|
};
|
|
285
297
|
|
|
@@ -288,10 +300,11 @@ export const Component = ({
|
|
|
288
300
|
accordionItems = [],
|
|
289
301
|
}: FAQModuleProps) => {
|
|
290
302
|
const variant = style?.variant;
|
|
303
|
+
const icon = style?.icon;
|
|
291
304
|
|
|
292
305
|
return (
|
|
293
306
|
<Accordion>
|
|
294
|
-
{accordionItems.map(({ title, content
|
|
307
|
+
{accordionItems.map(({ title, content }, index) => (
|
|
295
308
|
<AccordionItem key={index} variant={variant}>
|
|
296
309
|
<AccordionTitle icon={icon}>{title}</AccordionTitle>
|
|
297
310
|
<AccordionContent>{content}</AccordionContent>
|
|
@@ -326,7 +339,6 @@ import {
|
|
|
326
339
|
const defaultItem = {
|
|
327
340
|
title: 'Accordion Title',
|
|
328
341
|
content: '<p>Accordion content goes here. You can add multiple lines of text.</p>',
|
|
329
|
-
icon: 'chevron',
|
|
330
342
|
};
|
|
331
343
|
|
|
332
344
|
export const fields = (
|
|
@@ -342,6 +354,7 @@ export const fields = (
|
|
|
342
354
|
</RepeatedFieldGroup>
|
|
343
355
|
<FieldGroup label="Style" name="style" tab="STYLE">
|
|
344
356
|
<AccordionItem.StyleFields />
|
|
357
|
+
<AccordionTitle.StyleFields />
|
|
345
358
|
</FieldGroup>
|
|
346
359
|
</ModuleFields>
|
|
347
360
|
);
|
|
@@ -1,35 +1,23 @@
|
|
|
1
|
-
import { ChoiceField
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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"
|
|
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
|
|
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
|
|
@@ -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,
|
|
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
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FormField } from '@hubspot/cms-components/fields';
|
|
2
|
+
import { ContentFieldsProps } from './types.js';
|
|
3
|
+
|
|
4
|
+
const ContentFields = ({
|
|
5
|
+
formIdLabel = 'Form',
|
|
6
|
+
formIdName = 'form',
|
|
7
|
+
formIdDefault = {},
|
|
8
|
+
}: ContentFieldsProps) => {
|
|
9
|
+
return (
|
|
10
|
+
<FormField
|
|
11
|
+
label={formIdLabel}
|
|
12
|
+
name={formIdName}
|
|
13
|
+
embedVersions={['v4']}
|
|
14
|
+
default={formIdDefault}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default ContentFields;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @ts-expect-error -- ?island not typed
|
|
2
|
+
import FormIsland from './islands/FormIsland.js?island';
|
|
3
|
+
import { FormProps } from './types.js';
|
|
4
|
+
import { Island } from '@hubspot/cms-components';
|
|
5
|
+
import ContentFields from './ContentFields.js';
|
|
6
|
+
|
|
7
|
+
const FormComponent = (props: FormProps) => {
|
|
8
|
+
return <Island module={FormIsland} {...props} />;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type FormComponentType = typeof FormComponent & {
|
|
12
|
+
ContentFields: typeof ContentFields;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const Form = FormComponent as FormComponentType;
|
|
16
|
+
Form.ContentFields = ContentFields;
|
|
17
|
+
|
|
18
|
+
export default Form;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { FormProps, InternalFormProps } from '../types.js';
|
|
3
|
+
import { getHubID } from '@hubspot/cms-components';
|
|
4
|
+
|
|
5
|
+
const getScriptSrc = (portalId: number, env: string) => {
|
|
6
|
+
const host = env === 'qa' ? 'js.hsformsqa.net' : 'js.hsforms.net';
|
|
7
|
+
return `https://${host}/forms/embed/${portalId}.js`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const FormIsland = ({ formId, env }: FormProps & InternalFormProps) => {
|
|
11
|
+
const portalId = getHubID();
|
|
12
|
+
const resolvedEnv = env === 'qa' ? env : 'prod';
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!formId || !portalId) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const scriptSrc = getScriptSrc(portalId, resolvedEnv);
|
|
20
|
+
const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
|
|
21
|
+
|
|
22
|
+
if (existingScript) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const script = document.createElement('script');
|
|
27
|
+
script.src = scriptSrc;
|
|
28
|
+
document.head.appendChild(script);
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
script.remove();
|
|
32
|
+
};
|
|
33
|
+
}, [formId, portalId, resolvedEnv]);
|
|
34
|
+
|
|
35
|
+
if (!formId || !portalId) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
<script src="https://js.hsformsqa.net/forms/embed/102771136.js" defer></script>
|
|
41
|
+
<div class="hs-form-frame" data-env="qa" data-region="na1" data-form-id="53e5b258-4526-4012-9274-8bbe23ab2d09" data-portal-id="102771136"></div>
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
className="hs-form-frame"
|
|
47
|
+
data-form-id={formId}
|
|
48
|
+
data-portal-id={portalId}
|
|
49
|
+
data-env={resolvedEnv}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default FormIsland;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FormFieldDefaults } from '@hubspot/cms-components/fields';
|
|
2
|
+
export type InternalFormProps = {
|
|
3
|
+
env?: 'qa' | 'prod';
|
|
4
|
+
};
|
|
5
|
+
export type FormProps = {
|
|
6
|
+
formId: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ContentFieldsProps = {
|
|
10
|
+
formIdLabel?: string;
|
|
11
|
+
formIdName?: string;
|
|
12
|
+
formIdDefault?: typeof FormFieldDefaults;
|
|
13
|
+
};
|
|
@@ -11,10 +11,10 @@ const StyleFields = ({
|
|
|
11
11
|
iconBackgroundColorDefault = { color: '#2C2C2C', opacity: 100 },
|
|
12
12
|
iconShapeLabel = 'Icon shape',
|
|
13
13
|
iconShapeName = 'iconShape',
|
|
14
|
-
iconShapeDefault = '
|
|
14
|
+
iconShapeDefault = 'square',
|
|
15
15
|
iconSizeLabel = 'Icon size',
|
|
16
16
|
iconSizeName = 'iconSize',
|
|
17
|
-
iconSizeDefault = '
|
|
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
|
-
['
|
|
45
|
-
['
|
|
46
|
-
['
|
|
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
|
-
['
|
|
58
|
-
['
|
|
59
|
-
['
|
|
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
|
|
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
|
-
|
|
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={
|
|
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
|
-
...(
|
|
54
|
-
'--hscl-iconBackground-shape':
|
|
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;
|
|
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;
|
|
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="
|
|
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 (`'
|
|
148
|
-
- `iconSize`: ChoiceField for icon size (16px
|
|
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={
|
|
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:
|
|
197
|
-
- `--hscl-icon-size`: Icon width and height (set
|
|
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
|
-
- **
|
|
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**:
|
|
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:
|
|
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
|
|
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 (
|
|
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?:
|
|
66
|
+
iconShapeDefault?: IconShape;
|
|
64
67
|
iconSizeLabel?: string;
|
|
65
68
|
iconSizeName?: string;
|
|
66
|
-
iconSizeDefault?:
|
|
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'] = [
|