@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.
Files changed (143) 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 +5 -3
  11. package/components/componentLibrary/Button/index.module.scss +22 -14
  12. package/components/componentLibrary/Button/index.tsx +6 -6
  13. package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +30 -1
  14. package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +38 -1
  15. package/components/componentLibrary/Button/stories/ButtonDecorator.tsx +1 -1
  16. package/components/componentLibrary/Card/StyleFields.tsx +5 -3
  17. package/components/componentLibrary/Card/stories/Card.stories.tsx +46 -1
  18. package/components/componentLibrary/Card/stories/CardDecorator.tsx +1 -1
  19. package/components/componentLibrary/Divider/ContentFields.tsx +5 -3
  20. package/components/componentLibrary/Divider/StyleFields.tsx +5 -3
  21. package/components/componentLibrary/Divider/index.module.scss +6 -6
  22. package/components/componentLibrary/Divider/index.tsx +7 -3
  23. package/components/componentLibrary/Divider/stories/Divider.stories.tsx +44 -50
  24. package/components/componentLibrary/Divider/stories/{DividerDecorator.module.css → DividerDecorator.module.scss} +5 -4
  25. package/components/componentLibrary/Divider/stories/DividerDecorator.tsx +1 -1
  26. package/components/componentLibrary/Divider/types.ts +3 -1
  27. package/components/componentLibrary/Drawer/hooks/index.tsx +13 -0
  28. package/components/componentLibrary/Drawer/index.module.scss +94 -0
  29. package/components/componentLibrary/Drawer/index.tsx +131 -0
  30. package/components/componentLibrary/Drawer/llm.txt +416 -0
  31. package/components/componentLibrary/Drawer/stories/Drawer.stories.tsx +512 -0
  32. package/components/componentLibrary/Drawer/stories/DrawerDecorator.module.scss +8 -0
  33. package/components/componentLibrary/Drawer/stories/DrawerDecorator.tsx +18 -0
  34. package/components/componentLibrary/Drawer/types.ts +25 -0
  35. package/components/componentLibrary/Flex/stories/FlexDecorator.tsx +1 -1
  36. package/components/componentLibrary/Flex/types.ts +3 -1
  37. package/components/componentLibrary/Grid/stories/Grid.stories.tsx +454 -152
  38. package/components/componentLibrary/Grid/stories/GridDecorator.tsx +2 -2
  39. package/components/componentLibrary/Heading/ContentFields.tsx +5 -3
  40. package/components/componentLibrary/Heading/StyleFields.tsx +11 -9
  41. package/components/componentLibrary/Heading/index.tsx +3 -3
  42. package/components/componentLibrary/Heading/llm.txt +8 -8
  43. package/components/componentLibrary/Heading/stories/Heading.stories.tsx +3 -3
  44. package/components/componentLibrary/Heading/stories/HeadingDecorator.tsx +1 -1
  45. package/components/componentLibrary/Heading/types.ts +4 -4
  46. package/components/componentLibrary/Icon/ContentFields.tsx +5 -3
  47. package/components/componentLibrary/Icon/stories/Icon.stories.tsx +1 -1
  48. package/components/componentLibrary/Icon/stories/IconDecorator.tsx +1 -1
  49. package/components/componentLibrary/Image/ContentFields.tsx +5 -3
  50. package/components/componentLibrary/Image/index.tsx +4 -4
  51. package/components/componentLibrary/Image/llm.txt +17 -17
  52. package/components/componentLibrary/Image/stories/Image.stories.tsx +61 -18
  53. package/components/componentLibrary/Image/stories/ImageDecorator.tsx +1 -1
  54. package/components/componentLibrary/Image/types.ts +2 -2
  55. package/components/componentLibrary/LanguageSwitcher/ContentFields.tsx +18 -0
  56. package/components/componentLibrary/LanguageSwitcher/LanguageOptions.module.scss +37 -0
  57. package/components/componentLibrary/LanguageSwitcher/LanguageOptions.tsx +65 -0
  58. package/components/componentLibrary/LanguageSwitcher/StyleFields.tsx +48 -0
  59. package/components/componentLibrary/LanguageSwitcher/_dummyData.tsx +247 -0
  60. package/components/componentLibrary/LanguageSwitcher/assets/Globe.tsx +16 -0
  61. package/components/componentLibrary/LanguageSwitcher/index.module.scss +58 -0
  62. package/components/componentLibrary/LanguageSwitcher/index.tsx +125 -0
  63. package/components/componentLibrary/LanguageSwitcher/llm.txt +380 -0
  64. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcher.stories.tsx +349 -0
  65. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.module.scss +5 -0
  66. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.tsx +8 -0
  67. package/components/componentLibrary/LanguageSwitcher/types.ts +48 -0
  68. package/components/componentLibrary/LanguageSwitcher/utils.tsx +38 -0
  69. package/components/componentLibrary/Link/ContentFields.tsx +5 -3
  70. package/components/componentLibrary/Link/StyleFields.tsx +5 -3
  71. package/components/componentLibrary/Link/index.module.scss +10 -0
  72. package/components/componentLibrary/Link/index.tsx +24 -14
  73. package/components/componentLibrary/Link/stories/Link.stories.tsx +35 -5
  74. package/components/componentLibrary/Link/stories/LinkDecorator.tsx +11 -1
  75. package/components/componentLibrary/Link/types.ts +22 -13
  76. package/components/componentLibrary/List/ContentFields.tsx +5 -3
  77. package/components/componentLibrary/List/ListItem/ContentFields.tsx +6 -17
  78. package/components/componentLibrary/List/ListItem/index.module.scss +1 -13
  79. package/components/componentLibrary/List/ListItem/index.tsx +3 -30
  80. package/components/componentLibrary/List/ListItem/types.ts +1 -16
  81. package/components/componentLibrary/List/StyleFields.tsx +15 -18
  82. package/components/componentLibrary/List/index.module.scss +3 -0
  83. package/components/componentLibrary/List/index.tsx +5 -2
  84. package/components/componentLibrary/List/llm.txt +73 -103
  85. package/components/componentLibrary/List/stories/List.stories.tsx +56 -80
  86. package/components/componentLibrary/List/stories/ListDecorator.tsx +3 -6
  87. package/components/componentLibrary/List/types.ts +1 -3
  88. package/components/componentLibrary/Logo/_dummyLogoData.ts +12 -0
  89. package/components/componentLibrary/Logo/assets/hubspot-logo.png +0 -0
  90. package/components/componentLibrary/Logo/index.module.scss +22 -0
  91. package/components/componentLibrary/Logo/index.tsx +73 -0
  92. package/components/componentLibrary/Logo/llm.txt +262 -0
  93. package/components/componentLibrary/Logo/stories/Logo.stories.tsx +88 -0
  94. package/components/componentLibrary/Logo/stories/LogoDecorator.module.scss +10 -0
  95. package/components/componentLibrary/Logo/stories/LogoDecorator.tsx +8 -0
  96. package/components/componentLibrary/Logo/types.tsx +16 -0
  97. package/components/componentLibrary/Menu/ContentFields.tsx +16 -0
  98. package/components/componentLibrary/Menu/MenuItem/Chevron/index.module.scss +6 -0
  99. package/components/componentLibrary/Menu/MenuItem/Chevron/index.tsx +17 -0
  100. package/components/componentLibrary/Menu/MenuItem/index.module.scss +7 -0
  101. package/components/componentLibrary/Menu/MenuItem/index.tsx +266 -0
  102. package/components/componentLibrary/Menu/MenuItem/types.ts +17 -0
  103. package/components/componentLibrary/Menu/NavigationMenu/ContentFields.tsx +20 -0
  104. package/components/componentLibrary/Menu/NavigationMenu/index.tsx +18 -0
  105. package/components/componentLibrary/Menu/NavigationMenu/islands/NavigationMenuIsland.tsx +95 -0
  106. package/components/componentLibrary/Menu/NavigationMenu/islands/index.module.scss +100 -0
  107. package/components/componentLibrary/Menu/NavigationMenu/islands/types.ts +19 -0
  108. package/components/componentLibrary/Menu/NavigationMenu/llm.txt +197 -0
  109. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenu.stories.tsx +286 -0
  110. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.module.scss +15 -0
  111. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.tsx +12 -0
  112. package/components/componentLibrary/Menu/NavigationMenu/types.ts +3 -0
  113. package/components/componentLibrary/Menu/VerticalMenu/ContentFields.tsx +20 -0
  114. package/components/componentLibrary/Menu/VerticalMenu/index.tsx +18 -0
  115. package/components/componentLibrary/Menu/VerticalMenu/islands/index.module.scss +53 -0
  116. package/components/componentLibrary/Menu/VerticalMenu/islands/verticalMenuIsland.tsx +78 -0
  117. package/components/componentLibrary/Menu/VerticalMenu/llm.txt +177 -0
  118. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenu.stories.tsx +242 -0
  119. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.module.scss +19 -0
  120. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.tsx +12 -0
  121. package/components/componentLibrary/Menu/VerticalMenu/types.ts +21 -0
  122. package/components/componentLibrary/Menu/_dummyMenuData.js +1346 -0
  123. package/components/componentLibrary/Menu/types.ts +56 -0
  124. package/components/componentLibrary/Menu/utils/transformMenuData.ts +11 -0
  125. package/components/componentLibrary/_patterns/README.md +15 -17
  126. package/components/componentLibrary/_patterns/checklist-and-examples.md +17 -17
  127. package/components/componentLibrary/_patterns/component-structure.md +21 -23
  128. package/components/componentLibrary/_patterns/css-patterns.md +170 -18
  129. package/components/componentLibrary/_patterns/field-patterns.md +97 -27
  130. package/components/componentLibrary/_patterns/function-declaration-patterns.md +281 -0
  131. package/components/componentLibrary/_patterns/llm-txt.template.md +4 -2
  132. package/components/componentLibrary/_patterns/prop-naming-patterns.md +208 -0
  133. package/components/componentLibrary/_patterns/storybook-patterns.md +25 -8
  134. package/components/componentLibrary/_patterns/typescript-patterns.md +6 -3
  135. package/package.json +4 -2
  136. /package/components/componentLibrary/Button/stories/{ButtonDecorator.module.css → ButtonDecorator.module.scss} +0 -0
  137. /package/components/componentLibrary/Card/stories/{CardDecorator.module.css → CardDecorator.module.scss} +0 -0
  138. /package/components/componentLibrary/Flex/stories/{FlexDecorator.module.css → FlexDecorator.module.scss} +0 -0
  139. /package/components/componentLibrary/Grid/stories/{GridDecorator.module.css → GridDecorator.module.scss} +0 -0
  140. /package/components/componentLibrary/Heading/stories/{HeadingDecorator.module.css → HeadingDecorator.module.scss} +0 -0
  141. /package/components/componentLibrary/Icon/stories/{IconDecorator.module.css → IconDecorator.module.scss} +0 -0
  142. /package/components/componentLibrary/Image/stories/{ImageDecorator.module.css → ImageDecorator.module.scss} +0 -0
  143. /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
- ## Quick Reference
26
-
27
- ### Creating a New Component Checklist
31
+ ## Creating a New Component
28
32
 
29
- When creating a new component, reference these files in order:
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. **[TypeScript Patterns](./typescript-patterns.md)** - Define types in `types.ts`
33
- 3. **[CSS Patterns](./css-patterns.md)** - Create styles with proper variable naming
34
- 4. **[Field Patterns](./field-patterns.md)** - Add ContentFields and StyleFields (if needed)
35
- 5. **[Storybook Patterns](./storybook-patterns.md)** - Create stories for documentation (if needed)
36
-
37
- ### Common Patterns Quick Links
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 follow destructuring pattern with defaults
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 = (props: ComponentProps) => {
100
- const {
101
- variant = 'primary',
102
- className = '',
103
- style = {},
104
- children,
105
- ...rest
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
- export default function ContentFields(props: ContentFieldsProps) {
188
- const {
189
- fieldLabel = 'Field',
190
- fieldName = 'field',
191
- fieldDefault = 'Default',
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 follow a consistent prop destructuring pattern:
83
+ Components destructure props inline in the function signature with defaults:
84
84
 
85
85
  ```typescript
86
- const ComponentName = (props: ComponentProps) => {
87
- const {
88
- // Required props first (no defaults)
89
- requiredProp,
90
-
91
- // Optional props with defaults
92
- variant = 'primary',
93
- className = '',
94
- style = {},
95
- children = 'Default content',
96
-
97
- // Spread remaining props
98
- ...rest
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 = (props: ComponentProps) => {
236
- const {
237
- variant = 'primary',
238
- className = '',
239
- style = {},
240
- children,
241
- ...rest
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-property-state`
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`, `listIcon`)
11
- - **Property:** CSS property name in camelCase (e.g., `backgroundColor`, `fontSize`, `borderColor`)
12
- - **State (optional):** State suffix for interactive states (e.g., `-hover`, `-focus`, `-disabled`)
13
-
14
- **Examples:**
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-image-borderRadius
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
- // 1. Define CSS variables object with proper typing
40
- const cssVariables: React.CSSProperties & {
41
- [key: `--${string}`]: string | undefined;
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
- // 2. Merge with user's style prop (treat as override)
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: React.CSSProperties = {
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
- } as React.CSSProperties;
115
+ };
62
116
 
63
117
  // Divider - direct values with units
64
- const cssVariables: React.CSSProperties = {
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
- } as React.CSSProperties;
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 accept configuration props for customization:
7
+ Field components destructure props inline with defaults for customization:
8
8
 
9
9
  ```typescript
10
- export default function ContentFields(props: ContentFieldsProps) {
11
- const {
12
- // Each field gets label, name, and default props
13
- fieldLabel = 'Default label',
14
- fieldName = 'defaultName',
15
- fieldDefault = 'Default value',
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
- Like components, all field components should follow a consistent destructuring pattern with label, name, and default props:
131
+ All field components destructure props inline in the function signature with defaults:
62
132
 
63
133
  ```typescript
64
- export default function ContentFields(props: ContentFieldsProps) {
65
- const {
66
- fieldLabel = 'Default label',
67
- fieldName = 'defaultName',
68
- fieldDefault = 'default value',
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
- export default function ContentFields(props: ContentFieldsProps) {
155
- const {
156
- fieldLabel = 'Field',
157
- fieldName = 'field',
158
- fieldDefault = 'Default',
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
  ```