@hubspot/cms-component-library 0.1.0 → 0.2.0
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/AccordionContent/ContentFields.tsx +5 -3
- package/components/componentLibrary/Accordion/AccordionItem/StyleFields.tsx +5 -3
- package/components/componentLibrary/Accordion/AccordionItem/index.module.scss +2 -2
- package/components/componentLibrary/Accordion/AccordionItem/index.tsx +3 -3
- package/components/componentLibrary/Accordion/AccordionTitle/ContentFields.tsx +5 -3
- package/components/componentLibrary/Accordion/AccordionTitle/index.module.scss +2 -2
- package/components/componentLibrary/Accordion/stories/Accordion.stories.tsx +80 -1
- package/components/componentLibrary/Accordion/stories/AccordionDecorator.tsx +14 -14
- package/components/componentLibrary/Button/ContentFields.tsx +5 -3
- package/components/componentLibrary/Button/StyleFields.tsx +5 -3
- package/components/componentLibrary/Button/index.module.scss +22 -14
- package/components/componentLibrary/Button/index.tsx +6 -6
- package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +30 -1
- package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +38 -1
- package/components/componentLibrary/Button/stories/ButtonDecorator.tsx +1 -1
- package/components/componentLibrary/Card/StyleFields.tsx +5 -3
- package/components/componentLibrary/Card/stories/Card.stories.tsx +46 -1
- package/components/componentLibrary/Card/stories/CardDecorator.tsx +1 -1
- package/components/componentLibrary/Divider/ContentFields.tsx +5 -3
- package/components/componentLibrary/Divider/StyleFields.tsx +5 -3
- package/components/componentLibrary/Divider/index.module.scss +6 -6
- package/components/componentLibrary/Divider/index.tsx +7 -3
- package/components/componentLibrary/Divider/stories/Divider.stories.tsx +44 -50
- package/components/componentLibrary/Divider/stories/{DividerDecorator.module.css → DividerDecorator.module.scss} +5 -4
- package/components/componentLibrary/Divider/stories/DividerDecorator.tsx +1 -1
- package/components/componentLibrary/Divider/types.ts +3 -1
- package/components/componentLibrary/Drawer/hooks/index.tsx +13 -0
- package/components/componentLibrary/Drawer/index.module.scss +94 -0
- package/components/componentLibrary/Drawer/index.tsx +131 -0
- package/components/componentLibrary/Drawer/llm.txt +416 -0
- package/components/componentLibrary/Drawer/stories/Drawer.stories.tsx +512 -0
- package/components/componentLibrary/Drawer/stories/DrawerDecorator.module.scss +8 -0
- package/components/componentLibrary/Drawer/stories/DrawerDecorator.tsx +18 -0
- package/components/componentLibrary/Drawer/types.ts +25 -0
- package/components/componentLibrary/Flex/stories/FlexDecorator.tsx +1 -1
- package/components/componentLibrary/Flex/types.ts +3 -1
- package/components/componentLibrary/Grid/stories/Grid.stories.tsx +454 -152
- package/components/componentLibrary/Grid/stories/GridDecorator.tsx +2 -2
- package/components/componentLibrary/Heading/ContentFields.tsx +5 -3
- package/components/componentLibrary/Heading/StyleFields.tsx +11 -9
- package/components/componentLibrary/Heading/index.tsx +3 -3
- package/components/componentLibrary/Heading/llm.txt +8 -8
- package/components/componentLibrary/Heading/stories/Heading.stories.tsx +3 -3
- package/components/componentLibrary/Heading/stories/HeadingDecorator.tsx +1 -1
- package/components/componentLibrary/Heading/types.ts +4 -4
- package/components/componentLibrary/Icon/ContentFields.tsx +5 -3
- package/components/componentLibrary/Icon/stories/Icon.stories.tsx +1 -1
- package/components/componentLibrary/Icon/stories/IconDecorator.tsx +1 -1
- package/components/componentLibrary/Image/ContentFields.tsx +5 -3
- package/components/componentLibrary/Image/index.tsx +4 -4
- package/components/componentLibrary/Image/llm.txt +17 -17
- package/components/componentLibrary/Image/stories/Image.stories.tsx +61 -18
- package/components/componentLibrary/Image/stories/ImageDecorator.tsx +1 -1
- package/components/componentLibrary/Image/types.ts +2 -2
- package/components/componentLibrary/LanguageSwitcher/ContentFields.tsx +18 -0
- package/components/componentLibrary/LanguageSwitcher/LanguageOptions.module.scss +37 -0
- package/components/componentLibrary/LanguageSwitcher/LanguageOptions.tsx +65 -0
- package/components/componentLibrary/LanguageSwitcher/StyleFields.tsx +48 -0
- package/components/componentLibrary/LanguageSwitcher/_dummyData.tsx +247 -0
- package/components/componentLibrary/LanguageSwitcher/assets/Globe.tsx +16 -0
- package/components/componentLibrary/LanguageSwitcher/index.module.scss +58 -0
- package/components/componentLibrary/LanguageSwitcher/index.tsx +125 -0
- package/components/componentLibrary/LanguageSwitcher/llm.txt +380 -0
- package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcher.stories.tsx +349 -0
- package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.module.scss +5 -0
- package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.tsx +8 -0
- package/components/componentLibrary/LanguageSwitcher/types.ts +48 -0
- package/components/componentLibrary/LanguageSwitcher/utils.tsx +38 -0
- package/components/componentLibrary/Link/ContentFields.tsx +5 -3
- package/components/componentLibrary/Link/StyleFields.tsx +5 -3
- package/components/componentLibrary/Link/index.module.scss +10 -0
- package/components/componentLibrary/Link/index.tsx +24 -14
- package/components/componentLibrary/Link/stories/Link.stories.tsx +35 -5
- package/components/componentLibrary/Link/stories/LinkDecorator.tsx +11 -1
- package/components/componentLibrary/Link/types.ts +22 -13
- package/components/componentLibrary/List/ContentFields.tsx +5 -3
- package/components/componentLibrary/List/ListItem/ContentFields.tsx +6 -17
- package/components/componentLibrary/List/ListItem/index.module.scss +1 -13
- package/components/componentLibrary/List/ListItem/index.tsx +3 -30
- package/components/componentLibrary/List/ListItem/types.ts +1 -16
- package/components/componentLibrary/List/StyleFields.tsx +15 -18
- package/components/componentLibrary/List/index.module.scss +3 -0
- package/components/componentLibrary/List/index.tsx +5 -2
- package/components/componentLibrary/List/llm.txt +73 -103
- package/components/componentLibrary/List/stories/List.stories.tsx +56 -80
- package/components/componentLibrary/List/stories/ListDecorator.tsx +3 -6
- package/components/componentLibrary/List/types.ts +1 -3
- package/components/componentLibrary/Logo/_dummyLogoData.ts +12 -0
- package/components/componentLibrary/Logo/assets/hubspot-logo.png +0 -0
- package/components/componentLibrary/Logo/index.module.scss +22 -0
- package/components/componentLibrary/Logo/index.tsx +73 -0
- package/components/componentLibrary/Logo/llm.txt +262 -0
- package/components/componentLibrary/Logo/stories/Logo.stories.tsx +88 -0
- package/components/componentLibrary/Logo/stories/LogoDecorator.module.scss +10 -0
- package/components/componentLibrary/Logo/stories/LogoDecorator.tsx +8 -0
- package/components/componentLibrary/Logo/types.tsx +16 -0
- package/components/componentLibrary/Menu/ContentFields.tsx +16 -0
- package/components/componentLibrary/Menu/MenuItem/Chevron/index.module.scss +6 -0
- package/components/componentLibrary/Menu/MenuItem/Chevron/index.tsx +17 -0
- package/components/componentLibrary/Menu/MenuItem/index.module.scss +7 -0
- package/components/componentLibrary/Menu/MenuItem/index.tsx +266 -0
- package/components/componentLibrary/Menu/MenuItem/types.ts +17 -0
- package/components/componentLibrary/Menu/NavigationMenu/ContentFields.tsx +20 -0
- package/components/componentLibrary/Menu/NavigationMenu/index.tsx +18 -0
- package/components/componentLibrary/Menu/NavigationMenu/islands/NavigationMenuIsland.tsx +95 -0
- package/components/componentLibrary/Menu/NavigationMenu/islands/index.module.scss +100 -0
- package/components/componentLibrary/Menu/NavigationMenu/islands/types.ts +19 -0
- package/components/componentLibrary/Menu/NavigationMenu/llm.txt +197 -0
- package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenu.stories.tsx +286 -0
- package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.module.scss +15 -0
- package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.tsx +12 -0
- package/components/componentLibrary/Menu/NavigationMenu/types.ts +3 -0
- package/components/componentLibrary/Menu/VerticalMenu/ContentFields.tsx +20 -0
- package/components/componentLibrary/Menu/VerticalMenu/index.tsx +18 -0
- package/components/componentLibrary/Menu/VerticalMenu/islands/index.module.scss +53 -0
- package/components/componentLibrary/Menu/VerticalMenu/islands/verticalMenuIsland.tsx +78 -0
- package/components/componentLibrary/Menu/VerticalMenu/llm.txt +177 -0
- package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenu.stories.tsx +242 -0
- package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.module.scss +19 -0
- package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.tsx +12 -0
- package/components/componentLibrary/Menu/VerticalMenu/types.ts +21 -0
- package/components/componentLibrary/Menu/_dummyMenuData.js +1346 -0
- package/components/componentLibrary/Menu/types.ts +56 -0
- package/components/componentLibrary/Menu/utils/transformMenuData.ts +11 -0
- package/components/componentLibrary/_patterns/README.md +15 -17
- package/components/componentLibrary/_patterns/checklist-and-examples.md +17 -17
- package/components/componentLibrary/_patterns/component-structure.md +21 -23
- package/components/componentLibrary/_patterns/css-patterns.md +170 -18
- package/components/componentLibrary/_patterns/field-patterns.md +97 -27
- package/components/componentLibrary/_patterns/function-declaration-patterns.md +281 -0
- package/components/componentLibrary/_patterns/llm-txt.template.md +4 -2
- package/components/componentLibrary/_patterns/prop-naming-patterns.md +208 -0
- package/components/componentLibrary/_patterns/storybook-patterns.md +25 -8
- package/components/componentLibrary/_patterns/typescript-patterns.md +6 -3
- package/package.json +4 -2
- /package/components/componentLibrary/Button/stories/{ButtonDecorator.module.css → ButtonDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Card/stories/{CardDecorator.module.css → CardDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Flex/stories/{FlexDecorator.module.css → FlexDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Grid/stories/{GridDecorator.module.css → GridDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Heading/stories/{HeadingDecorator.module.css → HeadingDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Icon/stories/{IconDecorator.module.css → IconDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Image/stories/{ImageDecorator.module.css → ImageDecorator.module.scss} +0 -0
- /package/components/componentLibrary/Image/stories/assets/{catSmile.jpg → cat-smile.jpg} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlignmentFieldType,
|
|
3
|
+
MenuFieldDefaults,
|
|
4
|
+
} from '@hubspot/cms-components/fields';
|
|
5
|
+
import { PageTreeNode } from '@hubspot/cms-components';
|
|
6
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
export type MenuOrientation = 'horizontal' | 'vertical';
|
|
9
|
+
|
|
10
|
+
export type MenuItem = MenuDataItem;
|
|
11
|
+
|
|
12
|
+
export type MenuRenderProps = {
|
|
13
|
+
orientation?: MenuOrientation;
|
|
14
|
+
alignment?: AlignmentFieldType['default'];
|
|
15
|
+
maxDepth?: number;
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
variant?: 'primary' | 'secondary';
|
|
18
|
+
className?: string;
|
|
19
|
+
style?: CSSProperties;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type MenuProps = MenuRenderProps & {
|
|
23
|
+
menuId?: string;
|
|
24
|
+
items?: MenuItem[];
|
|
25
|
+
children?: MenuItem[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ContentFieldsProps = {
|
|
29
|
+
menuLabel?: string;
|
|
30
|
+
menuName?: string;
|
|
31
|
+
menuDefault?: typeof MenuFieldDefaults;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type StyleFieldsProps = {
|
|
35
|
+
orientationLabel?: string;
|
|
36
|
+
orientationName?: string;
|
|
37
|
+
orientationDefault?: MenuOrientation;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface MenuProviderProps {
|
|
41
|
+
maxDepth?: number;
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
useDummyData?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type MenuDataItem = PageTreeNode;
|
|
47
|
+
|
|
48
|
+
export type EnrichedMenuDataItem = MenuDataItem & {
|
|
49
|
+
_id: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export interface MenuContextValue {
|
|
53
|
+
maxDepth?: number;
|
|
54
|
+
orientation?: 'horizontal' | 'vertical';
|
|
55
|
+
children?: EnrichedMenuDataItem[];
|
|
56
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EnrichedMenuDataItem, MenuDataItem } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export const addIdsToMenuItems = (
|
|
4
|
+
items: MenuDataItem[]
|
|
5
|
+
): EnrichedMenuDataItem[] => {
|
|
6
|
+
return items.map(item => ({
|
|
7
|
+
...item,
|
|
8
|
+
_id: crypto.randomUUID(),
|
|
9
|
+
children: item.children.length > 0 ? addIdsToMenuItems(item.children) : [],
|
|
10
|
+
}));
|
|
11
|
+
};
|
|
@@ -13,6 +13,12 @@ CSS variable naming, styling conventions, modules, and state-based patterns.
|
|
|
13
13
|
### 📝 [TypeScript Patterns](./typescript-patterns.md)
|
|
14
14
|
Type definitions, union types, discriminated unions, and conditional field props.
|
|
15
15
|
|
|
16
|
+
### ⚡ [Function Declaration Patterns](./function-declaration-patterns.md)
|
|
17
|
+
React-related code (components, JSX-returning functions, callbacks) uses arrow functions. Non-React utilities can use traditional functions.
|
|
18
|
+
|
|
19
|
+
### 🏷️ [Prop Naming Patterns](./prop-naming-patterns.md)
|
|
20
|
+
Standardized naming conventions for component props to ensure consistency across the library.
|
|
21
|
+
|
|
16
22
|
### 📋 [Field Patterns](./field-patterns.md)
|
|
17
23
|
Field components, property ordering, visibility rules, and nested patterns.
|
|
18
24
|
|
|
@@ -22,23 +28,15 @@ Story structure, utilities, mocks, and essential story types.
|
|
|
22
28
|
### ✅ [Checklist and Examples](./checklist-and-examples.md)
|
|
23
29
|
Comprehensive component creation checklist, complete reference examples, step-by-step validation guide, and full component implementation examples.
|
|
24
30
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
### Creating a New Component Checklist
|
|
31
|
+
## Creating a New Component
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Reference these pattern files in order when building a new component:
|
|
30
34
|
|
|
31
35
|
1. **[Component Structure](./component-structure.md)** - Set up files and basic structure
|
|
32
|
-
2. **[
|
|
33
|
-
3. **[
|
|
34
|
-
4. **[
|
|
35
|
-
5. **[
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
**CSS Variable Naming:** `--hscl-componentName-property-state` → [CSS Patterns](./css-patterns.md#css-variable-naming-convention)
|
|
40
|
-
**Static Class Names:** `'hscl-componentName'` → [Component Structure](./component-structure.md#static-class-names)
|
|
41
|
-
**Props Destructuring:** Standard pattern → [Component Structure](./component-structure.md#props-destructuring-pattern)
|
|
42
|
-
**Field Props:** `{field}Label`, `{field}Name`, `{field}Default` → [TypeScript Patterns](./typescript-patterns.md#field-props-types)
|
|
43
|
-
**Story Categories:** Basic, Content, Icon, etc. → [Storybook Patterns](./storybook-patterns.md#argtypes-organization)
|
|
44
|
-
|
|
36
|
+
2. **[Function Declaration Patterns](./function-declaration-patterns.md)** - Use arrow functions for React code
|
|
37
|
+
3. **[TypeScript Patterns](./typescript-patterns.md)** - Define types in `types.ts`
|
|
38
|
+
4. **[Prop Naming Patterns](./prop-naming-patterns.md)** - Name props consistently
|
|
39
|
+
5. **[CSS Patterns](./css-patterns.md)** - Create styles with proper variable naming
|
|
40
|
+
6. **[Field Patterns](./field-patterns.md)** - Add ContentFields and StyleFields (if needed)
|
|
41
|
+
7. **[Storybook Patterns](./storybook-patterns.md)** - Create stories for documentation (optional)
|
|
42
|
+
8. **[LLM Documentation Template](./llm-txt.template.md)** - Create `llm.txt` for AI/LLM consumption
|
|
@@ -31,13 +31,14 @@ When creating a new component in componentLibrary, ensure:
|
|
|
31
31
|
|
|
32
32
|
### Component Implementation
|
|
33
33
|
- [ ] Component exports as compound component with field attachments
|
|
34
|
-
- [ ] Props
|
|
34
|
+
- [ ] Props destructured inline in function signature with defaults
|
|
35
35
|
- [ ] Helper functions defined outside component (when reusable or for readability)
|
|
36
36
|
- [ ] Proper use of early returns for conditional rendering
|
|
37
37
|
- [ ] `{...rest}` spread to underlying HTML element
|
|
38
38
|
|
|
39
39
|
### Field Components
|
|
40
40
|
- [ ] ContentFields and StyleFields export as default functions
|
|
41
|
+
- [ ] Field props destructured inline in function signature with defaults
|
|
41
42
|
- [ ] Field props support label, name, and default customization
|
|
42
43
|
- [ ] ChoiceField choices use `[value, displayLabel]` format
|
|
43
44
|
- [ ] NumberFields include appropriate `suffix`, `min`, `max` props
|
|
@@ -96,14 +97,13 @@ import StyleFields from './StyleFields.js';
|
|
|
96
97
|
import cx from '../utils/classname.js';
|
|
97
98
|
import { ComponentProps } from './types.js';
|
|
98
99
|
|
|
99
|
-
const ComponentNameComponent = (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} = props;
|
|
100
|
+
const ComponentNameComponent = ({
|
|
101
|
+
variant = 'primary',
|
|
102
|
+
className = '',
|
|
103
|
+
style = {},
|
|
104
|
+
children,
|
|
105
|
+
...rest
|
|
106
|
+
}: ComponentProps) => {
|
|
107
107
|
|
|
108
108
|
const cssVariables: React.CSSProperties & {
|
|
109
109
|
[key: `--${string}`]: string | undefined;
|
|
@@ -184,13 +184,11 @@ export type StyleFieldsProps = {
|
|
|
184
184
|
import { TextField } from '@hubspot/cms-components/fields';
|
|
185
185
|
import { ContentFieldsProps } from './types.js';
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
} = props;
|
|
193
|
-
|
|
187
|
+
const ContentFields = ({
|
|
188
|
+
fieldLabel = 'Field',
|
|
189
|
+
fieldName = 'field',
|
|
190
|
+
fieldDefault = 'Default',
|
|
191
|
+
}: ContentFieldsProps) => {
|
|
194
192
|
return (
|
|
195
193
|
<TextField
|
|
196
194
|
label={fieldLabel}
|
|
@@ -198,5 +196,7 @@ export default function ContentFields(props: ContentFieldsProps) {
|
|
|
198
196
|
default={fieldDefault}
|
|
199
197
|
/>
|
|
200
198
|
);
|
|
201
|
-
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export default ContentFields;
|
|
202
202
|
```
|
|
@@ -80,23 +80,22 @@ const sharedProps = {
|
|
|
80
80
|
|
|
81
81
|
## Props Destructuring Pattern
|
|
82
82
|
|
|
83
|
-
Components
|
|
83
|
+
Components destructure props inline in the function signature with defaults:
|
|
84
84
|
|
|
85
85
|
```typescript
|
|
86
|
-
const ComponentName = (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} = props;
|
|
86
|
+
const ComponentName = ({
|
|
87
|
+
// Required props first (no defaults)
|
|
88
|
+
requiredProp,
|
|
89
|
+
|
|
90
|
+
// Optional props with defaults
|
|
91
|
+
variant = 'primary',
|
|
92
|
+
className = '',
|
|
93
|
+
style = {},
|
|
94
|
+
children = 'Default content',
|
|
95
|
+
|
|
96
|
+
// Spread remaining props
|
|
97
|
+
...rest
|
|
98
|
+
}: ComponentProps) => {
|
|
100
99
|
```
|
|
101
100
|
|
|
102
101
|
**Standard props every component should accept:**
|
|
@@ -232,14 +231,13 @@ import StyleFields from './StyleFields.js';
|
|
|
232
231
|
import cx from '../utils/classname.js';
|
|
233
232
|
import { ComponentProps } from './types.js';
|
|
234
233
|
|
|
235
|
-
const ComponentNameComponent = (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
} = props;
|
|
234
|
+
const ComponentNameComponent = ({
|
|
235
|
+
variant = 'primary',
|
|
236
|
+
className = '',
|
|
237
|
+
style = {},
|
|
238
|
+
children,
|
|
239
|
+
...rest
|
|
240
|
+
}: ComponentProps) => {
|
|
243
241
|
|
|
244
242
|
const cssVariables: React.CSSProperties & {
|
|
245
243
|
[key: `--${string}`]: string | undefined;
|
|
@@ -4,14 +4,17 @@ This document outlines CSS variable naming conventions, styling patterns, and CS
|
|
|
4
4
|
|
|
5
5
|
## CSS Variable Naming Convention
|
|
6
6
|
|
|
7
|
-
**Pattern:** `--hscl-componentName-
|
|
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`, `
|
|
11
|
-
- **
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
**
|
|
10
|
+
- **Component name:** Lowercase component name in camelCase, with name taken from the index.tsx file (e.g., `button`, `heading`, `icon`, `divider`, `image`, `accordion`)
|
|
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
|
+
- Omit for properties that apply to the main component element itself
|
|
13
|
+
- Include for properties that apply to specific sub-elements
|
|
14
|
+
- **CSS Property:** CSS property name in camelCase (e.g., `backgroundColor`, `fontSize`, `borderColor`, `paddingBlock`)
|
|
15
|
+
- **State (optional):** State suffix for interactive states (e.g., `-hover`, `-focus`, `-disabled`, `-active`)
|
|
16
|
+
|
|
17
|
+
**Examples for main component element:**
|
|
15
18
|
```css
|
|
16
19
|
--hscl-button-backgroundColor
|
|
17
20
|
--hscl-button-backgroundColor-hover
|
|
@@ -19,11 +22,29 @@ This document outlines CSS variable naming conventions, styling patterns, and CS
|
|
|
19
22
|
--hscl-heading-fontSize
|
|
20
23
|
--hscl-heading-textAlign
|
|
21
24
|
--hscl-divider-borderColor
|
|
22
|
-
--hscl-divider-borderStyle
|
|
23
25
|
--hscl-icon-fill
|
|
24
|
-
--hscl-
|
|
26
|
+
--hscl-card-padding
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Examples for sub-elements:**
|
|
30
|
+
```css
|
|
31
|
+
--hscl-button-icon-fill
|
|
32
|
+
--hscl-accordion-body-color
|
|
33
|
+
--hscl-accordion-body-paddingBlock
|
|
34
|
+
--hscl-accordion-title-backgroundColor
|
|
35
|
+
--hscl-accordion-title-fontSize
|
|
36
|
+
--hscl-drawer-overlay-backgroundColor
|
|
37
|
+
--hscl-navigationMenu-submenu-linkColor
|
|
38
|
+
--hscl-navigationMenu-submenu-backgroundColor-hover
|
|
25
39
|
```
|
|
26
40
|
|
|
41
|
+
**Naming Best Practices:**
|
|
42
|
+
- Use camelCase throughout (never use underscores or hyphens within segments)
|
|
43
|
+
- Property names should match CSS property names when possible (e.g., `backgroundColor` not `bgColor`, `fill` not `fillColor`)
|
|
44
|
+
- Element names should be descriptive and singular (e.g., `icon`, `body`, `header`)
|
|
45
|
+
- State suffixes should match CSS pseudo-classes (e.g., `hover`, `focus`, `active`, `disabled`)
|
|
46
|
+
- Avoid redundant words (e.g., use `--hscl-list-item-color` not `--hscl-list-item-text-color`)
|
|
47
|
+
|
|
27
48
|
**Nested/Dynamic Variables:**
|
|
28
49
|
```typescript
|
|
29
50
|
// Example from Heading component - uses template variables
|
|
@@ -31,44 +52,86 @@ This document outlines CSS variable naming conventions, styling patterns, and CS
|
|
|
31
52
|
'--hscl-heading-fontSize': `var(--hscl-heading-${displayAsValue}-fontSize)`
|
|
32
53
|
```
|
|
33
54
|
|
|
55
|
+
**Note:** When using dynamic variants (like h1, h2, display1), use numbers without underscores (e.g., `display1` not `display_1`).
|
|
56
|
+
|
|
34
57
|
## CSS Variables Application
|
|
35
58
|
|
|
36
|
-
Components apply CSS variables through the `style` prop:
|
|
59
|
+
Components apply CSS variables through the `style` prop using the `CSSVariables` type:
|
|
37
60
|
|
|
38
61
|
```typescript
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
// Import the CSSVariables type
|
|
63
|
+
import type { CSSVariables } from '../utils/types.js';
|
|
64
|
+
|
|
65
|
+
// ALWAYS define CSS variables as typed constants
|
|
66
|
+
const cssVariables: CSSVariables = {
|
|
43
67
|
'--hscl-component-property': value,
|
|
44
68
|
'--hscl-component-property2': `${numericValue}px`, // Include units when needed
|
|
45
69
|
};
|
|
46
70
|
|
|
47
|
-
//
|
|
71
|
+
// Then use in JSX
|
|
48
72
|
<Element style={{ ...cssVariables, ...style }} />
|
|
73
|
+
<Element style={cssVariables} />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**The CSSVariables Type:**
|
|
77
|
+
|
|
78
|
+
The `CSSVariables` type extends `React.CSSProperties` to support CSS custom properties:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
export type CSSVariables = React.CSSProperties & {
|
|
82
|
+
[key: `--${string}`]: string | undefined;
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Important: Always Define Variables, Never Inline**
|
|
87
|
+
|
|
88
|
+
❌ **Don't** inline CSS variable objects directly in JSX (causes TypeScript errors):
|
|
89
|
+
```typescript
|
|
90
|
+
// This will cause TypeScript errors
|
|
91
|
+
<Element style={{ '--hscl-property': value }} />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
✅ **Do** define them as typed constants:
|
|
95
|
+
```typescript
|
|
96
|
+
// This works correctly
|
|
97
|
+
const elementStyle: CSSVariables = {
|
|
98
|
+
'--hscl-property': value,
|
|
99
|
+
};
|
|
100
|
+
<Element style={elementStyle} />
|
|
49
101
|
```
|
|
50
102
|
|
|
51
103
|
**Examples:**
|
|
52
104
|
|
|
53
105
|
```typescript
|
|
106
|
+
import type { CSSVariables } from '../utils/types.js';
|
|
107
|
+
|
|
54
108
|
// Heading - dynamic variable references
|
|
55
|
-
const cssVariables:
|
|
109
|
+
const cssVariables: CSSVariables = {
|
|
56
110
|
'--hscl-heading-font': `var(--hscl-heading-${displayAsValue}-font)`,
|
|
57
111
|
'--hscl-heading-fontSize': `var(--hscl-heading-${displayAsValue}-fontSize)`,
|
|
58
112
|
...(alignment && {
|
|
59
113
|
'--hscl-heading-textAlign': alignment.toLowerCase(),
|
|
60
114
|
}),
|
|
61
|
-
}
|
|
115
|
+
};
|
|
62
116
|
|
|
63
117
|
// Divider - direct values with units
|
|
64
|
-
const cssVariables:
|
|
118
|
+
const cssVariables: CSSVariables = {
|
|
65
119
|
'--hscl-divider-alignment': getAlignmentCSSVar(alignment),
|
|
66
120
|
'--hscl-divider-spacing': spacing,
|
|
67
121
|
'--hscl-divider-borderStyle': borderStyle,
|
|
68
122
|
'--hscl-divider-length': `${length}%`,
|
|
69
123
|
'--hscl-divider-thickness': `${thickness}px`,
|
|
70
124
|
...style,
|
|
71
|
-
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// In stories - define within render function
|
|
128
|
+
render: () => {
|
|
129
|
+
const blueColor: CSSVariables = {
|
|
130
|
+
'--hscl-divider-borderColor': '#3b82f6',
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return <Divider thickness={2} style={blueColor} />;
|
|
134
|
+
}
|
|
72
135
|
```
|
|
73
136
|
|
|
74
137
|
## CSS Module Patterns
|
|
@@ -208,6 +271,95 @@ Use `&` for pseudo-classes and nested selectors:
|
|
|
208
271
|
}
|
|
209
272
|
```
|
|
210
273
|
|
|
274
|
+
## Accessibility Patterns for Interactive Components
|
|
275
|
+
|
|
276
|
+
Interactive components (Button, Link, etc.) should implement all relevant accessibility states following HubSpot's CSS guidelines.
|
|
277
|
+
|
|
278
|
+
### Required Interactive States
|
|
279
|
+
|
|
280
|
+
All interactive components should style **hover, focus, active** states:
|
|
281
|
+
|
|
282
|
+
```scss
|
|
283
|
+
.button {
|
|
284
|
+
// Base styles
|
|
285
|
+
background-color: var(--hscl-button-backgroundColor);
|
|
286
|
+
color: var(--hscl-button-color);
|
|
287
|
+
transition: all 0.2s ease;
|
|
288
|
+
|
|
289
|
+
// Respect user's motion preferences
|
|
290
|
+
@media (prefers-reduced-motion: reduce) {
|
|
291
|
+
transition: none;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Hover - visual feedback on mouse over
|
|
295
|
+
&:hover {
|
|
296
|
+
background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
|
|
297
|
+
color: var(--hscl-button-color-hover, var(--hscl-button-color));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Focus-visible - keyboard navigation only (not mouse clicks)
|
|
301
|
+
&:focus-visible {
|
|
302
|
+
outline: var(--hscl-button-outlineWidth-focus, 2px) solid var(--hscl-button-outlineColor-focus, currentColor);
|
|
303
|
+
outline-offset: var(--hscl-button-outlineOffset-focus, 2px);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Active - pressed/clicked state
|
|
307
|
+
&:active {
|
|
308
|
+
background-color: var(--hscl-button-backgroundColor-active, var(--hscl-button-backgroundColor));
|
|
309
|
+
color: var(--hscl-button-color-active, var(--hscl-button-color));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Disabled - non-interactive state
|
|
313
|
+
&:disabled {
|
|
314
|
+
opacity: 0.5;
|
|
315
|
+
cursor: not-allowed;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Key Accessibility Principles
|
|
321
|
+
|
|
322
|
+
1. **Use `:focus-visible` instead of `:focus`**
|
|
323
|
+
- Shows focus outline only for keyboard navigation
|
|
324
|
+
- Better UX - no outline when clicking with mouse
|
|
325
|
+
- Follows modern accessibility best practices
|
|
326
|
+
|
|
327
|
+
2. **Provide fallback values that inherit from base state**
|
|
328
|
+
```scss
|
|
329
|
+
// Good - inherits from base if not customized
|
|
330
|
+
background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
|
|
331
|
+
|
|
332
|
+
// Avoid - no fallback to base state
|
|
333
|
+
background-color: var(--hscl-button-backgroundColor-hover);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
3. **Use `currentColor` for focus outlines**
|
|
337
|
+
- Adapts to component's text color automatically
|
|
338
|
+
- Ensures sufficient contrast in most cases
|
|
339
|
+
```scss
|
|
340
|
+
outline: var(--hscl-button-outlineColor-focus, currentColor);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
4. **Respect reduced motion preferences**
|
|
344
|
+
```scss
|
|
345
|
+
transition: all 0.2s ease;
|
|
346
|
+
|
|
347
|
+
@media (prefers-reduced-motion: reduce) {
|
|
348
|
+
transition: none;
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
5. **All states should be customizable via CSS variables**
|
|
353
|
+
- Enables theme customization
|
|
354
|
+
- Maintains consistent API across components
|
|
355
|
+
|
|
356
|
+
### Pattern Reference
|
|
357
|
+
|
|
358
|
+
See these components for implementation examples:
|
|
359
|
+
- **Button** (`devWorkspace/src/components/componentLibrary/Button/index.module.scss`) - Full implementation with all states and reduced motion support
|
|
360
|
+
- **Link** (`devWorkspace/src/components/componentLibrary/Link/index.module.scss`) - Minimal interactive component with hover, focus, and active states
|
|
361
|
+
- **Logo** (`devWorkspace/src/components/componentLibrary/Logo/index.tsx`) - Composition example: inherits focus styles from Link component when wrapped
|
|
362
|
+
|
|
211
363
|
## Complete CSS Module Example
|
|
212
364
|
|
|
213
365
|
```scss
|
|
@@ -4,17 +4,15 @@ This document outlines patterns for creating and configuring field components in
|
|
|
4
4
|
|
|
5
5
|
## Field Components Pattern
|
|
6
6
|
|
|
7
|
-
Field components
|
|
7
|
+
Field components destructure props inline with defaults for customization:
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} = props;
|
|
17
|
-
|
|
10
|
+
const ContentFields = ({
|
|
11
|
+
// Each field gets label, name, and default props
|
|
12
|
+
fieldLabel = 'Default label',
|
|
13
|
+
fieldName = 'defaultName',
|
|
14
|
+
fieldDefault = 'Default value',
|
|
15
|
+
}: ContentFieldsProps) => {
|
|
18
16
|
return (
|
|
19
17
|
<>
|
|
20
18
|
<TextField
|
|
@@ -25,7 +23,79 @@ export default function ContentFields(props: ContentFieldsProps) {
|
|
|
25
23
|
{/* Additional fields */}
|
|
26
24
|
</>
|
|
27
25
|
);
|
|
28
|
-
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default ContentFields;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Subcomponent Field Placement
|
|
32
|
+
|
|
33
|
+
**Rule:** Define fields on the subcomponent that consumes them.
|
|
34
|
+
|
|
35
|
+
**Anti-pattern:** Rolling fields up to a parent component for "convenience" or centralization.
|
|
36
|
+
|
|
37
|
+
### Reference Example: Accordion
|
|
38
|
+
|
|
39
|
+
| Component | Fields | Reason |
|
|
40
|
+
|-----------|--------|--------|
|
|
41
|
+
| `Accordion` | `StyleFields` (gap) | Parent layout concern |
|
|
42
|
+
| `AccordionItem` | `StyleFields` (variant) | Item-level styling |
|
|
43
|
+
| `AccordionTitle` | `ContentFields` (title, icon) | Title-specific content |
|
|
44
|
+
| `AccordionContent` | `ContentFields` (content) | Content-specific field |
|
|
45
|
+
|
|
46
|
+
**File Structure:**
|
|
47
|
+
```
|
|
48
|
+
Accordion/
|
|
49
|
+
├── index.tsx
|
|
50
|
+
├── types.ts # Accordion StyleFieldsProps (gap)
|
|
51
|
+
├── AccordionItem/
|
|
52
|
+
│ ├── StyleFields.tsx # variant field
|
|
53
|
+
│ └── types.ts
|
|
54
|
+
├── AccordionTitle/
|
|
55
|
+
│ ├── ContentFields.tsx # title, icon fields
|
|
56
|
+
│ └── types.ts
|
|
57
|
+
└── AccordionContent/
|
|
58
|
+
├── ContentFields.tsx # content field
|
|
59
|
+
└── types.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Field Ordering with Multiple Subcomponents
|
|
63
|
+
|
|
64
|
+
When a module uses multiple subcomponents, order fields as follows:
|
|
65
|
+
1. Parent component fields first
|
|
66
|
+
2. Child component fields in DOM/render order
|
|
67
|
+
3. Group fields by subcomponent for clarity
|
|
68
|
+
|
|
69
|
+
**Code Example (module fields.tsx):**
|
|
70
|
+
```typescript
|
|
71
|
+
import {
|
|
72
|
+
FieldGroup,
|
|
73
|
+
ModuleFields,
|
|
74
|
+
RepeatedFieldGroup,
|
|
75
|
+
} from '@hubspot/cms-components/fields';
|
|
76
|
+
import {
|
|
77
|
+
AccordionContent,
|
|
78
|
+
AccordionItem,
|
|
79
|
+
AccordionTitle,
|
|
80
|
+
} from '../../componentLibrary/Accordion/index.js';
|
|
81
|
+
|
|
82
|
+
export const fields = (
|
|
83
|
+
<ModuleFields>
|
|
84
|
+
{/* Content fields in DOM/render order */}
|
|
85
|
+
<RepeatedFieldGroup
|
|
86
|
+
label="Accordion items"
|
|
87
|
+
name="accordionItems"
|
|
88
|
+
occurrence={{ min: 1, max: 20, default: 3 }}
|
|
89
|
+
>
|
|
90
|
+
<AccordionTitle.ContentFields />
|
|
91
|
+
<AccordionContent.ContentFields />
|
|
92
|
+
</RepeatedFieldGroup>
|
|
93
|
+
{/* Style fields grouped in STYLE tab */}
|
|
94
|
+
<FieldGroup label="Style" name="style" tab="STYLE">
|
|
95
|
+
<AccordionItem.StyleFields />
|
|
96
|
+
</FieldGroup>
|
|
97
|
+
</ModuleFields>
|
|
98
|
+
);
|
|
29
99
|
```
|
|
30
100
|
|
|
31
101
|
## Field Label Casing
|
|
@@ -58,18 +128,18 @@ Field properties should be ordered in the following order:
|
|
|
58
128
|
|
|
59
129
|
## Field Component Props Destructuring
|
|
60
130
|
|
|
61
|
-
|
|
131
|
+
All field components destructure props inline in the function signature with defaults:
|
|
62
132
|
|
|
63
133
|
```typescript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} = props;
|
|
70
|
-
|
|
134
|
+
const ContentFields = ({
|
|
135
|
+
fieldLabel = 'Default label',
|
|
136
|
+
fieldName = 'defaultName',
|
|
137
|
+
fieldDefault = 'default value',
|
|
138
|
+
}: ContentFieldsProps) => {
|
|
71
139
|
return <Field label={fieldLabel} name={fieldName} default={fieldDefault} />;
|
|
72
|
-
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export default ContentFields;
|
|
73
143
|
```
|
|
74
144
|
|
|
75
145
|
## Common Field Patterns
|
|
@@ -151,13 +221,11 @@ const SHOW_ICON_VISIBILITY_RULES: Visibility = {
|
|
|
151
221
|
import { TextField } from '@hubspot/cms-components/fields';
|
|
152
222
|
import { ContentFieldsProps } from './types.js';
|
|
153
223
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} = props;
|
|
160
|
-
|
|
224
|
+
const ContentFields = ({
|
|
225
|
+
fieldLabel = 'Field',
|
|
226
|
+
fieldName = 'field',
|
|
227
|
+
fieldDefault = 'Default',
|
|
228
|
+
}: ContentFieldsProps) => {
|
|
161
229
|
return (
|
|
162
230
|
<TextField
|
|
163
231
|
label={fieldLabel}
|
|
@@ -165,5 +233,7 @@ export default function ContentFields(props: ContentFieldsProps) {
|
|
|
165
233
|
default={fieldDefault}
|
|
166
234
|
/>
|
|
167
235
|
);
|
|
168
|
-
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export default ContentFields;
|
|
169
239
|
```
|