@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,416 @@
1
+ # Drawer Component
2
+
3
+ A slide-out panel component that overlays content from the side (left/right), top, or bottom of the viewport. Provides a modal-like experience for displaying supplementary content, navigation menus, or forms without navigating away from the current page.
4
+
5
+ ## Import path
6
+ ```tsx
7
+ import Drawer, { useDrawer } from '@hubspot/cms-component-library/Drawer';
8
+ ```
9
+
10
+ ## Purpose
11
+
12
+ The Drawer component provides a consistent way to create slide-out panels. It renders a fixed-position container that slides in from any edge of the viewport with smooth animations. The component automatically manages body scroll locking, keyboard interactions (ESC to close), and accessibility attributes. Use this component when you need to display additional content without losing the current page context, such as navigation menus, filters, settings panels, or detailed forms.
13
+
14
+ ## Component Structure
15
+
16
+ ```
17
+ Drawer/
18
+ ├── index.tsx # Main component with render logic
19
+ ├── types.ts # TypeScript type definitions
20
+ ├── hooks/
21
+ │ └── index.tsx # useDrawer hook for state management
22
+ ├── index.module.scss # CSS module with animations
23
+ └── stories/
24
+ ├── Drawer.stories.tsx # Component usage examples
25
+ ├── DrawerDecorator.tsx # Storybook decorator
26
+ └── DrawerDecorator.module.css # Decorator styles
27
+ ```
28
+
29
+ ## Components
30
+
31
+ ### Drawer (Main Component)
32
+
33
+ **Purpose:** Renders a fixed-position panel that slides in from any viewport edge with overlay support and body scroll locking.
34
+
35
+ **Props:**
36
+ ```tsx
37
+ {
38
+ open: boolean; // Controls drawer visibility (required)
39
+ direction: 'left' | 'right' | 'top' | 'bottom'; // Edge from which drawer slides (required)
40
+ variant?: 'primary' | 'secondary' | 'tertiary'; // Visual style variant (default: 'primary')
41
+ className?: string; // Additional CSS classes
42
+ style?: React.CSSProperties; // Inline styles
43
+ children?: React.ReactNode; // Drawer content
44
+ showOverlay?: boolean; // Show dark backdrop overlay (default: true)
45
+ onOverlayClick?: () => void; // Callback when overlay is clicked
46
+ fullScreen?: boolean; // Make drawer fill entire viewport (default: false)
47
+ size?: string; // Drawer width/height in CSS units (default: '300px')
48
+ 'aria-label'?: string; // Accessible label for the drawer
49
+ 'aria-labelledby'?: string; // ID of element that labels the drawer
50
+ }
51
+ ```
52
+
53
+ ### useDrawer Hook
54
+
55
+ **Purpose:** Provides drawer state management with open, close, and toggle functions.
56
+
57
+ **Return Type:**
58
+ ```tsx
59
+ {
60
+ isOpen: boolean; // Current open/closed state
61
+ open: () => void; // Opens the drawer
62
+ close: () => void; // Closes the drawer
63
+ toggle: () => void; // Toggles drawer state
64
+ }
65
+ ```
66
+
67
+ ## Usage Examples
68
+
69
+ ### Island Usage
70
+
71
+ The Drawer component requires JavaScript for interactive state management and must be used within an Island component.
72
+
73
+ #### Island Component
74
+
75
+ ```tsx
76
+ // islands/DrawerIsland.tsx
77
+ import Drawer, { useDrawer } from '@hubspot/cms-component-library/Drawer';
78
+
79
+ export default function DrawerIsland({ menuItems, direction = 'right', size = '320px' }) {
80
+ const { isOpen, open, close } = useDrawer();
81
+
82
+ return (
83
+ <>
84
+ <button onClick={open}>Open Menu</button>
85
+ <Drawer
86
+ open={isOpen}
87
+ direction={direction}
88
+ size={size}
89
+ onOverlayClick={close}
90
+ aria-label="Navigation menu"
91
+ >
92
+ <nav style={{ padding: '24px' }}>
93
+ <h2>Menu</h2>
94
+ <ul>
95
+ {menuItems?.map((item, index) => (
96
+ <li key={index}>
97
+ <a href={item.url}>{item.label}</a>
98
+ </li>
99
+ ))}
100
+ </ul>
101
+ <button onClick={close}>Close</button>
102
+ </nav>
103
+ </Drawer>
104
+ </>
105
+ );
106
+ }
107
+ ```
108
+
109
+ #### Module File
110
+
111
+ ```tsx
112
+ import { Island } from '@hubspot/cms-components';
113
+ import DrawerIsland from './islands/DrawerIsland?island';
114
+
115
+ export const Component = ({ fieldValues }) => {
116
+ return (
117
+ <Island
118
+ module={DrawerIsland}
119
+ menuItems={fieldValues.menuItems}
120
+ direction={fieldValues.direction}
121
+ size={fieldValues.size}
122
+ />
123
+ );
124
+ };
125
+ ```
126
+
127
+ ### Basic Drawer (Right Side)
128
+
129
+ ```tsx
130
+ import Drawer, { useDrawer } from '@hubspot/cms-component-library/Drawer';
131
+
132
+ export default function MyComponent() {
133
+ const { isOpen, open, close } = useDrawer();
134
+
135
+ return (
136
+ <>
137
+ <button onClick={open}>Open Menu</button>
138
+ <Drawer
139
+ open={isOpen}
140
+ direction="right"
141
+ onOverlayClick={close}
142
+ aria-label="Navigation menu"
143
+ >
144
+ <div style={{ padding: '24px' }}>
145
+ <h2>Menu</h2>
146
+ <nav>
147
+ {/* Navigation content */}
148
+ </nav>
149
+ <button onClick={close}>Close</button>
150
+ </div>
151
+ </Drawer>
152
+ </>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ### Left Side Drawer
158
+
159
+ ```tsx
160
+ <Drawer
161
+ open={isOpen}
162
+ direction="left"
163
+ onOverlayClick={close}
164
+ aria-label="Side navigation"
165
+ >
166
+ <div style={{ padding: '24px' }}>
167
+ <h2>Navigation</h2>
168
+ {/* Drawer content */}
169
+ </div>
170
+ </Drawer>
171
+ ```
172
+
173
+ ### Top Drawer
174
+
175
+ ```tsx
176
+ <Drawer
177
+ open={isOpen}
178
+ direction="top"
179
+ size="400px"
180
+ onOverlayClick={close}
181
+ aria-label="Details panel"
182
+ >
183
+ <div style={{ padding: '24px' }}>
184
+ <h2>Details</h2>
185
+ {/* Content */}
186
+ </div>
187
+ </Drawer>
188
+ ```
189
+
190
+ ### Bottom Drawer
191
+
192
+ ```tsx
193
+ <Drawer
194
+ open={isOpen}
195
+ direction="bottom"
196
+ size="50vh"
197
+ onOverlayClick={close}
198
+ aria-label="Details panel"
199
+ >
200
+ <div style={{ padding: '24px' }}>
201
+ <h2>Details</h2>
202
+ {/* Content */}
203
+ </div>
204
+ </Drawer>
205
+ ```
206
+
207
+ ### Custom Size Drawer
208
+
209
+ ```tsx
210
+ <Drawer
211
+ open={isOpen}
212
+ direction="right"
213
+ size="500px"
214
+ onOverlayClick={close}
215
+ aria-label="Details panel"
216
+ >
217
+ <div style={{ padding: '24px' }}>
218
+ <h2>Details</h2>
219
+ {/* Content */}
220
+ </div>
221
+ </Drawer>
222
+ ```
223
+
224
+ ### Full Screen Drawer
225
+
226
+ ```tsx
227
+ <Drawer
228
+ open={isOpen}
229
+ direction="right"
230
+ fullScreen
231
+ onOverlayClick={close}
232
+ aria-label="Full screen content"
233
+ >
234
+ <div style={{ padding: '48px' }}>
235
+ <h1>Full Screen Content</h1>
236
+ <p>This drawer takes up the entire viewport.</p>
237
+ <button onClick={close}>Close</button>
238
+ </div>
239
+ </Drawer>
240
+ ```
241
+
242
+ ### Without Overlay
243
+
244
+ ```tsx
245
+ <Drawer
246
+ open={isOpen}
247
+ direction="right"
248
+ showOverlay={false}
249
+ aria-label="Side panel"
250
+ >
251
+ <div style={{ padding: '24px' }}>
252
+ <h2>Panel</h2>
253
+ <p>Background remains visible and interactive.</p>
254
+ <button onClick={close}>Close</button>
255
+ </div>
256
+ </Drawer>
257
+ ```
258
+
259
+ ### With Toggle Control
260
+
261
+ ```tsx
262
+ const { isOpen, toggle } = useDrawer();
263
+
264
+ <>
265
+ <button onClick={toggle}>
266
+ {isOpen ? 'Close' : 'Open'} Drawer
267
+ </button>
268
+ <Drawer
269
+ open={isOpen}
270
+ direction="right"
271
+ showOverlay={false}
272
+ aria-label="Toggleable drawer"
273
+ >
274
+ <div style={{ padding: '24px' }}>
275
+ {/* Content */}
276
+ </div>
277
+ </Drawer>
278
+ </>
279
+ ```
280
+
281
+ ## HubSpot CMS Integration
282
+
283
+ ### Field Definitions
284
+
285
+ The Drawer component is primarily interactive and does not provide pre-built field definitions. Since drawers are typically controlled programmatically (open/close state, callbacks), the component focuses on behavior rather than static content configuration. You can define your own fields based on the content you want to display inside the drawer (such as menu items, settings options, or form data).
286
+
287
+ ### Module Usage Example
288
+
289
+ When integrating Drawer into a HubSpot module, create an Island component that manages drawer state and pass any necessary data from field values.
290
+
291
+ ```tsx
292
+ import { Island } from '@hubspot/cms-components';
293
+ import DrawerIsland from './islands/DrawerIsland?island';
294
+
295
+ export default function NavigationModule({ fieldValues }) {
296
+ return (
297
+ <Island
298
+ module={DrawerIsland}
299
+ menuItems={fieldValues.menuItems}
300
+ direction={fieldValues.direction || 'left'}
301
+ size={fieldValues.size || '320px'}
302
+ />
303
+ );
304
+ }
305
+ ```
306
+
307
+ ## Styling
308
+
309
+ ### CSS Variables
310
+
311
+ The Drawer component uses CSS variables for theming and customization:
312
+
313
+ **Base Styles:**
314
+ - `--hscl-drawer-backgroundColor`: Drawer background color (default: white)
315
+ - `--hscl-drawer-boxShadow`: Shadow effect for depth (default: 0 2px 8px rgba(0, 0, 0, 0.15))
316
+ - `--hscl-drawer-zIndex`: Drawer z-index (default: 1000)
317
+ - `--hscl-drawer-size`: Drawer width (for left/right) or height (for top/bottom)
318
+
319
+ **Overlay Styles:**
320
+ - `--hscl-drawer-overlay-backgroundColor`: Overlay background (default: rgba(0, 0, 0, 0.5))
321
+ - `--hscl-drawer-overlay-backdropFilter`: Backdrop blur effect (default: blur(3px))
322
+ - `--hscl-drawer-overlay-zIndex`: Overlay z-index (default: 999)
323
+
324
+ ### Custom Styling Example
325
+
326
+ ```tsx
327
+ <Drawer
328
+ open={isOpen}
329
+ direction="right"
330
+ onOverlayClick={close}
331
+ style={{
332
+ '--hscl-drawer-backgroundColor': '#f5f5f5',
333
+ '--hscl-drawer-boxShadow': '0 4px 16px rgba(0, 0, 0, 0.2)',
334
+ } as React.CSSProperties}
335
+ >
336
+ {/* Content */}
337
+ </Drawer>
338
+ ```
339
+
340
+ ## Accessibility
341
+
342
+ The Drawer component follows accessibility best practices:
343
+
344
+ - **Semantic HTML**: Uses `role="dialog"` and `aria-modal="true"` for proper semantics
345
+ - **Keyboard Navigation**:
346
+ - `ESC` key closes the drawer
347
+ - Focus management is handled automatically
348
+ - **Screen Reader Support**:
349
+ - `aria-label` or `aria-labelledby` should be provided to describe drawer purpose
350
+ - `aria-hidden` attribute toggles based on open state
351
+ - Proper ARIA roles announce drawer as a modal dialog
352
+ - **Body Scroll Locking**: Background content scroll is disabled when drawer is open
353
+ - **Focus Trap**: Overlay click closes drawer, preventing accidental interactions with background
354
+ - **Visual Indicators**: Overlay provides clear visual feedback that drawer is modal
355
+ - **Reduced Motion**: Respects `prefers-reduced-motion` for users with motion sensitivity
356
+
357
+ ## Best Practices
358
+
359
+ - **Use Islands for interactivity**: The Drawer component is interactive and requires JavaScript, so it must be used within an Island component in modules
360
+ - **Provide aria-label**: Always include `aria-label` or `aria-labelledby` to describe the drawer's purpose
361
+ - **Choose appropriate direction**:
362
+ - `left` or `right`: Best for navigation menus, settings panels, or supplementary content
363
+ - `top`: Good for notification panels or collapsible headers
364
+ - `bottom`: Ideal for mobile-style action sheets or details panels
365
+ - **Size considerations**:
366
+ - Consider `fullScreen` for complex workflows or extensive forms
367
+ - **Handle close actions**: Always provide a way to close the drawer (overlay click, close button)
368
+ - **Overlay usage**: Keep `showOverlay={true}` (default) for modal behavior, use `false` only for persistent sidebars
369
+ - **Body scroll locking**: The component automatically prevents background scrolling when open
370
+ - **ESC key support**: The drawer closes automatically when ESC is pressed (built-in)
371
+ - **Content structure**: Include a clear heading and close button within drawer content for usability
372
+ - **Avoid nested drawers**: Don't open multiple non-full-screen drawers simultaneously as it creates confusing UX
373
+ - **State management**: Use the `useDrawer` hook for consistent state management
374
+ - **Loading states**: Consider showing loading indicators within drawer content during async operations
375
+
376
+ ## Common Patterns
377
+
378
+ ### Navigation Menu Drawer
379
+
380
+ ```tsx
381
+ import Drawer, { useDrawer } from '@hubspot/cms-component-library/Drawer';
382
+
383
+ export default function NavigationDrawer({ menuItems }) {
384
+ const { isOpen, open, close } = useDrawer();
385
+
386
+ return (
387
+ <>
388
+ <button onClick={open}>Menu</button>
389
+ <Drawer
390
+ open={isOpen}
391
+ direction="left"
392
+ size="320px"
393
+ onOverlayClick={close}
394
+ aria-label="Navigation menu"
395
+ >
396
+ <nav style={{ padding: '24px' }}>
397
+ <h2>Navigation</h2>
398
+ <ul>
399
+ {menuItems.map((item, i) => (
400
+ <li key={i}>
401
+ <a href={item.url}>{item.label}</a>
402
+ </li>
403
+ ))}
404
+ </ul>
405
+ <button onClick={close}>Close</button>
406
+ </nav>
407
+ </Drawer>
408
+ </>
409
+ );
410
+ }
411
+ ```
412
+
413
+
414
+ ## Related Components
415
+
416
+ - **useDrawer**: Hook for managing drawer open/close state. Always use this hook rather than managing state manually.