@hashicorp/react-components 0.0.0-canary.43d0406 → 0.0.0-canary.484207e

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 +3 -3
  7. package/src/components/nav-panel/components/panel/root/style.module.css +6 -6
  8. package/src/components/nav-panel/components/promo/index.tsx +15 -9
  9. package/src/components/nav-panel/components/promo/style.module.css +28 -13
  10. package/src/components/nav-panel/components/text/index.tsx +34 -0
  11. package/src/components/nav-panel/product-panel/index.tsx +42 -27
  12. package/src/components/nav-panel/product-panel/style.module.css +103 -43
  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
@@ -1,8 +1,31 @@
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%;
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-06) 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,54 +109,58 @@
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);
87
- grid-auto-flow: column;
112
+ padding: var(--mds-spacing-06) var(--mds-spacing-05) var(--mds-spacing-04);
113
+ grid-auto-flow: row;
88
114
  grid-template-columns: repeat(2, 300px);
89
- grid-template-rows: repeat(var(--row-count-above-mobile, 1), max-content);
115
+ grid-template-rows: repeat(2, max-content);
90
116
  gap: 0;
91
117
  }
92
118
  }
93
119
 
94
120
  @media (--dev-dot-viewport) {
95
121
  @nest .hasSide & {
96
- padding: var(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
97
- var(--hdsplus-spacing-04);
98
- grid-auto-flow: column;
122
+ padding: var(--mds-spacing-06) var(--mds-spacing-05) var(--mds-spacing-04);
123
+ grid-auto-flow: row;
99
124
  grid-template-columns: repeat(2, 300px);
100
- grid-template-rows: repeat(var(--row-count-above-mobile, 1), max-content);
125
+ grid-template-rows: repeat(2, max-content);
101
126
  gap: 0;
102
127
  }
103
128
  }
129
+
130
+ & li:last-child {
131
+ & .productWrapper {
132
+ margin-bottom: 0;
133
+ }
134
+ }
104
135
  }
105
136
 
106
137
  /* NavProduct styles */
107
138
 
108
139
  .productWrapper {
109
140
  display: flex;
110
- margin-bottom: var(--hdsplus-spacing-05);
111
- gap: var(--hdsplus-spacing-05);
141
+ margin-bottom: var(--mds-spacing-05);
142
+ gap: var(--mds-spacing-05);
112
143
 
113
144
  @media (--large) {
114
145
  @nest .noSide & {
115
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
146
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
116
147
  margin-bottom: 0;
117
148
 
118
149
  &:hover {
119
150
  background-color: var(--token-color-surface-interactive-hover);
120
- border-radius: var(--hdsplus-spacing-01);
151
+ border-radius: var(--mds-spacing-01);
121
152
  }
122
153
  }
123
154
  }
124
155
 
125
156
  @media (--dev-dot-viewport) {
126
157
  @nest .hasSide & {
127
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
158
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
128
159
  margin-bottom: 0;
129
160
 
130
161
  &:hover {
131
162
  background-color: var(--token-color-surface-interactive-hover);
132
- border-radius: var(--hdsplus-spacing-01);
163
+ border-radius: var(--mds-spacing-01);
133
164
  }
134
165
  }
135
166
  }
@@ -146,8 +177,8 @@
146
177
  @nest .noSide & {
147
178
  & svg {
148
179
  @media (--large) {
149
- height: var(--hdsplus-spacing-08);
150
- width: var(--hdsplus-spacing-08);
180
+ height: var(--mds-spacing-08);
181
+ width: var(--mds-spacing-08);
151
182
  }
152
183
  }
153
184
  }
@@ -155,8 +186,8 @@
155
186
  @nest .hasSide & {
156
187
  & svg {
157
188
  @media (--dev-dot-viewport) {
158
- height: var(--hdsplus-spacing-08);
159
- width: var(--hdsplus-spacing-08);
189
+ height: var(--mds-spacing-08);
190
+ width: var(--mds-spacing-08);
160
191
  }
161
192
  }
162
193
  }
@@ -189,17 +220,29 @@
189
220
  justify-content: space-between;
190
221
  flex-direction: column;
191
222
 
223
+ @nest .topPromo & {
224
+ justify-content: flex-start;
225
+ }
226
+
192
227
  @media (--large) {
193
228
  @nest .noSide & {
194
- padding: var(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-05)
195
- var(--hdsplus-spacing-04);
229
+ padding: var(--mds-spacing-07) 0 var(--mds-spacing-09)
230
+ var(--mds-spacing-04);
231
+ }
232
+
233
+ @nest .topPromo & {
234
+ padding-bottom: var(--mds-spacing-04);
196
235
  }
197
236
  }
198
237
 
199
238
  @media (--dev-dot-viewport) {
200
239
  @nest .hasSide & {
201
- padding: var(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-05)
202
- var(--hdsplus-spacing-04);
240
+ padding: var(--mds-spacing-07) 0 var(--mds-spacing-09)
241
+ var(--mds-spacing-04);
242
+ }
243
+
244
+ @nest .topPromo & {
245
+ padding-bottom: var(--mds-spacing-04);
203
246
  }
204
247
  }
205
248
  }
@@ -209,10 +252,10 @@
209
252
  background: var(--token-color-surface-faint);
210
253
  display: flex;
211
254
  flex-direction: column;
212
- gap: var(--hdsplus-spacing-05);
255
+ gap: var(--mds-spacing-05);
213
256
  margin: 0;
214
- padding: var(--hdsplus-spacing-07) var(--hdsplus-spacing-04)
215
- var(--hdsplus-spacing-09) var(--hdsplus-spacing-07);
257
+ padding: var(--mds-spacing-07) var(--mds-spacing-04) var(--mds-spacing-09)
258
+ var(--mds-spacing-07);
216
259
  }
217
260
 
218
261
  .sidePanelItems {
@@ -221,26 +264,24 @@
221
264
  padding: 0;
222
265
  display: grid;
223
266
  grid-template-columns: 1fr;
224
- gap: var(--hdsplus-spacing-07);
267
+ gap: var(--mds-spacing-07);
225
268
 
226
269
  @media (--dev-dot-viewport) {
227
- gap: var(--hdsplus-spacing-05);
270
+ gap: var(--mds-spacing-05);
228
271
  }
229
272
  }
230
273
 
231
274
  /* Lifted from .productWrapper */
232
275
  .sidePanelItem {
233
276
  display: flex;
234
- margin-bottom: var(--hdsplus-spacing-05);
235
- gap: var(--hdsplus-spacing-05);
277
+ gap: var(--mds-spacing-05);
236
278
 
237
279
  @media (--dev-dot-viewport) {
238
- padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
239
- margin-bottom: 0;
280
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
240
281
 
241
282
  &:hover {
242
283
  background-color: var(--token-color-surface-interactive-hover);
243
- border-radius: var(--hdsplus-spacing-01);
284
+ border-radius: var(--mds-spacing-01);
244
285
  }
245
286
  }
246
287
  }
@@ -249,8 +290,8 @@
249
290
  packages/react-components/src/components/nav-panel/components/link-list/style.module.css */
250
291
  .iconBackground {
251
292
  background: var(--token-color-foreground-high-contrast);
252
- height: var(--hdsplus-spacing-08);
253
- width: var(--hdsplus-spacing-08);
293
+ height: var(--mds-spacing-08);
294
+ width: var(--mds-spacing-08);
254
295
  display: flex;
255
296
  align-items: center;
256
297
  justify-content: center;
@@ -276,13 +317,28 @@ packages/react-components/src/components/nav-panel/components/link-list/style.mo
276
317
 
277
318
  /* Branding exception classnames */
278
319
 
320
+ /* Allows switching to a dark-mode-specific color for Terraform */
321
+ .terraform {
322
+ & use {
323
+ fill: var(--token-color-terraform-brand);
324
+
325
+ @nest [data-theme='dark'] & {
326
+ fill: var(--token-color-terraform-foreground);
327
+ }
328
+ }
329
+
330
+ & path {
331
+ fill: inherit;
332
+ }
333
+ }
334
+
279
335
  /* Styles for HCP Vault mimic the spec for `product-square` icons,
280
336
  while allowing the symbol inside the icon border to change color
281
337
  based on the site's theme
282
338
  */
283
339
  .hcpVaultSecrets,
284
340
  .hcpVaultRadar {
285
- padding: var(--hdsplus-spacing-01);
341
+ padding: var(--mds-spacing-01);
286
342
  border: 2px solid var(--token-color-vault-brand);
287
343
  border-radius: 3px;
288
344
 
@@ -294,3 +350,7 @@ packages/react-components/src/components/nav-panel/components/link-list/style.mo
294
350
  fill: inherit;
295
351
  }
296
352
  }
353
+
354
+ .sidePanelText {
355
+ font-weight: var(--mds-typography-font-weight-semibold);
356
+ }
@@ -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,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