@hashicorp/react-components 0.0.0-canary.29ac326 → 0.0.0-canary.32bcc38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashicorp/react-components",
3
- "version": "0.0.0-canary.29ac326",
3
+ "version": "0.0.0-canary.32bcc38",
4
4
  "main": "src/index.ts",
5
5
  "scripts": {
6
6
  "lint": "next-hashicorp lint",
@@ -8,7 +8,7 @@
8
8
  "test:watch": "vitest watch"
9
9
  },
10
10
  "dependencies": {
11
- "@hashicorp/flight-icons": "^2.15.0",
11
+ "@hashicorp/flight-icons": "^3.5.0",
12
12
  "@hashicorp/platform-product-meta": "^0.1.1",
13
13
  "@hashicorp/platform-util": "^0.2.0",
14
14
  "@hashicorp/react-design-system-components": "*",
@@ -24,7 +24,7 @@
24
24
  "react-hook-form": "^7.43.5"
25
25
  },
26
26
  "devDependencies": {
27
- "@hashicorp/platform-cli": "^2.6.0",
27
+ "@hashicorp/platform-cli": "^2.8.0",
28
28
  "@hashicorp/platform-types": "^0.4.0",
29
29
  "@testing-library/jest-dom": "^6.4.2",
30
30
  "@vitejs/plugin-react": "^4.2.1",
@@ -4,8 +4,21 @@ import s from './style.module.css'
4
4
  export interface NavColumnProps {
5
5
  className?: string
6
6
  children: React.ReactNode
7
+ hasSidePanel?: boolean
7
8
  }
8
9
 
9
- export default function Column({ className, children }: NavColumnProps) {
10
- return <div className={classNames(s.column, className)}>{children}</div>
10
+ export default function Column({
11
+ className,
12
+ children,
13
+ hasSidePanel,
14
+ }: NavColumnProps) {
15
+ return (
16
+ <div
17
+ className={classNames(s.column, className, {
18
+ [s.hasSide]: hasSidePanel,
19
+ })}
20
+ >
21
+ {children}
22
+ </div>
23
+ )
11
24
  }
@@ -1,3 +1,5 @@
1
+ @custom-media --dev-dot-viewport (min-width: 924px);
2
+
1
3
  .column {
2
4
  position: relative;
3
5
  display: flex;
@@ -5,10 +7,18 @@
5
7
  justify-content: flex-start;
6
8
  min-width: 190px;
7
9
  padding: 0;
8
- margin: 0 var(--hdsplus-spacing-05);
10
+ margin: 0 var(--hdsplus-spacing-07);
9
11
  border-radius: var(--hdsplus-spacing-03);
10
12
 
11
13
  @media (--large) {
12
- margin: 0;
14
+ &:not(.hasSide) {
15
+ margin: 0;
16
+ }
17
+ }
18
+
19
+ @media (--dev-dot-viewport) {
20
+ &.hasSide {
21
+ margin: 0;
22
+ }
13
23
  }
14
24
  }
@@ -4,8 +4,17 @@ import s from './style.module.css'
4
4
  interface RootProps {
5
5
  className?: string
6
6
  children: React.ReactNode
7
+ hasSidePanel?: boolean
7
8
  }
8
9
 
9
- export default function Root({ className, children }: RootProps) {
10
- return <div className={classNames(s.root, className)}>{children}</div>
10
+ export default function Root({ className, children, hasSidePanel }: RootProps) {
11
+ return (
12
+ <div
13
+ className={classNames(s.root, className, {
14
+ [s.hasSide]: hasSidePanel,
15
+ })}
16
+ >
17
+ {children}
18
+ </div>
19
+ )
11
20
  }
@@ -1,3 +1,5 @@
1
+ @custom-media --dev-dot-viewport (min-width: 924px);
2
+
1
3
  .root {
2
4
  width: 100%;
3
5
  display: flex;
@@ -7,10 +9,22 @@
7
9
  min-height: calc(100dvh - var(--hdsplus-spacing-12));
8
10
 
9
11
  @media (--large) {
10
- justify-content: flex-start;
11
- height: 100%;
12
- min-height: fit-content;
13
- padding: var(--hdsplus-spacing-05) var(--hdsplus-spacing-04);
14
- gap: var(--hdsplus-spacing-04);
12
+ &:not(.hasSide) {
13
+ justify-content: flex-start;
14
+ height: 100%;
15
+ min-height: fit-content;
16
+ padding: var(--hdsplus-spacing-05) var(--hdsplus-spacing-04);
17
+ gap: var(--hdsplus-spacing-04);
18
+ }
19
+ }
20
+
21
+ @media (--dev-dot-viewport) {
22
+ &.hasSide {
23
+ justify-content: flex-start;
24
+ height: 100%;
25
+ min-height: fit-content;
26
+ padding: var(--hdsplus-spacing-05) var(--hdsplus-spacing-04);
27
+ gap: var(--hdsplus-spacing-04);
28
+ }
15
29
  }
16
30
  }
@@ -16,6 +16,7 @@ export interface PromoProps {
16
16
  slug?: string
17
17
  icon?: string
18
18
  theme: string
19
+ hasSidePanel?: boolean
19
20
  }
20
21
 
21
22
  export default function Promo({
@@ -26,6 +27,7 @@ export default function Promo({
26
27
  slug,
27
28
  icon,
28
29
  theme,
30
+ hasSidePanel,
29
31
  }: PromoProps) {
30
32
  const handleClick = useCallback((title, linkUrl, linkTitle, slug) => {
31
33
  if (window && window.analytics) {
@@ -40,7 +42,12 @@ export default function Promo({
40
42
  }, [])
41
43
 
42
44
  return (
43
- <div className={s.promo}>
45
+ <div
46
+ className={classNames(s.promo, {
47
+ [s.hasSide]: hasSidePanel,
48
+ [s.noSide]: !hasSidePanel,
49
+ })}
50
+ >
44
51
  {icon && (
45
52
  <div
46
53
  className={classNames(s.icon, {
@@ -1,3 +1,5 @@
1
+ @custom-media --dev-dot-viewport (min-width: 924px);
2
+
1
3
  .promo {
2
4
  display: flex;
3
5
  border-radius: var(--hdsplus-spacing-01);
@@ -7,12 +9,23 @@
7
9
  var(--hdsplus-spacing-06) var(--hdsplus-spacing-08);
8
10
  flex-direction: row;
9
11
  gap: var(--hdsplus-spacing-04);
10
- margin: var(--hdsplus-spacing-05);
12
+ margin: var(--hdsplus-spacing-07);
13
+ margin-bottom: var(--hdsplus-spacing-09);
11
14
 
12
15
  @media (--large) {
13
- margin: 0;
14
- width: min-content;
15
- min-width: 100%;
16
+ &.noSide {
17
+ margin: 0;
18
+ width: min-content;
19
+ min-width: 100%;
20
+ }
21
+ }
22
+
23
+ @media (--dev-dot-viewport) {
24
+ &.hasSide {
25
+ margin: 0;
26
+ width: min-content;
27
+ min-width: 100%;
28
+ }
16
29
  }
17
30
  }
18
31
 
@@ -38,8 +51,17 @@
38
51
  display: none;
39
52
 
40
53
  @media (--large) {
41
- display: block;
42
- margin-top: 3px;
54
+ @nest .noSide & {
55
+ display: block;
56
+ margin-top: 3px;
57
+ }
58
+ }
59
+
60
+ @media (--dev-dot-viewport) {
61
+ @nest .hasSide & {
62
+ display: block;
63
+ margin-top: 3px;
64
+ }
43
65
  }
44
66
  }
45
67
 
@@ -1,3 +1,4 @@
1
+ import classNames from 'classnames'
1
2
  import Link from 'next/link'
2
3
  import * as NavPanel from '../components/panel'
3
4
  import Promo, { type PromoProps } from '../components/promo'
@@ -10,7 +11,7 @@ import * as NavigationMenu from '@radix-ui/react-navigation-menu'
10
11
  import s from './style.module.css'
11
12
 
12
13
  const PRODUCT_LOGO_MAP = new Map([
13
- ['Terraform', 'terraform-fill-color'],
14
+ ['Terraform', 'terraform-fill'],
14
15
  ['Packer', 'packer-fill-color'],
15
16
  ['Vault', 'vault-fill-color'],
16
17
  ['Boundary', 'boundary-fill-color'],
@@ -23,6 +24,7 @@ const PRODUCT_LOGO_MAP = new Map([
23
24
  ])
24
25
 
25
26
  const PRODUCT_LOGO_EXCEPTION_CLASSNAMES = {
27
+ Terraform: 'terraform',
26
28
  'HCP Vault Secrets': 'hcpVaultSecrets',
27
29
  'HCP Vault Radar': 'hcpVaultRadar',
28
30
  }
@@ -30,6 +32,7 @@ const PRODUCT_LOGO_EXCEPTION_CLASSNAMES = {
30
32
  export interface ProductsPanelProps {
31
33
  productCategories: any
32
34
  promo?: PromoProps
35
+ noMarginOnMobile?: boolean
33
36
  sidePanel?: {
34
37
  label: string
35
38
  navItems: {
@@ -44,12 +47,22 @@ export interface ProductsPanelProps {
44
47
  const ProductPanel = ({
45
48
  productCategories,
46
49
  promo,
50
+ noMarginOnMobile = false,
47
51
  sidePanel,
48
52
  }: ProductsPanelProps) => {
49
53
  return (
50
- <NavPanel.Root className={s.productPanel}>
54
+ <NavPanel.Root
55
+ className={classNames(s.productPanel, {
56
+ [s.hasSide]: sidePanel,
57
+ [s.noSide]: !sidePanel,
58
+ })}
59
+ hasSidePanel={Boolean(sidePanel)}
60
+ >
51
61
  <div className={s.mainPanel}>
52
- <NavPanel.Column>
62
+ <NavPanel.Column
63
+ className={noMarginOnMobile && s.noMarginOnMobile}
64
+ hasSidePanel={Boolean(sidePanel)}
65
+ >
53
66
  <ul className={s.productCategories}>
54
67
  {productCategories.map(({ title, products }) => {
55
68
  return (
@@ -69,7 +82,7 @@ const ProductPanel = ({
69
82
  })}
70
83
  </ul>
71
84
  </NavPanel.Column>
72
- {promo ? <Promo {...promo} /> : null}
85
+ {promo ? <Promo {...promo} hasSidePanel={Boolean(sidePanel)} /> : null}
73
86
  </div>
74
87
 
75
88
  {sidePanel ? (
@@ -87,7 +100,11 @@ const ProductPanel = ({
87
100
  {sidePanel.navItems.map(({ icon, title, description, url }) => {
88
101
  return (
89
102
  <li key={title}>
90
- <Link href={parseUrl(url).href} className={s.sidePanelItem}>
103
+ <Link
104
+ aria-label={`${title} - ${description}`}
105
+ href={parseUrl(url).href}
106
+ className={classNames(s.sidePanelItem, s.focusIndicator)}
107
+ >
91
108
  <div className={s.iconBackground}>
92
109
  <FlightIcon
93
110
  size={16}
@@ -122,6 +139,7 @@ function NavProduct({ url, product, description }: NavProductProps) {
122
139
  return (
123
140
  <NavigationMenu.Link asChild>
124
141
  <Link
142
+ aria-label={`${product} - ${description}`}
125
143
  href={parseUrl(url).href}
126
144
  onClickCapture={() => {
127
145
  trackNavClickEvent(product, url, 'products')
@@ -1,11 +1,24 @@
1
+ @custom-media --dev-dot-viewport (min-width: 924px);
2
+
1
3
  .productPanel {
2
4
  height: 100%;
3
5
 
4
6
  @media (--large) {
5
- display: grid;
6
- grid-template-rows: 1fr;
7
- grid-template-columns: max-content max-content;
8
- padding: 0;
7
+ &.noSide {
8
+ display: grid;
9
+ grid-template-rows: 1fr;
10
+ grid-template-columns: max-content max-content;
11
+ padding: 0;
12
+ }
13
+ }
14
+
15
+ @media (--dev-dot-viewport) {
16
+ &.hasSide {
17
+ display: grid;
18
+ grid-template-rows: 1fr;
19
+ grid-template-columns: max-content max-content;
20
+ padding: 0;
21
+ }
9
22
  }
10
23
  }
11
24
 
@@ -13,10 +26,18 @@
13
26
  list-style: none;
14
27
  padding: 0;
15
28
  display: grid;
16
- margin: var(--hdsplus-spacing-06) 0 0;
29
+ margin: var(--hdsplus-spacing-07) 0 0;
17
30
 
18
31
  @media (--large) {
19
- margin: 0;
32
+ @nest .noSide & {
33
+ margin: 0;
34
+ }
35
+ }
36
+
37
+ @media (--dev-dot-viewport) {
38
+ @nest .hasSide & {
39
+ margin: 0;
40
+ }
20
41
  }
21
42
  }
22
43
 
@@ -38,7 +59,17 @@
38
59
  font-weight: var(--font-weight-semi-bold);
39
60
 
40
61
  @media (--large) {
41
- padding: 0 0 var(--hdsplus-spacing-01) var(--hdsplus-spacing-04);
62
+ @nest .noSide & {
63
+ padding-bottom: 0;
64
+ padding-left: var(--hdsplus-spacing-06);
65
+ }
66
+ }
67
+
68
+ @media (--dev-dot-viewport) {
69
+ @nest .hasSide & {
70
+ padding-bottom: 0;
71
+ padding-left: var(--hdsplus-spacing-06);
72
+ }
42
73
  }
43
74
  }
44
75
 
@@ -50,10 +81,31 @@
50
81
  padding: 0;
51
82
 
52
83
  @media (--large) {
53
- grid-auto-flow: row;
54
- grid-template-columns: repeat(2, 300px);
55
- grid-template-rows: repeat(2, max-content);
56
- gap: 0;
84
+ @nest .noSide & {
85
+ padding: var(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
86
+ var(--hdsplus-spacing-04);
87
+ grid-auto-flow: row;
88
+ grid-template-columns: repeat(2, 300px);
89
+ grid-template-rows: repeat(2, max-content);
90
+ gap: 0;
91
+ }
92
+ }
93
+
94
+ @media (--dev-dot-viewport) {
95
+ @nest .hasSide & {
96
+ padding: var(--hdsplus-spacing-06) var(--hdsplus-spacing-05)
97
+ var(--hdsplus-spacing-04);
98
+ grid-auto-flow: row;
99
+ grid-template-columns: repeat(2, 300px);
100
+ grid-template-rows: repeat(2, max-content);
101
+ gap: 0;
102
+ }
103
+ }
104
+
105
+ & li:last-child {
106
+ & .productWrapper {
107
+ margin-bottom: 0;
108
+ }
57
109
  }
58
110
  }
59
111
 
@@ -65,12 +117,26 @@
65
117
  gap: var(--hdsplus-spacing-05);
66
118
 
67
119
  @media (--large) {
68
- padding: var(--hdsplus-spacing-05);
69
- margin-bottom: 0;
120
+ @nest .noSide & {
121
+ padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
122
+ margin-bottom: 0;
123
+
124
+ &:hover {
125
+ background-color: var(--token-color-surface-interactive-hover);
126
+ border-radius: var(--hdsplus-spacing-01);
127
+ }
128
+ }
129
+ }
70
130
 
71
- &:hover {
72
- background-color: var(--token-color-surface-interactive-hover);
73
- border-radius: var(--hdsplus-spacing-01);
131
+ @media (--dev-dot-viewport) {
132
+ @nest .hasSide & {
133
+ padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
134
+ margin-bottom: 0;
135
+
136
+ &:hover {
137
+ background-color: var(--token-color-surface-interactive-hover);
138
+ border-radius: var(--hdsplus-spacing-01);
139
+ }
74
140
  }
75
141
  }
76
142
  }
@@ -81,10 +147,23 @@
81
147
  & svg {
82
148
  height: 42px;
83
149
  width: 42px;
150
+ }
84
151
 
85
- @media (--large) {
86
- height: var(--hdsplus-spacing-08);
87
- width: var(--hdsplus-spacing-08);
152
+ @nest .noSide & {
153
+ & svg {
154
+ @media (--large) {
155
+ height: var(--hdsplus-spacing-08);
156
+ width: var(--hdsplus-spacing-08);
157
+ }
158
+ }
159
+ }
160
+
161
+ @nest .hasSide & {
162
+ & svg {
163
+ @media (--dev-dot-viewport) {
164
+ height: var(--hdsplus-spacing-08);
165
+ width: var(--hdsplus-spacing-08);
166
+ }
88
167
  }
89
168
  }
90
169
  }
@@ -117,8 +196,33 @@
117
196
  flex-direction: column;
118
197
 
119
198
  @media (--large) {
120
- padding: var(--hdsplus-spacing-05) 0 var(--hdsplus-spacing-05)
121
- var(--hdsplus-spacing-04);
199
+ @nest .noSide & {
200
+ padding: var(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-09)
201
+ var(--hdsplus-spacing-04);
202
+ }
203
+ }
204
+
205
+ @media (--dev-dot-viewport) {
206
+ @nest .hasSide & {
207
+ padding: var(--hdsplus-spacing-07) 0 var(--hdsplus-spacing-09)
208
+ var(--hdsplus-spacing-04);
209
+ }
210
+ }
211
+ }
212
+
213
+ .noMarginOnMobile {
214
+ margin: 0;
215
+
216
+ @media (--large) {
217
+ @nest .noSide & {
218
+ margin: 0 var(--hdsplus-spacing-07);
219
+ }
220
+ }
221
+
222
+ @media (--dev-dot-viewport) {
223
+ @nest .hasSide & {
224
+ margin: 0 var(--hdsplus-spacing-07);
225
+ }
122
226
  }
123
227
  }
124
228
 
@@ -129,7 +233,7 @@
129
233
  flex-direction: column;
130
234
  gap: var(--hdsplus-spacing-05);
131
235
  margin: 0;
132
- padding: var(--hdsplus-spacing-05) var(--hdsplus-spacing-07)
236
+ padding: var(--hdsplus-spacing-07) var(--hdsplus-spacing-04)
133
237
  var(--hdsplus-spacing-09) var(--hdsplus-spacing-07);
134
238
  }
135
239
 
@@ -141,7 +245,7 @@
141
245
  grid-template-columns: 1fr;
142
246
  gap: var(--hdsplus-spacing-07);
143
247
 
144
- @media (--large) {
248
+ @media (--dev-dot-viewport) {
145
249
  gap: var(--hdsplus-spacing-05);
146
250
  }
147
251
  }
@@ -149,12 +253,10 @@
149
253
  /* Lifted from .productWrapper */
150
254
  .sidePanelItem {
151
255
  display: flex;
152
- margin-bottom: var(--hdsplus-spacing-05);
153
256
  gap: var(--hdsplus-spacing-05);
154
257
 
155
- @media (--large) {
258
+ @media (--dev-dot-viewport) {
156
259
  padding: var(--hdsplus-spacing-03) var(--hdsplus-spacing-05);
157
- margin-bottom: 0;
158
260
 
159
261
  &:hover {
160
262
  background-color: var(--token-color-surface-interactive-hover);
@@ -194,6 +296,25 @@ packages/react-components/src/components/nav-panel/components/link-list/style.mo
194
296
 
195
297
  /* Branding exception classnames */
196
298
 
299
+ /* Allows switching to a dark-mode-specific color for Terraform */
300
+ .terraform {
301
+ & use {
302
+ fill: var(--token-color-terraform-brand);
303
+
304
+ @nest [data-theme='dark'] & {
305
+ fill: var(--token-color-terraform-foreground);
306
+ }
307
+ }
308
+
309
+ & path {
310
+ fill: inherit;
311
+ }
312
+ }
313
+
314
+ /* Styles for HCP Vault mimic the spec for `product-square` icons,
315
+ while allowing the symbol inside the icon border to change color
316
+ based on the site's theme
317
+ */
197
318
  .hcpVaultSecrets,
198
319
  .hcpVaultRadar {
199
320
  padding: var(--hdsplus-spacing-01);
@@ -2,7 +2,7 @@
2
2
  display: flex;
3
3
  gap: var(--hdsplus-spacing-03);
4
4
  flex-direction: column;
5
- margin-top: var(--hdsplus-spacing-06);
5
+ margin-top: var(--hdsplus-spacing-07);
6
6
 
7
7
  @media (--large) {
8
8
  flex-direction: row;
@@ -5,5 +5,6 @@ import packer from '@hashicorp/web-mktg-logos/src/packer/packer-community/packer
5
5
  import terraform from '@hashicorp/web-mktg-logos/src/terraform/terraform-community/terraform-community_on-dark.svg'
6
6
  import vagrant from '@hashicorp/web-mktg-logos/src/vagrant/vagrant-community/vagrant-community_on-dark.svg'
7
7
  import vault from '@hashicorp/web-mktg-logos/src/vault/vault-community/vault-community_on-dark.svg'
8
+ import waypoint from '@hashicorp/web-mktg-logos/src/waypoint/hashicorp-waypoint-attributed/hashicorp-waypoint_on-dark.svg'
8
9
 
9
- export { boundary, consul, nomad, packer, terraform, vagrant, vault }
10
+ export { boundary, consul, nomad, packer, terraform, vagrant, vault, waypoint }
@@ -5,5 +5,6 @@ import packer from '@hashicorp/web-mktg-logos/src/packer/packer-community/packer
5
5
  import terraform from '@hashicorp/web-mktg-logos/src/terraform/terraform-community/terraform-community_on-light.svg'
6
6
  import vagrant from '@hashicorp/web-mktg-logos/src/vagrant/vagrant-community/vagrant-community_on-light.svg'
7
7
  import vault from '@hashicorp/web-mktg-logos/src/vault/vault-community/vault-community_on-light.svg'
8
+ import waypoint from '@hashicorp/web-mktg-logos/src/waypoint/hashicorp-waypoint-attributed/hashicorp-waypoint_on-light.svg'
8
9
 
9
- export { boundary, consul, nomad, packer, terraform, vagrant, vault }
10
+ export { boundary, consul, nomad, packer, terraform, vagrant, vault, waypoint }
@@ -27,4 +27,9 @@ describe('parseUrl', () => {
27
27
  )
28
28
  expect(url.href).toBe('/resources?type=case-study&products=terraform')
29
29
  })
30
+
31
+ it('leaves HashiCorp subdomain URLs intact', () => {
32
+ const url = parseUrl('https://developer.hashicorp.com/terraform')
33
+ expect(url.href).toBe('https://developer.hashicorp.com/terraform')
34
+ })
30
35
  })
@@ -26,5 +26,7 @@ export function parseUrl(url: string | undefined): {
26
26
  const isRelative = (url) => url.startsWith('/')
27
27
  const isAnchor = (url) => url.startsWith('#')
28
28
 
29
- export const isInternal = (url) =>
30
- new URL(url).hostname.includes('hashicorp.com')
29
+ export const isInternal = (url) => {
30
+ const hostname = new URL(url).hostname
31
+ return hostname === 'hashicorp.com'
32
+ }