@hashicorp/react-components 0.0.0-nightly.8df4a87 → 0.0.0-nightly.928c674

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 (170) hide show
  1. package/.eslintrc.js +1 -2
  2. package/.stylelintrc.js +1 -1
  3. package/README.md +1 -1
  4. package/package.json +9 -10
  5. package/postcss.config.js +1 -1
  6. package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
  7. package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
  8. package/src/components/nav-panel/components/link/index.tsx +127 -0
  9. package/src/components/nav-panel/components/link/style.module.css +64 -0
  10. package/src/components/nav-panel/components/link-list/index.tsx +13 -64
  11. package/src/components/nav-panel/components/link-list/style.module.css +1 -50
  12. package/src/components/nav-panel/components/panel/column/index.tsx +17 -2
  13. package/src/components/nav-panel/components/panel/column/style.module.css +13 -3
  14. package/src/components/nav-panel/components/panel/root/index.tsx +13 -2
  15. package/src/components/nav-panel/components/panel/root/style.module.css +20 -5
  16. package/src/components/nav-panel/components/promo/index.tsx +46 -19
  17. package/src/components/nav-panel/components/promo/style.module.css +59 -23
  18. package/src/components/nav-panel/components/text/index.tsx +34 -0
  19. package/src/components/nav-panel/product-panel/index.tsx +146 -32
  20. package/src/components/nav-panel/product-panel/style.module.css +321 -26
  21. package/src/components/nav-panel/standard-panel/index.tsx +4 -1
  22. package/src/components/nav-panel/standard-panel/style.module.css +2 -2
  23. package/src/lib/get-localized-link-props/index.test.ts +30 -0
  24. package/src/lib/get-localized-link-props/index.ts +45 -0
  25. package/src/lib/parse-url/index.test.ts +69 -0
  26. package/src/lib/parse-url/index.ts +97 -0
  27. package/src/lib/send-posthog-event/index.ts +22 -0
  28. package/src/lib/supported-locales/index.ts +10 -0
  29. package/src/lib/track-nav-click-event/index.ts +22 -0
  30. package/src/lib/v/index.ts +10 -0
  31. package/src/components/actions/index.tsx +0 -107
  32. package/src/components/actions/style.module.css +0 -28
  33. package/src/components/actions/types.ts +0 -73
  34. package/src/components/call-to-action/index.tsx +0 -205
  35. package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
  36. package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
  37. package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
  38. package/src/components/call-to-action/styles/variant-links.module.css +0 -85
  39. package/src/components/card/README.md +0 -11
  40. package/src/components/card/card.tsx +0 -45
  41. package/src/components/card/index.test.tsx +0 -69
  42. package/src/components/card/index.tsx +0 -14
  43. package/src/components/card/newsroom-card/index.test.tsx +0 -39
  44. package/src/components/card/newsroom-card/index.tsx +0 -37
  45. package/src/components/card/newsroom-card/style.module.css +0 -12
  46. package/src/components/card/partner-card/index.test.tsx +0 -57
  47. package/src/components/card/partner-card/index.tsx +0 -69
  48. package/src/components/card/partner-card/style.module.css +0 -24
  49. package/src/components/card/person-card/index.test.tsx +0 -71
  50. package/src/components/card/person-card/index.tsx +0 -93
  51. package/src/components/card/person-card/style.module.css +0 -33
  52. package/src/components/card/primitives.tsx +0 -176
  53. package/src/components/card/promo-card/index.tsx +0 -51
  54. package/src/components/card/resource-card/index.test.tsx +0 -48
  55. package/src/components/card/resource-card/index.tsx +0 -37
  56. package/src/components/card/style.module.css +0 -145
  57. package/src/components/card/types.ts +0 -66
  58. package/src/components/card/unified-card/index.tsx +0 -64
  59. package/src/components/consent-manager/README.md +0 -73
  60. package/src/components/consent-manager/components/banner/banner.module.css +0 -23
  61. package/src/components/consent-manager/components/banner/banner.test.tsx +0 -31
  62. package/src/components/consent-manager/components/banner/banner.tsx +0 -73
  63. package/src/components/consent-manager/components/dialog/dialog.module.css +0 -145
  64. package/src/components/consent-manager/components/dialog/dialog.test.tsx +0 -194
  65. package/src/components/consent-manager/components/dialog/dialog.tsx +0 -339
  66. package/src/components/consent-manager/img/icn_close.tsx +0 -19
  67. package/src/components/consent-manager/index.test.tsx +0 -334
  68. package/src/components/consent-manager/index.tsx +0 -186
  69. package/src/components/consent-manager/loader/README.md +0 -29
  70. package/src/components/consent-manager/loader/categories.ts +0 -22
  71. package/src/components/consent-manager/loader/index.tsx +0 -94
  72. package/src/components/consent-manager/loader/presets/enterprise.ts +0 -12
  73. package/src/components/consent-manager/loader/presets/oss.ts +0 -10
  74. package/src/components/consent-manager/loader/services.ts +0 -45
  75. package/src/components/consent-manager/scripts/custom.tsx +0 -59
  76. package/src/components/consent-manager/scripts/segment.tsx +0 -87
  77. package/src/components/consent-manager/style.module.css +0 -9
  78. package/src/components/consent-manager/types.ts +0 -55
  79. package/src/components/consent-manager/util/cookies.test.ts +0 -75
  80. package/src/components/consent-manager/util/cookies.ts +0 -70
  81. package/src/components/consent-manager/util/integrations.test.ts +0 -115
  82. package/src/components/consent-manager/util/integrations.ts +0 -110
  83. package/src/components/intro/index.tsx +0 -82
  84. package/src/components/intro/style.module.css +0 -67
  85. package/src/components/intro/types.ts +0 -44
  86. package/src/components/marketo-form/CHANGELOG.md +0 -122
  87. package/src/components/marketo-form/README.md +0 -1
  88. package/src/components/marketo-form/contexts/FormMetadata.ts +0 -11
  89. package/src/components/marketo-form/docs.mdx +0 -197
  90. package/src/components/marketo-form/fixtures/index.ts +0 -185
  91. package/src/components/marketo-form/form/index.tsx +0 -369
  92. package/src/components/marketo-form/index.test.tsx +0 -320
  93. package/src/components/marketo-form/index.ts +0 -11
  94. package/src/components/marketo-form/package.original.json +0 -30
  95. package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
  96. package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
  97. package/src/components/marketo-form/partials/field/index.tsx +0 -90
  98. package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
  99. package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
  100. package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
  101. package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -21
  102. package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -37
  103. package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
  104. package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -6
  105. package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
  106. package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
  107. package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
  108. package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
  109. package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
  110. package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
  111. package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
  112. package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
  113. package/src/components/marketo-form/partials/label/index.tsx +0 -24
  114. package/src/components/marketo-form/partials/label/style.module.css +0 -12
  115. package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
  116. package/src/components/marketo-form/props.js +0 -66
  117. package/src/components/marketo-form/server/api-routes.ts +0 -109
  118. package/src/components/marketo-form/server/client.ts +0 -98
  119. package/src/components/marketo-form/server/index.ts +0 -8
  120. package/src/components/marketo-form/types.ts +0 -268
  121. package/src/components/marketo-form/utils.test.tsx +0 -103
  122. package/src/components/marketo-form/utils.ts +0 -445
  123. package/src/components/next-steps/index.tsx +0 -143
  124. package/src/components/next-steps/style.module.css +0 -270
  125. package/src/components/next-steps/types.ts +0 -63
  126. package/src/components/product-badge/index.test.tsx +0 -11
  127. package/src/components/product-badge/index.tsx +0 -42
  128. package/src/components/product-badge/style.module.css +0 -76
  129. package/src/components/products-used/index.tsx +0 -84
  130. package/src/components/products-used/style.module.css +0 -87
  131. package/src/components/products-used/types.ts +0 -31
  132. package/src/components/related-content/index.test.tsx +0 -74
  133. package/src/components/related-content/index.tsx +0 -56
  134. package/src/components/related-content/style.module.css +0 -89
  135. package/src/components/related-content/types.ts +0 -21
  136. package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
  137. package/src/components/subnav/helpers/traverse/index.ts +0 -42
  138. package/src/components/subnav/helpers/useHeightRef.ts +0 -42
  139. package/src/components/subnav/helpers/useNavRef.ts +0 -30
  140. package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
  141. package/src/components/subnav/helpers/useStuckRef.ts +0 -51
  142. package/src/components/subnav/index.tsx +0 -199
  143. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
  144. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
  145. package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
  146. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
  147. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
  148. package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -85
  149. package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
  150. package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
  151. package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
  152. package/src/components/subnav/partials/CtaLinks/index.tsx +0 -130
  153. package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
  154. package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
  155. package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -36
  156. package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -151
  157. package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -91
  158. package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -139
  159. package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -82
  160. package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
  161. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -9
  162. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -9
  163. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
  164. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
  165. package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
  166. package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
  167. package/src/components/subnav/partials/nav-item-text/style.module.css +0 -15
  168. package/src/components/subnav/style.module.css +0 -68
  169. package/src/components/subnav/subnav-consts.ts +0 -1
  170. package/src/components/subnav/theme.module.css +0 -50
@@ -1,11 +1,70 @@
1
+ @custom-media --dev-dot-viewport (min-width: 924px);
2
+
3
+ .productPanel {
4
+ height: 100%;
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
+
29
+ @media (--large) {
30
+ &.noSide {
31
+ display: grid;
32
+ grid-template-rows: 1fr;
33
+ grid-template-columns: max-content max-content;
34
+ padding: 0;
35
+ }
36
+ }
37
+
38
+ @media (--dev-dot-viewport) {
39
+ &.hasSide {
40
+ display: grid;
41
+ grid-template-rows: 1fr;
42
+ grid-template-columns: max-content max-content;
43
+ padding: 0;
44
+ }
45
+ }
46
+ }
47
+
1
48
  .productCategories {
2
49
  list-style: none;
3
50
  padding: 0;
4
51
  display: grid;
5
- 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
+ }
6
57
 
7
58
  @media (--large) {
8
- margin: 0;
59
+ @nest .noSide & {
60
+ margin: 0;
61
+ }
62
+ }
63
+
64
+ @media (--dev-dot-viewport) {
65
+ @nest .hasSide & {
66
+ margin: 0;
67
+ }
9
68
  }
10
69
  }
11
70
 
@@ -15,19 +74,29 @@
15
74
  min-width: 160px;
16
75
 
17
76
  &:first-child {
18
- border-bottom: 1px solid var(--token-color-border-primary);
19
- margin-bottom: var(--hdsplus-spacing-05);
20
- padding-bottom: var(--hdsplus-spacing-05);
77
+ border-bottom: 1px solid var(--mds-color-border-primary);
78
+ margin-bottom: var(--mds-spacing-05);
79
+ padding-bottom: var(--mds-spacing-05);
21
80
  }
22
81
  }
23
82
 
24
83
  .eyebrow {
25
- padding-bottom: var(--hdsplus-spacing-04);
26
- color: var(--token-color-foreground-strong);
27
- font-weight: var(--font-weight-semi-bold);
84
+ padding-bottom: var(--mds-spacing-04);
85
+ color: var(--mds-color-foreground-strong);
86
+ font-weight: var(--mds-typography-font-weight-semibold);
28
87
 
29
88
  @media (--large) {
30
- padding: 0 0 var(--hdsplus-spacing-01) var(--hdsplus-spacing-04);
89
+ @nest .noSide & {
90
+ padding-bottom: 0;
91
+ padding-left: var(--mds-spacing-06);
92
+ }
93
+ }
94
+
95
+ @media (--dev-dot-viewport) {
96
+ @nest .hasSide & {
97
+ padding-bottom: 0;
98
+ padding-left: var(--mds-spacing-06);
99
+ }
31
100
  }
32
101
  }
33
102
 
@@ -39,10 +108,29 @@
39
108
  padding: 0;
40
109
 
41
110
  @media (--large) {
42
- grid-auto-flow: row;
43
- grid-template-columns: repeat(2, 300px);
44
- grid-template-rows: repeat(2, max-content);
45
- gap: 0;
111
+ @nest .noSide & {
112
+ padding: var(--mds-spacing-06) var(--mds-spacing-05) var(--mds-spacing-04);
113
+ grid-auto-flow: row;
114
+ grid-template-columns: repeat(2, 300px);
115
+ grid-template-rows: repeat(2, max-content);
116
+ gap: 0;
117
+ }
118
+ }
119
+
120
+ @media (--dev-dot-viewport) {
121
+ @nest .hasSide & {
122
+ padding: var(--mds-spacing-06) var(--mds-spacing-05) var(--mds-spacing-04);
123
+ grid-auto-flow: row;
124
+ grid-template-columns: repeat(2, 300px);
125
+ grid-template-rows: repeat(2, max-content);
126
+ gap: 0;
127
+ }
128
+ }
129
+
130
+ & li:last-child {
131
+ & .productWrapper {
132
+ margin-bottom: 0;
133
+ }
46
134
  }
47
135
  }
48
136
 
@@ -50,16 +138,30 @@
50
138
 
51
139
  .productWrapper {
52
140
  display: flex;
53
- margin-bottom: var(--hdsplus-spacing-05);
54
- gap: var(--hdsplus-spacing-05);
141
+ margin-bottom: var(--mds-spacing-05);
142
+ gap: var(--mds-spacing-05);
55
143
 
56
144
  @media (--large) {
57
- padding: var(--hdsplus-spacing-05);
58
- margin-bottom: 0;
145
+ @nest .noSide & {
146
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
147
+ margin-bottom: 0;
59
148
 
60
- &:hover {
61
- background-color: var(--token-color-surface-interactive-hover);
62
- border-radius: var(--hdsplus-spacing-01);
149
+ &:hover {
150
+ background-color: var(--mds-color-surface-interactive-hover);
151
+ border-radius: var(--mds-spacing-01);
152
+ }
153
+ }
154
+ }
155
+
156
+ @media (--dev-dot-viewport) {
157
+ @nest .hasSide & {
158
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
159
+ margin-bottom: 0;
160
+
161
+ &:hover {
162
+ background-color: var(--mds-color-surface-interactive-hover);
163
+ border-radius: var(--mds-spacing-01);
164
+ }
63
165
  }
64
166
  }
65
167
  }
@@ -70,10 +172,23 @@
70
172
  & svg {
71
173
  height: 42px;
72
174
  width: 42px;
175
+ }
73
176
 
74
- @media (--large) {
75
- height: var(--hdsplus-spacing-08);
76
- width: var(--hdsplus-spacing-08);
177
+ @nest .noSide & {
178
+ & svg {
179
+ @media (--large) {
180
+ height: var(--mds-spacing-08);
181
+ width: var(--mds-spacing-08);
182
+ }
183
+ }
184
+ }
185
+
186
+ @nest .hasSide & {
187
+ & svg {
188
+ @media (--dev-dot-viewport) {
189
+ height: var(--mds-spacing-08);
190
+ width: var(--mds-spacing-08);
191
+ }
77
192
  }
78
193
  }
79
194
  }
@@ -81,7 +196,7 @@
81
196
  .productTextContent {
82
197
  display: flex;
83
198
  flex-direction: column;
84
- color: var(--token-color-foreground-strong);
199
+ color: var(--mds-color-foreground-strong);
85
200
  }
86
201
 
87
202
  .productDesc {
@@ -89,12 +204,192 @@
89
204
  font-size: 14px;
90
205
  line-height: 20px;
91
206
  letter-spacing: 0.0125em;
92
- color: var(--token-color-foreground-faint);
207
+ color: var(--mds-color-foreground-faint);
93
208
  }
94
209
 
95
210
  .focusIndicator {
96
211
  &:focus {
97
- outline: 2px solid var(--token-color-focus-action-internal);
212
+ outline: 2px solid var(--mds-color-focus-action-internal);
98
213
  outline-style: auto;
99
214
  }
100
215
  }
216
+
217
+ .mainPanel {
218
+ height: 100%;
219
+ display: flex;
220
+ justify-content: space-between;
221
+ flex-direction: column;
222
+
223
+ @nest .topPromo & {
224
+ justify-content: flex-start;
225
+ }
226
+
227
+ @media (--large) {
228
+ @nest .noSide & {
229
+ padding: var(--mds-spacing-05) 0 var(--mds-spacing-05)
230
+ var(--mds-spacing-04);
231
+ }
232
+
233
+ @nest .topPromo & {
234
+ padding-bottom: var(--mds-spacing-04);
235
+ }
236
+ }
237
+
238
+ @media (--dev-dot-viewport) {
239
+ @nest .hasSide & {
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);
246
+ }
247
+ }
248
+ }
249
+
250
+ .sidePanel {
251
+ height: 100%;
252
+ background: var(--mds-color-surface-faint);
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: var(--mds-spacing-05);
256
+ margin: 0;
257
+ padding: var(--mds-spacing-07) var(--mds-spacing-04) var(--mds-spacing-09)
258
+ var(--mds-spacing-07);
259
+ }
260
+
261
+ .sidePanelItems {
262
+ list-style: none;
263
+ margin: 0;
264
+ padding: 0;
265
+ display: grid;
266
+ grid-template-columns: 1fr;
267
+ gap: var(--mds-spacing-07);
268
+
269
+ @media (--dev-dot-viewport) {
270
+ gap: var(--mds-spacing-05);
271
+ }
272
+ }
273
+
274
+ /* Lifted from .productWrapper */
275
+ .sidePanelItem {
276
+ display: flex;
277
+ gap: var(--mds-spacing-05);
278
+
279
+ @media (--dev-dot-viewport) {
280
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
281
+
282
+ &:hover {
283
+ background-color: var(--mds-color-surface-interactive-hover);
284
+ border-radius: var(--mds-spacing-01);
285
+ }
286
+ }
287
+ }
288
+
289
+ /* Lifted from:
290
+ packages/react-components/src/components/nav-panel/components/link-list/style.module.css */
291
+ .iconBackground {
292
+ background: var(--mds-color-foreground-high-contrast);
293
+ height: var(--mds-spacing-08);
294
+ width: var(--mds-spacing-08);
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ border-radius: 3px;
299
+ flex: none;
300
+ margin-top: 3px;
301
+ border: 1px solid var(--mds-color-border-primary);
302
+ }
303
+
304
+ /* Lifted from .productTextContent */
305
+ .sidePanelItemContent {
306
+ display: flex;
307
+ flex-direction: column;
308
+ color: var(--mds-color-foreground-strong);
309
+ }
310
+
311
+ .sidePanelItemDesc {
312
+ font-weight: 400;
313
+ font-size: 13px;
314
+ line-height: 18px;
315
+ color: var(--mds-color-foreground-primary);
316
+ }
317
+
318
+ /* Branding exception classnames */
319
+
320
+ /* Allows switching to a dark-mode-specific color for Terraform */
321
+ .terraform {
322
+ & use {
323
+ fill: var(--mds-color-terraform-brand);
324
+
325
+ @nest [data-theme='dark'] & {
326
+ fill: var(--mds-color-terraform-foreground);
327
+ }
328
+ }
329
+
330
+ & path {
331
+ fill: inherit;
332
+ }
333
+ }
334
+
335
+ /* Styles for HCP Vault mimic the spec for `product-square` icons,
336
+ while allowing the symbol inside the icon border to change color
337
+ based on the site's theme
338
+ */
339
+ .hcpVaultSecrets,
340
+ .hcpVaultRadar {
341
+ padding: var(--mds-spacing-01);
342
+ border: 2px solid var(--mds-color-vault-brand);
343
+ border-radius: 3px;
344
+
345
+ & use {
346
+ fill: var(--mds-color-foreground-strong);
347
+ }
348
+
349
+ & path {
350
+ fill: inherit;
351
+ }
352
+ }
353
+
354
+ .sidePanelText {
355
+ font-weight: var(--mds-typography-font-weight-semibold);
356
+ }
357
+
358
+ .productCategoryHasGroup {
359
+ & .productList {
360
+ padding-left: 0;
361
+ padding-right: 0;
362
+ padding-top: 0;
363
+ }
364
+
365
+ & .productWrapper {
366
+ gap: var(--mds-spacing-04);
367
+
368
+ @media (--small) {
369
+ padding: var(--mds-spacing-03) var(--mds-spacing-05);
370
+ }
371
+ }
372
+
373
+ & .productLogo {
374
+ margin-top: 3px;
375
+ height: 32px;
376
+ width: 32px;
377
+ min-width: 32px;
378
+
379
+ & svg {
380
+ width: 100%;
381
+ height: 100%;
382
+ }
383
+ }
384
+
385
+ & .terraform {
386
+ width: 100%;
387
+ height: 100%;
388
+ }
389
+ }
390
+
391
+ .productGroupLink {
392
+ min-width: 100%;
393
+ max-width: 100%;
394
+ margin-bottom: var(--mds-spacing-03);
395
+ }
@@ -1,4 +1,4 @@
1
- import { trackNavClickEvent } from '../../../../../../apps/www/layouts/standard/nav'
1
+ import { trackNavClickEvent } from '../../../lib/track-nav-click-event'
2
2
  import * as NavPanel from '../components/panel'
3
3
  import LinkList, { type LinkListProps } from '../components/link-list'
4
4
  import Promo, { type PromoProps } from '../components/promo'
@@ -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
+ }
@@ -0,0 +1,69 @@
1
+ import { parseUrl, hasHashiCorpDestination } from '.'
2
+
3
+ describe('parseUrl', () => {
4
+ it('detects hashicorp.com URLs as internal', () => {
5
+ const url = parseUrl('https://hashicorp.com/example')
6
+ expect(url.href).toBe('/example')
7
+ expect(url.linkType).toBe('inbound')
8
+ })
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
+
16
+ it('detects URLs not containing hashicorp.com as external', () => {
17
+ const externalUrl = 'https://google.com/example'
18
+ const url = parseUrl(externalUrl)
19
+ expect(url.href).toBe(externalUrl)
20
+ expect(url.linkType).toBe('outbound')
21
+ })
22
+
23
+ it('detects relative URLs as internal', () => {
24
+ const relativeUrl = '/resources'
25
+ const url = parseUrl(relativeUrl)
26
+ expect(url.href).toBe(relativeUrl)
27
+ expect(url.linkType).toBe('inbound')
28
+ })
29
+
30
+ it('leaves URL search params intact', () => {
31
+ const url = parseUrl(
32
+ 'https://hashicorp.com/resources?type=case-study&products=terraform'
33
+ )
34
+ expect(url.href).toBe('/resources?type=case-study&products=terraform')
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
+ })
69
+ })
@@ -0,0 +1,97 @@
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
+ */
37
+ export function parseUrl(url: string | undefined): {
38
+ linkType: 'inbound' | 'outbound' | ''
39
+ href: string
40
+ } {
41
+ if (!url) {
42
+ return { linkType: '', href: '' }
43
+ }
44
+
45
+ if (isRelative(url) || isAnchor(url)) {
46
+ return { linkType: 'inbound', href: url }
47
+ }
48
+
49
+ if (isInternal(url)) {
50
+ try {
51
+ const { pathname, search, hash } = new URL(url)
52
+ const href = pathname + search + hash
53
+ return { linkType: 'inbound', href }
54
+ } catch (error) {
55
+ console.log(`Invalid href: ${url}`)
56
+ return { linkType: '', href: '' }
57
+ }
58
+ }
59
+
60
+ return { linkType: 'outbound', href: url }
61
+ }
62
+
63
+ const isRelative = (url: string) => url.startsWith('/')
64
+ const isAnchor = (url: string) => url.startsWith('#')
65
+
66
+ /**
67
+ * Determines whether the provided url uses hashicorp.com or www.hashicorp.com as the hostname.
68
+ *
69
+ * @param {string} url - The URL to check.
70
+ * @returns {boolean} - Returns true if the link uses a HashiCorp domain, otherwise false.
71
+ */
72
+
73
+ export const isInternal = (url: string): boolean => {
74
+ const hostname = new URL(url).hostname
75
+ return hostname === 'hashicorp.com' || hostname === 'www.hashicorp.com'
76
+ }
77
+
78
+ /**
79
+ * Determines whether the provided url uses a HashiCorp hostname. This is used in several
80
+ * components to open a new tab for non-HashiCorp links.
81
+ *
82
+ * @param {string} url - The URL to check.
83
+ * @returns {boolean} - Returns true if the link uses a HashiCorp domain, otherwise false.
84
+ */
85
+
86
+ export const hasHashiCorpDestination = (url: string): boolean => {
87
+ // Fast return in case a relative URL or anchor is passed in
88
+ if (isRelative(url) || isAnchor(url)) return true
89
+ try {
90
+ const hostname = new URL(url).hostname
91
+ const isHashiCorpDestination = containsHashiCorpDestination(hostname)
92
+ return isHashiCorpDestination
93
+ } catch (e) {
94
+ console.error('Error: `url` is not a valid URL', e)
95
+ return false
96
+ }
97
+ }