@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,78 @@
1
+ import { useMemo } from 'react';
2
+ import MenuItem from '../../MenuItem/index.js';
3
+ import { VerticalMenuProps } from '../types.js';
4
+ import { addIdsToMenuItems } from '../../utils/transformMenuData.js';
5
+ import styles from './index.module.scss';
6
+ import cx from '../../../utils/classname.js';
7
+ const VerticalMenu = ({
8
+ maxDepth,
9
+ className = '',
10
+ style = {},
11
+ linkColor,
12
+ linkColorHover,
13
+ backgroundColor,
14
+ backgroundColorHover,
15
+ submenuLinkColor,
16
+ submenuLinkColorHover,
17
+ submenuBackgroundColor,
18
+ submenuBackgroundColorHover,
19
+ indentation,
20
+ menuData,
21
+ }: VerticalMenuProps) => {
22
+ if (!menuData?.pagesTree?.children) return null;
23
+
24
+ const menuItems = useMemo(
25
+ () => addIdsToMenuItems(menuData.pagesTree.children),
26
+ [menuData]
27
+ );
28
+ const level = 1;
29
+
30
+ const cssVariables = {
31
+ ...(linkColor && { '--hscl-verticalMenu-color': linkColor }),
32
+ ...(linkColorHover && {
33
+ '--hscl-verticalMenu-color-hover': linkColorHover,
34
+ }),
35
+ ...(backgroundColor && {
36
+ '--hscl-verticalMenu-backgroundColor': backgroundColor,
37
+ }),
38
+ ...(backgroundColorHover && {
39
+ '--hscl-verticalMenu-backgroundColor-hover': backgroundColorHover,
40
+ }),
41
+ ...(submenuLinkColor && {
42
+ '--hscl-verticalMenu-submenu-color': submenuLinkColor,
43
+ }),
44
+ ...(submenuLinkColorHover && {
45
+ '--hscl-verticalMenu-submenu-color-hover': submenuLinkColorHover,
46
+ }),
47
+ ...(submenuBackgroundColor && {
48
+ '--hscl-verticalMenu-submenu-backgroundColor': submenuBackgroundColor,
49
+ }),
50
+ ...(submenuBackgroundColorHover && {
51
+ '--hscl-verticalMenu-submenu-backgroundColor-hover':
52
+ submenuBackgroundColorHover,
53
+ }),
54
+ ...(indentation && {
55
+ '--hscl-verticalMenu-paddingInlineStart': indentation,
56
+ }),
57
+ };
58
+
59
+ return (
60
+ <nav
61
+ className={cx(styles.verticalMenu, className)}
62
+ style={{ ...cssVariables, ...style }}
63
+ >
64
+ <ul className={styles.menuList}>
65
+ {menuItems.map(item => (
66
+ <MenuItem
67
+ key={item._id}
68
+ item={item}
69
+ level={level}
70
+ maxDepth={maxDepth}
71
+ />
72
+ ))}
73
+ </ul>
74
+ </nav>
75
+ );
76
+ };
77
+
78
+ export default VerticalMenu;
@@ -0,0 +1,177 @@
1
+ # VerticalMenu Component
2
+
3
+ A vertical navigation menu component that renders a hierarchical list of links with support for nested submenus, customizable depth, and flexible styling options.
4
+
5
+ ## Import path
6
+ ```tsx
7
+ import VerticalMenu from '@hubspot/cms-component-library/VerticalMenu';
8
+ ```
9
+
10
+ ## Purpose
11
+
12
+ The VerticalMenu component provides a consistent way to render vertical navigation menus in HubSpot CMS projects. It automatically integrates with HubSpot's site menu system, supporting multi-level navigation with configurable maximum depth, customizable colors for links and backgrounds, and adjustable indentation for submenu items.
13
+
14
+ ## Component Structure
15
+
16
+ ```
17
+ VerticalMenu/
18
+ ├── index.tsx # Main component wrapper (Island integration)
19
+ ├── types.ts # TypeScript type definitions
20
+ ├── ContentFields.tsx # HubSpot field definitions for menu selection
21
+ ├── islands/
22
+ │ ├── verticalMenuIsland.tsx # Island component with rendering logic
23
+ │ └── index.module.scss # CSS module with design tokens
24
+ └── stories/
25
+ ├── VerticalMenu.stories.tsx # Storybook examples
26
+ └── VerticalMenuDecorator.tsx # Storybook decorator
27
+ ```
28
+
29
+ ## Components
30
+
31
+ ### VerticalMenu (Main Component)
32
+
33
+ **Purpose:** Renders a vertical navigation menu from HubSpot site menu data with support for nested submenus.
34
+
35
+ **Props:**
36
+ ```tsx
37
+ {
38
+ menuData: SiteMenu; // Menu data from useMenu hook (required)
39
+ maxDepth?: number; // Maximum nesting depth (default: unlimited)
40
+ indentation?: string; // Submenu indentation (default: '0px')
41
+ linkColor?: string; // Top-level link text color
42
+ linkColorHover?: string; // Top-level link hover text color
43
+ backgroundColor?: string; // Top-level link background color
44
+ backgroundColorHover?: string; // Top-level link hover background color
45
+ submenuLinkColor?: string; // Submenu link text color
46
+ submenuLinkColorHover?: string; // Submenu link hover text color
47
+ submenuBackgroundColor?: string; // Submenu link background color
48
+ submenuBackgroundColorHover?: string; // Submenu link hover background color
49
+ className?: string; // Additional CSS classes
50
+ style?: React.CSSProperties; // Inline styles with CSS variables
51
+ }
52
+ ```
53
+
54
+ ## Consuming the Component with useMenu Hook
55
+
56
+ The VerticalMenu component requires menu data from HubSpot's `useMenu` hook. This hook fetches the site menu structure based on a menu ID defined in your module fields.
57
+
58
+ ### Basic Module Implementation
59
+
60
+ ```tsx
61
+ import { ModuleMeta } from '../types/modules.js';
62
+ import VerticalMenu from '@hubspot/cms-component-library/VerticalMenu';
63
+ import { useMenu, fieldPath } from '@hubspot/cms-components';
64
+
65
+ export const Component = () => {
66
+ // Fetch menu data using the useMenu hook
67
+ // fieldPath('menu') references the menu field defined in fields.tsx
68
+ const menuData = useMenu(fieldPath('menu'));
69
+
70
+ return (
71
+ <VerticalMenu
72
+ menuData={menuData}
73
+ maxDepth={2}
74
+ indentation="20px"
75
+ />
76
+ );
77
+ };
78
+
79
+ export { fields } from './fields.js';
80
+
81
+ export const meta: ModuleMeta = {
82
+ label: 'Vertical Navigation Menu',
83
+ content_types: [],
84
+ icon: '',
85
+ categories: ['navigation'],
86
+ };
87
+ ```
88
+
89
+ ### Module Fields Definition
90
+
91
+ ```tsx
92
+ import { ModuleFields } from '@hubspot/cms-components/fields';
93
+ import VerticalMenu from '@hubspot/cms-component-library/VerticalMenu';
94
+
95
+ export const fields = (
96
+ <ModuleFields>
97
+ <VerticalMenu.ContentFields />
98
+ </ModuleFields>
99
+ );
100
+ ```
101
+
102
+ **What this does:**
103
+ 1. `VerticalMenu.ContentFields` creates a HubSpot MenuField in your module
104
+ 2. This field allows content editors to select which site menu to display
105
+ 3. The `useMenu(fieldPath('menu'))` hook fetches the selected menu's data
106
+ 4. The menu data is passed to the VerticalMenu component via the `menuData` prop
107
+
108
+ ## HubSpot CMS Integration
109
+
110
+ ### Field Definitions
111
+
112
+ The VerticalMenu component provides field definitions for menu selection in HubSpot CMS modules.
113
+
114
+ #### ContentFields
115
+
116
+ Configurable props for customizing field labels, names, and defaults:
117
+
118
+ ```tsx
119
+ <VerticalMenu.ContentFields
120
+ menuLabel="Menu"
121
+ menuName="menu"
122
+ menuDefault="default"
123
+ />
124
+ ```
125
+
126
+ **Fields:**
127
+ - `menu`: MenuField for selecting which site menu to display (default: 'default')
128
+
129
+ ## Styling
130
+
131
+ ### CSS Variables
132
+
133
+ The VerticalMenu component uses CSS variables for theming and customization:
134
+
135
+ **Base Menu Styles:**
136
+ - `--hscl-verticalMenu-width`: Menu container width (default: 100%)
137
+ - `--hscl-verticalMenu-gap`: Space between menu items (default: 0)
138
+ - `--hscl-verticalMenu-paddingInline`: Horizontal padding for links (default: 10px)
139
+ - `--hscl-verticalMenu-paddingBlock`: Vertical padding for links (default: 8px)
140
+ - `--hscl-verticalMenu-color`: Link text color (default: currentColor)
141
+ - `--hscl-verticalMenu-backgroundColor`: Link background color (default: transparent)
142
+ - `--hscl-verticalMenu-color-hover`: Link hover text color
143
+ - `--hscl-verticalMenu-backgroundColor-hover`: Link hover background color
144
+
145
+ **Submenu-Specific Styles:**
146
+ - `--hscl-verticalMenu-paddingInlineStart`: Indentation for submenu items (default: 0px)
147
+ - `--hscl-verticalMenu-submenu-color`: Submenu link text color
148
+ - `--hscl-verticalMenu-submenu-backgroundColor`: Submenu link background color
149
+ - `--hscl-verticalMenu-submenu-color-hover`: Submenu link hover text color
150
+ - `--hscl-verticalMenu-submenu-backgroundColor-hover`: Submenu link hover background color
151
+
152
+
153
+ ## Accessibility
154
+
155
+ The VerticalMenu component follows accessibility best practices:
156
+
157
+ - **Semantic HTML**: Renders as a `<nav>` element with nested unordered lists (`<ul>`)
158
+ - **ARIA Roles**: List items have `role="menuitem"` for proper screen reader support
159
+ - **Keyboard Navigation**: Full keyboard support provided by the MenuItem component
160
+ - **Link Semantics**: Uses proper `<a>` elements for links with `href` attributes
161
+ - **External Links**: Automatically adds `target="_blank"` and `rel="noopener noreferrer"` for external links
162
+ - **Focus Management**: Native focus styles preserved for keyboard users
163
+
164
+ ## Best Practices
165
+
166
+ - **Always use useMenu hook**: The VerticalMenu component requires menu data from the `useMenu` hook—never pass hardcoded menu structures
167
+ - **Control depth wisely**: Use `maxDepth` to limit nesting levels for better UX (1-3 levels recommended)
168
+ - **Consistent indentation**: Use consistent indentation values across your site (typically 16px, 20px, or 24px)
169
+ - **Color contrast**: Ensure sufficient contrast between link colors and backgrounds (WCAG AA minimum)
170
+ - **Hover states**: Always define hover colors for better visual feedback
171
+ - **Submenu styling**: Use distinct colors for submenu items to create visual hierarchy
172
+
173
+
174
+ ## Related Components
175
+
176
+ - **NavigationMenu**: Horizontal navigation menu component for top-level navigation bars
177
+ - **MenuItem**: Used internally to render individual menu items with support for nested children
@@ -0,0 +1,242 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import VerticalMenu from '../index.js';
3
+ import { VerticalMenuProps } from '../types.js';
4
+ import { withVerticalMenuStyles } from './VerticalMenuDecorator.js';
5
+ import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
+ import { dummyMenuData } from '../../_dummyMenuData.js';
7
+
8
+ const meta: Meta<VerticalMenuProps> = {
9
+ title: 'Component Library/Menu/Vertical Menu',
10
+ component: VerticalMenu,
11
+ parameters: {
12
+ layout: 'centered',
13
+ docs: {
14
+ description: {
15
+ component:
16
+ 'The VerticalMenu component creates a vertical navigation menu with collapsible nested submenus. It supports depth control and custom indentation, making it ideal for sidebars and secondary navigation.',
17
+ },
18
+ },
19
+ },
20
+ decorators: [withVerticalMenuStyles],
21
+ };
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ export const Default: Story = {
27
+ render: () => (
28
+ <SBContainer addBackground width="300px">
29
+ <VerticalMenu menuData={dummyMenuData} />
30
+ </SBContainer>
31
+ ),
32
+ };
33
+
34
+ export const MaxDepthControl: Story = {
35
+ name: 'Max Depth Control',
36
+ render: () => (
37
+ <SBContainer flex direction="column" gap="extralarge">
38
+ <SBContainer addBackground width="300px">
39
+ <h4>No Depth Limit (Default)</h4>
40
+ <p>Shows all nested menu levels</p>
41
+ <VerticalMenu menuData={dummyMenuData} />
42
+ </SBContainer>
43
+
44
+ <SBContainer addBackground width="300px">
45
+ <h4>Max Depth: 1</h4>
46
+ <p>Only shows top-level items</p>
47
+ <VerticalMenu maxDepth={1} menuData={dummyMenuData} />
48
+ </SBContainer>
49
+
50
+ <SBContainer addBackground width="300px">
51
+ <h4>Max Depth: 2</h4>
52
+ <p>Shows up to 2 levels deep</p>
53
+ <VerticalMenu maxDepth={2} menuData={dummyMenuData} />
54
+ </SBContainer>
55
+
56
+ <SBContainer addBackground width="300px">
57
+ <h4>Max Depth: 3</h4>
58
+ <p>Shows up to 3 levels deep</p>
59
+ <VerticalMenu maxDepth={3} menuData={dummyMenuData} />
60
+ </SBContainer>
61
+ </SBContainer>
62
+ ),
63
+ };
64
+
65
+ export const IndentationOptions: Story = {
66
+ name: 'Indentation Options',
67
+ render: () => (
68
+ <SBContainer flex direction="column" gap="extralarge">
69
+ <SBContainer addBackground width="300px">
70
+ <h4>Default Indentation (20px)</h4>
71
+ <VerticalMenu menuData={dummyMenuData} />
72
+ </SBContainer>
73
+
74
+ <SBContainer addBackground width="300px">
75
+ <h4>No Indentation</h4>
76
+ <VerticalMenu indentation="0px" menuData={dummyMenuData} />
77
+ </SBContainer>
78
+
79
+ <SBContainer addBackground width="300px">
80
+ <h4>Small Indentation (10px)</h4>
81
+ <VerticalMenu indentation="10px" menuData={dummyMenuData} />
82
+ </SBContainer>
83
+
84
+ <SBContainer addBackground width="300px">
85
+ <h4>Large Indentation (40px)</h4>
86
+ <VerticalMenu indentation="40px" menuData={dummyMenuData} />
87
+ </SBContainer>
88
+ </SBContainer>
89
+ ),
90
+ };
91
+
92
+ export const ColorCustomization: Story = {
93
+ name: 'Color Customization',
94
+ render: () => (
95
+ <SBContainer flex direction="column" gap="extralarge">
96
+ <SBContainer addBackground width="300px">
97
+ <h4>Default Colors</h4>
98
+ <VerticalMenu menuData={dummyMenuData} />
99
+ </SBContainer>
100
+
101
+ <SBContainer addBackground width="300px">
102
+ <h4>Bold Link Colors</h4>
103
+ <VerticalMenu
104
+ linkColor="#1a1a1a"
105
+ linkColorHover="#ff5722"
106
+ backgroundColor="#fff3e0"
107
+ backgroundColorHover="#ffe0b2"
108
+ menuData={dummyMenuData}
109
+ />
110
+ </SBContainer>
111
+
112
+ <SBContainer addBackground width="300px">
113
+ <h4>Vibrant Theme</h4>
114
+ <VerticalMenu
115
+ linkColor="#ffffff"
116
+ linkColorHover="#ffeb3b"
117
+ backgroundColor="#1976d2"
118
+ backgroundColorHover="#1565c0"
119
+ submenuLinkColor="#e3f2fd"
120
+ submenuLinkColorHover="#ffeb3b"
121
+ submenuBackgroundColor="#0d47a1"
122
+ submenuBackgroundColorHover="#01579b"
123
+ indentation="30px"
124
+ menuData={dummyMenuData}
125
+ />
126
+ </SBContainer>
127
+
128
+ <SBContainer addBackground width="300px">
129
+ <h4>High Contrast</h4>
130
+ <VerticalMenu
131
+ linkColor="#000000"
132
+ linkColorHover="#d32f2f"
133
+ backgroundColor="#ffeb3b"
134
+ backgroundColorHover="#fdd835"
135
+ submenuLinkColor="#1a1a1a"
136
+ submenuLinkColorHover="#c62828"
137
+ submenuBackgroundColor="#fff59d"
138
+ submenuBackgroundColorHover="#fff176"
139
+ indentation="30px"
140
+ menuData={dummyMenuData}
141
+ />
142
+ </SBContainer>
143
+
144
+ <SBContainer
145
+ flex
146
+ justifyContent="center"
147
+ style={{
148
+ backgroundColor: '#0a0a0a',
149
+ padding: '20px',
150
+ minHeight: '400px',
151
+ }}
152
+ >
153
+ <SBContainer width="300px">
154
+ <h4 style={{ color: '#ffffff', marginTop: 0 }}>Dark Theme</h4>
155
+ <VerticalMenu
156
+ linkColor="#e0e0e0"
157
+ linkColorHover="#00e676"
158
+ backgroundColor="#212121"
159
+ backgroundColorHover="#424242"
160
+ submenuLinkColor="#9e9e9e"
161
+ submenuLinkColorHover="#69f0ae"
162
+ submenuBackgroundColor="#1a1a1a"
163
+ submenuBackgroundColorHover="#2c2c2c"
164
+ indentation="30px"
165
+ menuData={dummyMenuData}
166
+ />
167
+ </SBContainer>
168
+ </SBContainer>
169
+ </SBContainer>
170
+ ),
171
+ };
172
+
173
+ export const FocusState: Story = {
174
+ name: 'Focus State',
175
+ render: () => (
176
+ <SBContainer flex direction="column" gap="large">
177
+ <SBContainer addBackground width="300px">
178
+ <h4>Focus State</h4>
179
+ <p>
180
+ In production, this focus outline appears when navigating with the
181
+ keyboard (Tab key). This Storybook demo auto-applies the focus state
182
+ to show how it looks.
183
+ </p>
184
+ <SBFocusWrapper>
185
+ <VerticalMenu menuData={dummyMenuData} />
186
+ </SBFocusWrapper>
187
+ </SBContainer>
188
+ </SBContainer>
189
+ ),
190
+ };
191
+
192
+ export const SidebarExample: Story = {
193
+ name: 'In Context',
194
+ render: () => (
195
+ <SBContainer
196
+ flex
197
+ direction="row"
198
+ style={{
199
+ minHeight: '600px',
200
+ border: '1px solid #cbd6e2',
201
+ }}
202
+ >
203
+ <SBContainer
204
+ width="280px"
205
+ style={{
206
+ backgroundColor: '#f5f8fa',
207
+ borderRight: '1px solid #cbd6e2',
208
+ padding: '20px 0',
209
+ }}
210
+ >
211
+ <VerticalMenu
212
+ linkColor="#33475b"
213
+ linkColorHover="#0066cc"
214
+ backgroundColor="transparent"
215
+ backgroundColorHover="#ffffff"
216
+ submenuLinkColor="#516f90"
217
+ submenuLinkColorHover="#0066cc"
218
+ submenuBackgroundColor="transparent"
219
+ submenuBackgroundColorHover="#eaf0f6"
220
+ indentation="24px"
221
+ menuData={dummyMenuData}
222
+ />
223
+ </SBContainer>
224
+ <SBContainer
225
+ style={{
226
+ flex: 1,
227
+ padding: '40px',
228
+ backgroundColor: '#ffffff',
229
+ }}
230
+ >
231
+ <h2>Main Content Area</h2>
232
+ <p>
233
+ This demonstrates how the vertical menu works in a sidebar layout.
234
+ </p>
235
+ <p>
236
+ The sidebar navigation provides easy access to different sections
237
+ while the main content area remains spacious.
238
+ </p>
239
+ </SBContainer>
240
+ </SBContainer>
241
+ ),
242
+ };
@@ -0,0 +1,19 @@
1
+ .verticalMenuContainer {
2
+ --hscl-verticalMenu-width: 100%;
3
+ --hscl-verticalMenu-gap: 0px;
4
+ --hscl-verticalMenu-paddingInline: 10px;
5
+ --hscl-verticalMenu-paddingBlock: 8px;
6
+ --hscl-verticalMenu-color: #33475b;
7
+ --hscl-verticalMenu-color-hover: #0066cc;
8
+ --hscl-verticalMenu-backgroundColor: transparent;
9
+ --hscl-verticalMenu-backgroundColor-hover: #f5f8fa;
10
+ --hscl-verticalMenu-submenu-color: #516f90;
11
+ --hscl-verticalMenu-submenu-color-hover: #0066cc;
12
+ --hscl-verticalMenu-submenu-backgroundColor: transparent;
13
+ --hscl-verticalMenu-submenu-backgroundColor-hover: #eaf0f6;
14
+ --hscl-verticalMenu-paddingInlineStart: 20px;
15
+
16
+ nav li {
17
+ margin-bottom: 0;
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ import type { Decorator } from '@storybook/react';
2
+ import styles from './VerticalMenuDecorator.module.scss';
3
+
4
+ /**
5
+ * Shared decorator for VerticalMenu stories that applies default CSS variables
6
+ * and scoped styles to prevent pollution across other Storybook stories.
7
+ */
8
+ export const withVerticalMenuStyles: Decorator = Story => (
9
+ <div className={styles.verticalMenuContainer}>
10
+ <Story />
11
+ </div>
12
+ );
@@ -0,0 +1,21 @@
1
+ import { CSSVariables } from '../../utils/types.js';
2
+ import { ContentFieldsProps } from '../types.js';
3
+ import { SiteMenu } from '@hubspot/cms-components';
4
+
5
+ export type VerticalMenuProps = {
6
+ maxDepth?: number;
7
+ className?: string;
8
+ style?: CSSVariables;
9
+ linkColor?: string;
10
+ linkColorHover?: string;
11
+ backgroundColor?: string;
12
+ backgroundColorHover?: string;
13
+ submenuLinkColor?: string;
14
+ submenuLinkColorHover?: string;
15
+ submenuBackgroundColor?: string;
16
+ submenuBackgroundColorHover?: string;
17
+ indentation?: string;
18
+ menuData: SiteMenu;
19
+ };
20
+
21
+ export type VerticalMenuContentFieldProps = ContentFieldsProps;