@hashicorp/react-components 0.0.0-nightly.a075e59 → 0.0.0-nightly.a120cb6

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 (158) hide show
  1. package/package.json +4 -8
  2. package/src/components/nav-panel/components/link-list/index.tsx +15 -9
  3. package/src/components/nav-panel/components/link-list/style.module.css +10 -10
  4. package/src/components/nav-panel/components/panel/column/style.module.css +3 -3
  5. package/src/components/nav-panel/components/panel/root/style.module.css +6 -6
  6. package/src/components/nav-panel/components/promo/index.tsx +12 -9
  7. package/src/components/nav-panel/components/promo/style.module.css +13 -12
  8. package/src/components/nav-panel/product-panel/index.tsx +25 -25
  9. package/src/components/nav-panel/product-panel/style.module.css +56 -41
  10. package/src/components/nav-panel/standard-panel/index.tsx +3 -0
  11. package/src/components/nav-panel/standard-panel/style.module.css +2 -2
  12. package/src/lib/get-localized-link-props/index.test.ts +30 -0
  13. package/src/lib/get-localized-link-props/index.ts +45 -0
  14. package/src/lib/parse-url/index.test.ts +40 -1
  15. package/src/lib/parse-url/index.ts +75 -9
  16. package/src/lib/supported-locales/index.ts +1 -0
  17. package/src/lib/track-nav-click-event/index.ts +4 -4
  18. package/src/lib/v/index.ts +4 -5
  19. package/src/components/actions/index.tsx +0 -107
  20. package/src/components/actions/style.module.css +0 -28
  21. package/src/components/actions/types.ts +0 -73
  22. package/src/components/call-to-action/index.tsx +0 -205
  23. package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
  24. package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
  25. package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
  26. package/src/components/call-to-action/styles/variant-links.module.css +0 -85
  27. package/src/components/card/README.md +0 -11
  28. package/src/components/card/card.tsx +0 -45
  29. package/src/components/card/index.test.tsx +0 -69
  30. package/src/components/card/index.tsx +0 -14
  31. package/src/components/card/newsroom-card/index.test.tsx +0 -39
  32. package/src/components/card/newsroom-card/index.tsx +0 -37
  33. package/src/components/card/newsroom-card/style.module.css +0 -12
  34. package/src/components/card/partner-card/index.test.tsx +0 -57
  35. package/src/components/card/partner-card/index.tsx +0 -69
  36. package/src/components/card/partner-card/style.module.css +0 -24
  37. package/src/components/card/person-card/index.test.tsx +0 -71
  38. package/src/components/card/person-card/index.tsx +0 -93
  39. package/src/components/card/person-card/style.module.css +0 -33
  40. package/src/components/card/primitives.tsx +0 -176
  41. package/src/components/card/promo-card/index.tsx +0 -51
  42. package/src/components/card/resource-card/index.test.tsx +0 -48
  43. package/src/components/card/resource-card/index.tsx +0 -37
  44. package/src/components/card/style.module.css +0 -145
  45. package/src/components/card/types.ts +0 -66
  46. package/src/components/card/unified-card/index.tsx +0 -64
  47. package/src/components/consent-manager/README.md +0 -73
  48. package/src/components/consent-manager/components/banner/banner.module.css +0 -23
  49. package/src/components/consent-manager/components/banner/banner.test.tsx +0 -31
  50. package/src/components/consent-manager/components/banner/banner.tsx +0 -73
  51. package/src/components/consent-manager/components/dialog/dialog.module.css +0 -145
  52. package/src/components/consent-manager/components/dialog/dialog.test.tsx +0 -194
  53. package/src/components/consent-manager/components/dialog/dialog.tsx +0 -339
  54. package/src/components/consent-manager/img/icn_close.tsx +0 -19
  55. package/src/components/consent-manager/index.test.tsx +0 -334
  56. package/src/components/consent-manager/index.tsx +0 -186
  57. package/src/components/consent-manager/loader/README.md +0 -29
  58. package/src/components/consent-manager/loader/categories.ts +0 -22
  59. package/src/components/consent-manager/loader/index.tsx +0 -94
  60. package/src/components/consent-manager/loader/presets/enterprise.ts +0 -12
  61. package/src/components/consent-manager/loader/presets/oss.ts +0 -10
  62. package/src/components/consent-manager/loader/services.ts +0 -45
  63. package/src/components/consent-manager/scripts/custom.tsx +0 -59
  64. package/src/components/consent-manager/scripts/segment.tsx +0 -87
  65. package/src/components/consent-manager/style.module.css +0 -9
  66. package/src/components/consent-manager/types.ts +0 -55
  67. package/src/components/consent-manager/util/cookies.test.ts +0 -75
  68. package/src/components/consent-manager/util/cookies.ts +0 -70
  69. package/src/components/consent-manager/util/integrations.test.ts +0 -115
  70. package/src/components/consent-manager/util/integrations.ts +0 -110
  71. package/src/components/intro/index.tsx +0 -82
  72. package/src/components/intro/style.module.css +0 -67
  73. package/src/components/intro/types.ts +0 -44
  74. package/src/components/marketo-form/CHANGELOG.md +0 -122
  75. package/src/components/marketo-form/README.md +0 -1
  76. package/src/components/marketo-form/contexts/FormMetadata.ts +0 -11
  77. package/src/components/marketo-form/docs.mdx +0 -197
  78. package/src/components/marketo-form/fixtures/index.ts +0 -185
  79. package/src/components/marketo-form/form/index.tsx +0 -369
  80. package/src/components/marketo-form/index.test.tsx +0 -320
  81. package/src/components/marketo-form/index.ts +0 -11
  82. package/src/components/marketo-form/package.original.json +0 -30
  83. package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
  84. package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
  85. package/src/components/marketo-form/partials/field/index.tsx +0 -90
  86. package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
  87. package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
  88. package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
  89. package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -21
  90. package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -37
  91. package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
  92. package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -18
  93. package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
  94. package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
  95. package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
  96. package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
  97. package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
  98. package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
  99. package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
  100. package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
  101. package/src/components/marketo-form/partials/label/index.tsx +0 -24
  102. package/src/components/marketo-form/partials/label/style.module.css +0 -12
  103. package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
  104. package/src/components/marketo-form/props.js +0 -66
  105. package/src/components/marketo-form/server/api-routes.ts +0 -109
  106. package/src/components/marketo-form/server/client.ts +0 -98
  107. package/src/components/marketo-form/server/index.ts +0 -8
  108. package/src/components/marketo-form/types.ts +0 -268
  109. package/src/components/marketo-form/utils.test.tsx +0 -103
  110. package/src/components/marketo-form/utils.ts +0 -449
  111. package/src/components/next-steps/index.tsx +0 -137
  112. package/src/components/next-steps/style.module.css +0 -270
  113. package/src/components/next-steps/types.ts +0 -63
  114. package/src/components/product-badge/index.test.tsx +0 -11
  115. package/src/components/product-badge/index.tsx +0 -42
  116. package/src/components/product-badge/style.module.css +0 -76
  117. package/src/components/products-used/index.tsx +0 -84
  118. package/src/components/products-used/style.module.css +0 -87
  119. package/src/components/products-used/types.ts +0 -31
  120. package/src/components/related-content/index.test.tsx +0 -74
  121. package/src/components/related-content/index.tsx +0 -56
  122. package/src/components/related-content/style.module.css +0 -89
  123. package/src/components/related-content/types.ts +0 -21
  124. package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
  125. package/src/components/subnav/helpers/traverse/index.ts +0 -42
  126. package/src/components/subnav/helpers/useHeightRef.ts +0 -42
  127. package/src/components/subnav/helpers/useNavRef.ts +0 -30
  128. package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
  129. package/src/components/subnav/helpers/useStuckRef.ts +0 -51
  130. package/src/components/subnav/index.tsx +0 -199
  131. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
  132. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
  133. package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
  134. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
  135. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
  136. package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -85
  137. package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
  138. package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
  139. package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
  140. package/src/components/subnav/partials/CtaLinks/index.tsx +0 -130
  141. package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
  142. package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
  143. package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -36
  144. package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -151
  145. package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -91
  146. package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -139
  147. package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -82
  148. package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
  149. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -9
  150. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -9
  151. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
  152. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
  153. package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
  154. package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
  155. package/src/components/subnav/partials/nav-item-text/style.module.css +0 -15
  156. package/src/components/subnav/style.module.css +0 -68
  157. package/src/components/subnav/subnav-consts.ts +0 -1
  158. package/src/components/subnav/theme.module.css +0 -50
@@ -1,4 +1,4 @@
1
- @custom-media --dev-dot-viewport (min-width: 1230px);
1
+ @custom-media --dev-dot-viewport (min-width: 924px);
2
2
 
3
3
  .productPanel {
4
4
  height: 100%;
@@ -26,7 +26,7 @@
26
26
  list-style: none;
27
27
  padding: 0;
28
28
  display: grid;
29
- margin: var(--hdsplus-spacing-06) 0 0;
29
+ margin: var(--mds-spacing-07) 0 0;
30
30
 
31
31
  @media (--large) {
32
32
  @nest .noSide & {
@@ -48,27 +48,27 @@
48
48
 
49
49
  &:first-child {
50
50
  border-bottom: 1px solid var(--token-color-border-primary);
51
- margin-bottom: var(--hdsplus-spacing-05);
52
- padding-bottom: var(--hdsplus-spacing-05);
51
+ margin-bottom: var(--mds-spacing-05);
52
+ padding-bottom: var(--mds-spacing-05);
53
53
  }
54
54
  }
55
55
 
56
56
  .eyebrow {
57
- padding-bottom: var(--hdsplus-spacing-04);
57
+ padding-bottom: var(--mds-spacing-04);
58
58
  color: var(--token-color-foreground-strong);
59
- font-weight: var(--font-weight-semi-bold);
59
+ font-weight: var(--mds-typography-font-weight-semibold);
60
60
 
61
61
  @media (--large) {
62
62
  @nest .noSide & {
63
63
  padding-bottom: 0;
64
- padding-left: var(--hdsplus-spacing-06);
64
+ padding-left: var(--mds-spacing-06);
65
65
  }
66
66
  }
67
67
 
68
68
  @media (--dev-dot-viewport) {
69
69
  @nest .hasSide & {
70
70
  padding-bottom: 0;
71
- padding-left: var(--hdsplus-spacing-06);
71
+ padding-left: var(--mds-spacing-06);
72
72
  }
73
73
  }
74
74
  }
@@ -82,22 +82,22 @@
82
82
 
83
83
  @media (--large) {
84
84
  @nest .noSide & {
85
- padding: var(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
86
- var(--hdsplus-spacing-04);
87
- grid-auto-flow: column;
85
+ padding: var(--mds-spacing-06) var(--mds-spacing-05)
86
+ var(--mds-spacing-04);
87
+ grid-auto-flow: row;
88
88
  grid-template-columns: repeat(2, 300px);
89
- grid-template-rows: repeat(var(--row-count-above-mobile, 1), max-content);
89
+ grid-template-rows: repeat(2, max-content);
90
90
  gap: 0;
91
91
  }
92
92
  }
93
93
 
94
94
  @media (--dev-dot-viewport) {
95
95
  @nest .hasSide & {
96
- padding: var(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
97
- var(--hdsplus-spacing-04);
98
- grid-auto-flow: column;
96
+ padding: var(--mds-spacing-06) var(--mds-spacing-05)
97
+ var(--mds-spacing-04);
98
+ grid-auto-flow: row;
99
99
  grid-template-columns: repeat(2, 300px);
100
- grid-template-rows: repeat(var(--row-count-above-mobile, 1), max-content);
100
+ grid-template-rows: repeat(2, max-content);
101
101
  gap: 0;
102
102
  }
103
103
  }
@@ -113,29 +113,29 @@
113
113
 
114
114
  .productWrapper {
115
115
  display: flex;
116
- margin-bottom: var(--hdsplus-spacing-05);
117
- gap: var(--hdsplus-spacing-05);
116
+ margin-bottom: var(--mds-spacing-05);
117
+ gap: var(--mds-spacing-05);
118
118
 
119
119
  @media (--large) {
120
120
  @nest .noSide & {
121
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
121
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
122
122
  margin-bottom: 0;
123
123
 
124
124
  &:hover {
125
125
  background-color: var(--token-color-surface-interactive-hover);
126
- border-radius: var(--hdsplus-spacing-01);
126
+ border-radius: var(--mds-spacing-01);
127
127
  }
128
128
  }
129
129
  }
130
130
 
131
131
  @media (--dev-dot-viewport) {
132
132
  @nest .hasSide & {
133
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
133
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
134
134
  margin-bottom: 0;
135
135
 
136
136
  &:hover {
137
137
  background-color: var(--token-color-surface-interactive-hover);
138
- border-radius: var(--hdsplus-spacing-01);
138
+ border-radius: var(--mds-spacing-01);
139
139
  }
140
140
  }
141
141
  }
@@ -152,8 +152,8 @@
152
152
  @nest .noSide & {
153
153
  & svg {
154
154
  @media (--large) {
155
- height: var(--hdsplus-spacing-08);
156
- width: var(--hdsplus-spacing-08);
155
+ height: var(--mds-spacing-08);
156
+ width: var(--mds-spacing-08);
157
157
  }
158
158
  }
159
159
  }
@@ -161,8 +161,8 @@
161
161
  @nest .hasSide & {
162
162
  & svg {
163
163
  @media (--dev-dot-viewport) {
164
- height: var(--hdsplus-spacing-08);
165
- width: var(--hdsplus-spacing-08);
164
+ height: var(--mds-spacing-08);
165
+ width: var(--mds-spacing-08);
166
166
  }
167
167
  }
168
168
  }
@@ -197,15 +197,15 @@
197
197
 
198
198
  @media (--large) {
199
199
  @nest .noSide & {
200
- padding: var(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-05)
201
- var(--hdsplus-spacing-04);
200
+ padding: var(--mds-spacing-07) 0 var(--mds-spacing-09)
201
+ var(--mds-spacing-04);
202
202
  }
203
203
  }
204
204
 
205
205
  @media (--dev-dot-viewport) {
206
206
  @nest .hasSide & {
207
- padding: var(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-05)
208
- var(--hdsplus-spacing-04);
207
+ padding: var(--mds-spacing-07) 0 var(--mds-spacing-09)
208
+ var(--mds-spacing-04);
209
209
  }
210
210
  }
211
211
  }
@@ -215,10 +215,10 @@
215
215
  background: var(--token-color-surface-faint);
216
216
  display: flex;
217
217
  flex-direction: column;
218
- gap: var(--hdsplus-spacing-05);
218
+ gap: var(--mds-spacing-05);
219
219
  margin: 0;
220
- padding: var(--hdsplus-spacing-07) var(--hdsplus-spacing-04)
221
- var(--hdsplus-spacing-09) var(--hdsplus-spacing-07);
220
+ padding: var(--mds-spacing-07) var(--mds-spacing-04)
221
+ var(--mds-spacing-09) var(--mds-spacing-07);
222
222
  }
223
223
 
224
224
  .sidePanelItems {
@@ -227,24 +227,24 @@
227
227
  padding: 0;
228
228
  display: grid;
229
229
  grid-template-columns: 1fr;
230
- gap: var(--hdsplus-spacing-07);
230
+ gap: var(--mds-spacing-07);
231
231
 
232
232
  @media (--dev-dot-viewport) {
233
- gap: var(--hdsplus-spacing-05);
233
+ gap: var(--mds-spacing-05);
234
234
  }
235
235
  }
236
236
 
237
237
  /* Lifted from .productWrapper */
238
238
  .sidePanelItem {
239
239
  display: flex;
240
- gap: var(--hdsplus-spacing-05);
240
+ gap: var(--mds-spacing-05);
241
241
 
242
242
  @media (--dev-dot-viewport) {
243
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
243
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
244
244
 
245
245
  &:hover {
246
246
  background-color: var(--token-color-surface-interactive-hover);
247
- border-radius: var(--hdsplus-spacing-01);
247
+ border-radius: var(--mds-spacing-01);
248
248
  }
249
249
  }
250
250
  }
@@ -253,8 +253,8 @@
253
253
  packages/react-components/src/components/nav-panel/components/link-list/style.module.css */
254
254
  .iconBackground {
255
255
  background: var(--token-color-foreground-high-contrast);
256
- height: var(--hdsplus-spacing-08);
257
- width: var(--hdsplus-spacing-08);
256
+ height: var(--mds-spacing-08);
257
+ width: var(--mds-spacing-08);
258
258
  display: flex;
259
259
  align-items: center;
260
260
  justify-content: center;
@@ -280,13 +280,28 @@ packages/react-components/src/components/nav-panel/components/link-list/style.mo
280
280
 
281
281
  /* Branding exception classnames */
282
282
 
283
+ /* Allows switching to a dark-mode-specific color for Terraform */
284
+ .terraform {
285
+ & use {
286
+ fill: var(--token-color-terraform-brand);
287
+
288
+ @nest [data-theme='dark'] & {
289
+ fill: var(--token-color-terraform-foreground);
290
+ }
291
+ }
292
+
293
+ & path {
294
+ fill: inherit;
295
+ }
296
+ }
297
+
283
298
  /* Styles for HCP Vault mimic the spec for `product-square` icons,
284
299
  while allowing the symbol inside the icon border to change color
285
300
  based on the site's theme
286
301
  */
287
302
  .hcpVaultSecrets,
288
303
  .hcpVaultRadar {
289
- padding: var(--hdsplus-spacing-01);
304
+ padding: var(--mds-spacing-01);
290
305
  border: 2px solid var(--token-color-vault-brand);
291
306
  border-radius: 3px;
292
307
 
@@ -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-06);
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 @@
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 '@web/utils/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
- }
@@ -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
- }