@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.
Files changed (145) hide show
  1. package/components/componentLibrary/Accordion/AccordionContent/ContentFields.tsx +5 -3
  2. package/components/componentLibrary/Accordion/AccordionItem/StyleFields.tsx +5 -3
  3. package/components/componentLibrary/Accordion/AccordionItem/index.module.scss +2 -2
  4. package/components/componentLibrary/Accordion/AccordionItem/index.tsx +3 -3
  5. package/components/componentLibrary/Accordion/AccordionTitle/ContentFields.tsx +5 -3
  6. package/components/componentLibrary/Accordion/AccordionTitle/index.module.scss +2 -2
  7. package/components/componentLibrary/Accordion/stories/Accordion.stories.tsx +80 -1
  8. package/components/componentLibrary/Accordion/stories/AccordionDecorator.tsx +14 -14
  9. package/components/componentLibrary/Button/ContentFields.tsx +5 -3
  10. package/components/componentLibrary/Button/StyleFields.tsx +26 -4
  11. package/components/componentLibrary/Button/index.module.scss +22 -14
  12. package/components/componentLibrary/Button/index.tsx +6 -6
  13. package/components/componentLibrary/Button/llm.txt +5 -1
  14. package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +30 -1
  15. package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +38 -1
  16. package/components/componentLibrary/Button/stories/ButtonDecorator.tsx +1 -1
  17. package/components/componentLibrary/Button/types.ts +6 -1
  18. package/components/componentLibrary/Card/StyleFields.tsx +5 -3
  19. package/components/componentLibrary/Card/stories/Card.stories.tsx +46 -1
  20. package/components/componentLibrary/Card/stories/CardDecorator.tsx +1 -1
  21. package/components/componentLibrary/Divider/ContentFields.tsx +5 -3
  22. package/components/componentLibrary/Divider/StyleFields.tsx +5 -3
  23. package/components/componentLibrary/Divider/index.module.scss +6 -6
  24. package/components/componentLibrary/Divider/index.tsx +7 -3
  25. package/components/componentLibrary/Divider/stories/Divider.stories.tsx +44 -50
  26. package/components/componentLibrary/Divider/stories/{DividerDecorator.module.css → DividerDecorator.module.scss} +5 -4
  27. package/components/componentLibrary/Divider/stories/DividerDecorator.tsx +1 -1
  28. package/components/componentLibrary/Divider/types.ts +3 -1
  29. package/components/componentLibrary/Drawer/hooks/index.tsx +13 -0
  30. package/components/componentLibrary/Drawer/index.module.scss +94 -0
  31. package/components/componentLibrary/Drawer/index.tsx +131 -0
  32. package/components/componentLibrary/Drawer/llm.txt +416 -0
  33. package/components/componentLibrary/Drawer/stories/Drawer.stories.tsx +512 -0
  34. package/components/componentLibrary/Drawer/stories/DrawerDecorator.module.scss +8 -0
  35. package/components/componentLibrary/Drawer/stories/DrawerDecorator.tsx +18 -0
  36. package/components/componentLibrary/Drawer/types.ts +25 -0
  37. package/components/componentLibrary/Flex/stories/FlexDecorator.tsx +1 -1
  38. package/components/componentLibrary/Flex/types.ts +3 -1
  39. package/components/componentLibrary/Grid/stories/Grid.stories.tsx +454 -152
  40. package/components/componentLibrary/Grid/stories/GridDecorator.tsx +2 -2
  41. package/components/componentLibrary/Heading/ContentFields.tsx +5 -3
  42. package/components/componentLibrary/Heading/StyleFields.tsx +11 -9
  43. package/components/componentLibrary/Heading/index.tsx +3 -3
  44. package/components/componentLibrary/Heading/llm.txt +8 -8
  45. package/components/componentLibrary/Heading/stories/Heading.stories.tsx +3 -3
  46. package/components/componentLibrary/Heading/stories/HeadingDecorator.tsx +1 -1
  47. package/components/componentLibrary/Heading/types.ts +4 -4
  48. package/components/componentLibrary/Icon/ContentFields.tsx +5 -3
  49. package/components/componentLibrary/Icon/stories/Icon.stories.tsx +1 -1
  50. package/components/componentLibrary/Icon/stories/IconDecorator.tsx +1 -1
  51. package/components/componentLibrary/Image/ContentFields.tsx +5 -3
  52. package/components/componentLibrary/Image/index.tsx +4 -4
  53. package/components/componentLibrary/Image/llm.txt +17 -17
  54. package/components/componentLibrary/Image/stories/Image.stories.tsx +61 -18
  55. package/components/componentLibrary/Image/stories/ImageDecorator.tsx +1 -1
  56. package/components/componentLibrary/Image/types.ts +2 -2
  57. package/components/componentLibrary/LanguageSwitcher/ContentFields.tsx +18 -0
  58. package/components/componentLibrary/LanguageSwitcher/LanguageOptions.module.scss +37 -0
  59. package/components/componentLibrary/LanguageSwitcher/LanguageOptions.tsx +65 -0
  60. package/components/componentLibrary/LanguageSwitcher/StyleFields.tsx +48 -0
  61. package/components/componentLibrary/LanguageSwitcher/_dummyData.tsx +247 -0
  62. package/components/componentLibrary/LanguageSwitcher/assets/Globe.tsx +16 -0
  63. package/components/componentLibrary/LanguageSwitcher/index.module.scss +58 -0
  64. package/components/componentLibrary/LanguageSwitcher/index.tsx +125 -0
  65. package/components/componentLibrary/LanguageSwitcher/llm.txt +380 -0
  66. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcher.stories.tsx +349 -0
  67. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.module.scss +5 -0
  68. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.tsx +8 -0
  69. package/components/componentLibrary/LanguageSwitcher/types.ts +48 -0
  70. package/components/componentLibrary/LanguageSwitcher/utils.tsx +38 -0
  71. package/components/componentLibrary/Link/ContentFields.tsx +5 -3
  72. package/components/componentLibrary/Link/StyleFields.tsx +5 -3
  73. package/components/componentLibrary/Link/index.module.scss +10 -0
  74. package/components/componentLibrary/Link/index.tsx +24 -14
  75. package/components/componentLibrary/Link/stories/Link.stories.tsx +35 -5
  76. package/components/componentLibrary/Link/stories/LinkDecorator.tsx +11 -1
  77. package/components/componentLibrary/Link/types.ts +22 -13
  78. package/components/componentLibrary/List/ContentFields.tsx +5 -3
  79. package/components/componentLibrary/List/ListItem/ContentFields.tsx +6 -17
  80. package/components/componentLibrary/List/ListItem/index.module.scss +1 -13
  81. package/components/componentLibrary/List/ListItem/index.tsx +3 -30
  82. package/components/componentLibrary/List/ListItem/types.ts +1 -16
  83. package/components/componentLibrary/List/StyleFields.tsx +15 -18
  84. package/components/componentLibrary/List/index.module.scss +3 -0
  85. package/components/componentLibrary/List/index.tsx +5 -2
  86. package/components/componentLibrary/List/llm.txt +73 -103
  87. package/components/componentLibrary/List/stories/List.stories.tsx +56 -80
  88. package/components/componentLibrary/List/stories/ListDecorator.tsx +3 -6
  89. package/components/componentLibrary/List/types.ts +1 -3
  90. package/components/componentLibrary/Logo/_dummyLogoData.ts +12 -0
  91. package/components/componentLibrary/Logo/assets/hubspot-logo.png +0 -0
  92. package/components/componentLibrary/Logo/index.module.scss +22 -0
  93. package/components/componentLibrary/Logo/index.tsx +73 -0
  94. package/components/componentLibrary/Logo/llm.txt +262 -0
  95. package/components/componentLibrary/Logo/stories/Logo.stories.tsx +88 -0
  96. package/components/componentLibrary/Logo/stories/LogoDecorator.module.scss +10 -0
  97. package/components/componentLibrary/Logo/stories/LogoDecorator.tsx +8 -0
  98. package/components/componentLibrary/Logo/types.tsx +16 -0
  99. package/components/componentLibrary/Menu/ContentFields.tsx +16 -0
  100. package/components/componentLibrary/Menu/MenuItem/Chevron/index.module.scss +6 -0
  101. package/components/componentLibrary/Menu/MenuItem/Chevron/index.tsx +17 -0
  102. package/components/componentLibrary/Menu/MenuItem/index.module.scss +7 -0
  103. package/components/componentLibrary/Menu/MenuItem/index.tsx +266 -0
  104. package/components/componentLibrary/Menu/MenuItem/types.ts +17 -0
  105. package/components/componentLibrary/Menu/NavigationMenu/ContentFields.tsx +20 -0
  106. package/components/componentLibrary/Menu/NavigationMenu/index.tsx +18 -0
  107. package/components/componentLibrary/Menu/NavigationMenu/islands/NavigationMenuIsland.tsx +95 -0
  108. package/components/componentLibrary/Menu/NavigationMenu/islands/index.module.scss +100 -0
  109. package/components/componentLibrary/Menu/NavigationMenu/islands/types.ts +19 -0
  110. package/components/componentLibrary/Menu/NavigationMenu/llm.txt +197 -0
  111. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenu.stories.tsx +286 -0
  112. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.module.scss +15 -0
  113. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.tsx +12 -0
  114. package/components/componentLibrary/Menu/NavigationMenu/types.ts +3 -0
  115. package/components/componentLibrary/Menu/VerticalMenu/ContentFields.tsx +20 -0
  116. package/components/componentLibrary/Menu/VerticalMenu/index.tsx +18 -0
  117. package/components/componentLibrary/Menu/VerticalMenu/islands/index.module.scss +53 -0
  118. package/components/componentLibrary/Menu/VerticalMenu/islands/verticalMenuIsland.tsx +78 -0
  119. package/components/componentLibrary/Menu/VerticalMenu/llm.txt +177 -0
  120. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenu.stories.tsx +242 -0
  121. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.module.scss +19 -0
  122. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.tsx +12 -0
  123. package/components/componentLibrary/Menu/VerticalMenu/types.ts +21 -0
  124. package/components/componentLibrary/Menu/_dummyMenuData.js +1346 -0
  125. package/components/componentLibrary/Menu/types.ts +56 -0
  126. package/components/componentLibrary/Menu/utils/transformMenuData.ts +11 -0
  127. package/components/componentLibrary/_patterns/README.md +15 -17
  128. package/components/componentLibrary/_patterns/checklist-and-examples.md +17 -17
  129. package/components/componentLibrary/_patterns/component-structure.md +21 -23
  130. package/components/componentLibrary/_patterns/css-patterns.md +170 -18
  131. package/components/componentLibrary/_patterns/field-patterns.md +97 -27
  132. package/components/componentLibrary/_patterns/function-declaration-patterns.md +281 -0
  133. package/components/componentLibrary/_patterns/llm-txt.template.md +4 -2
  134. package/components/componentLibrary/_patterns/prop-naming-patterns.md +208 -0
  135. package/components/componentLibrary/_patterns/storybook-patterns.md +25 -8
  136. package/components/componentLibrary/_patterns/typescript-patterns.md +6 -3
  137. package/package.json +6 -3
  138. /package/components/componentLibrary/Button/stories/{ButtonDecorator.module.css → ButtonDecorator.module.scss} +0 -0
  139. /package/components/componentLibrary/Card/stories/{CardDecorator.module.css → CardDecorator.module.scss} +0 -0
  140. /package/components/componentLibrary/Flex/stories/{FlexDecorator.module.css → FlexDecorator.module.scss} +0 -0
  141. /package/components/componentLibrary/Grid/stories/{GridDecorator.module.css → GridDecorator.module.scss} +0 -0
  142. /package/components/componentLibrary/Heading/stories/{HeadingDecorator.module.css → HeadingDecorator.module.scss} +0 -0
  143. /package/components/componentLibrary/Icon/stories/{IconDecorator.module.css → IconDecorator.module.scss} +0 -0
  144. /package/components/componentLibrary/Image/stories/{ImageDecorator.module.css → ImageDecorator.module.scss} +0 -0
  145. /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
- export default function [ModuleName]({ fieldValues }) {
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
- └── ComponentNameDecorator.tsx # Decorators for CSS variables (optional)
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
- └── ButtonDecorator.tsx # Shared decorators
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: React.CSSProperties = {
163
+ const defaultComponentStyles: CSSVariables = {
161
164
  '--hscl-component-property': 'value',
162
- } as React.CSSProperties;
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?: string` - Gap between flex items (default: '16px')
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="16px">
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 = (props: ButtonProps) => {
89
- const { buttonType, ...rest } = props;
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.1.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.0.37",
24
+ "@hubspot/cms-components": "^1.2.13",
22
25
  "sass-embedded": "^1.90.0",
23
26
  "tsx": "^4.20.5"
24
27
  },