@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.
Files changed (34) hide show
  1. package/.eslintrc.js +4 -0
  2. package/.stylelintrc.js +3 -0
  3. package/.test/setup-vitest.js +1 -0
  4. package/README.md +3 -0
  5. package/global.d.ts +6 -0
  6. package/package.json +30 -0
  7. package/postcss.config.js +21 -0
  8. package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
  9. package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
  10. package/src/components/nav-panel/components/link-list/index.tsx +94 -0
  11. package/src/components/nav-panel/components/link-list/style.module.css +57 -0
  12. package/src/components/nav-panel/components/panel/column/index.tsx +24 -0
  13. package/src/components/nav-panel/components/panel/column/style.module.css +24 -0
  14. package/src/components/nav-panel/components/panel/index.tsx +4 -0
  15. package/src/components/nav-panel/components/panel/root/index.tsx +20 -0
  16. package/src/components/nav-panel/components/panel/root/style.module.css +30 -0
  17. package/src/components/nav-panel/components/promo/index.tsx +92 -0
  18. package/src/components/nav-panel/components/promo/style.module.css +128 -0
  19. package/src/components/nav-panel/components/text/index.tsx +34 -0
  20. package/src/components/nav-panel/index.tsx +6 -0
  21. package/src/components/nav-panel/product-panel/index.tsx +185 -0
  22. package/src/components/nav-panel/product-panel/style.module.css +355 -0
  23. package/src/components/nav-panel/standard-panel/index.tsx +47 -0
  24. package/src/components/nav-panel/standard-panel/style.module.css +11 -0
  25. package/src/lib/get-localized-link-props/index.test.ts +30 -0
  26. package/src/lib/get-localized-link-props/index.ts +45 -0
  27. package/src/lib/parse-url/index.test.ts +69 -0
  28. package/src/lib/parse-url/index.ts +98 -0
  29. package/src/lib/send-posthog-event/index.ts +22 -0
  30. package/src/lib/supported-locales/index.ts +1 -0
  31. package/src/lib/track-nav-click-event/index.ts +22 -0
  32. package/src/lib/v/index.ts +10 -0
  33. package/tsconfig.json +14 -0
  34. package/vitest.config.mts +12 -0
@@ -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,98 @@
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
+ console.log('No url to parse, returning empty string')
43
+ return { linkType: '', href: '' }
44
+ }
45
+
46
+ if (isRelative(url) || isAnchor(url)) {
47
+ return { linkType: 'inbound', href: url }
48
+ }
49
+
50
+ if (isInternal(url)) {
51
+ try {
52
+ const { pathname, search, hash } = new URL(url)
53
+ const href = pathname + search + hash
54
+ return { linkType: 'inbound', href }
55
+ } catch (error) {
56
+ console.log(`Invalid href: ${url}`)
57
+ return { linkType: '', href: '' }
58
+ }
59
+ }
60
+
61
+ return { linkType: 'outbound', href: url }
62
+ }
63
+
64
+ const isRelative = (url: string) => url.startsWith('/')
65
+ const isAnchor = (url: string) => url.startsWith('#')
66
+
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 => {
75
+ const hostname = new URL(url).hostname
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
+ }
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']
@@ -0,0 +1,22 @@
1
+ import sendPosthogEvent from '../send-posthog-event'
2
+
3
+ /**
4
+ * Pushes a 'Nav link click' custom event to PostHog.
5
+ * A wrapper around the PostHog custom event method.
6
+ *
7
+ * @param {string} name - The name of the nav link
8
+ * @param {string} href - The href for the nav link
9
+ * @param {string} [section] - The name of the section of the nav. Top-level links do not have a section.
10
+ */
11
+
12
+ export const trackNavClickEvent = (
13
+ name: string,
14
+ href: string,
15
+ section: string
16
+ ) => {
17
+ sendPosthogEvent('Nav link click', {
18
+ name,
19
+ href,
20
+ section,
21
+ })
22
+ }
@@ -0,0 +1,10 @@
1
+ import { vercelStegaSplit } from '@vercel/stega'
2
+
3
+ /**
4
+ * Takes in a Vercel Visual Editing string and returns the string without
5
+ * hidden data.
6
+ *
7
+ */
8
+ export const vercelSplit = <VercelString>(
9
+ original: VercelString
10
+ ): VercelString => vercelStegaSplit(original as string).cleaned as VercelString
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../base.tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "plugins": [
6
+ {
7
+ "name": "next"
8
+ }
9
+ ],
10
+ "types": ["vitest/globals", "@testing-library/jest-dom"]
11
+ },
12
+ "include": ["./**/*"],
13
+ "exclude": ["node_modules"]
14
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig, configDefaults } from 'vitest/config'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ test: {
7
+ globals: true,
8
+ environment: 'jsdom',
9
+ exclude: [...configDefaults.exclude, 'src/__tests__/e2e'],
10
+ setupFiles: ['.test/setup-vitest.js'],
11
+ },
12
+ })