@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
@@ -27,12 +27,13 @@ export const Default: Story = {
27
27
  listType: 'unordered',
28
28
  variant: 'primary',
29
29
  gap: '0',
30
+ showMarker: true,
30
31
  },
31
32
  render: args => (
32
33
  <List {...args}>
33
- <ListItem showIcon={false}>First item</ListItem>
34
- <ListItem showIcon={false}>Second item</ListItem>
35
- <ListItem showIcon={false}>Third item</ListItem>
34
+ <ListItem>First item</ListItem>
35
+ <ListItem>Second item</ListItem>
36
+ <ListItem>Third item</ListItem>
36
37
  </List>
37
38
  ),
38
39
  };
@@ -44,9 +45,9 @@ export const OrderedList: Story = {
44
45
  <SBContainer addBackground>
45
46
  <h4>Numbered List</h4>
46
47
  <List listType="ordered" variant="primary">
47
- <ListItem showIcon={false}>First step</ListItem>
48
- <ListItem showIcon={false}>Second step</ListItem>
49
- <ListItem showIcon={false}>Third step</ListItem>
48
+ <ListItem>First step</ListItem>
49
+ <ListItem>Second step</ListItem>
50
+ <ListItem>Third step</ListItem>
50
51
  </List>
51
52
  </SBContainer>
52
53
  </SBContainer>
@@ -60,9 +61,9 @@ export const UnorderedList: Story = {
60
61
  <SBContainer addBackground>
61
62
  <h4>Bulleted List</h4>
62
63
  <List listType="unordered" variant="primary">
63
- <ListItem showIcon={false}>First item</ListItem>
64
- <ListItem showIcon={false}>Second item</ListItem>
65
- <ListItem showIcon={false}>Third item</ListItem>
64
+ <ListItem>First item</ListItem>
65
+ <ListItem>Second item</ListItem>
66
+ <ListItem>Third item</ListItem>
66
67
  </List>
67
68
  </SBContainer>
68
69
  </SBContainer>
@@ -93,14 +94,7 @@ export const DynamicRendering: Story = {
93
94
  <h4>Feature List (Mapped from Array)</h4>
94
95
  <List listType="unordered" variant="primary">
95
96
  {features.map((item, index) => (
96
- <ListItem
97
- key={index}
98
- showIcon={false}
99
- index={index}
100
- itemsFieldPath="features"
101
- >
102
- {item.text}
103
- </ListItem>
97
+ <ListItem key={index}>{item.text}</ListItem>
104
98
  ))}
105
99
  </List>
106
100
  </SBContainer>
@@ -109,14 +103,7 @@ export const DynamicRendering: Story = {
109
103
  <h4>Step-by-Step Instructions</h4>
110
104
  <List listType="ordered" variant="primary">
111
105
  {steps.map((item, index) => (
112
- <ListItem
113
- key={index}
114
- showIcon={false}
115
- index={index}
116
- itemsFieldPath="steps"
117
- >
118
- {item.text}
119
- </ListItem>
106
+ <ListItem key={index}>{item.text}</ListItem>
120
107
  ))}
121
108
  </List>
122
109
  </SBContainer>
@@ -125,40 +112,24 @@ export const DynamicRendering: Story = {
125
112
  },
126
113
  };
127
114
 
128
- export const WithIcons: Story = {
129
- name: 'List with Icons',
115
+ export const NoMarkers: Story = {
116
+ name: 'List without Markers',
130
117
  render: () => (
131
118
  <SBContainer flex direction="column" gap="large">
132
119
  <SBContainer addBackground>
133
- <h4>List Items with Icons</h4>
134
- <List listType="unordered" variant="primary">
135
- <ListItem
136
- showIcon={true}
137
- iconName="icon"
138
- iconSize={16}
139
- itemsFieldPath="items"
140
- index={0}
141
- >
142
- Item with icon
143
- </ListItem>
144
- <ListItem
145
- showIcon={true}
146
- iconName="icon"
147
- iconSize={16}
148
- itemsFieldPath="items"
149
- index={1}
150
- >
151
- Another item with icon
152
- </ListItem>
153
- <ListItem
154
- showIcon={true}
155
- iconName="icon"
156
- iconSize={16}
157
- itemsFieldPath="items"
158
- index={2}
159
- >
160
- Third item with icon
161
- </ListItem>
120
+ <h4>Unordered List (No Markers)</h4>
121
+ <List listType="unordered" variant="primary" showMarker={false}>
122
+ <ListItem>First item without bullet</ListItem>
123
+ <ListItem>Second item without bullet</ListItem>
124
+ <ListItem>Third item without bullet</ListItem>
125
+ </List>
126
+ </SBContainer>
127
+ <SBContainer addBackground>
128
+ <h4>Ordered List (No Markers)</h4>
129
+ <List listType="ordered" variant="primary" showMarker={false}>
130
+ <ListItem>First step without number</ListItem>
131
+ <ListItem>Second step without number</ListItem>
132
+ <ListItem>Third step without number</ListItem>
162
133
  </List>
163
134
  </SBContainer>
164
135
  </SBContainer>
@@ -172,18 +143,18 @@ export const Spacing: Story = {
172
143
  <SBContainer addBackground>
173
144
  <h4>Default Spacing (gap 0 but inherits li styles)</h4>
174
145
  <List listType="unordered" variant="primary">
175
- <ListItem showIcon={false}>Default item one</ListItem>
176
- <ListItem showIcon={false}>Default item two</ListItem>
177
- <ListItem showIcon={false}>Default item three</ListItem>
146
+ <ListItem>Default item one</ListItem>
147
+ <ListItem>Default item two</ListItem>
148
+ <ListItem>Default item three</ListItem>
178
149
  </List>
179
150
  </SBContainer>
180
151
 
181
152
  <SBContainer addBackground>
182
153
  <h4>Generous Spacing (1rem)</h4>
183
154
  <List listType="unordered" variant="primary" gap="1rem">
184
- <ListItem showIcon={false}>Generous item one</ListItem>
185
- <ListItem showIcon={false}>Generous item two</ListItem>
186
- <ListItem showIcon={false}>Generous item three</ListItem>
155
+ <ListItem>Generous item one</ListItem>
156
+ <ListItem>Generous item two</ListItem>
157
+ <ListItem>Generous item three</ListItem>
187
158
  </List>
188
159
  </SBContainer>
189
160
  </SBContainer>
@@ -197,20 +168,20 @@ export const NestedLists: Story = {
197
168
  <SBContainer addBackground>
198
169
  <h4>Nested Unordered Lists</h4>
199
170
  <List listType="unordered" variant="primary">
200
- <ListItem showIcon={false}>Parent item one</ListItem>
171
+ <ListItem>Parent item one</ListItem>
201
172
  <li>
202
173
  Parent item two
203
174
  <List listType="unordered" variant="primary">
204
- <ListItem showIcon={false}>Child item one</ListItem>
205
- <ListItem showIcon={false}>Child item two</ListItem>
175
+ <ListItem>Child item one</ListItem>
176
+ <ListItem>Child item two</ListItem>
206
177
  </List>
207
178
  </li>
208
- <ListItem showIcon={false}>Parent item three</ListItem>
179
+ <ListItem>Parent item three</ListItem>
209
180
  <li>
210
181
  Parent item four
211
182
  <List listType="unordered" variant="primary">
212
- <ListItem showIcon={false}>Child item one</ListItem>
213
- <ListItem showIcon={false}>Child item two</ListItem>
183
+ <ListItem>Child item one</ListItem>
184
+ <ListItem>Child item two</ListItem>
214
185
  </List>
215
186
  </li>
216
187
  </List>
@@ -219,21 +190,21 @@ export const NestedLists: Story = {
219
190
  <SBContainer addBackground>
220
191
  <h4>Nested Ordered Lists</h4>
221
192
  <List listType="ordered" variant="primary">
222
- <ListItem showIcon={false}>Step one</ListItem>
193
+ <ListItem>Step one</ListItem>
223
194
  <li>
224
195
  Step two
225
196
  <List listType="ordered" variant="secondary" gap="0.25rem">
226
- <ListItem showIcon={false}>Sub-step A</ListItem>
227
- <ListItem showIcon={false}>Sub-step B</ListItem>
197
+ <ListItem>Sub-step A</ListItem>
198
+ <ListItem>Sub-step B</ListItem>
228
199
  </List>
229
200
  </li>
230
- <ListItem showIcon={false}>Step three</ListItem>
231
- <ListItem showIcon={false}>Step four</ListItem>
201
+ <ListItem>Step three</ListItem>
202
+ <ListItem>Step four</ListItem>
232
203
  <li>
233
204
  Step five
234
205
  <List listType="ordered" variant="secondary" gap="0.25rem">
235
- <ListItem showIcon={false}>Sub-step A</ListItem>
236
- <ListItem showIcon={false}>Sub-step B</ListItem>
206
+ <ListItem>Sub-step A</ListItem>
207
+ <ListItem>Sub-step B</ListItem>
237
208
  </List>
238
209
  </li>
239
210
  </List>
@@ -249,11 +220,16 @@ export const EdgeCases: Story = {
249
220
  <SBContainer addBackground>
250
221
  <h4>Long Text Content</h4>
251
222
  <List listType="unordered" variant="primary">
252
- <ListItem showIcon={false}>
253
- This is a very long list item that demonstrates how the component handles overflow and wrapping of content across multiple lines when the text becomes too long to fit on a single line.
223
+ <ListItem>
224
+ This is a very long list item that demonstrates how the component
225
+ handles overflow and wrapping of content across multiple lines when
226
+ the text becomes too long to fit on a single line.
254
227
  </ListItem>
255
- <ListItem showIcon={false}>
256
- Another extremely long list item with lots of text to show the wrapping behavior and how the component maintains proper spacing and alignment even when the content spans multiple lines in the list item element.
228
+ <ListItem>
229
+ Another extremely long list item with lots of text to show the
230
+ wrapping behavior and how the component maintains proper spacing and
231
+ alignment even when the content spans multiple lines in the list
232
+ item element.
257
233
  </ListItem>
258
234
  </List>
259
235
  </SBContainer>
@@ -266,7 +242,7 @@ export const EdgeCases: Story = {
266
242
  <SBContainer addBackground>
267
243
  <h4>Single Item</h4>
268
244
  <List listType="unordered" variant="primary">
269
- <ListItem showIcon={false}>Only one item</ListItem>
245
+ <ListItem>Only one item</ListItem>
270
246
  </List>
271
247
  </SBContainer>
272
248
  </SBContainer>
@@ -2,12 +2,9 @@ import type { Decorator } from '@storybook/react';
2
2
  import type { CSSVariables } from '../../utils/types.js';
3
3
 
4
4
  const defaultListStyles: CSSVariables = {
5
- '--hscl-listItem-textColor-primary': '#33475b',
6
- '--hscl-listItem-textColor-secondary': '#516f90',
7
- '--hscl-listItem-textColor-tertiary': '#7c98b6',
8
- '--hscl-listItem-iconColor-primary': '#33475b',
9
- '--hscl-listItem-iconColor-secondary': '#516f90',
10
- '--hscl-listItem-iconColor-tertiary': '#7c98b6',
5
+ '--hscl-listItem-color-primary': '#33475b',
6
+ '--hscl-listItem-color-secondary': '#516f90',
7
+ '--hscl-listItem-color-tertiary': '#7c98b6',
11
8
  };
12
9
 
13
10
  export const withListStyles: Decorator = Story => (
@@ -10,6 +10,7 @@ export type ListProps = {
10
10
  listType?: ListType;
11
11
  variant?: ListVariant;
12
12
  gap?: GapValue;
13
+ showMarker?: boolean;
13
14
  className?: string;
14
15
  style?: CSSVariables;
15
16
  children?: React.ReactNode;
@@ -25,7 +26,4 @@ export type StyleFieldsProps = {
25
26
  variantName?: string;
26
27
  variantLabel?: string;
27
28
  variantDefault?: ListVariant;
28
- gapName?: string;
29
- gapLabel?: string;
30
- gapDefault?: GapValue;
31
29
  };
@@ -0,0 +1,12 @@
1
+ import { LogoData } from './types.js';
2
+ import hubspotLogo from './assets/hubspot-logo.png';
3
+
4
+ export const dummyLogoData: { logo: LogoData } = {
5
+ logo: {
6
+ src: hubspotLogo,
7
+ alt: 'HubSpot Logo',
8
+ width: 150,
9
+ height: 50,
10
+ link: 'https://www.hubspot.com',
11
+ },
12
+ };
@@ -0,0 +1,22 @@
1
+ .logo {
2
+ display: var(--hscl-logo-display, inline-block);
3
+ text-decoration: none;
4
+ inline-size: fit-content;
5
+ max-inline-size: var(--hscl-logo-maxWidth, 100%);
6
+ max-block-size: var(--hscl-logo-maxHeight, none);
7
+
8
+ &:hover {
9
+ cursor: var(--hscl-logo-cursor-hover, default);
10
+ }
11
+
12
+ &.link,
13
+ &.link:hover {
14
+ cursor: var(--hscl-logo-cursor-hover, pointer);
15
+ }
16
+
17
+ .logoImage {
18
+ display: block;
19
+ max-inline-size: var(--hscl-logo-maxWidth, 100%);
20
+ max-block-size: var(--hscl-logo-maxHeight, none);
21
+ }
22
+ }
@@ -0,0 +1,73 @@
1
+ import { useMemo } from 'react';
2
+ import { useBrandSettings } from '@hubspot/cms-components';
3
+ import { dummyLogoData } from './_dummyLogoData.js';
4
+ import { LogoData, LogoProps } from './types.js';
5
+ import styles from './index.module.scss';
6
+ import cx from '../utils/classname.js';
7
+ import Link from '../Link/index.js';
8
+ import Image from '../Image/index.js';
9
+ import type { CSSVariables } from '../utils/types.js';
10
+
11
+ export const Logo = ({
12
+ useDummyLogo = false,
13
+ className = '',
14
+ style = {},
15
+ loading = 'eager',
16
+ maxWidth,
17
+ maxHeight,
18
+ }: LogoProps) => {
19
+ const { primaryLogo } = useBrandSettings() ?? {};
20
+
21
+ const logo: LogoData | undefined = useMemo(() => {
22
+ return useDummyLogo ? dummyLogoData.logo : primaryLogo;
23
+ }, [useDummyLogo, primaryLogo]);
24
+
25
+ if (!logo?.src) {
26
+ return null;
27
+ }
28
+
29
+ const hasOnlyMaxHeight = maxHeight && !maxWidth;
30
+
31
+ const defaultClasses = styles.logo;
32
+ const combinedClasses = cx(defaultClasses, className);
33
+ const imageClasses = styles.logoImage;
34
+
35
+ const wrapperStyle: CSSVariables = {
36
+ ...(maxWidth && { '--hscl-logo-maxWidth': maxWidth }),
37
+ ...(maxHeight && { '--hscl-logo-maxHeight': maxHeight }),
38
+ ...(hasOnlyMaxHeight && { '--hscl-logo-maxWidth': 'fit-content' }),
39
+ ...style,
40
+ };
41
+
42
+ const imageStyle: CSSVariables = {
43
+ ...(hasOnlyMaxHeight && {
44
+ inlineSize: 'auto',
45
+ blockSize: 'auto',
46
+ }),
47
+ };
48
+
49
+ const content = (
50
+ <Image
51
+ src={logo.src}
52
+ alt={logo.alt ?? 'Logo'}
53
+ loading={loading}
54
+ width={logo.width}
55
+ height={logo.height}
56
+ className={imageClasses}
57
+ style={imageStyle}
58
+ responsive={true}
59
+ />
60
+ );
61
+
62
+ return logo.link ? (
63
+ <Link href={logo.link} className={combinedClasses} style={wrapperStyle}>
64
+ {content}
65
+ </Link>
66
+ ) : (
67
+ <div className={combinedClasses} style={wrapperStyle}>
68
+ {content}
69
+ </div>
70
+ );
71
+ };
72
+
73
+ export default Logo;
@@ -0,0 +1,262 @@
1
+ # Logo Component
2
+
3
+ A responsive logo component that displays brand logos from HubSpot brand settings with optional link wrapping, size constraints, and loading optimization.
4
+
5
+ ## Import path
6
+ ```tsx
7
+ import Logo from '@hubspot/cms-component-library/Logo';
8
+ ```
9
+
10
+ ## Purpose
11
+
12
+ The Logo component automatically integrates with HubSpot's brand settings to display the primary brand logo. It handles responsive image rendering, optional link wrapping for navigation, size constraints for different layout contexts (navigation bars, footers, headers), and loading optimization. This component eliminates the need to manually configure logo images across modules and ensures brand consistency throughout the site.
13
+
14
+ ## Component Structure
15
+
16
+ ```
17
+ Logo/
18
+ ├── index.tsx # Main component with render logic
19
+ ├── types.tsx # TypeScript type definitions
20
+ ├── index.module.scss # CSS module with design tokens
21
+ ├── _dummyLogoData.ts # Test data for development
22
+ └── assets/
23
+ └── hubspot-logo.png # Default dummy logo asset
24
+ ```
25
+
26
+ ## Props
27
+
28
+ ```tsx
29
+ {
30
+ useDummyLogo?: boolean; // Use test logo instead of brand settings (development only)
31
+ className?: string; // Additional CSS classes
32
+ style?: React.CSSProperties; // Inline styles (including CSS variables)
33
+ maxWidth?: string; // Maximum width constraint (e.g., "150px", "10rem", "50%")
34
+ maxHeight?: string; // Maximum height constraint (e.g., "50px", "5rem", "10vh")
35
+ loading?: 'lazy' | 'eager' | 'disabled'; // Image loading behavior (defaults to 'eager')
36
+ }
37
+ ```
38
+
39
+ **Note:** The Logo component does not accept `width` or `height` props directly. The intrinsic dimensions come from the brand settings logo data (`LogoData.width` and `LogoData.height`), which are used to set the image's intrinsic size via the `width` and `height` attributes on the `<img>` element. If no `maxWidth` or `maxHeight` props are provided, the logo will display at its intrinsic size from brand settings (constrained by responsive CSS to a maximum of 100% of the container width). Use `maxWidth` and `maxHeight` to constrain the display size while maintaining the logo's aspect ratio. Both props accept any valid CSS dimension value (pixels, rem, percentages, viewport units, etc.).
40
+
41
+ **Brand Settings Data Structure:**
42
+ The component uses `useBrandSettings()` which returns logo data in this format:
43
+ ```tsx
44
+ {
45
+ src: string; // Logo image URL
46
+ alt?: string; // Alt text for accessibility
47
+ width?: number; // Intrinsic width in pixels (sets image width attribute; establishes initial size when no maxWidth/maxHeight provided)
48
+ height?: number; // Intrinsic height in pixels (sets image height attribute; establishes initial size when no maxWidth/maxHeight provided)
49
+ link?: string; // Optional URL for logo link
50
+ }
51
+ ```
52
+
53
+ ## Usage Examples
54
+
55
+ ### Basic Logo
56
+
57
+ ```tsx
58
+ import Logo from '@hubspot/cms-component-library/Logo';
59
+
60
+ <Logo />
61
+ ```
62
+
63
+ ### Logo in Navigation Bar
64
+
65
+ When you need to constrain logo size in a navigation header:
66
+
67
+ ```tsx
68
+ <nav>
69
+ <Logo maxWidth="120px" />
70
+ {/* Navigation items */}
71
+ </nav>
72
+ ```
73
+
74
+ ### Logo with Lazy Loading
75
+
76
+ For logos that appear below the fold:
77
+
78
+ ```tsx
79
+ <Logo
80
+ loading="lazy"
81
+ maxWidth="150px"
82
+ />
83
+ ```
84
+
85
+ ### Height-Only Constraint
86
+
87
+ When you only need to constrain the height (e.g., in a navigation bar with fixed height), use `maxHeight` alone. The component automatically maintains the aspect ratio:
88
+
89
+ ```tsx
90
+ <nav>
91
+ <Logo maxHeight="50px" />
92
+ {/* Navigation items */}
93
+ </nav>
94
+ ```
95
+
96
+ **Special Behavior:** When only `maxHeight` is provided (without `maxWidth`), the component automatically:
97
+ - Sets `maxWidth` to `fit-content` via the `--hscl-logo-maxWidth` CSS variable
98
+ - Sets the image's `inlineSize` and `blockSize` to `auto`
99
+ - This ensures the logo maintains its natural aspect ratio while respecting the height constraint, allowing the width to scale proportionally
100
+
101
+ ### Custom Sizing
102
+
103
+ Constrain logo size using both maxWidth and maxHeight while maintaining aspect ratio:
104
+
105
+ ```tsx
106
+ <Logo
107
+ maxWidth="250px"
108
+ maxHeight="80px"
109
+ />
110
+ ```
111
+
112
+ ### Local Development Testing
113
+
114
+ When testing locally on the dev server where brand settings aren't available, use test data:
115
+
116
+ ```tsx
117
+ <Logo useDummyLogo={true} maxWidth="150px" />
118
+ ```
119
+
120
+ ## HubSpot CMS Integration
121
+
122
+ ### Brand Settings Integration
123
+
124
+ The Logo component automatically retrieves logo data from HubSpot brand settings using the `useBrandSettings()` hook from `@hubspot/cms-components`. While this hook provides access to multiple logos, this component specifically uses only the primary logo configured in the HubSpot brand kit settings.
125
+
126
+ ### Module Usage Example
127
+
128
+ ```tsx
129
+ import Logo from '@hubspot/cms-component-library/Logo';
130
+
131
+ export default function HeaderModule({ fieldValues }) {
132
+ return (
133
+ <header>
134
+ <Logo
135
+ maxWidth={fieldValues.logoMaxWidth}
136
+ loading="eager"
137
+ />
138
+ <nav>{/* Navigation items */}</nav>
139
+ </header>
140
+ );
141
+ }
142
+ ```
143
+
144
+ ### Field Configuration
145
+
146
+ The Logo component currently does not require field configuration. All logo data comes from the HubSpot brand settings via the `useBrandSettings()` hook, or can be passed down from the parent module. This eliminates the need for repetitive logo field configuration across modules and ensures brand consistency.
147
+
148
+ ## Styling
149
+
150
+ ### CSS Variables
151
+
152
+ The Logo component uses CSS variables for theming and customization:
153
+
154
+ **Layout:**
155
+ - `--hscl-logo-display`: Display property (default: `inline-block`)
156
+ - `--hscl-logo-maxWidth`: Maximum width constraint (set via `maxWidth` prop, defaults to `100%`). When only `maxHeight` is provided, this is automatically set to `fit-content` via the component logic to maintain aspect ratio
157
+ - `--hscl-logo-maxHeight`: Maximum height constraint (set via `maxHeight` prop, defaults to `none`)
158
+
159
+ **Interactive States:**
160
+ - `--hscl-logo-cursor-hover`: Cursor style on hover when logo is wrapped in a link (default: `pointer`). When the logo is not wrapped in a link, the default cursor is `default`.
161
+ - `--hscl-logo-outlineWidth-focus`: Focus outline width (default: `2px`)
162
+ - `--hscl-logo-outlineColor-focus`: Focus outline color (default: `currentColor`)
163
+ - `--hscl-logo-outlineOffset-focus`: Focus outline offset (default: `2px`)
164
+
165
+ ## Accessibility
166
+
167
+ The Logo component follows accessibility best practices:
168
+
169
+ - **Alt Text**: Uses alt text from brand settings for screen readers. Always provide meaningful alt text in your brand settings (e.g., "Company Name Logo" rather than just "Logo")
170
+ - **Semantic Links**: When a link is configured in brand settings, the logo automatically wraps in a semantic `<Link>` component with proper accessibility attributes
171
+ - **Keyboard Navigation**: Focus states are clearly visible with customizable outline styles for keyboard users
172
+ - **Image Loading**: The `loading` prop allows control over image loading behavior, preventing layout shifts that can confuse assistive technology users
173
+ - **Responsive Images**: Uses the Image component internally to provide responsive image handling for better performance across devices
174
+
175
+ ## Best Practices
176
+
177
+ ### Brand Settings Configuration
178
+ - **Data source**: Logo data comes from HubSpot brand settings via the `useBrandSettings()` hook. If logo data is not showing up correctly, check the brand settings configuration first
179
+ - **Alt text**: The component uses alt text from brand settings. If accessibility issues arise, verify that meaningful alt text (e.g., "Acme Corporation Logo") is configured in brand settings rather than generic text
180
+ - **Logo linking**: The component automatically wraps the logo in a link if a link URL is configured in brand settings. If linking behavior is unexpected, check the brand settings link configuration
181
+ - **Image quality**: Logo display quality depends on the image uploaded to brand settings. For debugging display issues, verify that appropriately sized images (recommended 2x resolution for retina displays) are configured
182
+
183
+ ### Size Constraints
184
+ - **Use maxWidth in constrained layouts**: Navigation bars and footers often need size limits to maintain layout integrity
185
+ - **Use maxHeight for height-only constraints**: When you only need to constrain height, the component automatically handles width sizing to maintain aspect ratio
186
+ - **Maintain aspect ratio**: The component preserves the logo's aspect ratio from brand settings. Use `maxWidth` and `maxHeight` together to constrain both dimensions, or use one alone to constrain a single dimension
187
+ - **Consider responsive breakpoints**: Use CSS variables or style prop with media queries for responsive sizing
188
+
189
+ ### Performance
190
+ - **Use eager loading for above-the-fold logos**: Navigation logos should load immediately (`loading="eager"`)
191
+ - **Use lazy loading for below-the-fold logos**: Footer or secondary logos can use `loading="lazy"` to improve initial page load
192
+ - **Optimize source images**: Ensure logos in brand settings are appropriately sized and compressed
193
+
194
+ ### Development
195
+ - **Use useDummyLogo only in development**: This prop is for testing and should not be used in production code
196
+ - **Test with real brand settings**: Always test your modules with actual brand settings before deployment
197
+
198
+ ### Layout Integration
199
+ - **Combine with navigation components**: Logos commonly appear alongside navigation menus in headers
200
+ - **Provide adequate spacing**: Use CSS margins or padding to ensure the logo has breathing room
201
+ - **Consider dark/light themes**: If your site has theme variations, test logo visibility in both contexts
202
+
203
+ ## Integration with Other Components
204
+
205
+ The Logo component internally uses:
206
+
207
+ - **Image Component**: For responsive image rendering with proper attributes
208
+ - **Link Component**: For accessible link wrapping when logo.link is configured
209
+
210
+ This ensures consistency with other components in the library and leverages shared functionality for images and navigation.
211
+
212
+ ## Performance Considerations
213
+
214
+ ### Loading Behavior
215
+ - `loading="eager"`: Image loads immediately (default) - use for critical above-the-fold logos
216
+ - `loading="lazy"`: Image loads when near viewport - use for below-the-fold logos to improve initial page load
217
+
218
+ ### Responsive Images
219
+ The component uses the Image component internally, which handles:
220
+ - Responsive image sizing based on container
221
+ - Proper aspect ratio maintenance
222
+ - Efficient loading strategies
223
+
224
+ ## Common Use Cases
225
+
226
+ ### Navigation Header
227
+ ```tsx
228
+ <header style={{ display: 'flex', alignItems: 'center', padding: '1rem' }}>
229
+ <Logo maxHeight="50px" loading="eager" />
230
+ <nav>{/* Navigation menu */}</nav>
231
+ </header>
232
+ ```
233
+
234
+ ### Footer Branding
235
+ ```tsx
236
+ <footer style={{ padding: '2rem', backgroundColor: '#f5f5f5' }}>
237
+ <Logo maxWidth="180px" loading="lazy" />
238
+ <p>© 2026 Company Name. All rights reserved.</p>
239
+ </footer>
240
+ ```
241
+
242
+ ### Centered Hero Logo
243
+ ```tsx
244
+ <section style={{ textAlign: 'center', padding: '4rem' }}>
245
+ <Logo
246
+ maxWidth="300px"
247
+ style={{ '--hscl-logo-display': 'block', margin: '0 auto' }}
248
+ />
249
+ <h1>Welcome to Our Site</h1>
250
+ </section>
251
+ ```
252
+
253
+ ### Sidebar Logo
254
+ ```tsx
255
+ <aside>
256
+ <Logo
257
+ maxWidth="100px"
258
+ style={{ '--hscl-logo-display': 'block' }}
259
+ />
260
+ {/* Sidebar content */}
261
+ </aside>
262
+ ```