@hubspot/cms-component-library 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/components/componentLibrary/Accordion/AccordionContent/ContentFields.tsx +5 -3
  2. package/components/componentLibrary/Accordion/AccordionItem/StyleFields.tsx +5 -3
  3. package/components/componentLibrary/Accordion/AccordionItem/index.module.scss +2 -2
  4. package/components/componentLibrary/Accordion/AccordionItem/index.tsx +3 -3
  5. package/components/componentLibrary/Accordion/AccordionTitle/ContentFields.tsx +5 -3
  6. package/components/componentLibrary/Accordion/AccordionTitle/index.module.scss +2 -2
  7. package/components/componentLibrary/Accordion/stories/Accordion.stories.tsx +80 -1
  8. package/components/componentLibrary/Accordion/stories/AccordionDecorator.tsx +14 -14
  9. package/components/componentLibrary/Button/ContentFields.tsx +5 -3
  10. package/components/componentLibrary/Button/StyleFields.tsx +26 -4
  11. package/components/componentLibrary/Button/index.module.scss +22 -14
  12. package/components/componentLibrary/Button/index.tsx +6 -6
  13. package/components/componentLibrary/Button/llm.txt +5 -1
  14. package/components/componentLibrary/Button/stories/Button.AsButton.stories.tsx +30 -1
  15. package/components/componentLibrary/Button/stories/Button.AsLink.stories.tsx +38 -1
  16. package/components/componentLibrary/Button/stories/ButtonDecorator.tsx +1 -1
  17. package/components/componentLibrary/Button/types.ts +6 -1
  18. package/components/componentLibrary/Card/StyleFields.tsx +5 -3
  19. package/components/componentLibrary/Card/stories/Card.stories.tsx +46 -1
  20. package/components/componentLibrary/Card/stories/CardDecorator.tsx +1 -1
  21. package/components/componentLibrary/Divider/ContentFields.tsx +5 -3
  22. package/components/componentLibrary/Divider/StyleFields.tsx +5 -3
  23. package/components/componentLibrary/Divider/index.module.scss +6 -6
  24. package/components/componentLibrary/Divider/index.tsx +7 -3
  25. package/components/componentLibrary/Divider/stories/Divider.stories.tsx +44 -50
  26. package/components/componentLibrary/Divider/stories/{DividerDecorator.module.css → DividerDecorator.module.scss} +5 -4
  27. package/components/componentLibrary/Divider/stories/DividerDecorator.tsx +1 -1
  28. package/components/componentLibrary/Divider/types.ts +3 -1
  29. package/components/componentLibrary/Drawer/hooks/index.tsx +13 -0
  30. package/components/componentLibrary/Drawer/index.module.scss +94 -0
  31. package/components/componentLibrary/Drawer/index.tsx +131 -0
  32. package/components/componentLibrary/Drawer/llm.txt +416 -0
  33. package/components/componentLibrary/Drawer/stories/Drawer.stories.tsx +512 -0
  34. package/components/componentLibrary/Drawer/stories/DrawerDecorator.module.scss +8 -0
  35. package/components/componentLibrary/Drawer/stories/DrawerDecorator.tsx +18 -0
  36. package/components/componentLibrary/Drawer/types.ts +25 -0
  37. package/components/componentLibrary/Flex/stories/FlexDecorator.tsx +1 -1
  38. package/components/componentLibrary/Flex/types.ts +3 -1
  39. package/components/componentLibrary/Grid/stories/Grid.stories.tsx +454 -152
  40. package/components/componentLibrary/Grid/stories/GridDecorator.tsx +2 -2
  41. package/components/componentLibrary/Heading/ContentFields.tsx +5 -3
  42. package/components/componentLibrary/Heading/StyleFields.tsx +11 -9
  43. package/components/componentLibrary/Heading/index.tsx +3 -3
  44. package/components/componentLibrary/Heading/llm.txt +8 -8
  45. package/components/componentLibrary/Heading/stories/Heading.stories.tsx +3 -3
  46. package/components/componentLibrary/Heading/stories/HeadingDecorator.tsx +1 -1
  47. package/components/componentLibrary/Heading/types.ts +4 -4
  48. package/components/componentLibrary/Icon/ContentFields.tsx +5 -3
  49. package/components/componentLibrary/Icon/stories/Icon.stories.tsx +1 -1
  50. package/components/componentLibrary/Icon/stories/IconDecorator.tsx +1 -1
  51. package/components/componentLibrary/Image/ContentFields.tsx +5 -3
  52. package/components/componentLibrary/Image/index.tsx +4 -4
  53. package/components/componentLibrary/Image/llm.txt +17 -17
  54. package/components/componentLibrary/Image/stories/Image.stories.tsx +61 -18
  55. package/components/componentLibrary/Image/stories/ImageDecorator.tsx +1 -1
  56. package/components/componentLibrary/Image/types.ts +2 -2
  57. package/components/componentLibrary/LanguageSwitcher/ContentFields.tsx +18 -0
  58. package/components/componentLibrary/LanguageSwitcher/LanguageOptions.module.scss +37 -0
  59. package/components/componentLibrary/LanguageSwitcher/LanguageOptions.tsx +65 -0
  60. package/components/componentLibrary/LanguageSwitcher/StyleFields.tsx +48 -0
  61. package/components/componentLibrary/LanguageSwitcher/_dummyData.tsx +247 -0
  62. package/components/componentLibrary/LanguageSwitcher/assets/Globe.tsx +16 -0
  63. package/components/componentLibrary/LanguageSwitcher/index.module.scss +58 -0
  64. package/components/componentLibrary/LanguageSwitcher/index.tsx +125 -0
  65. package/components/componentLibrary/LanguageSwitcher/llm.txt +380 -0
  66. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcher.stories.tsx +349 -0
  67. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.module.scss +5 -0
  68. package/components/componentLibrary/LanguageSwitcher/stories/LanguageSwitcherDecorator.tsx +8 -0
  69. package/components/componentLibrary/LanguageSwitcher/types.ts +48 -0
  70. package/components/componentLibrary/LanguageSwitcher/utils.tsx +38 -0
  71. package/components/componentLibrary/Link/ContentFields.tsx +5 -3
  72. package/components/componentLibrary/Link/StyleFields.tsx +5 -3
  73. package/components/componentLibrary/Link/index.module.scss +10 -0
  74. package/components/componentLibrary/Link/index.tsx +24 -14
  75. package/components/componentLibrary/Link/stories/Link.stories.tsx +35 -5
  76. package/components/componentLibrary/Link/stories/LinkDecorator.tsx +11 -1
  77. package/components/componentLibrary/Link/types.ts +22 -13
  78. package/components/componentLibrary/List/ContentFields.tsx +5 -3
  79. package/components/componentLibrary/List/ListItem/ContentFields.tsx +6 -17
  80. package/components/componentLibrary/List/ListItem/index.module.scss +1 -13
  81. package/components/componentLibrary/List/ListItem/index.tsx +3 -30
  82. package/components/componentLibrary/List/ListItem/types.ts +1 -16
  83. package/components/componentLibrary/List/StyleFields.tsx +15 -18
  84. package/components/componentLibrary/List/index.module.scss +3 -0
  85. package/components/componentLibrary/List/index.tsx +5 -2
  86. package/components/componentLibrary/List/llm.txt +73 -103
  87. package/components/componentLibrary/List/stories/List.stories.tsx +56 -80
  88. package/components/componentLibrary/List/stories/ListDecorator.tsx +3 -6
  89. package/components/componentLibrary/List/types.ts +1 -3
  90. package/components/componentLibrary/Logo/_dummyLogoData.ts +12 -0
  91. package/components/componentLibrary/Logo/assets/hubspot-logo.png +0 -0
  92. package/components/componentLibrary/Logo/index.module.scss +22 -0
  93. package/components/componentLibrary/Logo/index.tsx +73 -0
  94. package/components/componentLibrary/Logo/llm.txt +262 -0
  95. package/components/componentLibrary/Logo/stories/Logo.stories.tsx +88 -0
  96. package/components/componentLibrary/Logo/stories/LogoDecorator.module.scss +10 -0
  97. package/components/componentLibrary/Logo/stories/LogoDecorator.tsx +8 -0
  98. package/components/componentLibrary/Logo/types.tsx +16 -0
  99. package/components/componentLibrary/Menu/ContentFields.tsx +16 -0
  100. package/components/componentLibrary/Menu/MenuItem/Chevron/index.module.scss +6 -0
  101. package/components/componentLibrary/Menu/MenuItem/Chevron/index.tsx +17 -0
  102. package/components/componentLibrary/Menu/MenuItem/index.module.scss +7 -0
  103. package/components/componentLibrary/Menu/MenuItem/index.tsx +266 -0
  104. package/components/componentLibrary/Menu/MenuItem/types.ts +17 -0
  105. package/components/componentLibrary/Menu/NavigationMenu/ContentFields.tsx +20 -0
  106. package/components/componentLibrary/Menu/NavigationMenu/index.tsx +18 -0
  107. package/components/componentLibrary/Menu/NavigationMenu/islands/NavigationMenuIsland.tsx +95 -0
  108. package/components/componentLibrary/Menu/NavigationMenu/islands/index.module.scss +100 -0
  109. package/components/componentLibrary/Menu/NavigationMenu/islands/types.ts +19 -0
  110. package/components/componentLibrary/Menu/NavigationMenu/llm.txt +197 -0
  111. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenu.stories.tsx +286 -0
  112. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.module.scss +15 -0
  113. package/components/componentLibrary/Menu/NavigationMenu/stories/NavigationMenuDecorator.tsx +12 -0
  114. package/components/componentLibrary/Menu/NavigationMenu/types.ts +3 -0
  115. package/components/componentLibrary/Menu/VerticalMenu/ContentFields.tsx +20 -0
  116. package/components/componentLibrary/Menu/VerticalMenu/index.tsx +18 -0
  117. package/components/componentLibrary/Menu/VerticalMenu/islands/index.module.scss +53 -0
  118. package/components/componentLibrary/Menu/VerticalMenu/islands/verticalMenuIsland.tsx +78 -0
  119. package/components/componentLibrary/Menu/VerticalMenu/llm.txt +177 -0
  120. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenu.stories.tsx +242 -0
  121. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.module.scss +19 -0
  122. package/components/componentLibrary/Menu/VerticalMenu/stories/VerticalMenuDecorator.tsx +12 -0
  123. package/components/componentLibrary/Menu/VerticalMenu/types.ts +21 -0
  124. package/components/componentLibrary/Menu/_dummyMenuData.js +1346 -0
  125. package/components/componentLibrary/Menu/types.ts +56 -0
  126. package/components/componentLibrary/Menu/utils/transformMenuData.ts +11 -0
  127. package/components/componentLibrary/_patterns/README.md +15 -17
  128. package/components/componentLibrary/_patterns/checklist-and-examples.md +17 -17
  129. package/components/componentLibrary/_patterns/component-structure.md +21 -23
  130. package/components/componentLibrary/_patterns/css-patterns.md +170 -18
  131. package/components/componentLibrary/_patterns/field-patterns.md +97 -27
  132. package/components/componentLibrary/_patterns/function-declaration-patterns.md +281 -0
  133. package/components/componentLibrary/_patterns/llm-txt.template.md +4 -2
  134. package/components/componentLibrary/_patterns/prop-naming-patterns.md +208 -0
  135. package/components/componentLibrary/_patterns/storybook-patterns.md +25 -8
  136. package/components/componentLibrary/_patterns/typescript-patterns.md +6 -3
  137. package/package.json +6 -3
  138. /package/components/componentLibrary/Button/stories/{ButtonDecorator.module.css → ButtonDecorator.module.scss} +0 -0
  139. /package/components/componentLibrary/Card/stories/{CardDecorator.module.css → CardDecorator.module.scss} +0 -0
  140. /package/components/componentLibrary/Flex/stories/{FlexDecorator.module.css → FlexDecorator.module.scss} +0 -0
  141. /package/components/componentLibrary/Grid/stories/{GridDecorator.module.css → GridDecorator.module.scss} +0 -0
  142. /package/components/componentLibrary/Heading/stories/{HeadingDecorator.module.css → HeadingDecorator.module.scss} +0 -0
  143. /package/components/componentLibrary/Icon/stories/{IconDecorator.module.css → IconDecorator.module.scss} +0 -0
  144. /package/components/componentLibrary/Image/stories/{ImageDecorator.module.css → ImageDecorator.module.scss} +0 -0
  145. /package/components/componentLibrary/Image/stories/assets/{catSmile.jpg → cat-smile.jpg} +0 -0
@@ -1,11 +1,11 @@
1
1
  import { RichTextField } from '@hubspot/cms-components/fields';
2
2
  import type { ContentFieldsProps } from './types.js';
3
3
 
4
- export default function ContentFields({
4
+ const ContentFields = ({
5
5
  contentLabel = 'Content',
6
6
  contentName = 'content',
7
7
  contentDefault = '<p>Accordion content goes here. You can add multiple lines of text.</p>',
8
- }: ContentFieldsProps) {
8
+ }: ContentFieldsProps) => {
9
9
  return (
10
10
  // Todo: consider migrating over field library
11
11
  <RichTextField
@@ -14,4 +14,6 @@ export default function ContentFields({
14
14
  default={contentDefault}
15
15
  />
16
16
  );
17
- }
17
+ };
18
+
19
+ export default ContentFields;
@@ -1,11 +1,11 @@
1
1
  import { ChoiceField } from '@hubspot/cms-components/fields';
2
2
  import type { StyleFieldsProps } from './types.js';
3
3
 
4
- export default function StyleFields({
4
+ const StyleFields = ({
5
5
  variantLabel = 'Variant',
6
6
  variantName = 'variant',
7
7
  variantDefault = 'variant1',
8
- }: StyleFieldsProps) {
8
+ }: StyleFieldsProps) => {
9
9
  return (
10
10
  <ChoiceField
11
11
  label={variantLabel}
@@ -22,4 +22,6 @@ export default function StyleFields({
22
22
  default={variantDefault}
23
23
  />
24
24
  );
25
- }
25
+ };
26
+
27
+ export default StyleFields;
@@ -1,9 +1,9 @@
1
1
  .accordionItem {
2
- border: var(--hscl-accordion-borderThickness, 1px) solid
2
+ border: var(--hscl-accordion-borderWidth, 1px) solid
3
3
  var(--hscl-accordion-borderColor, #e0e0e0);
4
4
  border-radius: var(--hscl-accordion-borderRadius, 4px);
5
5
  background-color: var(--hscl-accordion-backgroundColor, transparent);
6
- color: var(--hscl-accordion-textColor, inherit);
6
+ color: var(--hscl-accordion-color, inherit);
7
7
  overflow: hidden;
8
8
  }
9
9
 
@@ -17,10 +17,10 @@ const AccordionItemComponent = ({
17
17
  ? mapVariantToCssVars('--hscl-accordion', variant, [
18
18
  'borderColor',
19
19
  'borderRadius',
20
- 'borderThickness',
20
+ 'borderWidth',
21
21
  'backgroundColor',
22
- 'textColor',
23
- 'icon-fillColor',
22
+ 'color',
23
+ 'icon-fill',
24
24
  ])
25
25
  : {};
26
26
 
@@ -1,14 +1,14 @@
1
1
  import { TextField, ChoiceField } from '@hubspot/cms-components/fields';
2
2
  import type { ContentFieldsProps } from './types.js';
3
3
 
4
- export default function ContentFields({
4
+ const ContentFields = ({
5
5
  titleLabel = 'Title',
6
6
  titleName = 'title',
7
7
  titleDefault = 'Accordion Title',
8
8
  iconLabel = 'Icon',
9
9
  iconName = 'icon',
10
10
  iconDefault = 'chevron',
11
- }: ContentFieldsProps) {
11
+ }: ContentFieldsProps) => {
12
12
  return (
13
13
  <>
14
14
  <ChoiceField
@@ -26,4 +26,6 @@ export default function ContentFields({
26
26
  <TextField label={titleLabel} name={titleName} default={titleDefault} />
27
27
  </>
28
28
  );
29
- }
29
+ };
30
+
31
+ export default ContentFields;
@@ -15,14 +15,14 @@
15
15
  .accordionTitleText {
16
16
  font-size: var(--hscl-accordion-title-fontSize, 24px);
17
17
  font-weight: var(--hscl-accordion-title-fontWeight, 600);
18
- margin-inline-end: var(--hscl-accordion-title-textMarginEnd, 16px);
18
+ margin-inline-end: var(--hscl-accordion-title-marginInlineEnd, 16px);
19
19
  flex: 1;
20
20
  }
21
21
 
22
22
  .accordionIcon {
23
23
  flex-shrink: 0;
24
24
  align-self: center;
25
- fill: var(--hscl-accordion-icon-fillColor, currentColor);
25
+ fill: var(--hscl-accordion-icon-fill, currentColor);
26
26
  margin-inline-start: auto;
27
27
  transition: transform 0.3s ease;
28
28
  display: block;
@@ -6,7 +6,7 @@ import Accordion, {
6
6
  } from '../index.js';
7
7
  import { AccordionProps } from '../types.js';
8
8
  import { withAccordionStyles } from './AccordionDecorator.js';
9
- import { SBContainer } from '@sb-utils/SBContainer.js';
9
+ import { SBContainer, SBFocusWrapper } from '@sb-utils';
10
10
 
11
11
  const meta: Meta<AccordionProps> = {
12
12
  title: 'Component Library/Accordion',
@@ -462,3 +462,82 @@ export const EdgeCases: Story = {
462
462
  </SBContainer>
463
463
  ),
464
464
  };
465
+
466
+ export const InteractionStates: Story = {
467
+ name: 'Interaction States',
468
+ render: () => (
469
+ <SBContainer flex direction="column" gap="large" width="800px">
470
+ <SBContainer addBackground>
471
+ <h4>Keyboard Navigation</h4>
472
+ <p>
473
+ <strong>Try:</strong> Press Tab to focus on accordion titles, then
474
+ press Enter or Space to expand/collapse them
475
+ </p>
476
+ <Accordion>
477
+ <AccordionItem>
478
+ <AccordionTitle>First accordion item</AccordionTitle>
479
+ <AccordionContent>
480
+ <p>
481
+ Accordion titles (summary elements) are natively keyboard
482
+ accessible. Tab to focus, Enter/Space to toggle.
483
+ </p>
484
+ </AccordionContent>
485
+ </AccordionItem>
486
+ <AccordionItem>
487
+ <AccordionTitle>Second accordion item</AccordionTitle>
488
+ <AccordionContent>
489
+ <p>
490
+ Focus rings appear automatically when navigating with the
491
+ keyboard, providing clear visual feedback.
492
+ </p>
493
+ </AccordionContent>
494
+ </AccordionItem>
495
+ <AccordionItem>
496
+ <AccordionTitle>Third accordion item</AccordionTitle>
497
+ <AccordionContent>
498
+ <p>
499
+ This demonstrates how users can navigate through all accordion
500
+ items using only the keyboard.
501
+ </p>
502
+ </AccordionContent>
503
+ </AccordionItem>
504
+ </Accordion>
505
+ </SBContainer>
506
+
507
+ <SBContainer addBackground>
508
+ <h4>Focus State</h4>
509
+ <p>
510
+ In production, this focus outline appears when navigating with the
511
+ keyboard (Tab key). This Storybook demo auto-applies the focus state
512
+ to show how it looks.
513
+ </p>
514
+ <SBFocusWrapper>
515
+ <Accordion>
516
+ <AccordionItem>
517
+ <AccordionTitle>Focused accordion item</AccordionTitle>
518
+ <AccordionContent>
519
+ <p>
520
+ This accordion title automatically receives focus to
521
+ demonstrate the focus ring appearance.
522
+ </p>
523
+ </AccordionContent>
524
+ </AccordionItem>
525
+ </Accordion>
526
+ </SBFocusWrapper>
527
+ </SBContainer>
528
+
529
+ <SBContainer addBackground>
530
+ <h4>Hover State</h4>
531
+ <p>Hover over accordion titles to see the hover effect</p>
532
+ <Accordion>
533
+ <AccordionItem>
534
+ <AccordionTitle>Hover over this title</AccordionTitle>
535
+ <AccordionContent>
536
+ <p>The title shows a subtle background change on hover.</p>
537
+ </AccordionContent>
538
+ </AccordionItem>
539
+ </Accordion>
540
+ </SBContainer>
541
+ </SBContainer>
542
+ ),
543
+ };
@@ -2,36 +2,36 @@ import type { Decorator } from '@storybook/react';
2
2
  import type { CSSVariables } from '../../utils/types.js';
3
3
 
4
4
  const defaultAccordionStyles: CSSVariables = {
5
- '--hscl-listItem-textColor-primary': '#33475b',
6
- '--hscl-listItem-textColor-secondary': '#516f90',
5
+ '--hscl-listItem-color-primary': '#33475b',
6
+ '--hscl-listItem-color-secondary': '#516f90',
7
7
 
8
8
  '--hscl-accordion-variant1-borderColor': '#e0e0e0',
9
9
  '--hscl-accordion-variant1-borderRadius': '4px',
10
- '--hscl-accordion-variant1-borderThickness': '1px',
10
+ '--hscl-accordion-variant1-borderWidth': '1px',
11
11
  '--hscl-accordion-variant1-backgroundColor': 'transparent',
12
- '--hscl-accordion-variant1-textColor': '#33475b',
13
- '--hscl-accordion-variant1-icon-fillColor': '#33475b',
12
+ '--hscl-accordion-variant1-color': '#33475b',
13
+ '--hscl-accordion-variant1-icon-fill': '#33475b',
14
14
 
15
15
  '--hscl-accordion-variant2-borderColor': '#cbd6e2',
16
16
  '--hscl-accordion-variant2-borderRadius': '8px',
17
- '--hscl-accordion-variant2-borderThickness': '1px',
17
+ '--hscl-accordion-variant2-borderWidth': '1px',
18
18
  '--hscl-accordion-variant2-backgroundColor': '#f5f8fa',
19
- '--hscl-accordion-variant2-textColor': '#33475b',
20
- '--hscl-accordion-variant2-icon-fillColor': '#516f90',
19
+ '--hscl-accordion-variant2-color': '#33475b',
20
+ '--hscl-accordion-variant2-icon-fill': '#516f90',
21
21
 
22
22
  '--hscl-accordion-variant3-borderColor': '#ff7a59',
23
23
  '--hscl-accordion-variant3-borderRadius': '0',
24
- '--hscl-accordion-variant3-borderThickness': '2px',
24
+ '--hscl-accordion-variant3-borderWidth': '2px',
25
25
  '--hscl-accordion-variant3-backgroundColor': 'transparent',
26
- '--hscl-accordion-variant3-textColor': '#33475b',
27
- '--hscl-accordion-variant3-icon-fillColor': '#ff7a59',
26
+ '--hscl-accordion-variant3-color': '#33475b',
27
+ '--hscl-accordion-variant3-icon-fill': '#ff7a59',
28
28
 
29
29
  '--hscl-accordion-variant4-borderColor': '#33475b',
30
30
  '--hscl-accordion-variant4-borderRadius': '12px',
31
- '--hscl-accordion-variant4-borderThickness': '1px',
31
+ '--hscl-accordion-variant4-borderWidth': '1px',
32
32
  '--hscl-accordion-variant4-backgroundColor': '#eaf0f6',
33
- '--hscl-accordion-variant4-textColor': '#33475b',
34
- '--hscl-accordion-variant4-icon-fillColor': '#33475b',
33
+ '--hscl-accordion-variant4-color': '#33475b',
34
+ '--hscl-accordion-variant4-icon-fill': '#33475b',
35
35
  };
36
36
 
37
37
  export const withAccordionStyles: Decorator = Story => (
@@ -2,7 +2,7 @@ import { TextField, LinkField } from '@hubspot/cms-components/fields';
2
2
  import Icon from '../Icon/index.js';
3
3
  import { ContentFieldsProps } from './types.js';
4
4
 
5
- export default function ContentFields({
5
+ const ContentFields = ({
6
6
  buttonTextLabel = 'Button text',
7
7
  buttonTextName = 'buttonText',
8
8
  buttonTextDefault = 'Click me',
@@ -22,7 +22,7 @@ export default function ContentFields({
22
22
  showIconLabel = 'Show button icon',
23
23
  showIconName,
24
24
  showIconDefault,
25
- }: ContentFieldsProps) {
25
+ }: ContentFieldsProps) => {
26
26
  const iconContentFieldsProps = {
27
27
  iconLabel,
28
28
  iconName,
@@ -49,4 +49,6 @@ export default function ContentFields({
49
49
  <Icon.ContentFields addIconToggle={true} {...iconContentFieldsProps} />
50
50
  </>
51
51
  );
52
- }
52
+ };
53
+
54
+ export default ContentFields;
@@ -1,11 +1,21 @@
1
- import { ChoiceField } from '@hubspot/cms-components/fields';
1
+ import { ChoiceField, Visibility } from '@hubspot/cms-components/fields';
2
2
  import { StyleFieldsProps } from './types.js';
3
3
 
4
- export default function StyleFields({
4
+ const StyleFields = ({
5
5
  buttonVariantLabel = 'Button variant',
6
6
  buttonVariantName = 'buttonVariant',
7
7
  buttonVariantDefault = 'primary',
8
- }: StyleFieldsProps) {
8
+ buttonIconPositionLabel = 'Icon position',
9
+ buttonIconPositionName = 'buttonIconPosition',
10
+ buttonIconPositionDefault = 'right',
11
+ }: StyleFieldsProps) => {
12
+ // iconComponentShowIcon is the hardcoded id in @components/componentLibrary/Icon/ContentFields.tsx
13
+ const iconPositionVisibility: Visibility = {
14
+ operator: 'EQUAL',
15
+ controlling_field: 'iconComponentShowIcon',
16
+ controlling_value_regex: 'true',
17
+ };
18
+
9
19
  return (
10
20
  <>
11
21
  <ChoiceField
@@ -18,6 +28,18 @@ export default function StyleFields({
18
28
  ]}
19
29
  default={buttonVariantDefault}
20
30
  />
31
+ <ChoiceField
32
+ label={buttonIconPositionLabel}
33
+ name={buttonIconPositionName}
34
+ choices={[
35
+ ['left', 'Left'],
36
+ ['right', 'Right'],
37
+ ]}
38
+ default={buttonIconPositionDefault}
39
+ visibility={iconPositionVisibility}
40
+ />
21
41
  </>
22
42
  );
23
- }
43
+ };
44
+
45
+ export default StyleFields;
@@ -17,21 +17,32 @@
17
17
  transition: all 0.2s ease;
18
18
  text-decoration: none;
19
19
 
20
+ @media (prefers-reduced-motion: reduce) {
21
+ transition: none;
22
+ }
23
+
20
24
  &:hover {
21
- background-color: var(--hscl-button-backgroundColor-hover);
22
- color: var(--hscl-button-color-hover);
23
- border-width: var(--hscl-button-borderWidth-hover);
24
- border-color: var(--hscl-button-borderColor-hover);
25
+ background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
26
+ color: var(--hscl-button-color-hover, var(--hscl-button-color));
27
+ border-width: var(--hscl-button-borderWidth-hover, var(--hscl-button-borderWidth));
28
+ border-color: var(--hscl-button-borderColor-hover, var(--hscl-button-borderColor));
25
29
  text-decoration: none;
26
30
  }
27
31
 
28
- &:focus {
29
- background-color: var(--hscl-button-backgroundColor-focus);
30
- color: var(--hscl-button-color-focus);
31
- border-width: var(--hscl-button-borderWidth-focus);
32
- border-color: var(--hscl-button-borderColor-focus);
33
- outline: var(--hscl-button-outlineWidth-focus) solid var(--hscl-button-outlineColor-focus);
34
- outline-offset: var(--hscl-button-outlineOffset-focus);
32
+ &:focus-visible {
33
+ background-color: var(--hscl-button-backgroundColor-focus, var(--hscl-button-backgroundColor));
34
+ color: var(--hscl-button-color-focus, var(--hscl-button-color));
35
+ border-width: var(--hscl-button-borderWidth-focus, var(--hscl-button-borderWidth));
36
+ border-color: var(--hscl-button-borderColor-focus, var(--hscl-button-borderColor));
37
+ outline: var(--hscl-button-outlineWidth-focus, 2px) solid var(--hscl-button-outlineColor-focus, currentColor);
38
+ outline-offset: var(--hscl-button-outlineOffset-focus, 2px);
39
+ }
40
+
41
+ &:active {
42
+ background-color: var(--hscl-button-backgroundColor-active, var(--hscl-button-backgroundColor));
43
+ color: var(--hscl-button-color-active, var(--hscl-button-color));
44
+ border-width: var(--hscl-button-borderWidth-active, var(--hscl-button-borderWidth));
45
+ border-color: var(--hscl-button-borderColor-active, var(--hscl-button-borderColor));
35
46
  }
36
47
 
37
48
  &:disabled {
@@ -39,7 +50,6 @@
39
50
  cursor: not-allowed;
40
51
  }
41
52
 
42
-
43
53
  svg {
44
54
  display: inline-flex;
45
55
  align-items: center;
@@ -47,5 +57,3 @@
47
57
  fill: var(--hscl-button-icon-fill);
48
58
  }
49
59
  }
50
-
51
-
@@ -56,7 +56,7 @@ const ButtonComponent = ({
56
56
  );
57
57
  };
58
58
 
59
- function renderButton({
59
+ const renderButton = ({
60
60
  onClick,
61
61
  disabled = false,
62
62
  children,
@@ -67,7 +67,7 @@ const ButtonComponent = ({
67
67
  iconPurpose,
68
68
  iconTitle,
69
69
  ...rest
70
- }: Omit<ButtonAsButtonProps, 'buttonType'> & Partial<BaseButtonProps>) {
70
+ }: Omit<ButtonAsButtonProps, 'buttonType'> & Partial<BaseButtonProps>) => {
71
71
  const iconProps = {
72
72
  iconFieldPath,
73
73
  iconSize,
@@ -81,9 +81,9 @@ const ButtonComponent = ({
81
81
  <RenderWithIcon {...iconProps}>{children}</RenderWithIcon>
82
82
  </button>
83
83
  );
84
- }
84
+ };
85
85
 
86
- function renderLink({
86
+ const renderLink = ({
87
87
  href = '',
88
88
  target = '_self',
89
89
  rel = '',
@@ -95,7 +95,7 @@ const ButtonComponent = ({
95
95
  iconPurpose,
96
96
  iconTitle,
97
97
  ...rest
98
- }: Omit<ButtonAsLinkProps, 'buttonType'> & Partial<BaseButtonProps>) {
98
+ }: Omit<ButtonAsLinkProps, 'buttonType'> & Partial<BaseButtonProps>) => {
99
99
  const iconProps = {
100
100
  iconFieldPath,
101
101
  iconSize,
@@ -109,7 +109,7 @@ const ButtonComponent = ({
109
109
  <RenderWithIcon {...iconProps}>{children}</RenderWithIcon>
110
110
  </a>
111
111
  );
112
- }
112
+ };
113
113
 
114
114
  // TypeScript properly discriminates based on buttonType
115
115
  if (buttonType === 'button') return renderButton(rest);
@@ -234,18 +234,22 @@ Configurable props for customizing field labels, names, and defaults:
234
234
 
235
235
  #### StyleFields.tsx
236
236
 
237
- Configurable props for variant selection:
237
+ Configurable props for variant and icon position:
238
238
 
239
239
  ```tsx
240
240
  <Button.StyleFields
241
241
  buttonVariantLabel="Button variant"
242
242
  buttonVariantName="buttonVariant"
243
243
  buttonVariantDefault="primary"
244
+ buttonIconPositionLabel="Icon position"
245
+ buttonIconPositionName="buttonIconPosition"
246
+ buttonIconPositionDefault="right"
244
247
  />
245
248
  ```
246
249
 
247
250
  **Fields:**
248
251
  - `buttonVariant`: ChoiceField for selecting visual style (primary, secondary, tertiary)
252
+ - `buttonIconPosition`: ChoiceField for selecting icon placement (left, right). This field has a hardcoded visibility rule — it is only shown when the icon toggle is enabled (`iconComponentShowIcon === true`), which corresponds to the `BooleanField` id hardcoded in `Icon.ContentFields`.
249
253
 
250
254
  ### Module Usage Example
251
255
 
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import Button from '../index.js';
3
3
  import { ButtonAsButtonProps } from '../types.js';
4
4
  import { withButtonStyles } from './ButtonDecorator.js';
5
- import { SBContainer } from '@sb-utils/SBContainer.js';
5
+ import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
6
 
7
7
  const meta: Meta<ButtonAsButtonProps> = {
8
8
  title: 'Component Library/Button/As Button',
@@ -64,3 +64,32 @@ export const DisabledStates: Story = {
64
64
  </SBContainer>
65
65
  ),
66
66
  };
67
+
68
+ export const InteractionStates: Story = {
69
+ name: 'Interaction States',
70
+ render: () => (
71
+ <SBContainer flex direction="column" gap="large">
72
+ <SBContainer addBackground>
73
+ <h4>Hover State</h4>
74
+ <p>Hover over the button to see the hover effect</p>
75
+ <Button buttonType="button" variant="primary">
76
+ Hover me
77
+ </Button>
78
+ </SBContainer>
79
+
80
+ <SBContainer addBackground>
81
+ <h4>Focus State</h4>
82
+ <p>
83
+ In production, this focus outline appears when navigating with the
84
+ keyboard (Tab key). This Storybook demo auto-applies the focus state
85
+ to show how it looks.
86
+ </p>
87
+ <SBFocusWrapper>
88
+ <Button buttonType="button" variant="primary">
89
+ Focused button
90
+ </Button>
91
+ </SBFocusWrapper>
92
+ </SBContainer>
93
+ </SBContainer>
94
+ ),
95
+ };
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import Button from '../index.js';
3
3
  import { ButtonAsLinkProps } from '../types.js';
4
4
  import { withButtonStyles } from './ButtonDecorator.js';
5
- import { SBContainer } from '@sb-utils/SBContainer.js';
5
+ import { SBContainer, SBFocusWrapper } from '@sb-utils';
6
6
 
7
7
  const meta: Meta<ButtonAsLinkProps> = {
8
8
  title: 'Component Library/Button/As Link',
@@ -68,3 +68,40 @@ export const LinkTargets: Story = {
68
68
  </SBContainer>
69
69
  ),
70
70
  };
71
+
72
+ export const InteractionStates: Story = {
73
+ name: 'Interaction States',
74
+ render: () => (
75
+ <SBContainer flex direction="column" gap="large">
76
+ <SBContainer addBackground>
77
+ <h4>Hover State</h4>
78
+ <p>Hover over the button to see the hover effect</p>
79
+ <Button
80
+ buttonType="link"
81
+ href="https://www.hubspot.com"
82
+ variant="primary"
83
+ >
84
+ Hover me
85
+ </Button>
86
+ </SBContainer>
87
+
88
+ <SBContainer addBackground>
89
+ <h4>Focus State</h4>
90
+ <p>
91
+ In production, this focus outline appears when navigating with the
92
+ keyboard (Tab key). This Storybook demo auto-applies the focus state
93
+ to show how it looks.
94
+ </p>
95
+ <SBFocusWrapper>
96
+ <Button
97
+ buttonType="link"
98
+ href="https://www.hubspot.com"
99
+ variant="primary"
100
+ >
101
+ Focused button
102
+ </Button>
103
+ </SBFocusWrapper>
104
+ </SBContainer>
105
+ </SBContainer>
106
+ ),
107
+ };
@@ -1,5 +1,5 @@
1
1
  import type { Decorator } from '@storybook/react';
2
- import styles from './ButtonDecorator.module.css';
2
+ import styles from './ButtonDecorator.module.scss';
3
3
  /**
4
4
  * Shared decorator for Button stories that applies default CSS variables
5
5
  */
@@ -4,6 +4,8 @@ import {
4
4
  } from '@hubspot/cms-components/fields';
5
5
  import { IconContentFieldsWithToggleProps } from '../Icon/types.js';
6
6
 
7
+ export type IconPositionHorizontal = 'left' | 'right';
8
+
7
9
  // Base props shared by all variants
8
10
  export type BaseButtonProps = {
9
11
  variant?: 'primary' | 'secondary' | 'tertiary'; // !todo: not used atm but keeping for when we need to add variant system.
@@ -11,7 +13,7 @@ export type BaseButtonProps = {
11
13
  style?: React.CSSProperties;
12
14
  children?: React.ReactNode;
13
15
  iconFieldPath?: string;
14
- iconPosition?: 'left' | 'right';
16
+ iconPosition?: IconPositionHorizontal;
15
17
  iconSize?: number;
16
18
  showIcon?: boolean;
17
19
  iconPurpose?: 'SEMANTIC' | 'DECORATIVE';
@@ -56,6 +58,9 @@ export type StyleFieldsProps = {
56
58
  buttonVariantLabel?: string;
57
59
  buttonVariantName?: string;
58
60
  buttonVariantDefault?: string;
61
+ buttonIconPositionLabel?: string;
62
+ buttonIconPositionName?: string;
63
+ buttonIconPositionDefault?: IconPositionHorizontal;
59
64
  };
60
65
 
61
66
  export type RenderWithIconProps = Pick<
@@ -1,14 +1,14 @@
1
1
  import { ChoiceField, NumberField } from '@hubspot/cms-components/fields';
2
2
  import { StyleFieldsProps } from './types.js';
3
3
 
4
- export default function StyleFields({
4
+ const StyleFields = ({
5
5
  variantLabel = 'Card Variant',
6
6
  variantName = 'variant',
7
7
  variantDefault = 'elevated',
8
8
  borderRadiusLabel = 'Border Radius',
9
9
  borderRadiusName = 'borderRadius',
10
10
  borderRadiusDefault = 8,
11
- }: StyleFieldsProps) {
11
+ }: StyleFieldsProps) => {
12
12
  return (
13
13
  <>
14
14
  <ChoiceField
@@ -31,4 +31,6 @@ export default function StyleFields({
31
31
  />
32
32
  </>
33
33
  );
34
- }
34
+ };
35
+
36
+ export default StyleFields;