@hashicorp/react-components 0.0.0-nightly.f535c63 → 0.0.0-nightly.f8bc604
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/package.json +3 -7
- package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
- package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
- package/src/components/nav-panel/components/link-list/index.tsx +9 -4
- package/src/components/nav-panel/components/link-list/style.module.css +10 -10
- package/src/components/nav-panel/components/panel/column/style.module.css +2 -2
- package/src/components/nav-panel/components/panel/root/style.module.css +5 -5
- package/src/components/nav-panel/components/promo/index.tsx +9 -3
- package/src/components/nav-panel/components/promo/style.module.css +22 -12
- package/src/components/nav-panel/components/text/index.tsx +34 -0
- package/src/components/nav-panel/product-panel/index.tsx +30 -14
- package/src/components/nav-panel/product-panel/style.module.css +63 -36
- package/src/components/nav-panel/standard-panel/index.tsx +3 -0
- package/src/components/nav-panel/standard-panel/style.module.css +2 -2
- package/src/lib/get-localized-link-props/index.test.ts +30 -0
- package/src/lib/get-localized-link-props/index.ts +45 -0
- package/src/lib/parse-url/index.test.ts +35 -1
- package/src/lib/parse-url/index.ts +75 -9
- package/src/lib/send-posthog-event/index.ts +22 -0
- package/src/lib/supported-locales/index.ts +1 -0
- package/src/lib/track-nav-click-event/index.ts +4 -4
- package/src/components/actions/index.tsx +0 -107
- package/src/components/actions/style.module.css +0 -28
- package/src/components/actions/types.ts +0 -73
- package/src/components/call-to-action/index.tsx +0 -205
- package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
- package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
- package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
- package/src/components/call-to-action/styles/variant-links.module.css +0 -85
- package/src/components/card/README.md +0 -11
- package/src/components/card/card.tsx +0 -45
- package/src/components/card/index.test.tsx +0 -69
- package/src/components/card/index.tsx +0 -14
- package/src/components/card/newsroom-card/index.test.tsx +0 -39
- package/src/components/card/newsroom-card/index.tsx +0 -37
- package/src/components/card/newsroom-card/style.module.css +0 -12
- package/src/components/card/partner-card/index.test.tsx +0 -57
- package/src/components/card/partner-card/index.tsx +0 -69
- package/src/components/card/partner-card/style.module.css +0 -24
- package/src/components/card/person-card/index.test.tsx +0 -71
- package/src/components/card/person-card/index.tsx +0 -93
- package/src/components/card/person-card/style.module.css +0 -33
- package/src/components/card/primitives.tsx +0 -177
- package/src/components/card/promo-card/index.tsx +0 -51
- package/src/components/card/resource-card/index.test.tsx +0 -48
- package/src/components/card/resource-card/index.tsx +0 -37
- package/src/components/card/style.module.css +0 -145
- package/src/components/card/types.ts +0 -66
- package/src/components/card/unified-card/index.tsx +0 -58
- package/src/components/consent-manager/README.md +0 -73
- package/src/components/consent-manager/components/banner/banner.module.css +0 -23
- package/src/components/consent-manager/components/banner/banner.test.tsx +0 -31
- package/src/components/consent-manager/components/banner/banner.tsx +0 -73
- package/src/components/consent-manager/components/dialog/dialog.module.css +0 -145
- package/src/components/consent-manager/components/dialog/dialog.test.tsx +0 -194
- package/src/components/consent-manager/components/dialog/dialog.tsx +0 -339
- package/src/components/consent-manager/img/icn_close.tsx +0 -19
- package/src/components/consent-manager/index.test.tsx +0 -334
- package/src/components/consent-manager/index.tsx +0 -186
- package/src/components/consent-manager/loader/README.md +0 -29
- package/src/components/consent-manager/loader/categories.ts +0 -22
- package/src/components/consent-manager/loader/index.tsx +0 -94
- package/src/components/consent-manager/loader/presets/enterprise.ts +0 -12
- package/src/components/consent-manager/loader/presets/oss.ts +0 -10
- package/src/components/consent-manager/loader/services.ts +0 -45
- package/src/components/consent-manager/scripts/custom.tsx +0 -59
- package/src/components/consent-manager/scripts/segment.tsx +0 -87
- package/src/components/consent-manager/style.module.css +0 -9
- package/src/components/consent-manager/types.ts +0 -55
- package/src/components/consent-manager/util/cookies.test.ts +0 -75
- package/src/components/consent-manager/util/cookies.ts +0 -70
- package/src/components/consent-manager/util/integrations.test.ts +0 -115
- package/src/components/consent-manager/util/integrations.ts +0 -110
- package/src/components/intro/index.tsx +0 -82
- package/src/components/intro/style.module.css +0 -67
- package/src/components/intro/types.ts +0 -44
- package/src/components/marketo-form/CHANGELOG.md +0 -122
- package/src/components/marketo-form/README.md +0 -1
- package/src/components/marketo-form/contexts/FormMetadata.ts +0 -11
- package/src/components/marketo-form/docs.mdx +0 -197
- package/src/components/marketo-form/fixtures/index.ts +0 -185
- package/src/components/marketo-form/form/index.tsx +0 -369
- package/src/components/marketo-form/index.test.tsx +0 -320
- package/src/components/marketo-form/index.ts +0 -11
- package/src/components/marketo-form/package.original.json +0 -30
- package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
- package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
- package/src/components/marketo-form/partials/field/index.tsx +0 -90
- package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
- package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
- package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
- package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -21
- package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -37
- package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
- package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -18
- package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
- package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
- package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
- package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
- package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
- package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
- package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
- package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
- package/src/components/marketo-form/partials/label/index.tsx +0 -24
- package/src/components/marketo-form/partials/label/style.module.css +0 -12
- package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
- package/src/components/marketo-form/props.js +0 -66
- package/src/components/marketo-form/server/api-routes.ts +0 -109
- package/src/components/marketo-form/server/client.ts +0 -98
- package/src/components/marketo-form/server/index.ts +0 -8
- package/src/components/marketo-form/types.ts +0 -268
- package/src/components/marketo-form/utils.test.tsx +0 -103
- package/src/components/marketo-form/utils.ts +0 -449
- package/src/components/next-steps/index.tsx +0 -137
- package/src/components/next-steps/style.module.css +0 -270
- package/src/components/next-steps/types.ts +0 -63
- package/src/components/product-badge/index.test.tsx +0 -11
- package/src/components/product-badge/index.tsx +0 -42
- package/src/components/product-badge/style.module.css +0 -76
- package/src/components/products-used/index.tsx +0 -84
- package/src/components/products-used/style.module.css +0 -87
- package/src/components/products-used/types.ts +0 -31
- package/src/components/related-content/index.test.tsx +0 -74
- package/src/components/related-content/index.tsx +0 -56
- package/src/components/related-content/style.module.css +0 -89
- package/src/components/related-content/types.ts +0 -21
- package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
- package/src/components/subnav/helpers/traverse/index.ts +0 -42
- package/src/components/subnav/helpers/useHeightRef.ts +0 -42
- package/src/components/subnav/helpers/useNavRef.ts +0 -30
- package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
- package/src/components/subnav/helpers/useStuckRef.ts +0 -51
- package/src/components/subnav/index.tsx +0 -199
- package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
- package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
- package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
- package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
- package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
- package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -81
- package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
- package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
- package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
- package/src/components/subnav/partials/CtaLinks/index.tsx +0 -130
- package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
- package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
- package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -39
- package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -158
- package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -104
- package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -143
- package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -88
- package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -10
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -10
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
- package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
- package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
- package/src/components/subnav/partials/nav-item-text/style.module.css +0 -13
- package/src/components/subnav/style.module.css +0 -66
- package/src/components/subnav/subnav-consts.ts +0 -1
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
.productPanel {
|
|
4
4
|
height: 100%;
|
|
5
5
|
|
|
6
|
+
/* Temporarily included for devdot compatability
|
|
7
|
+
https://app.asana.com/1/90955849329269/project/1209320521286316/task/1210514680913728?focus=true */
|
|
8
|
+
--mds-spacing-01: 0.25rem;
|
|
9
|
+
--mds-spacing-02: 0.375rem;
|
|
10
|
+
--mds-spacing-03: 0.5rem;
|
|
11
|
+
--mds-spacing-04: 0.75rem;
|
|
12
|
+
--mds-spacing-05: 1rem;
|
|
13
|
+
--mds-spacing-06: 1.25rem;
|
|
14
|
+
--mds-spacing-07: 1.5rem;
|
|
15
|
+
--mds-spacing-08: 2rem;
|
|
16
|
+
--mds-spacing-09: 2.5rem;
|
|
17
|
+
--mds-spacing-10: 3rem;
|
|
18
|
+
--mds-spacing-11: 3.5rem;
|
|
19
|
+
--mds-spacing-12: 4rem;
|
|
20
|
+
--mds-spacing-13: 4.5rem;
|
|
21
|
+
--mds-spacing-14: 5.5rem;
|
|
22
|
+
--mds-spacing-15: 6rem;
|
|
23
|
+
--mds-spacing-16: 8rem;
|
|
24
|
+
--mds-spacing-17: 8.5rem;
|
|
25
|
+
--mds-typography-body-200-font-size: 0.875rem;
|
|
26
|
+
--mds-typography-font-weight-regular: 400;
|
|
27
|
+
--mds-typography-font-weight-semibold: 600;
|
|
28
|
+
|
|
6
29
|
@media (--large) {
|
|
7
30
|
&.noSide {
|
|
8
31
|
display: grid;
|
|
@@ -26,7 +49,11 @@
|
|
|
26
49
|
list-style: none;
|
|
27
50
|
padding: 0;
|
|
28
51
|
display: grid;
|
|
29
|
-
margin: var(--
|
|
52
|
+
margin: var(--mds-spacing-07) 0 0;
|
|
53
|
+
|
|
54
|
+
@nest .topPromo & {
|
|
55
|
+
margin-bottom: var(--mds-spacing-07);
|
|
56
|
+
}
|
|
30
57
|
|
|
31
58
|
@media (--large) {
|
|
32
59
|
@nest .noSide & {
|
|
@@ -48,27 +75,27 @@
|
|
|
48
75
|
|
|
49
76
|
&:first-child {
|
|
50
77
|
border-bottom: 1px solid var(--token-color-border-primary);
|
|
51
|
-
margin-bottom: var(--
|
|
52
|
-
padding-bottom: var(--
|
|
78
|
+
margin-bottom: var(--mds-spacing-05);
|
|
79
|
+
padding-bottom: var(--mds-spacing-05);
|
|
53
80
|
}
|
|
54
81
|
}
|
|
55
82
|
|
|
56
83
|
.eyebrow {
|
|
57
|
-
padding-bottom: var(--
|
|
84
|
+
padding-bottom: var(--mds-spacing-04);
|
|
58
85
|
color: var(--token-color-foreground-strong);
|
|
59
|
-
font-weight: var(--font-weight-
|
|
86
|
+
font-weight: var(--mds-typography-font-weight-semibold);
|
|
60
87
|
|
|
61
88
|
@media (--large) {
|
|
62
89
|
@nest .noSide & {
|
|
63
90
|
padding-bottom: 0;
|
|
64
|
-
padding-left: var(--
|
|
91
|
+
padding-left: var(--mds-spacing-06);
|
|
65
92
|
}
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
@media (--dev-dot-viewport) {
|
|
69
96
|
@nest .hasSide & {
|
|
70
97
|
padding-bottom: 0;
|
|
71
|
-
padding-left: var(--
|
|
98
|
+
padding-left: var(--mds-spacing-06);
|
|
72
99
|
}
|
|
73
100
|
}
|
|
74
101
|
}
|
|
@@ -82,8 +109,8 @@
|
|
|
82
109
|
|
|
83
110
|
@media (--large) {
|
|
84
111
|
@nest .noSide & {
|
|
85
|
-
padding: var(--
|
|
86
|
-
var(--
|
|
112
|
+
padding: var(--mds-spacing-06) var(--mds-spacing-05)
|
|
113
|
+
var(--mds-spacing-04);
|
|
87
114
|
grid-auto-flow: row;
|
|
88
115
|
grid-template-columns: repeat(2, 300px);
|
|
89
116
|
grid-template-rows: repeat(2, max-content);
|
|
@@ -93,8 +120,8 @@
|
|
|
93
120
|
|
|
94
121
|
@media (--dev-dot-viewport) {
|
|
95
122
|
@nest .hasSide & {
|
|
96
|
-
padding: var(--
|
|
97
|
-
var(--
|
|
123
|
+
padding: var(--mds-spacing-06) var(--mds-spacing-05)
|
|
124
|
+
var(--mds-spacing-04);
|
|
98
125
|
grid-auto-flow: row;
|
|
99
126
|
grid-template-columns: repeat(2, 300px);
|
|
100
127
|
grid-template-rows: repeat(2, max-content);
|
|
@@ -113,29 +140,29 @@
|
|
|
113
140
|
|
|
114
141
|
.productWrapper {
|
|
115
142
|
display: flex;
|
|
116
|
-
margin-bottom: var(--
|
|
117
|
-
gap: var(--
|
|
143
|
+
margin-bottom: var(--mds-spacing-05);
|
|
144
|
+
gap: var(--mds-spacing-05);
|
|
118
145
|
|
|
119
146
|
@media (--large) {
|
|
120
147
|
@nest .noSide & {
|
|
121
|
-
padding: var(--
|
|
148
|
+
padding: var(--mds-spacing-03) var(--mds-spacing-05);
|
|
122
149
|
margin-bottom: 0;
|
|
123
150
|
|
|
124
151
|
&:hover {
|
|
125
152
|
background-color: var(--token-color-surface-interactive-hover);
|
|
126
|
-
border-radius: var(--
|
|
153
|
+
border-radius: var(--mds-spacing-01);
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
@media (--dev-dot-viewport) {
|
|
132
159
|
@nest .hasSide & {
|
|
133
|
-
padding: var(--
|
|
160
|
+
padding: var(--mds-spacing-03) var(--mds-spacing-05);
|
|
134
161
|
margin-bottom: 0;
|
|
135
162
|
|
|
136
163
|
&:hover {
|
|
137
164
|
background-color: var(--token-color-surface-interactive-hover);
|
|
138
|
-
border-radius: var(--
|
|
165
|
+
border-radius: var(--mds-spacing-01);
|
|
139
166
|
}
|
|
140
167
|
}
|
|
141
168
|
}
|
|
@@ -152,8 +179,8 @@
|
|
|
152
179
|
@nest .noSide & {
|
|
153
180
|
& svg {
|
|
154
181
|
@media (--large) {
|
|
155
|
-
height: var(--
|
|
156
|
-
width: var(--
|
|
182
|
+
height: var(--mds-spacing-08);
|
|
183
|
+
width: var(--mds-spacing-08);
|
|
157
184
|
}
|
|
158
185
|
}
|
|
159
186
|
}
|
|
@@ -161,8 +188,8 @@
|
|
|
161
188
|
@nest .hasSide & {
|
|
162
189
|
& svg {
|
|
163
190
|
@media (--dev-dot-viewport) {
|
|
164
|
-
height: var(--
|
|
165
|
-
width: var(--
|
|
191
|
+
height: var(--mds-spacing-08);
|
|
192
|
+
width: var(--mds-spacing-08);
|
|
166
193
|
}
|
|
167
194
|
}
|
|
168
195
|
}
|
|
@@ -197,15 +224,15 @@
|
|
|
197
224
|
|
|
198
225
|
@media (--large) {
|
|
199
226
|
@nest .noSide & {
|
|
200
|
-
padding: var(--
|
|
201
|
-
var(--
|
|
227
|
+
padding: var(--mds-spacing-07) 0 var(--mds-spacing-09)
|
|
228
|
+
var(--mds-spacing-04);
|
|
202
229
|
}
|
|
203
230
|
}
|
|
204
231
|
|
|
205
232
|
@media (--dev-dot-viewport) {
|
|
206
233
|
@nest .hasSide & {
|
|
207
|
-
padding: var(--
|
|
208
|
-
var(--
|
|
234
|
+
padding: var(--mds-spacing-07) 0 var(--mds-spacing-09)
|
|
235
|
+
var(--mds-spacing-04);
|
|
209
236
|
}
|
|
210
237
|
}
|
|
211
238
|
}
|
|
@@ -215,10 +242,10 @@
|
|
|
215
242
|
background: var(--token-color-surface-faint);
|
|
216
243
|
display: flex;
|
|
217
244
|
flex-direction: column;
|
|
218
|
-
gap: var(--
|
|
245
|
+
gap: var(--mds-spacing-05);
|
|
219
246
|
margin: 0;
|
|
220
|
-
padding: var(--
|
|
221
|
-
var(--
|
|
247
|
+
padding: var(--mds-spacing-07) var(--mds-spacing-04)
|
|
248
|
+
var(--mds-spacing-09) var(--mds-spacing-07);
|
|
222
249
|
}
|
|
223
250
|
|
|
224
251
|
.sidePanelItems {
|
|
@@ -227,24 +254,24 @@
|
|
|
227
254
|
padding: 0;
|
|
228
255
|
display: grid;
|
|
229
256
|
grid-template-columns: 1fr;
|
|
230
|
-
gap: var(--
|
|
257
|
+
gap: var(--mds-spacing-07);
|
|
231
258
|
|
|
232
259
|
@media (--dev-dot-viewport) {
|
|
233
|
-
gap: var(--
|
|
260
|
+
gap: var(--mds-spacing-05);
|
|
234
261
|
}
|
|
235
262
|
}
|
|
236
263
|
|
|
237
264
|
/* Lifted from .productWrapper */
|
|
238
265
|
.sidePanelItem {
|
|
239
266
|
display: flex;
|
|
240
|
-
gap: var(--
|
|
267
|
+
gap: var(--mds-spacing-05);
|
|
241
268
|
|
|
242
269
|
@media (--dev-dot-viewport) {
|
|
243
|
-
padding: var(--
|
|
270
|
+
padding: var(--mds-spacing-03) var(--mds-spacing-05);
|
|
244
271
|
|
|
245
272
|
&:hover {
|
|
246
273
|
background-color: var(--token-color-surface-interactive-hover);
|
|
247
|
-
border-radius: var(--
|
|
274
|
+
border-radius: var(--mds-spacing-01);
|
|
248
275
|
}
|
|
249
276
|
}
|
|
250
277
|
}
|
|
@@ -253,8 +280,8 @@
|
|
|
253
280
|
packages/react-components/src/components/nav-panel/components/link-list/style.module.css */
|
|
254
281
|
.iconBackground {
|
|
255
282
|
background: var(--token-color-foreground-high-contrast);
|
|
256
|
-
height: var(--
|
|
257
|
-
width: var(--
|
|
283
|
+
height: var(--mds-spacing-08);
|
|
284
|
+
width: var(--mds-spacing-08);
|
|
258
285
|
display: flex;
|
|
259
286
|
align-items: center;
|
|
260
287
|
justify-content: center;
|
|
@@ -301,7 +328,7 @@ packages/react-components/src/components/nav-panel/components/link-list/style.mo
|
|
|
301
328
|
*/
|
|
302
329
|
.hcpVaultSecrets,
|
|
303
330
|
.hcpVaultRadar {
|
|
304
|
-
padding: var(--
|
|
331
|
+
padding: var(--mds-spacing-01);
|
|
305
332
|
border: 2px solid var(--token-color-vault-brand);
|
|
306
333
|
border-radius: 3px;
|
|
307
334
|
|
|
@@ -12,12 +12,14 @@ interface StandardPanelProps {
|
|
|
12
12
|
navData: NavItem[]
|
|
13
13
|
promo?: PromoProps
|
|
14
14
|
sectionName?: string
|
|
15
|
+
locale?: any
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const StandardPanel = ({
|
|
18
19
|
navData,
|
|
19
20
|
promo,
|
|
20
21
|
sectionName = '',
|
|
22
|
+
locale,
|
|
21
23
|
}: StandardPanelProps) => {
|
|
22
24
|
return (
|
|
23
25
|
<NavPanel.Root>
|
|
@@ -26,6 +28,7 @@ const StandardPanel = ({
|
|
|
26
28
|
return (
|
|
27
29
|
<NavPanel.Column key={stableIdx}>
|
|
28
30
|
<LinkList
|
|
31
|
+
locale={locale}
|
|
29
32
|
links={navItems}
|
|
30
33
|
linkClickHandler={(text, href) => {
|
|
31
34
|
trackNavClickEvent(text, href, sectionName)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getLocalizedLinkProps } from '.'
|
|
2
|
+
|
|
3
|
+
describe('getLocalizedLinkProps', () => {
|
|
4
|
+
it('does not format localized homepage', () => {
|
|
5
|
+
const localizedLinkProps = getLocalizedLinkProps('/en', 'ja')
|
|
6
|
+
expect(localizedLinkProps.href).toBe('/en')
|
|
7
|
+
expect(localizedLinkProps.hrefLang).toBe('en')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('does not format localized path hrefs', () => {
|
|
11
|
+
const localizedLinkProps = getLocalizedLinkProps('/ja/solutions', 'de')
|
|
12
|
+
expect(localizedLinkProps.href).toBe('/ja/solutions')
|
|
13
|
+
expect(localizedLinkProps.hrefLang).toBe('ja')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('does format paths that start with a locale string', () => {
|
|
17
|
+
const localizedLinkProps = getLocalizedLinkProps(
|
|
18
|
+
'/deconstructing-cloud',
|
|
19
|
+
'en'
|
|
20
|
+
)
|
|
21
|
+
expect(localizedLinkProps.href).toBe('/en/deconstructing-cloud')
|
|
22
|
+
expect(localizedLinkProps.hrefLang).toBe('en')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('does not format external paths', () => {
|
|
26
|
+
const localizedLinkProps = getLocalizedLinkProps('https://google.com', 'ja')
|
|
27
|
+
expect(localizedLinkProps.href).toBe('https://google.com')
|
|
28
|
+
expect(localizedLinkProps.hrefLang).toBe(undefined)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { parseUrl } from '../parse-url'
|
|
2
|
+
import { allSupportedLocales } from '../supported-locales'
|
|
3
|
+
|
|
4
|
+
export const getLocalizedLinkProps = (
|
|
5
|
+
url: string,
|
|
6
|
+
locale?: string
|
|
7
|
+
): {
|
|
8
|
+
href: string
|
|
9
|
+
hrefLang: string | undefined
|
|
10
|
+
} => {
|
|
11
|
+
const parsedHref = parseUrl(url).href
|
|
12
|
+
|
|
13
|
+
// example: /en
|
|
14
|
+
const hrefIsLocalizedHomepage = allSupportedLocales.includes(
|
|
15
|
+
parsedHref.substring(1)
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// true example: /en/solutions
|
|
19
|
+
// false example: /entry-point
|
|
20
|
+
const hrefIsLocalized =
|
|
21
|
+
allSupportedLocales.includes(parsedHref.substring(1, 3)) &&
|
|
22
|
+
parsedHref[3] === '/'
|
|
23
|
+
|
|
24
|
+
// If the href is already localized:
|
|
25
|
+
// - do not format the href
|
|
26
|
+
// - use the locale from the href for the hrefLang
|
|
27
|
+
if (hrefIsLocalizedHomepage || hrefIsLocalized) {
|
|
28
|
+
return {
|
|
29
|
+
href: parsedHref,
|
|
30
|
+
hrefLang: parsedHref.substring(1, 3),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (parsedHref.startsWith('/') && locale) {
|
|
35
|
+
return {
|
|
36
|
+
href: `/${locale}${parsedHref}`,
|
|
37
|
+
hrefLang: locale,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
href: parsedHref,
|
|
43
|
+
hrefLang: undefined,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseUrl } from '.'
|
|
1
|
+
import { parseUrl, hasHashiCorpDestination } from '.'
|
|
2
2
|
|
|
3
3
|
describe('parseUrl', () => {
|
|
4
4
|
it('detects hashicorp.com URLs as internal', () => {
|
|
@@ -7,6 +7,12 @@ describe('parseUrl', () => {
|
|
|
7
7
|
expect(url.linkType).toBe('inbound')
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
+
it('detects www.hashicorp.com URLs as internal', () => {
|
|
11
|
+
const url = parseUrl('https://hashicorp.com/foo')
|
|
12
|
+
expect(url.href).toBe('/foo')
|
|
13
|
+
expect(url.linkType).toBe('inbound')
|
|
14
|
+
})
|
|
15
|
+
|
|
10
16
|
it('detects URLs not containing hashicorp.com as external', () => {
|
|
11
17
|
const externalUrl = 'https://google.com/example'
|
|
12
18
|
const url = parseUrl(externalUrl)
|
|
@@ -28,8 +34,36 @@ describe('parseUrl', () => {
|
|
|
28
34
|
expect(url.href).toBe('/resources?type=case-study&products=terraform')
|
|
29
35
|
})
|
|
30
36
|
|
|
37
|
+
it('leaves URL hash intact', () => {
|
|
38
|
+
const url = parseUrl('https://hashicorp.com/about#leadership')
|
|
39
|
+
expect(url.href).toBe('/about#leadership')
|
|
40
|
+
})
|
|
41
|
+
|
|
31
42
|
it('leaves HashiCorp subdomain URLs intact', () => {
|
|
32
43
|
const url = parseUrl('https://developer.hashicorp.com/terraform')
|
|
33
44
|
expect(url.href).toBe('https://developer.hashicorp.com/terraform')
|
|
34
45
|
})
|
|
35
46
|
})
|
|
47
|
+
|
|
48
|
+
describe('hasHashiCorpDestination', () => {
|
|
49
|
+
it('returns true for supported hashicorp domains', () => {
|
|
50
|
+
const isHashiCorpDestination = hasHashiCorpDestination(
|
|
51
|
+
'https://portal.cloud.hashicorp.com/foo'
|
|
52
|
+
)
|
|
53
|
+
expect(isHashiCorpDestination).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
it('returns false for external domains', () => {
|
|
56
|
+
const isHashiCorpDestination = hasHashiCorpDestination(
|
|
57
|
+
'https://foo.com/bar'
|
|
58
|
+
)
|
|
59
|
+
expect(isHashiCorpDestination).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
it('returns true for relative links', () => {
|
|
62
|
+
const isHashiCorpDestination = hasHashiCorpDestination('/bar')
|
|
63
|
+
expect(isHashiCorpDestination).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
it('returns true for hashes', () => {
|
|
66
|
+
const isHashiCorpDestination = hasHashiCorpDestination('#someHash')
|
|
67
|
+
expect(isHashiCorpDestination).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -1,9 +1,46 @@
|
|
|
1
|
+
export const hashicorpDestinations: string[] = [
|
|
2
|
+
'app.terraform.io',
|
|
3
|
+
'boundaryproject.io',
|
|
4
|
+
'cloud.hashicorp.com',
|
|
5
|
+
'consul.io',
|
|
6
|
+
'developer.hashicorp.com',
|
|
7
|
+
'hashicorp.com',
|
|
8
|
+
'nomadproject.io',
|
|
9
|
+
'packer.io',
|
|
10
|
+
'portal.cloud.hashicorp.com',
|
|
11
|
+
'registry.terraform.io',
|
|
12
|
+
'terraform.io',
|
|
13
|
+
'vagrantup.com',
|
|
14
|
+
'vaultproject.io',
|
|
15
|
+
'waypointproject.io',
|
|
16
|
+
] as const
|
|
17
|
+
|
|
18
|
+
const containsHashiCorpDestination = (str: string): boolean =>
|
|
19
|
+
hashicorpDestinations.some(function (destination) {
|
|
20
|
+
return str.indexOf(destination) >= 0
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This function determines if a `url` is a HashiCorp link (relative URLs, hashes,
|
|
25
|
+
* or a url with a HashiCorp domain) or a non-HashiCorp link.
|
|
26
|
+
*
|
|
27
|
+
* @param {string | undefined} url - The URL to be parsed. It may be a relative URL, an anchor, or a full external/internal URL.
|
|
28
|
+
*
|
|
29
|
+
* @returns {{ linkType: 'inbound' | 'outbound', href: string }}
|
|
30
|
+
* An object with two properties:
|
|
31
|
+
* - `linkType`: Indicates whether the URL is 'inbound' or 'outbound'. Can be used to conditionally
|
|
32
|
+
* render the value of the target attribute.
|
|
33
|
+
* - `href`: The parsed href (may include pathname, search, and hash for internal URLs).
|
|
34
|
+
*
|
|
35
|
+
* @throws {Error} Throws an error if the URL is undefined or invalid.
|
|
36
|
+
*/
|
|
1
37
|
export function parseUrl(url: string | undefined): {
|
|
2
|
-
linkType: 'inbound' | 'outbound'
|
|
38
|
+
linkType: 'inbound' | 'outbound' | ''
|
|
3
39
|
href: string
|
|
4
40
|
} {
|
|
5
41
|
if (!url) {
|
|
6
|
-
|
|
42
|
+
console.log('No url to parse, returning empty string')
|
|
43
|
+
return { linkType: '', href: '' }
|
|
7
44
|
}
|
|
8
45
|
|
|
9
46
|
if (isRelative(url) || isAnchor(url)) {
|
|
@@ -12,21 +49,50 @@ export function parseUrl(url: string | undefined): {
|
|
|
12
49
|
|
|
13
50
|
if (isInternal(url)) {
|
|
14
51
|
try {
|
|
15
|
-
const { pathname, search } = new URL(url)
|
|
16
|
-
const href = pathname + search
|
|
52
|
+
const { pathname, search, hash } = new URL(url)
|
|
53
|
+
const href = pathname + search + hash
|
|
17
54
|
return { linkType: 'inbound', href }
|
|
18
55
|
} catch (error) {
|
|
19
|
-
|
|
56
|
+
console.log(`Invalid href: ${url}`)
|
|
57
|
+
return { linkType: '', href: '' }
|
|
20
58
|
}
|
|
21
59
|
}
|
|
22
60
|
|
|
23
61
|
return { linkType: 'outbound', href: url }
|
|
24
62
|
}
|
|
25
63
|
|
|
26
|
-
const isRelative = (url) => url.startsWith('/')
|
|
27
|
-
const isAnchor = (url) => url.startsWith('#')
|
|
64
|
+
const isRelative = (url: string) => url.startsWith('/')
|
|
65
|
+
const isAnchor = (url: string) => url.startsWith('#')
|
|
28
66
|
|
|
29
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Determines whether the provided url uses hashicorp.com or www.hashicorp.com as the hostname.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} url - The URL to check.
|
|
71
|
+
* @returns {boolean} - Returns true if the link uses a HashiCorp domain, otherwise false.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
export const isInternal = (url: string): boolean => {
|
|
30
75
|
const hostname = new URL(url).hostname
|
|
31
|
-
return hostname === 'hashicorp.com'
|
|
76
|
+
return hostname === 'hashicorp.com' || hostname === 'www.hashicorp.com'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Determines whether the provided url uses a HashiCorp hostname. This is used in several
|
|
81
|
+
* components to open a new tab for non-HashiCorp links.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} url - The URL to check.
|
|
84
|
+
* @returns {boolean} - Returns true if the link uses a HashiCorp domain, otherwise false.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
export const hasHashiCorpDestination = (url: string): boolean => {
|
|
88
|
+
// Fast return in case a relative URL or anchor is passed in
|
|
89
|
+
if (isRelative(url) || isAnchor(url)) return true
|
|
90
|
+
try {
|
|
91
|
+
const hostname = new URL(url).hostname
|
|
92
|
+
const isHashiCorpDestination = containsHashiCorpDestination(hostname)
|
|
93
|
+
return isHashiCorpDestination
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error('Error: `url` is not a valid URL', e)
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
32
98
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import posthog from 'posthog-js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A custom hook to send a custom event to PostHog.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} eventName - The name of the custom event to be tracked in PostHog.
|
|
7
|
+
* @param {Record<string, any>} properties - A key-value object containing properties associated with the event.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const MyComponent = () => {
|
|
11
|
+
* sendPosthogEvent('button_click', { buttonId: 'submit', action: 'clicked' });
|
|
12
|
+
* return <button id="submit">Submit</button>;
|
|
13
|
+
* };
|
|
14
|
+
*/
|
|
15
|
+
const sendPosthogEvent = (
|
|
16
|
+
eventName: string,
|
|
17
|
+
properties: Record<string, any>
|
|
18
|
+
) => {
|
|
19
|
+
posthog.capture(eventName, properties)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default sendPosthogEvent
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const allSupportedLocales = ['en', 'de', 'es', 'fr', 'ja', 'ko', 'pt']
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sendPosthogEvent from '../send-posthog-event'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Pushes a 'Nav link click' custom event to
|
|
5
|
-
* A wrapper around the
|
|
4
|
+
* Pushes a 'Nav link click' custom event to PostHog.
|
|
5
|
+
* A wrapper around the PostHog custom event method.
|
|
6
6
|
*
|
|
7
7
|
* @param {string} name - The name of the nav link
|
|
8
8
|
* @param {string} href - The href for the nav link
|
|
@@ -14,7 +14,7 @@ export const trackNavClickEvent = (
|
|
|
14
14
|
href: string,
|
|
15
15
|
section: string
|
|
16
16
|
) => {
|
|
17
|
-
|
|
17
|
+
sendPosthogEvent('Nav link click', {
|
|
18
18
|
name,
|
|
19
19
|
href,
|
|
20
20
|
section,
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) HashiCorp, Inc.
|
|
3
|
-
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import classNames from 'classnames'
|
|
7
|
-
import Button from '@hashicorp/react-design-system-components/src/components/button'
|
|
8
|
-
import StandaloneLink from '@hashicorp/react-design-system-components/src/components/standalone-link'
|
|
9
|
-
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
|
|
10
|
-
import { IconArrowRight24 } from '@hashicorp/flight-icons/svg-react/arrow-right-24'
|
|
11
|
-
import type { ActionsProps } from './types'
|
|
12
|
-
import s from './style.module.css'
|
|
13
|
-
import { ComponentProps } from 'react'
|
|
14
|
-
|
|
15
|
-
const hasButton = (item) => item.type === 'button' || item.type === undefined
|
|
16
|
-
const hasStandaloneLink = (item) => item.type === 'standalone-link'
|
|
17
|
-
|
|
18
|
-
export default function Actions({
|
|
19
|
-
appearance = 'light',
|
|
20
|
-
layout = 'inline',
|
|
21
|
-
alignment = 'left',
|
|
22
|
-
theme = 'primary',
|
|
23
|
-
size = 'large',
|
|
24
|
-
ctas,
|
|
25
|
-
}: ActionsProps) {
|
|
26
|
-
if (ctas.length < 1 || ctas.length > 2) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`Actions expects at least 1 cta, no more than 2. You provided ${ctas.length}.`
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
let mixed = false
|
|
32
|
-
// If CTAs contain a button and standalone link
|
|
33
|
-
// increase the column gap
|
|
34
|
-
if (ctas.some(hasButton) && ctas.some(hasStandaloneLink)) {
|
|
35
|
-
mixed = true
|
|
36
|
-
}
|
|
37
|
-
return (
|
|
38
|
-
<div
|
|
39
|
-
className={classNames(
|
|
40
|
-
s.actions,
|
|
41
|
-
s[layout],
|
|
42
|
-
s[alignment],
|
|
43
|
-
mixed && s.mixed
|
|
44
|
-
)}
|
|
45
|
-
data-testid="actions"
|
|
46
|
-
>
|
|
47
|
-
{ctas.map((cta, index) => {
|
|
48
|
-
// The first CTA and second CTA should always
|
|
49
|
-
// have the `primary` and `secondary` variations respectively.
|
|
50
|
-
|
|
51
|
-
const standaloneLinkVariant: ComponentProps<
|
|
52
|
-
typeof StandaloneLink
|
|
53
|
-
>['color'] =
|
|
54
|
-
index === 0
|
|
55
|
-
? 'primary'
|
|
56
|
-
: appearance === 'dark'
|
|
57
|
-
? 'secondary-inverted'
|
|
58
|
-
: 'secondary'
|
|
59
|
-
const isStandaloneLink = cta?.type === 'standalone-link'
|
|
60
|
-
|
|
61
|
-
if (isStandaloneLink) {
|
|
62
|
-
return (
|
|
63
|
-
<StandaloneLink
|
|
64
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
65
|
-
key={index}
|
|
66
|
-
text={cta.title}
|
|
67
|
-
icon={
|
|
68
|
-
size === 'large' ? (
|
|
69
|
-
<IconArrowRight24
|
|
70
|
-
width="100%"
|
|
71
|
-
height="100%"
|
|
72
|
-
style={{ display: 'block' }}
|
|
73
|
-
/>
|
|
74
|
-
) : (
|
|
75
|
-
<IconArrowRight16
|
|
76
|
-
width="100%"
|
|
77
|
-
height="100%"
|
|
78
|
-
style={{ display: 'block' }}
|
|
79
|
-
/>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
iconPosition="trailing"
|
|
83
|
-
href={cta.href}
|
|
84
|
-
color={standaloneLinkVariant}
|
|
85
|
-
size={size}
|
|
86
|
-
onClick={cta?.onClick}
|
|
87
|
-
data-testid={`standaloneLink-${index}`}
|
|
88
|
-
/>
|
|
89
|
-
)
|
|
90
|
-
} else {
|
|
91
|
-
return (
|
|
92
|
-
<Button
|
|
93
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
94
|
-
key={index}
|
|
95
|
-
text={cta.title}
|
|
96
|
-
href={cta.href}
|
|
97
|
-
onClick={cta.onClick}
|
|
98
|
-
size={size}
|
|
99
|
-
color={theme}
|
|
100
|
-
data-testid={`button-${index}`}
|
|
101
|
-
/>
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
})}
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) HashiCorp, Inc.
|
|
3
|
-
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
.actions {
|
|
7
|
-
display: flex;
|
|
8
|
-
align-items: center;
|
|
9
|
-
flex-wrap: wrap;
|
|
10
|
-
gap: 16px 18px;
|
|
11
|
-
|
|
12
|
-
&.stacked {
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
|
|
15
|
-
&.left {
|
|
16
|
-
align-items: flex-start;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
&.center {
|
|
20
|
-
align-items: center;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
&.mixed {
|
|
25
|
-
column-gap: 24px;
|
|
26
|
-
row-gap: 32px;
|
|
27
|
-
}
|
|
28
|
-
}
|