@hubspot/cms-component-library 0.3.9 → 0.3.11
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.
- package/components/componentLibrary/Button/index.module.scss +4 -4
- package/components/componentLibrary/Button/index.tsx +1 -1
- package/components/componentLibrary/Card/StyleFields.tsx +1 -1
- package/components/componentLibrary/Card/index.tsx +1 -1
- package/components/componentLibrary/Card/llm.txt +3 -3
- package/components/componentLibrary/Card/stories/Card.stories.tsx +5 -5
- package/components/componentLibrary/Card/stories/CardDecorator.module.scss +4 -4
- package/components/componentLibrary/Card/types.ts +4 -4
- package/components/componentLibrary/Form/StyleFields.tsx +1 -1
- package/components/componentLibrary/Form/llm.txt +2 -2
- package/components/componentLibrary/Link/StyleFields.tsx +10 -17
- package/components/componentLibrary/Link/index.module.scss +11 -9
- package/components/componentLibrary/Link/index.tsx +3 -8
- package/components/componentLibrary/Link/llm.txt +29 -85
- package/components/componentLibrary/Link/stories/Link.stories.tsx +4 -11
- package/components/componentLibrary/Link/stories/LinkDecorator.module.scss +15 -0
- package/components/componentLibrary/Link/stories/LinkDecorator.tsx +2 -11
- package/components/componentLibrary/Link/types.ts +6 -4
- package/components/componentLibrary/_patterns/css-patterns.md +34 -17
- package/package.json +1 -1
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
&:focus-visible {
|
|
32
|
-
background-color: var(--hs-button-backgroundColor-
|
|
33
|
-
color: var(--hs-button-color-
|
|
34
|
-
border: var(--hs-button-border-
|
|
35
|
-
outline: 2px solid;
|
|
32
|
+
background-color: var(--hs-button-backgroundColor-hover, var(--hs-button-backgroundColor));
|
|
33
|
+
color: var(--hs-button-color-hover, var(--hs-button-color));
|
|
34
|
+
border: var(--hs-button-border-hover, var(--hs-button-border));
|
|
35
|
+
outline: 2px solid Highlight;
|
|
36
36
|
outline-offset: 2px;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -4,7 +4,7 @@ import { StyleFieldsProps } from './types.js';
|
|
|
4
4
|
const StyleFields = ({
|
|
5
5
|
cardVariantLabel = 'Card variant',
|
|
6
6
|
cardVariantName = 'cardVariant',
|
|
7
|
-
cardVariantDefault = {
|
|
7
|
+
cardVariantDefault = { variant_name: 'card1' },
|
|
8
8
|
}: StyleFieldsProps) => {
|
|
9
9
|
return (
|
|
10
10
|
<VariantSelectionField
|
|
@@ -34,7 +34,7 @@ Card/
|
|
|
34
34
|
**Props:**
|
|
35
35
|
```tsx
|
|
36
36
|
{
|
|
37
|
-
variant?: '
|
|
37
|
+
variant?: 'card1' | 'card2' | 'card3' | 'card4'; // Theme variant (default: 'card1')
|
|
38
38
|
as?: 'div' | 'article' | 'section'; // HTML element to render (default: 'div')
|
|
39
39
|
children?: React.ReactNode; // Card content
|
|
40
40
|
className?: string; // Additional CSS classes
|
|
@@ -58,7 +58,7 @@ import Card from '@hubspot/cms-component-library/Card';
|
|
|
58
58
|
### Semantic Article Card
|
|
59
59
|
|
|
60
60
|
```tsx
|
|
61
|
-
<Card as="article" variant="
|
|
61
|
+
<Card as="article" variant="card3">
|
|
62
62
|
<h3>Blog Post Title</h3>
|
|
63
63
|
<p>Article content goes here...</p>
|
|
64
64
|
</Card>
|
|
@@ -97,7 +97,7 @@ Configurable props for customizing field labels, names, and defaults:
|
|
|
97
97
|
<Card.StyleFields
|
|
98
98
|
cardVariantLabel="Card variant"
|
|
99
99
|
cardVariantName="cardVariant"
|
|
100
|
-
cardVariantDefault={{ variant_name: '
|
|
100
|
+
cardVariantDefault={{ variant_name: 'card1' }}
|
|
101
101
|
/>
|
|
102
102
|
```
|
|
103
103
|
|
|
@@ -43,7 +43,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
43
43
|
|
|
44
44
|
export const Default: Story = {
|
|
45
45
|
args: {
|
|
46
|
-
variant: '
|
|
46
|
+
variant: 'card1',
|
|
47
47
|
},
|
|
48
48
|
render: args => (
|
|
49
49
|
<Card {...args}>
|
|
@@ -60,7 +60,7 @@ export const Variants: Story = {
|
|
|
60
60
|
<SBContainer flex direction="column" gap="large">
|
|
61
61
|
<SBContainer addBackground>
|
|
62
62
|
<h4>Card Style 1 (Light)</h4>
|
|
63
|
-
<Card variant="
|
|
63
|
+
<Card variant="card1">
|
|
64
64
|
<CardHeading>Card Style 1</CardHeading>
|
|
65
65
|
<CardDivider />
|
|
66
66
|
<CardContent>Light background with dark text.</CardContent>
|
|
@@ -69,7 +69,7 @@ export const Variants: Story = {
|
|
|
69
69
|
|
|
70
70
|
<SBContainer addBackground>
|
|
71
71
|
<h4>Card Style 2 (Subtle)</h4>
|
|
72
|
-
<Card variant="
|
|
72
|
+
<Card variant="card2">
|
|
73
73
|
<CardHeading>Card Style 2</CardHeading>
|
|
74
74
|
<CardDivider />
|
|
75
75
|
<CardContent>Subtle background with dark text.</CardContent>
|
|
@@ -78,7 +78,7 @@ export const Variants: Story = {
|
|
|
78
78
|
|
|
79
79
|
<SBContainer addBackground>
|
|
80
80
|
<h4>Card Style 3 (Dark)</h4>
|
|
81
|
-
<Card variant="
|
|
81
|
+
<Card variant="card3">
|
|
82
82
|
<CardHeading>Card Style 3</CardHeading>
|
|
83
83
|
<CardDivider />
|
|
84
84
|
<CardContent>Dark background with light text.</CardContent>
|
|
@@ -87,7 +87,7 @@ export const Variants: Story = {
|
|
|
87
87
|
|
|
88
88
|
<SBContainer addBackground>
|
|
89
89
|
<h4>Card Style 4 (Darker)</h4>
|
|
90
|
-
<Card variant="
|
|
90
|
+
<Card variant="card4">
|
|
91
91
|
<CardHeading>Card Style 4</CardHeading>
|
|
92
92
|
<CardDivider />
|
|
93
93
|
<CardContent>Darker background with light text.</CardContent>
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
.cardContainer {
|
|
2
2
|
/* card Variant Styles */
|
|
3
|
-
:global([data-card-variant='
|
|
3
|
+
:global([data-card-variant='card1']) {
|
|
4
4
|
--hs-card-borderRadius: 10px;
|
|
5
5
|
--hs-card-backgroundColor: rgb(255, 255, 255);
|
|
6
6
|
--hs-card-color: rgb(28, 28, 31);
|
|
7
7
|
--hs-card-border: 1px solid #e0e0e0;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
:global([data-card-variant='
|
|
10
|
+
:global([data-card-variant='card2']) {
|
|
11
11
|
--hs-card-borderRadius: 10px;
|
|
12
12
|
--hs-card-backgroundColor: rgb(249, 249, 249);
|
|
13
13
|
--hs-card-color: rgb(28, 28, 31);
|
|
14
14
|
--hs-card-border: 1px solid #e0e0e0;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
:global([data-card-variant='
|
|
17
|
+
:global([data-card-variant='card3']) {
|
|
18
18
|
--hs-card-borderRadius: 10px;
|
|
19
19
|
--hs-card-backgroundColor: rgb(28, 28, 31);
|
|
20
20
|
--hs-card-color: rgb(255,255,255);
|
|
21
21
|
--hs-card-border: 1px solid #e0e0e0;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
:global([data-card-variant='
|
|
24
|
+
:global([data-card-variant='card4']) {
|
|
25
25
|
--hs-card-borderRadius: 10px;
|
|
26
26
|
--hs-card-backgroundColor: rgb(36, 36, 36);
|
|
27
27
|
--hs-card-color: rgb(255,255,255);
|
|
@@ -4,7 +4,7 @@ import { StyleFieldsProps } from './types.js';
|
|
|
4
4
|
const StyleFields = ({
|
|
5
5
|
formVariantLabel = 'Form variant',
|
|
6
6
|
formVariantName = 'formVariant',
|
|
7
|
-
formVariantDefault = { variant_name: '
|
|
7
|
+
formVariantDefault = { variant_name: 'form1' },
|
|
8
8
|
}: StyleFieldsProps) => {
|
|
9
9
|
return (
|
|
10
10
|
<VariantSelectionField
|
|
@@ -123,7 +123,7 @@ Configurable props for variant selection:
|
|
|
123
123
|
<Form.StyleFields
|
|
124
124
|
formVariantLabel="Form variant"
|
|
125
125
|
formVariantName="formVariant"
|
|
126
|
-
formVariantDefault={{ variant_name: '
|
|
126
|
+
formVariantDefault={{ variant_name: 'form1' }}
|
|
127
127
|
/>
|
|
128
128
|
```
|
|
129
129
|
|
|
@@ -135,7 +135,7 @@ Configurable props for variant selection:
|
|
|
135
135
|
{
|
|
136
136
|
formVariantLabel?: string; // Label shown in the editor (default: "Form variant")
|
|
137
137
|
formVariantName?: string; // Field name (default: "formVariant")
|
|
138
|
-
formVariantDefault?: { variant_name: string }; // Default variant (default: { variant_name: '
|
|
138
|
+
formVariantDefault?: { variant_name: string }; // Default variant (default: { variant_name: 'form1' })
|
|
139
139
|
}
|
|
140
140
|
```
|
|
141
141
|
|
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VariantSelectionField } from '@hubspot/cms-components/fields';
|
|
2
2
|
import { StyleFieldsProps } from './types.js';
|
|
3
3
|
|
|
4
|
-
// !todo: may not need later, but keeping for now in case we have variant system for links
|
|
5
4
|
const StyleFields = ({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
variantLabel = 'Link variant',
|
|
6
|
+
variantName = 'linkVariant',
|
|
7
|
+
variantDefault = { variant_name: 'primaryLink' },
|
|
9
8
|
}: StyleFieldsProps) => {
|
|
10
9
|
return (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
['secondary', 'Secondary'],
|
|
18
|
-
['tertiary', 'Tertiary'],
|
|
19
|
-
]}
|
|
20
|
-
default={linkVariantDefault}
|
|
21
|
-
/>
|
|
22
|
-
</>
|
|
10
|
+
<VariantSelectionField
|
|
11
|
+
label={variantLabel}
|
|
12
|
+
name={variantName}
|
|
13
|
+
variantDefinitionName="link"
|
|
14
|
+
default={variantDefault}
|
|
15
|
+
/>
|
|
23
16
|
);
|
|
24
17
|
};
|
|
25
18
|
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
.link {
|
|
2
|
-
display:
|
|
3
|
-
color: var(--
|
|
4
|
-
text-decoration: var(--
|
|
2
|
+
display: inline-block;
|
|
3
|
+
color: var(--hs-link-color);
|
|
4
|
+
text-decoration: var(--hs-link-textDecoration);
|
|
5
5
|
cursor: pointer;
|
|
6
6
|
|
|
7
7
|
&:hover {
|
|
8
|
-
color: var(--
|
|
9
|
-
text-decoration: var(--
|
|
8
|
+
color: var(--hs-link-color-hover);
|
|
9
|
+
text-decoration: var(--hs-link-textDecoration-hover);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
&:focus-visible {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
color: var(--hs-link-color-hover);
|
|
14
|
+
text-decoration: var(--hs-link-textDecoration-hover);
|
|
15
|
+
outline: 2px solid Highlight;
|
|
16
|
+
outline-offset: 2px;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
&:active {
|
|
18
|
-
color: var(--
|
|
19
|
-
text-decoration: var(--
|
|
20
|
+
color: var(--hs-link-color-active, var(--hs-link-color));
|
|
21
|
+
text-decoration: var(--hs-link-textDecoration-active, var(--hs-link-textDecoration));
|
|
20
22
|
}
|
|
21
23
|
}
|
|
@@ -2,7 +2,6 @@ import styles from './index.module.scss';
|
|
|
2
2
|
import ContentFields from './ContentFields.js';
|
|
3
3
|
import StyleFields from './StyleFields.js';
|
|
4
4
|
import cx from '../utils/classname.js';
|
|
5
|
-
import type { CSSVariables } from '../utils/types.js';
|
|
6
5
|
import { LinkProps, BaseLinkProps, LinkHTMLProps } from './types.js';
|
|
7
6
|
import {
|
|
8
7
|
getLinkRel,
|
|
@@ -32,7 +31,7 @@ const getLinkValues = (props: Omit<LinkProps, keyof BaseLinkProps>) => {
|
|
|
32
31
|
};
|
|
33
32
|
|
|
34
33
|
const LinkComponent = ({
|
|
35
|
-
variant = '
|
|
34
|
+
variant = 'primaryLink',
|
|
36
35
|
className = '',
|
|
37
36
|
style = {},
|
|
38
37
|
children,
|
|
@@ -43,18 +42,14 @@ const LinkComponent = ({
|
|
|
43
42
|
const defaultClasses = styles.link;
|
|
44
43
|
const combinedClasses = cx(defaultClasses, className);
|
|
45
44
|
|
|
46
|
-
const cssVariables: CSSVariables = {
|
|
47
|
-
'--hscl-link-color': `var(--hscl-link-color-${variant})`,
|
|
48
|
-
'--hscl-link-color-hover': `var(--hscl-link-color-hover-${variant})`,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
45
|
return (
|
|
52
46
|
<a
|
|
53
47
|
className={combinedClasses}
|
|
54
|
-
style={
|
|
48
|
+
style={style}
|
|
55
49
|
href={href}
|
|
56
50
|
target={target}
|
|
57
51
|
rel={rel}
|
|
52
|
+
data-link-variant={variant}
|
|
58
53
|
{...anchorProps}
|
|
59
54
|
>
|
|
60
55
|
{children}
|
|
@@ -58,9 +58,9 @@ Pass a `linkField` prop containing HubSpot link field data. The component automa
|
|
|
58
58
|
open_in_new_tab?: boolean; // Open in new tab
|
|
59
59
|
no_follow?: boolean; // Add nofollow rel
|
|
60
60
|
};
|
|
61
|
-
variant?: '
|
|
61
|
+
variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
|
|
62
62
|
className?: string; // Additional CSS classes
|
|
63
|
-
style?: React.CSSProperties; // Inline styles
|
|
63
|
+
style?: React.CSSProperties; // Inline styles
|
|
64
64
|
children?: React.ReactNode; // Link text/content
|
|
65
65
|
}
|
|
66
66
|
```
|
|
@@ -74,9 +74,9 @@ Pass individual `href`, `target`, and `rel` props directly.
|
|
|
74
74
|
href: string; // Link destination URL (required)
|
|
75
75
|
target?: '_self' | '_blank' | '_parent' | '_top'; // Link target behavior
|
|
76
76
|
rel?: string; // Link relationship (e.g., 'noopener noreferrer')
|
|
77
|
-
variant?: '
|
|
77
|
+
variant?: 'primaryLink' | 'secondaryLink'; // Visual style variant (default: 'primaryLink')
|
|
78
78
|
className?: string; // Additional CSS classes
|
|
79
|
-
style?: React.CSSProperties; // Inline styles
|
|
79
|
+
style?: React.CSSProperties; // Inline styles
|
|
80
80
|
children?: React.ReactNode; // Link text/content
|
|
81
81
|
}
|
|
82
82
|
```
|
|
@@ -107,21 +107,7 @@ export default function MyModule({ fieldValues }) {
|
|
|
107
107
|
```tsx
|
|
108
108
|
<Link
|
|
109
109
|
linkField={fieldValues.link}
|
|
110
|
-
variant="
|
|
111
|
-
>
|
|
112
|
-
Visit HubSpot
|
|
113
|
-
</Link>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
#### Field-based Link with Style Override
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
<Link
|
|
120
|
-
linkField={fieldValues.link}
|
|
121
|
-
style={{
|
|
122
|
-
'--hscl-link-textDecoration': 'none',
|
|
123
|
-
'--hscl-link-textDecoration-hover': 'underline',
|
|
124
|
-
}}
|
|
110
|
+
variant="secondaryLink"
|
|
125
111
|
>
|
|
126
112
|
Visit HubSpot
|
|
127
113
|
</Link>
|
|
@@ -163,17 +149,13 @@ import Link from '@hubspot/cms-component-library/Link';
|
|
|
163
149
|
### Using Variants
|
|
164
150
|
|
|
165
151
|
```tsx
|
|
166
|
-
<Link href="https://www.hubspot.com" variant="
|
|
152
|
+
<Link href="https://www.hubspot.com" variant="primaryLink">
|
|
167
153
|
Primary Link
|
|
168
154
|
</Link>
|
|
169
155
|
|
|
170
|
-
<Link href="https://www.hubspot.com" variant="
|
|
156
|
+
<Link href="https://www.hubspot.com" variant="secondaryLink">
|
|
171
157
|
Secondary Link
|
|
172
158
|
</Link>
|
|
173
|
-
|
|
174
|
-
<Link href="https://www.hubspot.com" variant="tertiary">
|
|
175
|
-
Tertiary Link
|
|
176
|
-
</Link>
|
|
177
159
|
```
|
|
178
160
|
|
|
179
161
|
## HubSpot CMS Integration
|
|
@@ -220,12 +202,12 @@ The Link component provides field definitions for easy integration with HubSpot
|
|
|
220
202
|
<Link.StyleFields
|
|
221
203
|
variantLabel="Link style"
|
|
222
204
|
variantName="linkVariant"
|
|
223
|
-
variantDefault=
|
|
205
|
+
variantDefault={{ variant_name: 'primaryLink' }}
|
|
224
206
|
/>
|
|
225
207
|
```
|
|
226
208
|
|
|
227
209
|
**Fields:**
|
|
228
|
-
- `linkVariant`:
|
|
210
|
+
- `linkVariant`: VariantSelectionField for theme variant selection (primaryLink, secondaryLink)
|
|
229
211
|
|
|
230
212
|
### Module Usage Example
|
|
231
213
|
|
|
@@ -238,7 +220,7 @@ export default function NavigationModule({ fieldValues }) {
|
|
|
238
220
|
return (
|
|
239
221
|
<Link
|
|
240
222
|
linkField={fieldValues.link}
|
|
241
|
-
variant={fieldValues.linkVariant}
|
|
223
|
+
variant={fieldValues.style.linkVariant?.variantName}
|
|
242
224
|
>
|
|
243
225
|
{fieldValues.linkText}
|
|
244
226
|
</Link>
|
|
@@ -255,7 +237,7 @@ export default function NavigationModule({ fieldValues }) {
|
|
|
255
237
|
href="https://www.hubspot.com"
|
|
256
238
|
target="_blank"
|
|
257
239
|
rel="nofollow"
|
|
258
|
-
variant="
|
|
240
|
+
variant="primaryLink"
|
|
259
241
|
>
|
|
260
242
|
Go to HubSpot
|
|
261
243
|
</Link>
|
|
@@ -269,69 +251,32 @@ Note: The field-based approach is simpler and less error-prone.
|
|
|
269
251
|
|
|
270
252
|
### Variants
|
|
271
253
|
|
|
272
|
-
The Link component supports
|
|
254
|
+
The Link component supports two visual style variants through the `variant` prop:
|
|
273
255
|
|
|
274
|
-
- **
|
|
275
|
-
- **
|
|
276
|
-
- **tertiary**: Third alternative using `--hscl-link-color-tertiary` and `--hscl-link-color-hover-tertiary`
|
|
256
|
+
- **primaryLink** (default): Default link styling
|
|
257
|
+
- **secondaryLink**: Alternative link styling
|
|
277
258
|
|
|
278
|
-
The component
|
|
279
|
-
```tsx
|
|
280
|
-
style={{
|
|
281
|
-
'--hscl-link-color': `var(--hscl-link-color-${variant})`,
|
|
282
|
-
'--hscl-link-color-hover': `var(--hscl-link-color-hover-${variant})`,
|
|
283
|
-
}}
|
|
284
|
-
```
|
|
259
|
+
The component renders a `data-link-variant` attribute on the `<a>` element. The theme provides CSS variables scoped to `[data-link-variant]` selectors.
|
|
285
260
|
|
|
286
|
-
### CSS Variables
|
|
261
|
+
### CSS Variables (Theme-provided)
|
|
287
262
|
|
|
288
|
-
The
|
|
263
|
+
The theme provides these CSS variables via `[data-link-variant]` selectors:
|
|
289
264
|
|
|
290
265
|
**Base Styles:**
|
|
291
|
-
- `--
|
|
292
|
-
- `--
|
|
293
|
-
- `--hscl-link-textDecoration`: Text decoration (default: underline)
|
|
266
|
+
- `--hs-link-color`: Text color
|
|
267
|
+
- `--hs-link-textDecoration`: Text decoration
|
|
294
268
|
|
|
295
269
|
**Hover States:**
|
|
296
|
-
- `--
|
|
297
|
-
- `--
|
|
298
|
-
|
|
299
|
-
**Variant-specific Variables (must be defined globally):**
|
|
300
|
-
- `--hscl-link-color-primary`: Primary variant text color
|
|
301
|
-
- `--hscl-link-color-hover-primary`: Primary variant hover text color
|
|
302
|
-
- `--hscl-link-color-secondary`: Secondary variant text color
|
|
303
|
-
- `--hscl-link-color-hover-secondary`: Secondary variant hover text color
|
|
304
|
-
- `--hscl-link-color-tertiary`: Tertiary variant text color
|
|
305
|
-
- `--hscl-link-color-hover-tertiary`: Tertiary variant hover text color
|
|
306
|
-
|
|
307
|
-
### Custom Styling Examples
|
|
270
|
+
- `--hs-link-color-hover`: Hover text color
|
|
271
|
+
- `--hs-link-textDecoration-hover`: Hover text decoration
|
|
308
272
|
|
|
309
|
-
|
|
273
|
+
**Active States:**
|
|
274
|
+
- `--hs-link-color-active`: Active text color (falls back to `--hs-link-color`)
|
|
275
|
+
- `--hs-link-textDecoration-active`: Active text decoration (falls back to `--hs-link-textDecoration`)
|
|
310
276
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
style={{
|
|
315
|
-
'--hscl-link-textDecoration': 'none',
|
|
316
|
-
'--hscl-link-textDecoration-hover': 'underline',
|
|
317
|
-
}}
|
|
318
|
-
>
|
|
319
|
-
Hover for underline
|
|
320
|
-
</Link>
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
#### Override Display
|
|
324
|
-
|
|
325
|
-
```tsx
|
|
326
|
-
<Link
|
|
327
|
-
href="https://www.hubspot.com"
|
|
328
|
-
style={{
|
|
329
|
-
'--hscl-link-display': 'block',
|
|
330
|
-
}}
|
|
331
|
-
>
|
|
332
|
-
Block-level Link
|
|
333
|
-
</Link>
|
|
334
|
-
```
|
|
277
|
+
**Hardcoded Styles (not theme-configurable):**
|
|
278
|
+
- `display: inline-block`
|
|
279
|
+
- Focus outline: `2px solid currentColor` with `2px` offset
|
|
335
280
|
|
|
336
281
|
## Accessibility
|
|
337
282
|
|
|
@@ -349,9 +294,8 @@ The Link component follows accessibility best practices:
|
|
|
349
294
|
|
|
350
295
|
- **Use linkField prop when available**: If you have HubSpot field data, use the `linkField` prop instead of manually extracting href/target/rel
|
|
351
296
|
- **Do NOT mix approaches**: TypeScript will prevent you from using both `linkField` and `href` props together
|
|
352
|
-
- **Use variants for consistency**: Choose the appropriate variant (
|
|
297
|
+
- **Use variants for consistency**: Choose the appropriate variant (primaryLink/secondaryLink) rather than overriding colors directly
|
|
353
298
|
- **Security**: The component automatically adds `rel="noopener noreferrer"` when target is `_blank`
|
|
354
|
-
- **CSS Variables**: Override design tokens using CSS variables for advanced customization
|
|
355
299
|
- **Minimal styling**: Keep link styling simple; use Button component for prominent CTAs
|
|
356
300
|
|
|
357
301
|
## Common Patterns
|
|
@@ -359,7 +303,7 @@ The Link component follows accessibility best practices:
|
|
|
359
303
|
### Navigation Menu Item
|
|
360
304
|
|
|
361
305
|
```tsx
|
|
362
|
-
<Link href="/dashboard" variant="
|
|
306
|
+
<Link href="/dashboard" variant="secondaryLink">
|
|
363
307
|
Dashboard
|
|
364
308
|
</Link>
|
|
365
309
|
```
|
|
@@ -4,12 +4,6 @@ import { LinkProps } from '../types.js';
|
|
|
4
4
|
import { withLinkStyles } from './LinkDecorator.js';
|
|
5
5
|
import { SBContainer, SBFocusWrapper } from '@sb-utils';
|
|
6
6
|
import { SBCard } from '@sb-utils/SBCard.js';
|
|
7
|
-
import type { CSSVariables } from '../../utils/types.js';
|
|
8
|
-
|
|
9
|
-
const removeUnderlineStyle: CSSVariables = {
|
|
10
|
-
'--hscl-link-textDecoration': 'none',
|
|
11
|
-
'--hscl-link-textDecoration-hover': 'none',
|
|
12
|
-
};
|
|
13
7
|
|
|
14
8
|
const meta: Meta<LinkProps> = {
|
|
15
9
|
title: 'Component Library/Link',
|
|
@@ -34,7 +28,7 @@ export const Default: Story = {
|
|
|
34
28
|
href: 'https://www.hubspot.com',
|
|
35
29
|
target: '_blank',
|
|
36
30
|
rel: 'noopener noreferrer',
|
|
37
|
-
variant: '
|
|
31
|
+
variant: 'primaryLink',
|
|
38
32
|
className: 'test-class',
|
|
39
33
|
},
|
|
40
34
|
};
|
|
@@ -90,7 +84,7 @@ export const InContext: Story = {
|
|
|
90
84
|
gap="medium"
|
|
91
85
|
style={{ flexWrap: 'wrap' }}
|
|
92
86
|
>
|
|
93
|
-
<Link href="https://www.hubspot.com"
|
|
87
|
+
<Link href="https://www.hubspot.com">
|
|
94
88
|
<SBCard
|
|
95
89
|
title="Clickable Card"
|
|
96
90
|
description="The entire card is wrapped in a link, making it clickable."
|
|
@@ -143,7 +137,6 @@ export const InContext: Story = {
|
|
|
143
137
|
<Link
|
|
144
138
|
key={key}
|
|
145
139
|
href={`/image/${key}`}
|
|
146
|
-
style={removeUnderlineStyle}
|
|
147
140
|
>
|
|
148
141
|
<div
|
|
149
142
|
style={{
|
|
@@ -179,7 +172,7 @@ export const InteractionStates: Story = {
|
|
|
179
172
|
<SBContainer addBackground>
|
|
180
173
|
<h4>Hover State</h4>
|
|
181
174
|
<p>Hover over the link to see the hover effect</p>
|
|
182
|
-
<Link href="https://www.hubspot.com" variant="
|
|
175
|
+
<Link href="https://www.hubspot.com" variant="primaryLink">
|
|
183
176
|
Hover me
|
|
184
177
|
</Link>
|
|
185
178
|
</SBContainer>
|
|
@@ -192,7 +185,7 @@ export const InteractionStates: Story = {
|
|
|
192
185
|
to show how it looks.
|
|
193
186
|
</p>
|
|
194
187
|
<SBFocusWrapper>
|
|
195
|
-
<Link href="https://www.hubspot.com" variant="
|
|
188
|
+
<Link href="https://www.hubspot.com" variant="primaryLink">
|
|
196
189
|
Focused link
|
|
197
190
|
</Link>
|
|
198
191
|
</SBFocusWrapper>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.linkContainer {
|
|
2
|
+
[data-link-variant='primaryLink'] {
|
|
3
|
+
--hs-link-color: #0066cc;
|
|
4
|
+
--hs-link-textDecoration: none;
|
|
5
|
+
--hs-link-color-hover: #0052a3;
|
|
6
|
+
--hs-link-textDecoration-hover: underline;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
[data-link-variant='secondaryLink'] {
|
|
10
|
+
--hs-link-color: #516f90;
|
|
11
|
+
--hs-link-textDecoration: none;
|
|
12
|
+
--hs-link-color-hover: #33475b;
|
|
13
|
+
--hs-link-textDecoration-hover: underline;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import type { Decorator } from '@storybook/react';
|
|
2
|
-
import
|
|
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
|
+
import styles from './LinkDecorator.module.scss';
|
|
12
3
|
|
|
13
4
|
export const withLinkStyles: Decorator = Story => (
|
|
14
|
-
<div
|
|
5
|
+
<div className={styles.linkContainer}>
|
|
15
6
|
<Story />
|
|
16
7
|
</div>
|
|
17
8
|
);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { LinkFieldDefaults, Visibility } from '@hubspot/cms-components/fields';
|
|
2
2
|
import { LinkFieldValue } from '../utils/linkField.js';
|
|
3
3
|
|
|
4
|
+
export type LinkVariant = 'primaryLink' | 'secondaryLink';
|
|
5
|
+
|
|
4
6
|
export type BaseLinkProps = {
|
|
5
|
-
variant?:
|
|
7
|
+
variant?: LinkVariant;
|
|
6
8
|
className?: string;
|
|
7
9
|
style?: React.CSSProperties;
|
|
8
10
|
children?: React.ReactNode;
|
|
@@ -42,7 +44,7 @@ export type ContentFieldsProps<TLinkName extends string = 'link'> = {
|
|
|
42
44
|
);
|
|
43
45
|
|
|
44
46
|
export type StyleFieldsProps = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
variantLabel?: string;
|
|
48
|
+
variantName?: string;
|
|
49
|
+
variantDefault?: { variant_name: LinkVariant };
|
|
48
50
|
};
|
|
@@ -136,8 +136,11 @@ render: () => {
|
|
|
136
136
|
property: var(--hscl-componentName-property-hover);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// Focus-visible reuses hover styles + browser default outline
|
|
140
|
+
&:focus-visible {
|
|
141
|
+
property: var(--hscl-componentName-property-hover);
|
|
142
|
+
outline: 2px solid Highlight;
|
|
143
|
+
outline-offset: 2px;
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
&:disabled {
|
|
@@ -186,9 +189,11 @@ Use `&` for pseudo-classes and nested selectors:
|
|
|
186
189
|
background-color: var(--hscl-button-backgroundColor-hover);
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
&:focus {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
&:focus-visible {
|
|
193
|
+
background-color: var(--hscl-button-backgroundColor-hover);
|
|
194
|
+
color: var(--hscl-button-color-hover);
|
|
195
|
+
outline: 2px solid Highlight;
|
|
196
|
+
outline-offset: 2px;
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
&:disabled {
|
|
@@ -244,9 +249,11 @@ Use `&` for pseudo-classes and nested selectors:
|
|
|
244
249
|
background-color: var(--hscl-button-backgroundColor-hover);
|
|
245
250
|
}
|
|
246
251
|
|
|
247
|
-
&:focus {
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
&:focus-visible {
|
|
253
|
+
background-color: var(--hscl-button-backgroundColor-hover);
|
|
254
|
+
color: var(--hscl-button-color-hover);
|
|
255
|
+
outline: 2px solid Highlight;
|
|
256
|
+
outline-offset: 2px;
|
|
250
257
|
}
|
|
251
258
|
|
|
252
259
|
&:disabled {
|
|
@@ -286,10 +293,12 @@ All interactive components should style **hover, focus, active** states:
|
|
|
286
293
|
color: var(--hscl-button-color-hover, var(--hscl-button-color));
|
|
287
294
|
}
|
|
288
295
|
|
|
289
|
-
// Focus-visible -
|
|
296
|
+
// Focus-visible - reuses hover styles + browser default outline
|
|
290
297
|
&:focus-visible {
|
|
291
|
-
|
|
292
|
-
|
|
298
|
+
background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
|
|
299
|
+
color: var(--hscl-button-color-hover, var(--hscl-button-color));
|
|
300
|
+
outline: 2px solid Highlight;
|
|
301
|
+
outline-offset: 2px;
|
|
293
302
|
}
|
|
294
303
|
|
|
295
304
|
// Active - pressed/clicked state
|
|
@@ -322,11 +331,17 @@ All interactive components should style **hover, focus, active** states:
|
|
|
322
331
|
background-color: var(--hscl-button-backgroundColor-hover);
|
|
323
332
|
```
|
|
324
333
|
|
|
325
|
-
3. **
|
|
326
|
-
-
|
|
327
|
-
-
|
|
334
|
+
3. **Reuse hover styles for focus, rely on browser default outline**
|
|
335
|
+
- Focus-visible should apply the same visual treatment as hover (background, color, border)
|
|
336
|
+
- Use `outline: 2px solid initial` for the browser's default focus ring color
|
|
337
|
+
- No need for separate focus-specific CSS variables
|
|
328
338
|
```scss
|
|
329
|
-
|
|
339
|
+
&:focus-visible {
|
|
340
|
+
background-color: var(--hscl-button-backgroundColor-hover, var(--hscl-button-backgroundColor));
|
|
341
|
+
color: var(--hscl-button-color-hover, var(--hscl-button-color));
|
|
342
|
+
outline: 2px solid Highlight;
|
|
343
|
+
outline-offset: 2px;
|
|
344
|
+
}
|
|
330
345
|
```
|
|
331
346
|
|
|
332
347
|
4. **Respect reduced motion preferences**
|
|
@@ -360,8 +375,10 @@ See these components for implementation examples:
|
|
|
360
375
|
property: var(--hscl-componentName-property-hover);
|
|
361
376
|
}
|
|
362
377
|
|
|
363
|
-
&:focus {
|
|
364
|
-
property: var(--hscl-componentName-property-
|
|
378
|
+
&:focus-visible {
|
|
379
|
+
property: var(--hscl-componentName-property-hover);
|
|
380
|
+
outline: 2px solid Highlight;
|
|
381
|
+
outline-offset: 2px;
|
|
365
382
|
}
|
|
366
383
|
}
|
|
367
384
|
|