diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md
index 67dc62ddd2efe..8b019e3d08db2 100644
--- a/COLLABORATOR_GUIDE.md
+++ b/COLLABORATOR_GUIDE.md
@@ -79,6 +79,7 @@ The Website also uses several other Open Source libraries (not limited to) liste
- [PostCSS Simple Vars](https://github.com/postcss/postcss-simple-vars)
- [Tailwind][] is used as our CSS Framework and the Foundation of our Design System
- [Hero Icons](https://heroicons.com/) is an SVG Icon Library used within our Codebase
+- [Radix UI][] is a collection of customizable UI components
- [Shiki][] is a Syntax Highlighter used for our Codeboxes
- A [Rehype Plugin](https://rehype-pretty-code.netlify.app/) is used here for transforming `pre` and `code` tags into Syntax Highlighted Codeboxes
- [MDX][] and Markdown are used for structuring the Content of the Website
@@ -195,8 +196,8 @@ Finally, if you're unfamiliar with how to use Tailwind or how to use Tailwind wi
> You can apply Tailwind Tokens with Tailwind's `@apply` CSS rule. [Read more about applying Tailwind classes with `@apply`](https://tailwindcss.com/docs/functions-and-directives#apply).
> \[!IMPORTANT]\
-> When using IDEs such as Visual Studio Code, we recommend installing the official [Stylelint](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
-> and [Tailwind](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) Extensions.\
+> When using IDEs such as Visual Studio Code, we recommend installing the official [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)
+> and [Tailwind](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) Extensions.\
> These are recommended Extensions for IntelliSense, Syntax Highlighting and Error Checking when styling your Component.
### Best practices when creating a Component
@@ -475,6 +476,12 @@ Defining a `.vscode` configuration like this also aides browser-only development
The npm ecosystem resolution and installation of `peerDependencies` installation [changed in recent versions](https://nodejs.org/en/blog/npm/peer-dependencies#using-peer-dependencies). The project documents what version of `Node.js` and `npm` to use via the [`.nvmrc` file](https://github.com/nodejs/nodejs.org/blob/main/.nvmrc). Not all contributors have tooling to automatically read this file and adhere to the correct version, however. To ensure all contributors install dependencies the same way, a local `.npmrc` file directly configures peerDependency installation behavior.
+### Why we use RadixUI?
+
+- It is a minimalistic component library broken down in individual packages for each Component
+- It already handles all WAI-ARIA and Accessibility shortcuts/bindings needed for Interactive Elements
+- It allows us to focus on designing interactive Components without the effort of adding all the surrounding sugar and code needed to make the Component accessibility-friendly.
+
## Seeking additional clarification
A lot of the current structure is due to retro-compatibility, keeping a simple and familiar file structure and keeping files that have historical reasons or needs.
@@ -491,3 +498,4 @@ If you're unfamiliar or curious about something, we recommend opening a Discussi
[React]: https://react.dev/
[Shiki]: https://github.com/shikijs/shiki
[Tailwind]: https://tailwindcss.com/
+[Radix UI]: https://www.radix-ui.com/
diff --git a/components/Common/Tabs/__tests__/index.test.mjs b/components/Common/Tabs/__tests__/index.test.mjs
new file mode 100644
index 0000000000000..31957b4f1a228
--- /dev/null
+++ b/components/Common/Tabs/__tests__/index.test.mjs
@@ -0,0 +1,53 @@
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import Tabs from '../index';
+
+describe('Tabs', () => {
+ const tabs = [
+ { key: 'package', label: 'Package Manager' },
+ { key: 'prebuilt', label: 'Prebuilt Installer' },
+ { key: 'source', label: 'Source Code' },
+ ];
+
+ beforeEach(() => {
+ render(
+
+
+ Package Manager
+
+
+ Prebuilt Installer
+
+
+ Source Code
+
+
+ );
+ });
+
+ it('renders the correct number of tabs', () => {
+ const tabElements = screen.getAllByRole('tab');
+ expect(tabElements).toHaveLength(3);
+ });
+
+ it('renders the correct tab content when clicked', async () => {
+ const user = userEvent.setup();
+
+ const beforeActiveTabPanel = screen.getAllByRole('tabpanel');
+
+ expect(beforeActiveTabPanel).toHaveLength(1);
+
+ expect(beforeActiveTabPanel.at(0)).toHaveTextContent('Package Manager');
+
+ const tabElements = screen.getAllByRole('tab');
+ await user.click(tabElements.at(-1));
+
+ const afterActiveTabPanel = screen.getAllByRole('tabpanel');
+
+ expect(afterActiveTabPanel).toHaveLength(1);
+
+ expect(afterActiveTabPanel.at(0)).toHaveTextContent('Source Code');
+ });
+});
diff --git a/components/Common/Tabs/index.module.css b/components/Common/Tabs/index.module.css
new file mode 100644
index 0000000000000..1183a31fa86de
--- /dev/null
+++ b/components/Common/Tabs/index.module.css
@@ -0,0 +1,20 @@
+.tabsList {
+ @apply flex
+ gap-1
+ font-open-sans;
+
+ .tabsTrigger {
+ @apply border-b-2
+ border-b-transparent
+ px-1
+ pb-[11px]
+ text-sm
+ font-semibold
+ text-neutral-800
+ data-[state=active]:border-b-green-600
+ data-[state=active]:text-green-600
+ dark:text-neutral-200
+ dark:data-[state=active]:border-b-green-400
+ dark:data-[state=active]:text-green-400;
+ }
+}
diff --git a/components/Common/Tabs/index.stories.tsx b/components/Common/Tabs/index.stories.tsx
new file mode 100644
index 0000000000000..9453bb8f84415
--- /dev/null
+++ b/components/Common/Tabs/index.stories.tsx
@@ -0,0 +1,42 @@
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+import type { Meta as MetaObj, StoryObj } from '@storybook/react';
+
+import Tabs from './index';
+
+type Story = StoryObj;
+type Meta = MetaObj;
+
+export const Default: Story = {
+ args: {
+ defaultValue: 'prebuilt',
+ tabs: [
+ {
+ key: 'package',
+ label: 'Package Manager',
+ },
+ {
+ key: 'prebuilt',
+ label: 'Prebuilt Installer',
+ },
+ {
+ key: 'source',
+ label: 'Source Code',
+ },
+ ],
+ children: (
+ <>
+
+ Package Manager
+
+
+ Prebuilt Installer
+
+
+ Source Code
+
+ >
+ ),
+ },
+};
+
+export default { component: Tabs } as Meta;
diff --git a/components/Common/Tabs/index.tsx b/components/Common/Tabs/index.tsx
new file mode 100644
index 0000000000000..811b31319e754
--- /dev/null
+++ b/components/Common/Tabs/index.tsx
@@ -0,0 +1,41 @@
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+import classNames from 'classnames';
+import type { FC, PropsWithChildren } from 'react';
+
+import styles from './index.module.css';
+
+type Tab = {
+ key: string;
+ label: string;
+};
+
+type TabsProps = {
+ tabs: Tab[];
+ headerClassName?: string;
+} & TabsPrimitive.TabsProps;
+
+const Tabs: FC> = ({
+ tabs,
+ headerClassName,
+ children,
+ ...props
+}) => (
+
+
+ {tabs.map(tab => (
+
+ {tab.label}
+
+ ))}
+
+ {children}
+
+);
+
+export default Tabs;
diff --git a/package-lock.json b/package-lock.json
index 8077df7248794..9e92692b08354 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@nodevu/core": "~0.1.0",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-select": "^2.0.0",
+ "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@types/node": "18.18.3",
"@vcarl/remark-headings": "~0.1.0",
@@ -4812,7 +4813,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz",
"integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==",
- "dev": true,
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
@@ -4925,6 +4925,36 @@
}
}
},
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz",
+ "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.1",
+ "@radix-ui/react-context": "1.0.1",
+ "@radix-ui/react-direction": "1.0.1",
+ "@radix-ui/react-id": "1.0.1",
+ "@radix-ui/react-presence": "1.0.1",
+ "@radix-ui/react-primitive": "1.0.3",
+ "@radix-ui/react-roving-focus": "1.0.4",
+ "@radix-ui/react-use-controllable-state": "1.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-toast": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz",
diff --git a/package.json b/package.json
index 2dd70005a85c1..c065d20644177 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-toast": "^1.1.5",
+ "@radix-ui/react-tabs": "^1.0.4",
"@types/node": "18.18.3",
"@vcarl/remark-headings": "~0.1.0",
"@vercel/analytics": "^1.0.2",
diff --git a/tailwind.config.ts b/tailwind.config.ts
index b07380d091e06..bc160c2e55e79 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -90,6 +90,7 @@ export default {
900: '#411526',
},
white: '#FFFFFF',
+ transparent: 'transparent',
},
fontSize: {
xs: ['0.75rem', '1rem'],