@hashicorp/react-components 0.0.0-canary.0299c5d
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/.eslintrc.js +4 -0
- package/.stylelintrc.js +3 -0
- package/.test/setup-vitest.js +1 -0
- package/README.md +3 -0
- package/global.d.ts +6 -0
- package/package.json +30 -0
- package/postcss.config.js +21 -0
- package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
- package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
- package/src/components/nav-panel/components/link-list/index.tsx +94 -0
- package/src/components/nav-panel/components/link-list/style.module.css +57 -0
- package/src/components/nav-panel/components/panel/column/index.tsx +24 -0
- package/src/components/nav-panel/components/panel/column/style.module.css +24 -0
- package/src/components/nav-panel/components/panel/index.tsx +4 -0
- package/src/components/nav-panel/components/panel/root/index.tsx +20 -0
- package/src/components/nav-panel/components/panel/root/style.module.css +30 -0
- package/src/components/nav-panel/components/promo/index.tsx +92 -0
- package/src/components/nav-panel/components/promo/style.module.css +128 -0
- package/src/components/nav-panel/components/text/index.tsx +34 -0
- package/src/components/nav-panel/index.tsx +6 -0
- package/src/components/nav-panel/product-panel/index.tsx +185 -0
- package/src/components/nav-panel/product-panel/style.module.css +355 -0
- package/src/components/nav-panel/standard-panel/index.tsx +47 -0
- package/src/components/nav-panel/standard-panel/style.module.css +11 -0
- package/src/lib/get-localized-link-props/index.test.ts +30 -0
- package/src/lib/get-localized-link-props/index.ts +45 -0
- package/src/lib/parse-url/index.test.ts +69 -0
- package/src/lib/parse-url/index.ts +98 -0
- package/src/lib/send-posthog-event/index.ts +22 -0
- package/src/lib/supported-locales/index.ts +1 -0
- package/src/lib/track-nav-click-event/index.ts +22 -0
- package/src/lib/v/index.ts +10 -0
- package/tsconfig.json +14 -0
- package/vitest.config.mts +12 -0
package/.eslintrc.js
ADDED
package/.stylelintrc.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
package/README.md
ADDED
package/global.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hashicorp/react-components",
|
|
3
|
+
"version": "0.0.0-canary.0299c5d",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"lint": "next-hashicorp lint",
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"test:watch": "vitest watch"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@hashicorp/flight-icons": "^3.7.0",
|
|
12
|
+
"@hashicorp/platform-product-meta": "^0.1.1",
|
|
13
|
+
"@hashicorp/platform-util": "^0.2.0",
|
|
14
|
+
"@hashicorp/web-mktg-logos": "*",
|
|
15
|
+
"@radix-ui/react-navigation-menu": "^1.2.0",
|
|
16
|
+
"@radix-ui/react-visually-hidden": "^1.1.0",
|
|
17
|
+
"@vercel/fetch": "^7.0.0",
|
|
18
|
+
"@vercel/stega": "^0.1.2",
|
|
19
|
+
"posthog-js": "^1.195.0",
|
|
20
|
+
"classnames": "^2.3.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@hashicorp/platform-cli": "^2.8.0",
|
|
24
|
+
"@hashicorp/platform-types": "^0.4.0",
|
|
25
|
+
"@testing-library/jest-dom": "^6.4.2",
|
|
26
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
27
|
+
"nock": "^14.0.0-beta.5",
|
|
28
|
+
"vitest": "^1.4.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Next.js allows for plugins to be specified as an array of arrays, but this
|
|
2
|
+
// isn't supported by Vitest, so use a helper function to convert the array of
|
|
3
|
+
// arrays to an object.
|
|
4
|
+
function standardSyntax(plugins) {
|
|
5
|
+
const _plugins = {}
|
|
6
|
+
plugins.forEach((p) => {
|
|
7
|
+
if (Array.isArray(p)) {
|
|
8
|
+
_plugins[p[0]] = p[1]
|
|
9
|
+
} else {
|
|
10
|
+
_plugins[p] = {}
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
return _plugins
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const baseConfig = require('@hashicorp/platform-postcss-config')
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
...baseConfig,
|
|
20
|
+
plugins: standardSyntax(baseConfig.plugins),
|
|
21
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import svgSprite from '@hashicorp/flight-icons/svg-sprite/svg-sprite.svg'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { useId, type SVGProps } from 'react'
|
|
4
|
+
import s from './style.module.css'
|
|
5
|
+
|
|
6
|
+
interface Props extends SVGProps<SVGSVGElement> {
|
|
7
|
+
/**
|
|
8
|
+
* The name of the icon you wish to use. If the value does not match an
|
|
9
|
+
* existing icon name, an error will be thrown. Search for existing icon
|
|
10
|
+
* names in the Icon library.
|
|
11
|
+
*/
|
|
12
|
+
name: string
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The `color` prop can be used to change the color. It works by setting
|
|
16
|
+
* the value of the icon SVG’s fill property.
|
|
17
|
+
*/
|
|
18
|
+
color?: string
|
|
19
|
+
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// When an SVG file is imported in Next.js, it is an object with a `src`
|
|
24
|
+
// property. However, tools like Webpack and Vite default to returning a URL.
|
|
25
|
+
// This runtime check allows the component to work in all of these cases.
|
|
26
|
+
const svgSpriteSrc =
|
|
27
|
+
typeof svgSprite === 'string' ? svgSprite : (svgSprite as { src: string }).src
|
|
28
|
+
|
|
29
|
+
export default function FlightIcon({
|
|
30
|
+
name,
|
|
31
|
+
color = 'currentColor',
|
|
32
|
+
className,
|
|
33
|
+
...rest
|
|
34
|
+
}: Props) {
|
|
35
|
+
const iconId = useId()
|
|
36
|
+
return (
|
|
37
|
+
<svg
|
|
38
|
+
className={classNames(s['flight-icon'], className)}
|
|
39
|
+
{...rest}
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
fill={color}
|
|
42
|
+
id={iconId}
|
|
43
|
+
width={16}
|
|
44
|
+
height={16}
|
|
45
|
+
viewBox="0 0 16 16"
|
|
46
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
47
|
+
>
|
|
48
|
+
<use href={`${svgSpriteSrc}#flight-${name}-16`}></use>
|
|
49
|
+
</svg>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { vercelSplit } from '../../../../lib/v'
|
|
3
|
+
import { getLocalizedLinkProps } from '../../../../lib/get-localized-link-props'
|
|
4
|
+
import FlightIcon from '../flight-icon'
|
|
5
|
+
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
|
6
|
+
import s from './style.module.css'
|
|
7
|
+
|
|
8
|
+
export interface LinkListProps {
|
|
9
|
+
locale?: string
|
|
10
|
+
links: {
|
|
11
|
+
title: string
|
|
12
|
+
url: string
|
|
13
|
+
icon?: string
|
|
14
|
+
description?: string
|
|
15
|
+
iconGradient?: string
|
|
16
|
+
}[]
|
|
17
|
+
linkClickHandler?: (title: string, url: string) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const GRADIENT_VALUE_MAP = new Map([
|
|
21
|
+
[
|
|
22
|
+
'ILM',
|
|
23
|
+
{
|
|
24
|
+
iconGradient: 'var(--icon-gradient-infrastructure-lifecycle-management)',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
'SLM',
|
|
29
|
+
{
|
|
30
|
+
iconGradient: 'var(--icon-gradient-security-lifecycle-management)',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
'Cloud',
|
|
35
|
+
{
|
|
36
|
+
iconGradient: 'var(--icon-gradient-infrastructure-cloud)',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
export default function LinkList({
|
|
42
|
+
locale,
|
|
43
|
+
links,
|
|
44
|
+
linkClickHandler,
|
|
45
|
+
}: LinkListProps) {
|
|
46
|
+
return (
|
|
47
|
+
<ul className={s.linkList}>
|
|
48
|
+
{links.map((link) => {
|
|
49
|
+
let style
|
|
50
|
+
link.iconGradient
|
|
51
|
+
? (style = {
|
|
52
|
+
'--iconGradient': GRADIENT_VALUE_MAP.get(
|
|
53
|
+
vercelSplit(link.iconGradient)
|
|
54
|
+
)?.iconGradient,
|
|
55
|
+
} as React.CSSProperties)
|
|
56
|
+
: (style = null)
|
|
57
|
+
return (
|
|
58
|
+
<li key={link.title}>
|
|
59
|
+
<NavigationMenu.Link asChild>
|
|
60
|
+
<Link
|
|
61
|
+
className={s.link}
|
|
62
|
+
{...getLocalizedLinkProps(link.url, locale)}
|
|
63
|
+
onClickCapture={() => {
|
|
64
|
+
linkClickHandler && linkClickHandler(link.title, link.url)
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{link.iconGradient && link.icon ? (
|
|
68
|
+
<div className={s.iconBackground} style={style}>
|
|
69
|
+
<FlightIcon name={vercelSplit(link.icon)} />
|
|
70
|
+
</div>
|
|
71
|
+
) : (
|
|
72
|
+
link.icon && (
|
|
73
|
+
<FlightIcon
|
|
74
|
+
name={vercelSplit(link.icon)}
|
|
75
|
+
color="var(--token-color-foreground-faint)"
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
)}
|
|
79
|
+
<div>
|
|
80
|
+
<div className={link.iconGradient && s.boldTitle}>
|
|
81
|
+
{link.title}
|
|
82
|
+
</div>
|
|
83
|
+
{link.description && (
|
|
84
|
+
<div className={s.description}>{link.description}</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</Link>
|
|
88
|
+
</NavigationMenu.Link>
|
|
89
|
+
</li>
|
|
90
|
+
)
|
|
91
|
+
})}
|
|
92
|
+
</ul>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.linkList {
|
|
2
|
+
display: flex;
|
|
3
|
+
list-style: none;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: var(--mds-spacing-03);
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.link {
|
|
11
|
+
color: var(--token-color-foreground-primary);
|
|
12
|
+
display: flex;
|
|
13
|
+
gap: var(--mds-spacing-04);
|
|
14
|
+
padding: var(--mds-spacing-03) var(--mds-spacing-04);
|
|
15
|
+
min-width: 190px;
|
|
16
|
+
align-items: center;
|
|
17
|
+
|
|
18
|
+
&:hover {
|
|
19
|
+
background-color: var(--token-color-surface-interactive-hover);
|
|
20
|
+
border-radius: var(--mds-spacing-01);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&:has(.iconBackground) {
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (--large) {
|
|
28
|
+
max-width: 416px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:focus {
|
|
32
|
+
outline: 2px solid var(--token-color-focus-action-internal);
|
|
33
|
+
outline-style: auto;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.description {
|
|
38
|
+
font-size: var(--mds-typography-body-200-font-size);
|
|
39
|
+
font-weight: var(--mds-typography-font-weight-regular);
|
|
40
|
+
margin-top: var(--mds-spacing-02);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.iconBackground {
|
|
44
|
+
background: var(--iconGradient);
|
|
45
|
+
height: var(--mds-spacing-08);
|
|
46
|
+
width: var(--mds-spacing-08);
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
border-radius: 3px;
|
|
51
|
+
flex: none;
|
|
52
|
+
margin-top: 3px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.boldTitle {
|
|
56
|
+
font-weight: var(--mds-typography-font-weight-semibold);
|
|
57
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import s from './style.module.css'
|
|
3
|
+
|
|
4
|
+
export interface NavColumnProps {
|
|
5
|
+
className?: string
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
hasSidePanel?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
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
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@custom-media --dev-dot-viewport (min-width: 924px);
|
|
2
|
+
|
|
3
|
+
.column {
|
|
4
|
+
position: relative;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
justify-content: flex-start;
|
|
8
|
+
min-width: 190px;
|
|
9
|
+
padding: 0;
|
|
10
|
+
margin: 0 var(--mds-spacing-07);
|
|
11
|
+
border-radius: var(--mds-spacing-03);
|
|
12
|
+
|
|
13
|
+
@media (--large) {
|
|
14
|
+
&:not(.hasSide) {
|
|
15
|
+
margin: 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (--dev-dot-viewport) {
|
|
20
|
+
&.hasSide {
|
|
21
|
+
margin: 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import s from './style.module.css'
|
|
3
|
+
|
|
4
|
+
interface RootProps {
|
|
5
|
+
className?: string
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
hasSidePanel?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
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
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@custom-media --dev-dot-viewport (min-width: 924px);
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
width: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
/* stylelint-disable-next-line unit-no-unknown */
|
|
9
|
+
min-height: calc(100dvh - var(--mds-spacing-12));
|
|
10
|
+
|
|
11
|
+
@media (--large) {
|
|
12
|
+
&:not(.hasSide) {
|
|
13
|
+
justify-content: flex-start;
|
|
14
|
+
height: 100%;
|
|
15
|
+
min-height: fit-content;
|
|
16
|
+
padding: var(--mds-spacing-05) var(--mds-spacing-04);
|
|
17
|
+
gap: var(--mds-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(--mds-spacing-05) var(--mds-spacing-04);
|
|
27
|
+
gap: var(--mds-spacing-04);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { vercelSplit } from '../../../../lib/v'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { getLocalizedLinkProps } from '../../../../lib/get-localized-link-props'
|
|
6
|
+
import { trackNavClickEvent } from '../../../../lib/track-nav-click-event'
|
|
7
|
+
import FlightIcon from '../flight-icon'
|
|
8
|
+
import * as NavigationMenu from '@radix-ui/react-navigation-menu'
|
|
9
|
+
import s from './style.module.css'
|
|
10
|
+
|
|
11
|
+
export interface PromoProps {
|
|
12
|
+
locale?: string
|
|
13
|
+
title: string
|
|
14
|
+
description: string
|
|
15
|
+
linkUrl: string
|
|
16
|
+
linkTitle: string
|
|
17
|
+
slug?: string
|
|
18
|
+
icon?: string
|
|
19
|
+
theme: string
|
|
20
|
+
hasSidePanel?: boolean
|
|
21
|
+
isPromoOnTop?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function Promo({
|
|
25
|
+
locale,
|
|
26
|
+
title,
|
|
27
|
+
description,
|
|
28
|
+
linkUrl,
|
|
29
|
+
linkTitle,
|
|
30
|
+
slug,
|
|
31
|
+
icon,
|
|
32
|
+
theme,
|
|
33
|
+
hasSidePanel,
|
|
34
|
+
isPromoOnTop = false,
|
|
35
|
+
}: PromoProps) {
|
|
36
|
+
const handleClick = useCallback((title, linkUrl, linkTitle, slug) => {
|
|
37
|
+
if (window && window.analytics) {
|
|
38
|
+
window.analytics.track('Nav Promo', {
|
|
39
|
+
url: linkUrl,
|
|
40
|
+
title: title,
|
|
41
|
+
callout: linkTitle,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
trackNavClickEvent(title, linkUrl, slug)
|
|
45
|
+
}
|
|
46
|
+
}, [])
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className={classNames(s.promo, {
|
|
51
|
+
[s.hasSide]: hasSidePanel,
|
|
52
|
+
[s.noSide]: !hasSidePanel,
|
|
53
|
+
[s.topPromo]: isPromoOnTop,
|
|
54
|
+
})}
|
|
55
|
+
>
|
|
56
|
+
{icon && (
|
|
57
|
+
<div
|
|
58
|
+
className={classNames(s.icon, {
|
|
59
|
+
[s.hashidays]: vercelSplit(theme) === 'hashidays',
|
|
60
|
+
[s.hashiconf]: vercelSplit(theme) === 'hashiconf',
|
|
61
|
+
})}
|
|
62
|
+
>
|
|
63
|
+
<FlightIcon name={vercelSplit(icon)} />
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
<div className={s.rightContent}>
|
|
67
|
+
<div>
|
|
68
|
+
<div
|
|
69
|
+
className={classNames(s.title, {
|
|
70
|
+
[s.hashidays]: vercelSplit(theme) === 'hashidays',
|
|
71
|
+
[s.hashiconf]: vercelSplit(theme) === 'hashiconf',
|
|
72
|
+
})}
|
|
73
|
+
>
|
|
74
|
+
{title}
|
|
75
|
+
</div>
|
|
76
|
+
<div className={s.description}>{description}</div>
|
|
77
|
+
</div>
|
|
78
|
+
<NavigationMenu.Link asChild>
|
|
79
|
+
<Link
|
|
80
|
+
locale={locale ? locale : undefined}
|
|
81
|
+
{...getLocalizedLinkProps(linkUrl, locale)}
|
|
82
|
+
className={s.link}
|
|
83
|
+
onClickCapture={() => handleClick(title, linkUrl, linkTitle, slug)}
|
|
84
|
+
>
|
|
85
|
+
{linkTitle}
|
|
86
|
+
<FlightIcon name="external-link" />
|
|
87
|
+
</Link>
|
|
88
|
+
</NavigationMenu.Link>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
@custom-media --dev-dot-viewport (min-width: 924px);
|
|
2
|
+
|
|
3
|
+
.promo {
|
|
4
|
+
display: flex;
|
|
5
|
+
border-radius: var(--mds-spacing-01);
|
|
6
|
+
border: 1px solid var(--token-color-border-faint);
|
|
7
|
+
background-color: var(--token-color-surface-faint);
|
|
8
|
+
padding: var(--mds-spacing-05) var(--mds-spacing-08) var(--mds-spacing-06)
|
|
9
|
+
var(--mds-spacing-08);
|
|
10
|
+
flex-direction: row;
|
|
11
|
+
gap: var(--mds-spacing-04);
|
|
12
|
+
margin: var(--mds-spacing-07);
|
|
13
|
+
margin-bottom: var(--mds-spacing-09);
|
|
14
|
+
|
|
15
|
+
@media (--large) {
|
|
16
|
+
&.topPromo {
|
|
17
|
+
margin-bottom: var(--mds-spacing-05);
|
|
18
|
+
}
|
|
19
|
+
&.noSide {
|
|
20
|
+
margin: 0;
|
|
21
|
+
width: min-content;
|
|
22
|
+
min-width: 100%;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@media (--dev-dot-viewport) {
|
|
27
|
+
&.topPromo {
|
|
28
|
+
margin-bottom: var(--mds-spacing-05);
|
|
29
|
+
}
|
|
30
|
+
&.hasSide {
|
|
31
|
+
&.topPromo {
|
|
32
|
+
margin-bottom: var(--mds-spacing-05);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
margin: 0;
|
|
36
|
+
width: min-content;
|
|
37
|
+
min-width: 100%;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.topPromo {
|
|
43
|
+
margin-bottom: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.rightContent {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
gap: var(--mds-spacing-03);
|
|
50
|
+
width: 100%;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.title {
|
|
55
|
+
font-weight: var(--mds-typography-font-weight-semibold);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.description {
|
|
59
|
+
font-size: var(--mds-typography-body-200-font-size);
|
|
60
|
+
font-weight: var(--mds-typography-font-weight-regular);
|
|
61
|
+
margin-top: var(--mds-spacing-01);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.icon {
|
|
65
|
+
display: none;
|
|
66
|
+
|
|
67
|
+
@media (--large) {
|
|
68
|
+
@nest .noSide & {
|
|
69
|
+
display: block;
|
|
70
|
+
margin-top: 3px;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@media (--dev-dot-viewport) {
|
|
75
|
+
@nest .hasSide & {
|
|
76
|
+
display: block;
|
|
77
|
+
margin-top: 3px;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.link {
|
|
83
|
+
color: var(--token-color-foreground-strong);
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: row;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: var(--mds-spacing-02);
|
|
88
|
+
|
|
89
|
+
&:hover {
|
|
90
|
+
text-decoration: underline;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&:focus {
|
|
94
|
+
outline: 2px solid var(--token-color-focus-action-internal);
|
|
95
|
+
outline-style: auto;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.hashidays {
|
|
100
|
+
background: linear-gradient(
|
|
101
|
+
90deg,
|
|
102
|
+
#7b54b6 0%,
|
|
103
|
+
#4c69d8 74.77%,
|
|
104
|
+
#60d0dc 115.93%
|
|
105
|
+
);
|
|
106
|
+
-webkit-background-clip: text;
|
|
107
|
+
-webkit-text-fill-color: transparent;
|
|
108
|
+
max-width: fit-content;
|
|
109
|
+
|
|
110
|
+
& svg {
|
|
111
|
+
color: #7956b9;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.hashiconf {
|
|
116
|
+
background: linear-gradient(
|
|
117
|
+
to right,
|
|
118
|
+
var(--token-color-terraform-foreground),
|
|
119
|
+
var(--token-color-vagrant-foreground)
|
|
120
|
+
);
|
|
121
|
+
-webkit-background-clip: text;
|
|
122
|
+
-webkit-text-fill-color: transparent;
|
|
123
|
+
max-width: fit-content;
|
|
124
|
+
|
|
125
|
+
& svg {
|
|
126
|
+
color: var(--token-color-terraform-foreground);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import { type ReactNode, type HTMLAttributes } from 'react'
|
|
3
|
+
|
|
4
|
+
type TextSize = '200' | '300' | '400'
|
|
5
|
+
type TextWeight = 'regular' | 'medium' | 'semibold' | 'bold'
|
|
6
|
+
|
|
7
|
+
interface TextProps extends HTMLAttributes<HTMLSpanElement> {
|
|
8
|
+
size?: TextSize
|
|
9
|
+
weight?: TextWeight
|
|
10
|
+
children: ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Text = ({
|
|
14
|
+
size = '300',
|
|
15
|
+
weight,
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
...rest
|
|
19
|
+
}: TextProps) => {
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
className={classNames(
|
|
23
|
+
`mds-typography-body-${size}`,
|
|
24
|
+
weight && `mds-typography-font-weight-${weight}`,
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</span>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { Text }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import ProductPanel, { type NavProductProps } from './product-panel'
|
|
2
|
+
import StandardPanel, { type NavItem } from './standard-panel'
|
|
3
|
+
import { PromoProps } from './components/promo'
|
|
4
|
+
|
|
5
|
+
export { ProductPanel, StandardPanel }
|
|
6
|
+
export type { NavProductProps, PromoProps, NavItem }
|