@hashicorp/react-components 0.0.0-nightly.f7d3c1e → 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.
Files changed (162) hide show
  1. package/package.json +4 -8
  2. package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
  3. package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
  4. package/src/components/nav-panel/components/link-list/index.tsx +15 -9
  5. package/src/components/nav-panel/components/link-list/style.module.css +10 -10
  6. package/src/components/nav-panel/components/panel/column/style.module.css +2 -2
  7. package/src/components/nav-panel/components/panel/root/style.module.css +5 -5
  8. package/src/components/nav-panel/components/promo/index.tsx +15 -9
  9. package/src/components/nav-panel/components/promo/style.module.css +22 -12
  10. package/src/components/nav-panel/components/text/index.tsx +34 -0
  11. package/src/components/nav-panel/product-panel/index.tsx +30 -14
  12. package/src/components/nav-panel/product-panel/style.module.css +63 -36
  13. package/src/components/nav-panel/standard-panel/index.tsx +3 -0
  14. package/src/components/nav-panel/standard-panel/style.module.css +2 -2
  15. package/src/lib/get-localized-link-props/index.test.ts +30 -0
  16. package/src/lib/get-localized-link-props/index.ts +45 -0
  17. package/src/lib/parse-url/index.test.ts +40 -1
  18. package/src/lib/parse-url/index.ts +75 -9
  19. package/src/lib/send-posthog-event/index.ts +22 -0
  20. package/src/lib/supported-locales/index.ts +1 -0
  21. package/src/lib/track-nav-click-event/index.ts +4 -4
  22. package/src/lib/v/index.ts +4 -5
  23. package/src/components/actions/index.tsx +0 -107
  24. package/src/components/actions/style.module.css +0 -28
  25. package/src/components/actions/types.ts +0 -73
  26. package/src/components/call-to-action/index.tsx +0 -205
  27. package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
  28. package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
  29. package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
  30. package/src/components/call-to-action/styles/variant-links.module.css +0 -85
  31. package/src/components/card/README.md +0 -11
  32. package/src/components/card/card.tsx +0 -45
  33. package/src/components/card/index.test.tsx +0 -69
  34. package/src/components/card/index.tsx +0 -14
  35. package/src/components/card/newsroom-card/index.test.tsx +0 -39
  36. package/src/components/card/newsroom-card/index.tsx +0 -37
  37. package/src/components/card/newsroom-card/style.module.css +0 -12
  38. package/src/components/card/partner-card/index.test.tsx +0 -57
  39. package/src/components/card/partner-card/index.tsx +0 -69
  40. package/src/components/card/partner-card/style.module.css +0 -24
  41. package/src/components/card/person-card/index.test.tsx +0 -71
  42. package/src/components/card/person-card/index.tsx +0 -93
  43. package/src/components/card/person-card/style.module.css +0 -33
  44. package/src/components/card/primitives.tsx +0 -176
  45. package/src/components/card/promo-card/index.tsx +0 -51
  46. package/src/components/card/resource-card/index.test.tsx +0 -48
  47. package/src/components/card/resource-card/index.tsx +0 -37
  48. package/src/components/card/style.module.css +0 -145
  49. package/src/components/card/types.ts +0 -66
  50. package/src/components/card/unified-card/index.tsx +0 -64
  51. package/src/components/consent-manager/README.md +0 -73
  52. package/src/components/consent-manager/components/banner/banner.module.css +0 -23
  53. package/src/components/consent-manager/components/banner/banner.test.tsx +0 -31
  54. package/src/components/consent-manager/components/banner/banner.tsx +0 -73
  55. package/src/components/consent-manager/components/dialog/dialog.module.css +0 -145
  56. package/src/components/consent-manager/components/dialog/dialog.test.tsx +0 -194
  57. package/src/components/consent-manager/components/dialog/dialog.tsx +0 -339
  58. package/src/components/consent-manager/img/icn_close.tsx +0 -19
  59. package/src/components/consent-manager/index.test.tsx +0 -334
  60. package/src/components/consent-manager/index.tsx +0 -186
  61. package/src/components/consent-manager/loader/README.md +0 -29
  62. package/src/components/consent-manager/loader/categories.ts +0 -22
  63. package/src/components/consent-manager/loader/index.tsx +0 -94
  64. package/src/components/consent-manager/loader/presets/enterprise.ts +0 -12
  65. package/src/components/consent-manager/loader/presets/oss.ts +0 -10
  66. package/src/components/consent-manager/loader/services.ts +0 -45
  67. package/src/components/consent-manager/scripts/custom.tsx +0 -59
  68. package/src/components/consent-manager/scripts/segment.tsx +0 -87
  69. package/src/components/consent-manager/style.module.css +0 -9
  70. package/src/components/consent-manager/types.ts +0 -55
  71. package/src/components/consent-manager/util/cookies.test.ts +0 -75
  72. package/src/components/consent-manager/util/cookies.ts +0 -70
  73. package/src/components/consent-manager/util/integrations.test.ts +0 -115
  74. package/src/components/consent-manager/util/integrations.ts +0 -110
  75. package/src/components/intro/index.tsx +0 -82
  76. package/src/components/intro/style.module.css +0 -67
  77. package/src/components/intro/types.ts +0 -44
  78. package/src/components/marketo-form/CHANGELOG.md +0 -122
  79. package/src/components/marketo-form/README.md +0 -1
  80. package/src/components/marketo-form/contexts/FormMetadata.ts +0 -11
  81. package/src/components/marketo-form/docs.mdx +0 -197
  82. package/src/components/marketo-form/fixtures/index.ts +0 -185
  83. package/src/components/marketo-form/form/index.tsx +0 -369
  84. package/src/components/marketo-form/index.test.tsx +0 -320
  85. package/src/components/marketo-form/index.ts +0 -11
  86. package/src/components/marketo-form/package.original.json +0 -30
  87. package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
  88. package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
  89. package/src/components/marketo-form/partials/field/index.tsx +0 -90
  90. package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
  91. package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
  92. package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
  93. package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -21
  94. package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -37
  95. package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
  96. package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -18
  97. package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
  98. package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
  99. package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
  100. package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
  101. package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
  102. package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
  103. package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
  104. package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
  105. package/src/components/marketo-form/partials/label/index.tsx +0 -24
  106. package/src/components/marketo-form/partials/label/style.module.css +0 -12
  107. package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
  108. package/src/components/marketo-form/props.js +0 -66
  109. package/src/components/marketo-form/server/api-routes.ts +0 -109
  110. package/src/components/marketo-form/server/client.ts +0 -98
  111. package/src/components/marketo-form/server/index.ts +0 -8
  112. package/src/components/marketo-form/types.ts +0 -268
  113. package/src/components/marketo-form/utils.test.tsx +0 -103
  114. package/src/components/marketo-form/utils.ts +0 -449
  115. package/src/components/next-steps/index.tsx +0 -137
  116. package/src/components/next-steps/style.module.css +0 -270
  117. package/src/components/next-steps/types.ts +0 -63
  118. package/src/components/product-badge/index.test.tsx +0 -11
  119. package/src/components/product-badge/index.tsx +0 -42
  120. package/src/components/product-badge/style.module.css +0 -76
  121. package/src/components/products-used/index.tsx +0 -84
  122. package/src/components/products-used/style.module.css +0 -87
  123. package/src/components/products-used/types.ts +0 -31
  124. package/src/components/related-content/index.test.tsx +0 -74
  125. package/src/components/related-content/index.tsx +0 -56
  126. package/src/components/related-content/style.module.css +0 -89
  127. package/src/components/related-content/types.ts +0 -21
  128. package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
  129. package/src/components/subnav/helpers/traverse/index.ts +0 -42
  130. package/src/components/subnav/helpers/useHeightRef.ts +0 -42
  131. package/src/components/subnav/helpers/useNavRef.ts +0 -30
  132. package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
  133. package/src/components/subnav/helpers/useStuckRef.ts +0 -51
  134. package/src/components/subnav/index.tsx +0 -199
  135. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
  136. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
  137. package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
  138. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
  139. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
  140. package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -85
  141. package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
  142. package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
  143. package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
  144. package/src/components/subnav/partials/CtaLinks/index.tsx +0 -130
  145. package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
  146. package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
  147. package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -36
  148. package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -151
  149. package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -91
  150. package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -139
  151. package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -82
  152. package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
  153. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -9
  154. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -9
  155. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
  156. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
  157. package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
  158. package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
  159. package/src/components/subnav/partials/nav-item-text/style.module.css +0 -15
  160. package/src/components/subnav/style.module.css +0 -68
  161. package/src/components/subnav/subnav-consts.ts +0 -1
  162. package/src/components/subnav/theme.module.css +0 -50
@@ -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(--hdsplus-spacing-07) 0 0;
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(--hdsplus-spacing-05);
52
- padding-bottom: var(--hdsplus-spacing-05);
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(--hdsplus-spacing-04);
84
+ padding-bottom: var(--mds-spacing-04);
58
85
  color: var(--token-color-foreground-strong);
59
- font-weight: var(--font-weight-semi-bold);
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(--hdsplus-spacing-06);
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(--hdsplus-spacing-06);
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(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
86
- var(--hdsplus-spacing-04);
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(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
97
- var(--hdsplus-spacing-04);
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(--hdsplus-spacing-05);
117
- gap: var(--hdsplus-spacing-05);
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(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
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(--hdsplus-spacing-01);
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(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
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(--hdsplus-spacing-01);
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(--hdsplus-spacing-08);
156
- width: var(--hdsplus-spacing-08);
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(--hdsplus-spacing-08);
165
- width: var(--hdsplus-spacing-08);
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(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-09)
201
- var(--hdsplus-spacing-04);
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(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-09)
208
- var(--hdsplus-spacing-04);
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(--hdsplus-spacing-05);
245
+ gap: var(--mds-spacing-05);
219
246
  margin: 0;
220
- padding: var(--hdsplus-spacing-07) var(--hdsplus-spacing-04)
221
- var(--hdsplus-spacing-09) var(--hdsplus-spacing-07);
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(--hdsplus-spacing-07);
257
+ gap: var(--mds-spacing-07);
231
258
 
232
259
  @media (--dev-dot-viewport) {
233
- gap: var(--hdsplus-spacing-05);
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(--hdsplus-spacing-05);
267
+ gap: var(--mds-spacing-05);
241
268
 
242
269
  @media (--dev-dot-viewport) {
243
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
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(--hdsplus-spacing-01);
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(--hdsplus-spacing-08);
257
- width: var(--hdsplus-spacing-08);
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(--hdsplus-spacing-01);
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)
@@ -1,8 +1,8 @@
1
1
  .columns {
2
2
  display: flex;
3
- gap: var(--hdsplus-spacing-03);
3
+ gap: var(--mds-spacing-03);
4
4
  flex-direction: column;
5
- margin-top: var(--hdsplus-spacing-07);
5
+ margin-top: var(--mds-spacing-07);
6
6
 
7
7
  @media (--large) {
8
8
  flex-direction: row;
@@ -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)
@@ -27,4 +33,37 @@ describe('parseUrl', () => {
27
33
  )
28
34
  expect(url.href).toBe('/resources?type=case-study&products=terraform')
29
35
  })
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
+
42
+ it('leaves HashiCorp subdomain URLs intact', () => {
43
+ const url = parseUrl('https://developer.hashicorp.com/terraform')
44
+ expect(url.href).toBe('https://developer.hashicorp.com/terraform')
45
+ })
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
+ })
30
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
- throw new Error('url is required')
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
- throw new Error(`Invalid href: ${url}`)
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
- export const isInternal = (url) => {
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 va from '@vercel/analytics'
1
+ import sendPosthogEvent from '../send-posthog-event'
2
2
 
3
3
  /**
4
- * Pushes a 'Nav link click' custom event to Vercel Analytics.
5
- * A wrapper around the Vercel Analytics track method.
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
- va.track('Nav link click', {
17
+ sendPosthogEvent('Nav link click', {
18
18
  name,
19
19
  href,
20
20
  section,
@@ -3,9 +3,8 @@ import { vercelStegaSplit } from '@vercel/stega'
3
3
  /**
4
4
  * Takes in a Vercel Visual Editing string and returns the string without
5
5
  * hidden data.
6
+ *
6
7
  */
7
- export function v<VercelVEString extends string>(
8
- s: VercelVEString
9
- ): VercelVEString {
10
- return vercelStegaSplit(s).cleaned as VercelVEString
11
- }
8
+ export const vercelSplit = <VercelString>(
9
+ original: VercelString
10
+ ): VercelString => vercelStegaSplit(original as string).cleaned as VercelString
@@ -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
- }