@hubspot/cms-component-library 0.1.0 → 0.3.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 +26 -4
- package/components/componentLibrary/Button/index.module.scss +22 -14
- package/components/componentLibrary/Button/index.tsx +6 -6
- package/components/componentLibrary/Button/llm.txt +5 -1
- 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/Button/types.ts +6 -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 +6 -3
- /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,281 @@
|
|
|
1
|
+
# Function Declaration Patterns
|
|
2
|
+
|
|
3
|
+
This document establishes the standard function declaration patterns for the Component Library.
|
|
4
|
+
|
|
5
|
+
## The Rule
|
|
6
|
+
|
|
7
|
+
**React-related code uses arrow functions. Everything else uses traditional functions (or arrow functions if preferred).**
|
|
8
|
+
|
|
9
|
+
### What is "React-related"?
|
|
10
|
+
|
|
11
|
+
- React components (function components)
|
|
12
|
+
- Functions that return JSX
|
|
13
|
+
- React callbacks and event handlers
|
|
14
|
+
- Field components
|
|
15
|
+
- Helper functions within React components that work with JSX or React concepts
|
|
16
|
+
|
|
17
|
+
### What is "Everything else"?
|
|
18
|
+
|
|
19
|
+
- Pure utility functions that don't involve React or JSX
|
|
20
|
+
- Helper functions for data transformation, formatting, validation
|
|
21
|
+
- Functions that operate on plain JavaScript data structures
|
|
22
|
+
|
|
23
|
+
## ✅ Use Arrow Functions For
|
|
24
|
+
|
|
25
|
+
### React Components
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Main components
|
|
29
|
+
const Button = ({
|
|
30
|
+
variant = 'primary',
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}: ButtonProps) => {
|
|
34
|
+
return <button {...rest}>{children}</button>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default Button;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// Field components
|
|
42
|
+
const ContentFields = ({
|
|
43
|
+
fieldLabel = 'Field',
|
|
44
|
+
fieldName = 'field',
|
|
45
|
+
fieldDefault = 'Default',
|
|
46
|
+
}: ContentFieldsProps) => {
|
|
47
|
+
return <TextField label={fieldLabel} name={fieldName} default={fieldDefault} />;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default ContentFields;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Island components
|
|
55
|
+
const NavigationMenuIsland = ({
|
|
56
|
+
justifyMenu = 'flex-start',
|
|
57
|
+
...rest
|
|
58
|
+
}: NavigationMenuProps) => {
|
|
59
|
+
return <nav {...rest}>{/* JSX */}</nav>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default NavigationMenuIsland;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Functions That Return JSX
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const ButtonComponent = ({ buttonType, ...rest }: ButtonProps) => {
|
|
69
|
+
// Helper functions that return JSX use arrow functions
|
|
70
|
+
const renderButton = ({ onClick, disabled, children }: ButtonAsButtonProps) => {
|
|
71
|
+
return (
|
|
72
|
+
<button onClick={onClick} disabled={disabled}>
|
|
73
|
+
{children}
|
|
74
|
+
</button>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const renderLink = ({ href, children }: ButtonAsLinkProps) => {
|
|
79
|
+
return <a href={href}>{children}</a>;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (buttonType === 'button') return renderButton(rest);
|
|
83
|
+
return renderLink(rest);
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### React Callbacks and Event Handlers
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const Component = () => {
|
|
91
|
+
// Event handlers and callbacks use arrow functions
|
|
92
|
+
const handleClick = () => {
|
|
93
|
+
console.log('clicked');
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleKeyDown = (event: React.KeyboardEvent) => {
|
|
97
|
+
if (event.key === 'Enter') {
|
|
98
|
+
// handle enter
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const processData = (data: string) => {
|
|
103
|
+
// processing logic
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return <div onClick={handleClick} onKeyDown={handleKeyDown} />;
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## ✅ Use Traditional Functions For (Optional)
|
|
111
|
+
|
|
112
|
+
Pure utility functions that don't involve React or JSX can use traditional function declarations if preferred:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Pure utility - no React, no JSX
|
|
116
|
+
function formatCurrency(amount: number): string {
|
|
117
|
+
return new Intl.NumberFormat('en-US', {
|
|
118
|
+
style: 'currency',
|
|
119
|
+
currency: 'USD',
|
|
120
|
+
}).format(amount);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Data transformation - no React, no JSX
|
|
124
|
+
function sortByDate(items: Item[]): Item[] {
|
|
125
|
+
return [...items].sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Validation - no React, no JSX
|
|
129
|
+
function isValidEmail(email: string): boolean {
|
|
130
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Note:** Arrow functions are also fine for these cases - use whichever you prefer for non-React utilities.
|
|
135
|
+
|
|
136
|
+
## Why This Pattern?
|
|
137
|
+
|
|
138
|
+
### Arrow Functions for React Code
|
|
139
|
+
|
|
140
|
+
1. **Avoids `this` issues**: Arrow functions don't bind their own `this`, preventing common React pitfalls
|
|
141
|
+
2. **Consistency**: All React components use the same pattern
|
|
142
|
+
3. **Modern convention**: Aligns with current React and TypeScript best practices
|
|
143
|
+
4. **Lexical scoping**: Better behavior with closures and event handlers
|
|
144
|
+
|
|
145
|
+
### Traditional Functions for Utilities
|
|
146
|
+
|
|
147
|
+
1. **Flexibility**: Use either arrow or traditional - whatever fits the use case
|
|
148
|
+
2. **Readability**: Traditional functions can be more readable for complex utility logic
|
|
149
|
+
3. **Hoisting**: Traditional functions are hoisted, which can be useful for utility organization
|
|
150
|
+
|
|
151
|
+
## Scope
|
|
152
|
+
|
|
153
|
+
This pattern applies to:
|
|
154
|
+
|
|
155
|
+
### Must Use Arrow Functions
|
|
156
|
+
|
|
157
|
+
- All React components (`*/index.tsx`)
|
|
158
|
+
- All Field components (`*Fields.tsx`)
|
|
159
|
+
- All Island components (`*/islands/*.tsx`)
|
|
160
|
+
- All helper functions within React components
|
|
161
|
+
- All functions that return JSX
|
|
162
|
+
- All React callbacks and event handlers
|
|
163
|
+
|
|
164
|
+
### Can Use Either
|
|
165
|
+
|
|
166
|
+
- Utility functions that don't involve React/JSX
|
|
167
|
+
- Data transformation functions
|
|
168
|
+
- Validation functions
|
|
169
|
+
- Pure JavaScript helpers
|
|
170
|
+
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
### React Component with Helper Functions
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Button/index.tsx
|
|
177
|
+
const ButtonComponent = ({
|
|
178
|
+
buttonType = 'link',
|
|
179
|
+
variant = 'primary',
|
|
180
|
+
className = '',
|
|
181
|
+
style = {},
|
|
182
|
+
...rest
|
|
183
|
+
}: ButtonProps) => {
|
|
184
|
+
// Arrow function - returns JSX
|
|
185
|
+
const renderButton = ({ onClick, disabled, children }: ButtonAsButtonProps) => {
|
|
186
|
+
return (
|
|
187
|
+
<button onClick={onClick} disabled={disabled}>
|
|
188
|
+
{children}
|
|
189
|
+
</button>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Arrow function - returns JSX
|
|
194
|
+
const renderLink = ({ href, target, children }: ButtonAsLinkProps) => {
|
|
195
|
+
return <a href={href} target={target}>{children}</a>;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (buttonType === 'button') return renderButton(rest);
|
|
199
|
+
return renderLink(rest);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default ButtonComponent;
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Field Component
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// ContentFields.tsx
|
|
209
|
+
const ContentFields = ({
|
|
210
|
+
fieldLabel = 'Field',
|
|
211
|
+
fieldName = 'field',
|
|
212
|
+
fieldDefault = 'Default',
|
|
213
|
+
}: ContentFieldsProps) => {
|
|
214
|
+
return (
|
|
215
|
+
<TextField
|
|
216
|
+
label={fieldLabel}
|
|
217
|
+
name={fieldName}
|
|
218
|
+
default={fieldDefault}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export default ContentFields;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Island Component
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// NavigationMenuIsland.tsx
|
|
230
|
+
const NavigationMenuIsland = ({
|
|
231
|
+
justifyMenu = 'flex-start',
|
|
232
|
+
className = '',
|
|
233
|
+
...rest
|
|
234
|
+
}: NavigationMenuProps) => {
|
|
235
|
+
// Arrow function - React event handler
|
|
236
|
+
const handleKeyDown = (event: React.KeyboardEvent) => {
|
|
237
|
+
if (event.key === 'ArrowDown') {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
// handle navigation
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<nav onKeyDown={handleKeyDown} className={className} {...rest}>
|
|
245
|
+
{/* JSX */}
|
|
246
|
+
</nav>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export default NavigationMenuIsland;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Utility Functions (Non-React)
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// utils/transformMenuData.ts
|
|
257
|
+
// Arrow function is fine for utilities
|
|
258
|
+
export const addIdsToMenuItems = (items: MenuDataItem[]): EnrichedMenuDataItem[] => {
|
|
259
|
+
return items.map(item => ({
|
|
260
|
+
...item,
|
|
261
|
+
_id: crypto.randomUUID(),
|
|
262
|
+
}));
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Traditional function is also fine
|
|
266
|
+
export function formatDate(date: Date): string {
|
|
267
|
+
return date.toISOString().split('T')[0];
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Summary
|
|
272
|
+
|
|
273
|
+
- **React components, JSX-returning functions, React callbacks** → Always arrow functions
|
|
274
|
+
- **Pure utilities (no React/JSX)** → Either arrow or traditional (your choice)
|
|
275
|
+
- **When in doubt** → Use arrow functions
|
|
276
|
+
|
|
277
|
+
## Related Patterns
|
|
278
|
+
|
|
279
|
+
- [Component Structure](./component-structure.md) - General component patterns
|
|
280
|
+
- [TypeScript Patterns](./typescript-patterns.md) - Type definitions and patterns
|
|
281
|
+
- [Field Patterns](./field-patterns.md) - Field component patterns
|
|
@@ -152,7 +152,7 @@ import [COMPONENT_NAME] from '@components/componentLibrary/[COMPONENT_NAME]';
|
|
|
152
152
|
```tsx
|
|
153
153
|
import [COMPONENT_NAME] from '@hubspot/cms-component-library/[COMPONENT_NAME]';
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
const [ModuleName] = ({ fieldValues }) => {
|
|
156
156
|
return (
|
|
157
157
|
<[COMPONENT_NAME]
|
|
158
158
|
[prop1]={fieldValues.[fieldValue1]}
|
|
@@ -161,7 +161,9 @@ export default function [ModuleName]({ fieldValues }) {
|
|
|
161
161
|
{fieldValues.[contentField]}
|
|
162
162
|
</[COMPONENT_NAME]>
|
|
163
163
|
);
|
|
164
|
-
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default [ModuleName];
|
|
165
167
|
```
|
|
166
168
|
|
|
167
169
|
## Styling
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Component Style Prop Naming Patterns
|
|
2
|
+
|
|
3
|
+
**Default:** Use exact CSS property names for style props. Never abbreviate or modify them.
|
|
4
|
+
|
|
5
|
+
**Exception:** Use semantic names when describing intrinsic component characteristics (e.g., Divider's `thickness` instead of `borderWidth`). Use sparingly.
|
|
6
|
+
|
|
7
|
+
## Pattern Rules
|
|
8
|
+
|
|
9
|
+
### Rule 1: Main Component Props
|
|
10
|
+
|
|
11
|
+
For props that style the main component element, use the exact CSS property name with optional state suffix:
|
|
12
|
+
|
|
13
|
+
**Pattern:** `propertyState`
|
|
14
|
+
|
|
15
|
+
**Examples:**
|
|
16
|
+
|
|
17
|
+
- ✅ `color`, `colorHover`, `colorFocus`
|
|
18
|
+
- ✅ `backgroundColor`, `backgroundColorHover`, `backgroundColorFocus`
|
|
19
|
+
- ✅ `borderColor`, `borderWidth`, `borderStyle`
|
|
20
|
+
- ✅ `padding`, `paddingBlock`, `paddingInline`
|
|
21
|
+
|
|
22
|
+
**Common Mistakes:**
|
|
23
|
+
|
|
24
|
+
- ❌ `textColor` (wrong - use `color`)
|
|
25
|
+
- ❌ `background` (wrong - use `backgroundColor`)
|
|
26
|
+
- ❌ `bgColor` (wrong - use `backgroundColor`)
|
|
27
|
+
|
|
28
|
+
### Rule 2: Subcomponent Props
|
|
29
|
+
|
|
30
|
+
For props that style subcomponents or nested elements, prefix with the element name:
|
|
31
|
+
|
|
32
|
+
**Pattern:** `elementPropertyState`
|
|
33
|
+
|
|
34
|
+
**Examples:**
|
|
35
|
+
|
|
36
|
+
- ✅ `menuBackgroundColor`, `menuBackgroundColorHover`
|
|
37
|
+
- ✅ `linkColor`, `linkColorHover`
|
|
38
|
+
- ✅ `iconFill`, `iconStroke`
|
|
39
|
+
- ✅ `titleColor`, `contentPadding`
|
|
40
|
+
|
|
41
|
+
**Common Mistakes:**
|
|
42
|
+
|
|
43
|
+
- ❌ `menuTextColor` (wrong - use `menuColor`)
|
|
44
|
+
- ❌ `menuBackground` (wrong - use `menuBackgroundColor`)
|
|
45
|
+
|
|
46
|
+
### Rule 3: State Suffixes
|
|
47
|
+
|
|
48
|
+
State suffixes must be capitalized and match CSS pseudo-classes:
|
|
49
|
+
|
|
50
|
+
**Valid Suffixes:**
|
|
51
|
+
|
|
52
|
+
- `Hover` - for `:hover` state
|
|
53
|
+
- `Focus` - for `:focus` state
|
|
54
|
+
- `Active` - for `:active` state
|
|
55
|
+
- `Disabled` - for `:disabled` state
|
|
56
|
+
- `Visited` - for `:visited` state (links)
|
|
57
|
+
- `Checked` - for `:checked` state (inputs)
|
|
58
|
+
|
|
59
|
+
**Examples:**
|
|
60
|
+
|
|
61
|
+
- ✅ `colorHover`, `backgroundColorFocus`, `borderColorActive`
|
|
62
|
+
- ❌ `colorHOVER`, `colorHovering`, `colorOnHover`
|
|
63
|
+
|
|
64
|
+
## Field Props Pattern
|
|
65
|
+
|
|
66
|
+
Field configuration props (for CMS) build on the style prop name by adding standard suffixes:
|
|
67
|
+
|
|
68
|
+
**Pattern:** `{propName}Label`, `{propName}Name`, `{propName}Default`
|
|
69
|
+
|
|
70
|
+
**Examples:**
|
|
71
|
+
```typescript
|
|
72
|
+
// Style prop (follows CSS naming)
|
|
73
|
+
color?: ColorFieldValue;
|
|
74
|
+
|
|
75
|
+
// Field configuration props (use the prop name + suffix)
|
|
76
|
+
colorLabel?: string;
|
|
77
|
+
colorName?: string;
|
|
78
|
+
colorDefault?: ColorFieldValue;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Important:** The field props inherit the correct naming from the style prop. If your style prop is `color`, your field props are `colorLabel`, `colorName`, `colorDefault` - not `textColorLabel`, `textColorName`, `textColorDefault`.
|
|
82
|
+
|
|
83
|
+
## Complete Component Example
|
|
84
|
+
|
|
85
|
+
### LanguageSwitcher (Reference Implementation)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// types.ts
|
|
89
|
+
export type LanguageSwitcherProps = {
|
|
90
|
+
// Main component styles
|
|
91
|
+
color?: ColorFieldValue;
|
|
92
|
+
colorHover?: ColorFieldValue;
|
|
93
|
+
|
|
94
|
+
// Subcomponent styles
|
|
95
|
+
menuBackgroundColor?: ColorFieldValue;
|
|
96
|
+
menuBackgroundColorHover?: ColorFieldValue;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type StyleFieldsProps = {
|
|
100
|
+
// Field props for main component
|
|
101
|
+
colorLabel?: string;
|
|
102
|
+
colorName?: string;
|
|
103
|
+
colorDefault?: ColorFieldValue;
|
|
104
|
+
colorHoverLabel?: string;
|
|
105
|
+
colorHoverName?: string;
|
|
106
|
+
colorHoverDefault?: ColorFieldValue;
|
|
107
|
+
|
|
108
|
+
// Field props for subcomponent
|
|
109
|
+
menuBackgroundColorLabel?: string;
|
|
110
|
+
menuBackgroundColorName?: string;
|
|
111
|
+
menuBackgroundColorDefault?: ColorFieldValue;
|
|
112
|
+
menuBackgroundColorHoverLabel?: string;
|
|
113
|
+
menuBackgroundColorHoverName?: string;
|
|
114
|
+
menuBackgroundColorHoverDefault?: ColorFieldValue;
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// index.tsx
|
|
120
|
+
const LanguageSwitcherComponent = ({
|
|
121
|
+
color,
|
|
122
|
+
colorHover,
|
|
123
|
+
menuBackgroundColor,
|
|
124
|
+
menuBackgroundColorHover,
|
|
125
|
+
}: LanguageSwitcherProps) => {
|
|
126
|
+
const cssVariables = {
|
|
127
|
+
...(color?.rgba && { '--hscl-languageSwitcher-color': color.rgba }),
|
|
128
|
+
...(colorHover?.rgba && { '--hscl-languageSwitcher-color-hover': colorHover.rgba }),
|
|
129
|
+
...(menuBackgroundColor?.rgba && {
|
|
130
|
+
'--hscl-languageSwitcher-backgroundColor': menuBackgroundColor.rgba
|
|
131
|
+
}),
|
|
132
|
+
...(menuBackgroundColorHover?.rgba && {
|
|
133
|
+
'--hscl-languageSwitcher-backgroundColor-hover': menuBackgroundColorHover.rgba
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return <div style={cssVariables}>...</div>;
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Edge Cases
|
|
142
|
+
|
|
143
|
+
### 1. Multiple Background Properties
|
|
144
|
+
|
|
145
|
+
When a component needs multiple background-related properties, use exact CSS names:
|
|
146
|
+
|
|
147
|
+
- ✅ `backgroundColor`, `backgroundImage`, `backgroundSize`
|
|
148
|
+
- ❌ `background`, `bgImage`, `bgSize`
|
|
149
|
+
|
|
150
|
+
### 2. Logical Properties
|
|
151
|
+
|
|
152
|
+
Prefer logical properties for better internationalization:
|
|
153
|
+
|
|
154
|
+
- ✅ `paddingBlock`, `paddingInline`, `marginBlock`, `marginInline`
|
|
155
|
+
- ⚠️ `padding`, `margin` (acceptable but less specific)
|
|
156
|
+
|
|
157
|
+
### 3. Shorthand vs Longhand
|
|
158
|
+
|
|
159
|
+
When both exist, prefer the specific property:
|
|
160
|
+
|
|
161
|
+
- ✅ `borderColor`, `borderWidth`, `borderStyle` (specific)
|
|
162
|
+
- ⚠️ `border` (acceptable for simple use cases)
|
|
163
|
+
|
|
164
|
+
### 4. Prop Types
|
|
165
|
+
|
|
166
|
+
- **Style Props:** Use CSS property names (`color`, `backgroundColor`, `padding`)
|
|
167
|
+
- **Behavior Props:** Use descriptive names (`onClick`, `disabled`, `variant`)
|
|
168
|
+
- **Semantic Props:** Use sparingly for intrinsic characteristics (`thickness`, `length` for Divider)
|
|
169
|
+
|
|
170
|
+
### 5. Semantic Props Example
|
|
171
|
+
|
|
172
|
+
Divider uses semantic props because they describe what a divider *is*:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
thickness?: number; // Divider's thickness (not borderWidth)
|
|
176
|
+
length?: number; // Divider's length (abstracts width/height based on orientation)
|
|
177
|
+
spacing?: string; // Space around it (abstracts marginBlock/marginInline)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Use semantic props when the name matches users' mental model better than CSS properties. Otherwise, use CSS property names.
|
|
181
|
+
|
|
182
|
+
### 6. HTML Attributes vs CSS Properties
|
|
183
|
+
|
|
184
|
+
Some props map to HTML attributes rather than CSS properties (e.g., Image's `width`/`height`):
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Image component - props map to HTML attributes
|
|
188
|
+
width?: number; // Sets <img width="..."> (intrinsic sizing)
|
|
189
|
+
height?: number; // Sets <img height="..."> (intrinsic sizing)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This is acceptable because these control intrinsic rendering, not just styling. For CSS-based sizing, use `style` prop or `className`. If CSS props are needed in the future, they can be added separately (e.g., `cssWidth`).
|
|
193
|
+
|
|
194
|
+
### 7. Shared vs Subcomponent Styles
|
|
195
|
+
|
|
196
|
+
- Use `property` (main) when value is inherited/reused: `color`
|
|
197
|
+
- Use `elementProperty` (sub) when specific to a subcomponent: `menuBackgroundColor`
|
|
198
|
+
- Prefer sharing colors for consistency
|
|
199
|
+
|
|
200
|
+
## Validation
|
|
201
|
+
|
|
202
|
+
- ✅ Style props match exact CSS property names (no abbreviations)
|
|
203
|
+
- ✅ State suffixes are capitalized (`colorHover`)
|
|
204
|
+
- ✅ Subcomponent props include element prefix (`menuBackgroundColor`)
|
|
205
|
+
- ✅ Field props follow `{propName}Label/Name/Default` pattern
|
|
206
|
+
- ✅ Semantic props are documented and used sparingly
|
|
207
|
+
|
|
208
|
+
**When in doubt, use CSS property names.**
|
|
@@ -10,7 +10,8 @@ Each component with stories should have a `stories/` directory containing:
|
|
|
10
10
|
ComponentName/
|
|
11
11
|
└── stories/
|
|
12
12
|
├── ComponentName.stories.tsx # Main story file
|
|
13
|
-
|
|
13
|
+
├── ComponentNameDecorator.tsx # Decorators for CSS variables (optional)
|
|
14
|
+
└── ComponentNameDecorator.module.scss # Decorator styles (optional)
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
**For components with multiple variants** (like Button):
|
|
@@ -19,7 +20,8 @@ Button/
|
|
|
19
20
|
└── stories/
|
|
20
21
|
├── Button.AsButton.stories.tsx # Variant 1 stories
|
|
21
22
|
├── Button.AsLink.stories.tsx # Variant 2 stories
|
|
22
|
-
|
|
23
|
+
├── ButtonDecorator.tsx # Shared decorators
|
|
24
|
+
└── ButtonDecorator.module.scss # Decorator styles
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
## Meta Configuration
|
|
@@ -156,10 +158,11 @@ Create decorators to apply default CSS variables for stories:
|
|
|
156
158
|
|
|
157
159
|
```typescript
|
|
158
160
|
import type { Decorator } from '@storybook/react';
|
|
161
|
+
import type { CSSVariables } from '../../utils/types.js';
|
|
159
162
|
|
|
160
|
-
const defaultComponentStyles:
|
|
163
|
+
const defaultComponentStyles: CSSVariables = {
|
|
161
164
|
'--hscl-component-property': 'value',
|
|
162
|
-
}
|
|
165
|
+
};
|
|
163
166
|
|
|
164
167
|
export const withComponentStyles: Decorator = (Story) => (
|
|
165
168
|
<div style={defaultComponentStyles}>
|
|
@@ -178,12 +181,26 @@ Use `SBContainer` for consistent layout and spacing in stories:
|
|
|
178
181
|
|
|
179
182
|
**Props:**
|
|
180
183
|
- `children`: ReactNode - Content to render
|
|
184
|
+
- `className?: string` - Additional CSS classes
|
|
181
185
|
- `addBackground?: boolean` - Adds light gray background (default: false)
|
|
182
186
|
- `flex?: boolean` - Enables flexbox layout (default: false)
|
|
183
187
|
- `direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse'` - Flex direction (default: 'column')
|
|
184
|
-
- `gap?:
|
|
188
|
+
- `gap?: 'none' | 'small' | 'medium' | 'large' | 'extralarge'` - Gap between flex items (default: 'none')
|
|
189
|
+
- `padding?: 'none' | 'small' | 'medium' | 'large' | 'extralarge'` - Padding inside container (default: 'medium')
|
|
185
190
|
- `alignItems?: CSSProperties['alignItems']` - Flex align-items (default: 'stretch')
|
|
186
191
|
- `justifyContent?: CSSProperties['justifyContent']` - Flex justify-content (default: 'flex-start')
|
|
192
|
+
- `minWidth?: string` - Minimum width (default: 'auto')
|
|
193
|
+
- `maxWidth?: string` - Maximum width (default: 'auto')
|
|
194
|
+
- `width?: string` - Fixed width
|
|
195
|
+
- `height?: string` - Fixed height
|
|
196
|
+
- `style?: CSSProperties` - Additional inline styles
|
|
197
|
+
|
|
198
|
+
**Size Values:**
|
|
199
|
+
- `none`: 0
|
|
200
|
+
- `small`: 8px
|
|
201
|
+
- `medium`: 16px
|
|
202
|
+
- `large`: 24px
|
|
203
|
+
- `extralarge`: 32px
|
|
187
204
|
|
|
188
205
|
**Example Usage:**
|
|
189
206
|
```typescript
|
|
@@ -191,13 +208,13 @@ import { SBContainer } from '@sb-utils/SBContainer.js';
|
|
|
191
208
|
|
|
192
209
|
export const ExampleStory: Story = {
|
|
193
210
|
render: () => (
|
|
194
|
-
<SBContainer flex direction="column" gap="
|
|
195
|
-
<SBContainer addBackground>
|
|
211
|
+
<SBContainer flex direction="column" gap="large">
|
|
212
|
+
<SBContainer addBackground padding="large">
|
|
196
213
|
<h4>Example Title</h4>
|
|
197
214
|
<Component prop="value">Content</Component>
|
|
198
215
|
</SBContainer>
|
|
199
216
|
|
|
200
|
-
<SBContainer addBackground>
|
|
217
|
+
<SBContainer addBackground padding="medium">
|
|
201
218
|
<h4>Another Example</h4>
|
|
202
219
|
<Component prop="different">More content</Component>
|
|
203
220
|
</SBContainer>
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This document outlines TypeScript type definitions, patterns, and conventions for componentLibrary.
|
|
4
4
|
|
|
5
|
+
> **Note:** For prop naming conventions, see [Prop Naming Patterns](./prop-naming-patterns.md)
|
|
6
|
+
|
|
5
7
|
## Type Definitions Structure
|
|
6
8
|
|
|
7
9
|
All components maintain a separate `types.ts` file with:
|
|
@@ -85,9 +87,10 @@ export type ButtonProps = ButtonAsButtonProps | ButtonAsLinkProps;
|
|
|
85
87
|
|
|
86
88
|
**Usage in component:**
|
|
87
89
|
```typescript
|
|
88
|
-
const ButtonComponent = (
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
const ButtonComponent = ({
|
|
91
|
+
buttonType,
|
|
92
|
+
...rest
|
|
93
|
+
}: ButtonProps) => {
|
|
91
94
|
// TypeScript knows which props are available based on buttonType
|
|
92
95
|
if (buttonType === 'button') return renderButton(rest);
|
|
93
96
|
return renderLink(rest);
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cms-component-library",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "HubSpot CMS React component library for building CMS modules",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
7
|
+
"./VerticalMenu": "./components/componentLibrary/Menu/VerticalMenu/index.tsx",
|
|
8
|
+
"./NavigationMenu": "./components/componentLibrary/Menu/NavigationMenu/index.tsx",
|
|
7
9
|
"./*": "./components/componentLibrary/*/index.tsx"
|
|
8
10
|
},
|
|
9
11
|
"files": [
|
|
@@ -14,11 +16,12 @@
|
|
|
14
16
|
"url": "git@git.hubteam.com:HubSpot/cms-component-library.git"
|
|
15
17
|
},
|
|
16
18
|
"publishConfig": {
|
|
17
|
-
"access": "public"
|
|
19
|
+
"access": "public",
|
|
20
|
+
"registry": "https://registry.npmjs.org"
|
|
18
21
|
},
|
|
19
22
|
"type": "module",
|
|
20
23
|
"dependencies": {
|
|
21
|
-
"@hubspot/cms-components": "^1.
|
|
24
|
+
"@hubspot/cms-components": "^1.2.13",
|
|
22
25
|
"sass-embedded": "^1.90.0",
|
|
23
26
|
"tsx": "^4.20.5"
|
|
24
27
|
},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|