@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,349 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import LanguageSwitcher from '../index.js';
3
+ import { LanguageSwitcherProps } from '../types.js';
4
+ import { withLanguageSwitcherStyles } from './LanguageSwitcherDecorator.js';
5
+ import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
+
7
+ const meta: Meta<LanguageSwitcherProps> = {
8
+ title: 'Component Library/LanguageSwitcher',
9
+ component: LanguageSwitcher,
10
+ parameters: {
11
+ layout: 'fullscreen',
12
+ docs: {
13
+ description: {
14
+ component: `The LanguageSwitcher component provides a button and drawer interface for selecting different language variants of a page. It displays the current page language and opens a slide-out panel with all available language options. The component uses the Drawer component internally for the language selection panel.`,
15
+ },
16
+ story: {
17
+ inline: false,
18
+ iframeHeight: 500,
19
+ },
20
+ },
21
+ },
22
+ decorators: [withLanguageSwitcherStyles],
23
+ argTypes: {
24
+ useDummyData: {
25
+ control: 'boolean',
26
+ description: 'Use dummy translation data for testing',
27
+ table: {
28
+ category: 'Development',
29
+ defaultValue: { summary: 'false' },
30
+ },
31
+ },
32
+ languageSwitcherSelectText: {
33
+ control: 'text',
34
+ description: 'Text displayed in the drawer header',
35
+ table: {
36
+ category: 'Content',
37
+ defaultValue: { summary: 'Select a language' },
38
+ },
39
+ },
40
+ textColor: {
41
+ control: 'object',
42
+ description: 'Text color for button and menu items',
43
+ table: {
44
+ category: 'Visual',
45
+ },
46
+ },
47
+ textColorHover: {
48
+ control: 'object',
49
+ description: 'Text color for menu items on hover',
50
+ table: {
51
+ category: 'Visual',
52
+ },
53
+ },
54
+ menuBackgroundColor: {
55
+ control: 'object',
56
+ description: 'Background color for the drawer',
57
+ table: {
58
+ category: 'Visual',
59
+ },
60
+ },
61
+ menuBackgroundColorHover: {
62
+ control: 'object',
63
+ description: 'Background color for menu items on hover',
64
+ table: {
65
+ category: 'Visual',
66
+ },
67
+ },
68
+ className: {
69
+ control: 'text',
70
+ description: 'Additional CSS classes',
71
+ table: {
72
+ category: 'Basic',
73
+ },
74
+ },
75
+ style: {
76
+ control: 'object',
77
+ description: 'Inline styles',
78
+ table: {
79
+ category: 'Basic',
80
+ },
81
+ },
82
+ },
83
+ };
84
+
85
+ export default meta;
86
+ type Story = StoryObj<typeof meta>;
87
+
88
+ export const Default: Story = {
89
+ render: () => {
90
+ return (
91
+ <SBContainer>
92
+ <LanguageSwitcher
93
+ useDummyData={true}
94
+ languageSwitcherSelectText="Select a language"
95
+ />
96
+ </SBContainer>
97
+ );
98
+ },
99
+ };
100
+
101
+ export const ColorThemes: Story = {
102
+ name: 'Color Themes',
103
+ render: () => {
104
+ const purpleTextColor = {
105
+ rgba: 'rgba(139, 0, 139, 1)',
106
+ };
107
+
108
+ const purpleTextColorHover = {
109
+ rgba: 'rgba(186, 85, 211, 1)',
110
+ };
111
+
112
+ const purpleMenuBgColor = {
113
+ rgba: 'rgba(243, 229, 245, 1)',
114
+ };
115
+
116
+ const purpleMenuBgColorHover = {
117
+ rgba: 'rgba(225, 190, 231, 1)',
118
+ };
119
+
120
+ const darkTextColor = {
121
+ rgba: 'rgba(255, 255, 255, 0.9)',
122
+ };
123
+
124
+ const darkTextColorHover = {
125
+ rgba: 'rgba(100, 181, 246, 1)',
126
+ };
127
+
128
+ const darkMenuBgColor = {
129
+ rgba: 'rgba(33, 33, 33, 1)',
130
+ };
131
+
132
+ const darkMenuBgColorHover = {
133
+ rgba: 'rgba(66, 66, 66, 1)',
134
+ };
135
+
136
+ return (
137
+ <SBContainer flex direction="column" gap="large">
138
+ <SBContainer addBackground>
139
+ <h4>Purple Theme</h4>
140
+ <p>Custom color theming with purple tones.</p>
141
+ <SBContainer>
142
+ <LanguageSwitcher
143
+ useDummyData={true}
144
+ languageSwitcherSelectText="Wählen Sie eine Sprache"
145
+ textColor={purpleTextColor}
146
+ textColorHover={purpleTextColorHover}
147
+ menuBackgroundColor={purpleMenuBgColor}
148
+ menuBackgroundColorHover={purpleMenuBgColorHover}
149
+ />
150
+ </SBContainer>
151
+ </SBContainer>
152
+
153
+ <SBContainer style={{ backgroundColor: '#121212' }}>
154
+ <SBContainer style={{ marginBottom: '16px' }}>
155
+ <h4 style={{ color: 'white', margin: '0 0 8px 0' }}>
156
+ Dark Mode Theme
157
+ </h4>
158
+ <p style={{ color: 'white', margin: 0 }}>
159
+ Language switcher optimized for dark backgrounds with high
160
+ contrast colors.
161
+ </p>
162
+ </SBContainer>
163
+ <SBContainer>
164
+ <LanguageSwitcher
165
+ useDummyData={true}
166
+ languageSwitcherSelectText="Select a language"
167
+ textColor={darkTextColor}
168
+ textColorHover={darkTextColorHover}
169
+ menuBackgroundColor={darkMenuBgColor}
170
+ menuBackgroundColorHover={darkMenuBgColorHover}
171
+ />
172
+ </SBContainer>
173
+ </SBContainer>
174
+ </SBContainer>
175
+ );
176
+ },
177
+ };
178
+
179
+ export const CustomText: Story = {
180
+ name: 'Custom Header Text',
181
+ render: () => {
182
+ return (
183
+ <SBContainer flex direction="column" gap="large">
184
+ <SBContainer addBackground>
185
+ <h4>Different Languages for Header Text</h4>
186
+ <p>
187
+ The languageSwitcherSelectText prop can be customized to match your
188
+ site's language or messaging.
189
+ </p>
190
+ </SBContainer>
191
+
192
+ <SBContainer flex direction="row" gap="medium">
193
+ <SBContainer>
194
+ <p style={{ marginBottom: '12px' }}>
195
+ <strong>🇬🇧 English:</strong>
196
+ </p>
197
+ <LanguageSwitcher
198
+ useDummyData={true}
199
+ languageSwitcherSelectText="Select a language"
200
+ />
201
+ </SBContainer>
202
+
203
+ <SBContainer>
204
+ <p style={{ marginBottom: '12px' }}>
205
+ <strong>🇩🇪 German:</strong>
206
+ </p>
207
+ <LanguageSwitcher
208
+ useDummyData={true}
209
+ languageSwitcherSelectText="Sprache wählen"
210
+ />
211
+ </SBContainer>
212
+
213
+ <SBContainer>
214
+ <p style={{ marginBottom: '12px' }}>
215
+ <strong>🇪🇸 Spanish:</strong>
216
+ </p>
217
+ <LanguageSwitcher
218
+ useDummyData={true}
219
+ languageSwitcherSelectText="Seleccionar idioma"
220
+ />
221
+ </SBContainer>
222
+
223
+ <SBContainer>
224
+ <p style={{ marginBottom: '12px' }}>
225
+ <strong>🇫🇷 French:</strong>
226
+ </p>
227
+ <LanguageSwitcher
228
+ useDummyData={true}
229
+ languageSwitcherSelectText="Choisir une langue"
230
+ />
231
+ </SBContainer>
232
+ </SBContainer>
233
+ </SBContainer>
234
+ );
235
+ },
236
+ };
237
+
238
+ export const InContext: Story = {
239
+ name: 'In Page Context',
240
+ render: () => {
241
+ return (
242
+ <SBContainer style={{ minHeight: '100vh' }}>
243
+ <header
244
+ style={{
245
+ backgroundColor: '#0066cc',
246
+ color: 'white',
247
+ padding: '16px 24px',
248
+ display: 'flex',
249
+ justifyContent: 'space-between',
250
+ alignItems: 'center',
251
+ }}
252
+ >
253
+ <SBContainer>
254
+ <h1 style={{ margin: 0, fontSize: '24px', color: 'white' }}>
255
+ My Website
256
+ </h1>
257
+ <p
258
+ style={{
259
+ margin: '4px 0 0 0',
260
+ fontSize: '14px',
261
+ opacity: 0.75,
262
+ color: 'white',
263
+ }}
264
+ >
265
+ Global multilingual site
266
+ </p>
267
+ </SBContainer>
268
+
269
+ <nav
270
+ style={{
271
+ display: 'flex',
272
+ gap: '24px',
273
+ alignItems: 'center',
274
+ }}
275
+ >
276
+ <a href="#home" style={{ color: 'white', textDecoration: 'none' }}>
277
+ Home
278
+ </a>
279
+ <a href="#about" style={{ color: 'white', textDecoration: 'none' }}>
280
+ About
281
+ </a>
282
+ <a
283
+ href="#contact"
284
+ style={{ color: 'white', textDecoration: 'none' }}
285
+ >
286
+ Contact
287
+ </a>
288
+
289
+ <div style={{ paddingTop: '6px' }}>
290
+ <LanguageSwitcher
291
+ useDummyData={true}
292
+ textColor={{ rgba: 'rgb(55, 245, 255)' }}
293
+ textColorHover={{ rgba: 'rgb(255, 255, 255)' }}
294
+ menuBackgroundColor={{ rgba: 'rgba(100, 181, 246, 1)' }}
295
+ menuBackgroundColorHover={{ rgba: 'rgba(0, 160, 240, 1)' }}
296
+ />
297
+ </div>
298
+ </nav>
299
+ </header>
300
+
301
+ <main style={{ padding: '48px 24px' }}>
302
+ <SBContainer addBackground>
303
+ <h2>Welcome to our multilingual website</h2>
304
+ <p>
305
+ This example shows how the LanguageSwitcher component can be
306
+ integrated into a typical website header with navigation elements.
307
+ </p>
308
+ <p style={{ marginTop: '16px' }}>
309
+ The language switcher is positioned in the top right corner,
310
+ making it easily accessible to users who want to change the site
311
+ language.
312
+ </p>
313
+ </SBContainer>
314
+ </main>
315
+ </SBContainer>
316
+ );
317
+ },
318
+ };
319
+
320
+ export const InteractionStates: Story = {
321
+ name: 'Interaction States',
322
+ render: () => (
323
+ <SBContainer flex direction="column" gap="large">
324
+ <SBContainer addBackground>
325
+ <h4>Hover State</h4>
326
+ <p>Hover over the language switcher button to see the hover effect</p>
327
+ <LanguageSwitcher
328
+ useDummyData={true}
329
+ languageSwitcherSelectText="Select a language"
330
+ />
331
+ </SBContainer>
332
+
333
+ <SBContainer addBackground>
334
+ <h4>Focus State</h4>
335
+ <p>
336
+ In production, this focus outline appears when navigating with the
337
+ keyboard (Tab key). This Storybook demo auto-applies the focus state
338
+ to show how it looks.
339
+ </p>
340
+ <SBFocusWrapper>
341
+ <LanguageSwitcher
342
+ useDummyData={true}
343
+ languageSwitcherSelectText="Select a language"
344
+ />
345
+ </SBFocusWrapper>
346
+ </SBContainer>
347
+ </SBContainer>
348
+ ),
349
+ };
@@ -0,0 +1,5 @@
1
+ .decoratorContainer {
2
+ min-height: 100vh;
3
+ width: 100%;
4
+ position: relative;
5
+ }
@@ -0,0 +1,8 @@
1
+ import type { Decorator } from '@storybook/react';
2
+ import styles from './LanguageSwitcherDecorator.module.scss';
3
+
4
+ export const withLanguageSwitcherStyles: Decorator = Story => (
5
+ <div className={styles.decoratorContainer}>
6
+ <Story />
7
+ </div>
8
+ );
@@ -0,0 +1,48 @@
1
+ import { LanguageVariant } from '@hubspot/cms-components';
2
+ import {
3
+ ColorFieldDefaults,
4
+ TextFieldDefaults,
5
+ } from '@hubspot/cms-components/fields';
6
+ import type { CSSVariables } from '../utils/types.js';
7
+
8
+ type ColorFieldValue = typeof ColorFieldDefaults;
9
+
10
+ export type LanguageSwitcherProps = {
11
+ useDummyData?: boolean; // !todo - remove this in PROD
12
+ className?: string;
13
+ style?: CSSVariables;
14
+ color?: ColorFieldValue;
15
+ colorHover?: ColorFieldValue;
16
+ menuBackgroundColorHover?: ColorFieldValue;
17
+ menuBackgroundColor?: ColorFieldValue;
18
+ languageSwitcherSelectText?: string;
19
+ };
20
+
21
+ export type LanguageOptionsProps = {
22
+ translations: LanguageVariant[];
23
+ menuBackgroundColor?: ColorFieldValue;
24
+ menuBackgroundColorHover?: ColorFieldValue;
25
+ color?: ColorFieldValue;
26
+ colorHover?: ColorFieldValue;
27
+ };
28
+
29
+ export type ContentFieldsProps = {
30
+ languageSwitcherSelectTextLabel?: string;
31
+ languageSwitcherSelectTextName?: string;
32
+ languageSwitcherSelectTextDefault?: typeof TextFieldDefaults;
33
+ };
34
+
35
+ export type StyleFieldsProps = {
36
+ colorLabel?: string;
37
+ colorName?: string;
38
+ colorDefault?: ColorFieldValue;
39
+ colorHoverLabel?: string;
40
+ colorHoverName?: string;
41
+ colorHoverDefault?: ColorFieldValue;
42
+ menuBackgroundColorLabel?: string;
43
+ menuBackgroundColorName?: string;
44
+ menuBackgroundColorDefault?: ColorFieldValue;
45
+ menuBackgroundColorHoverLabel?: string;
46
+ menuBackgroundColorHoverName?: string;
47
+ menuBackgroundColorHoverDefault?: ColorFieldValue;
48
+ };
@@ -0,0 +1,38 @@
1
+ import { LanguageVariant } from '@hubspot/cms-components';
2
+
3
+ // Define interfaces for styled components props
4
+
5
+ type TranslationsArrayAsObject = Record<string, LanguageVariant>;
6
+
7
+ type GetLanguageDisplayNameArgs = {
8
+ currentPageLanguage: string;
9
+ translationsArrayAsObject: TranslationsArrayAsObject | null;
10
+ };
11
+
12
+ // Shared utility functions
13
+ export const getLanguageDisplayName = (args: GetLanguageDisplayNameArgs) => {
14
+ const { currentPageLanguage, translationsArrayAsObject } = args;
15
+
16
+ if (!translationsArrayAsObject || !currentPageLanguage) {
17
+ return '';
18
+ }
19
+
20
+ return (
21
+ translationsArrayAsObject[currentPageLanguage]?.languageDisplayName
22
+ .LOCALIZED ?? ''
23
+ );
24
+ };
25
+
26
+ export const createTranslationsArrayAsObject = (
27
+ translations: LanguageVariant[]
28
+ ): TranslationsArrayAsObject | null => {
29
+ // Early return if no translations
30
+ if (!translations || translations.length <= 1) {
31
+ return null;
32
+ }
33
+
34
+ return translations.reduce<TranslationsArrayAsObject>((acc, translation) => {
35
+ acc[translation.languageCode] = translation;
36
+ return acc;
37
+ }, {});
38
+ };
@@ -1,7 +1,7 @@
1
1
  import { LinkField } from '@hubspot/cms-components/fields';
2
2
  import { ContentFieldsProps } from './types.js';
3
3
 
4
- export default function ContentFields({
4
+ const ContentFields = ({
5
5
  linkLabel = 'Link',
6
6
  linkName = 'link',
7
7
  linkDefault = {
@@ -11,6 +11,8 @@ export default function ContentFields({
11
11
  href: 'https://www.hubspot.com',
12
12
  },
13
13
  },
14
- }: ContentFieldsProps) {
14
+ }: ContentFieldsProps) => {
15
15
  return <LinkField label={linkLabel} name={linkName} default={linkDefault} />;
16
- }
16
+ };
17
+
18
+ export default ContentFields;
@@ -2,11 +2,11 @@ import { ChoiceField } from '@hubspot/cms-components/fields';
2
2
  import { StyleFieldsProps } from './types.js';
3
3
 
4
4
  // !todo: may not need later, but keeping for now in case we have variant system for links
5
- export default function StyleFields({
5
+ const StyleFields = ({
6
6
  linkVariantLabel = 'Link variant',
7
7
  linkVariantName = 'linkVariant',
8
8
  linkVariantDefault = 'primary',
9
- }: StyleFieldsProps) {
9
+ }: StyleFieldsProps) => {
10
10
  return (
11
11
  <>
12
12
  <ChoiceField
@@ -21,4 +21,6 @@ export default function StyleFields({
21
21
  />
22
22
  </>
23
23
  );
24
- }
24
+ };
25
+
26
+ export default StyleFields;
@@ -8,4 +8,14 @@
8
8
  color: var(--hscl-link-color-hover);
9
9
  text-decoration: var(--hscl-link-textDecoration-hover, underline);
10
10
  }
11
+
12
+ &:focus-visible {
13
+ outline: var(--hscl-link-outlineWidth-focus, 2px) solid var(--hscl-link-outlineColor-focus, currentColor);
14
+ outline-offset: var(--hscl-link-outlineOffset-focus, 2px);
15
+ }
16
+
17
+ &:active {
18
+ color: var(--hscl-link-color-active, var(--hscl-link-color));
19
+ text-decoration: var(--hscl-link-textDecoration-active, var(--hscl-link-textDecoration, underline));
20
+ }
11
21
  }
@@ -3,34 +3,43 @@ import ContentFields from './ContentFields.js';
3
3
  import StyleFields from './StyleFields.js';
4
4
  import cx from '../utils/classname.js';
5
5
  import type { CSSVariables } from '../utils/types.js';
6
- import { LinkProps } from './types.js';
6
+ import { LinkProps, BaseLinkProps, LinkHTMLProps } from './types.js';
7
7
  import {
8
8
  getLinkRel,
9
9
  getLinkFieldTarget,
10
10
  getLinkFieldRel,
11
11
  } from '../utils/linkField.js';
12
12
 
13
- const LinkComponent = ({
14
- variant = 'primary',
15
- className = '',
16
- style = {},
17
- children,
18
- ...props
19
- }: LinkProps) => {
13
+ const getLinkValues = (props: Omit<LinkProps, keyof BaseLinkProps>) => {
20
14
  let href: string;
21
15
  let target: '_self' | '_blank' | '_parent' | '_top';
22
16
  let relValues: string | undefined;
17
+ let anchorProps: LinkHTMLProps;
23
18
 
19
+ // if linkField was passed in, use it for attrs; otherwise, build up link attrs from props
24
20
  if ('linkField' in props && props.linkField) {
25
- href = props.linkField.url?.href || '';
26
- target = getLinkFieldTarget(props.linkField);
27
- relValues = getLinkFieldRel(props.linkField);
21
+ const { linkField, ...otherProps } = props;
22
+ href = linkField.url?.href || '';
23
+ target = getLinkFieldTarget(linkField);
24
+ relValues = getLinkFieldRel(linkField);
25
+ anchorProps = otherProps;
28
26
  } else {
29
- href = props.href || '';
30
- target = props.target || '_self';
27
+ ({ href = '', target = '_self', ...anchorProps } = props);
31
28
  relValues = getLinkRel(target, props.rel);
32
29
  }
33
30
 
31
+ return { href, target, rel: relValues, anchorProps };
32
+ };
33
+
34
+ const LinkComponent = ({
35
+ variant = 'primary',
36
+ className = '',
37
+ style = {},
38
+ children,
39
+ ...rest
40
+ }: LinkProps) => {
41
+ const { href, target, rel, anchorProps } = getLinkValues(rest);
42
+
34
43
  const defaultClasses = styles.link;
35
44
  const combinedClasses = cx(defaultClasses, className);
36
45
 
@@ -45,7 +54,8 @@ const LinkComponent = ({
45
54
  style={{ ...cssVariables, ...style }}
46
55
  href={href}
47
56
  target={target}
48
- rel={relValues}
57
+ rel={rel}
58
+ {...anchorProps}
49
59
  >
50
60
  {children}
51
61
  </a>
@@ -2,13 +2,14 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import Link from '../index.js';
3
3
  import { LinkProps } from '../types.js';
4
4
  import { withLinkStyles } from './LinkDecorator.js';
5
- import { SBContainer } from '@sb-utils/SBContainer.js';
5
+ import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
6
  import { SBCard } from '@sb-utils/SBCard.js';
7
+ import type { CSSVariables } from '../../utils/types.js';
7
8
 
8
- const removeUnderlineStyle = {
9
- ['--hscl-link-textDecoration' as string]: 'none',
10
- ['--hscl-link-textDecoration-hover' as string]: 'none',
11
- } as React.CSSProperties;
9
+ const removeUnderlineStyle: CSSVariables = {
10
+ '--hscl-link-textDecoration': 'none',
11
+ '--hscl-link-textDecoration-hover': 'none',
12
+ };
12
13
 
13
14
  const meta: Meta<LinkProps> = {
14
15
  title: 'Component Library/Link',
@@ -170,3 +171,32 @@ export const InContext: Story = {
170
171
  </SBContainer>
171
172
  ),
172
173
  };
174
+
175
+ export const InteractionStates: Story = {
176
+ name: 'Interaction States',
177
+ render: () => (
178
+ <SBContainer flex direction="column" gap="large">
179
+ <SBContainer addBackground>
180
+ <h4>Hover State</h4>
181
+ <p>Hover over the link to see the hover effect</p>
182
+ <Link href="https://www.hubspot.com" variant="primary">
183
+ Hover me
184
+ </Link>
185
+ </SBContainer>
186
+
187
+ <SBContainer addBackground>
188
+ <h4>Focus State</h4>
189
+ <p>
190
+ In production, this focus outline appears when navigating with the
191
+ keyboard (Tab key). This Storybook demo auto-applies the focus state
192
+ to show how it looks.
193
+ </p>
194
+ <SBFocusWrapper>
195
+ <Link href="https://www.hubspot.com" variant="primary">
196
+ Focused link
197
+ </Link>
198
+ </SBFocusWrapper>
199
+ </SBContainer>
200
+ </SBContainer>
201
+ ),
202
+ };
@@ -1,7 +1,17 @@
1
1
  import type { Decorator } from '@storybook/react';
2
+ import type { CSSVariables } from '../../utils/types.js';
3
+
4
+ const defaultLinkStyles: CSSVariables = {
5
+ '--hscl-link-color-primary': '#0066cc',
6
+ '--hscl-link-color-secondary': '#516f90',
7
+ '--hscl-link-color-tertiary': '#7c98b6',
8
+ '--hscl-link-color-hover-primary': '#0052a3',
9
+ '--hscl-link-color-hover-secondary': '#33475b',
10
+ '--hscl-link-color-hover-tertiary': '#516f90',
11
+ };
2
12
 
3
13
  export const withLinkStyles: Decorator = Story => (
4
- <div>
14
+ <div style={defaultLinkStyles}>
5
15
  <Story />
6
16
  </div>
7
17
  );