Skip to content

Commit

Permalink
feat: create tabs component (nodejs#5964)
Browse files Browse the repository at this point in the history
* feat(Tabs): create component

* chore: update collaborator guide

* fix: use open sans font

* refactor: nest tabs trigger class

* refactor: format

* docs: add radix url

* style: ident

* refactor: remove decorated component

* refactor: use tabs prop

* refactor: change tab default value

* refactor: remove duplicated defaultValue prop

* refactor: remove react globals types

* refactor: review

* test(Tabs): create initial unit tests

* refactor: remove tabs content

* fix: fixes

* test: fix keys

* fix: package-lock

---------

Co-authored-by: Claudio Wunder <[email protected]>
  • Loading branch information
araujogui and ovflowd authored Oct 15, 2023
1 parent dafcdaa commit bf13056
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 3 deletions.
12 changes: 10 additions & 2 deletions COLLABORATOR_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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/
53 changes: 53 additions & 0 deletions components/Common/Tabs/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -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(
<Tabs tabs={tabs} defaultValue="package">
<TabsPrimitive.Content value="package">
Package Manager
</TabsPrimitive.Content>
<TabsPrimitive.Content value="prebuilt">
Prebuilt Installer
</TabsPrimitive.Content>
<TabsPrimitive.Content value="source">
Source Code
</TabsPrimitive.Content>
</Tabs>
);
});

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');
});
});
20 changes: 20 additions & 0 deletions components/Common/Tabs/index.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
42 changes: 42 additions & 0 deletions components/Common/Tabs/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Tabs>;
type Meta = MetaObj<typeof Tabs>;

export const Default: Story = {
args: {
defaultValue: 'prebuilt',
tabs: [
{
key: 'package',
label: 'Package Manager',
},
{
key: 'prebuilt',
label: 'Prebuilt Installer',
},
{
key: 'source',
label: 'Source Code',
},
],
children: (
<>
<TabsPrimitive.Content value="package">
Package Manager
</TabsPrimitive.Content>
<TabsPrimitive.Content value="prebuilt">
Prebuilt Installer
</TabsPrimitive.Content>
<TabsPrimitive.Content value="source">
Source Code
</TabsPrimitive.Content>
</>
),
},
};

export default { component: Tabs } as Meta;
41 changes: 41 additions & 0 deletions components/Common/Tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -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<PropsWithChildren<TabsProps>> = ({
tabs,
headerClassName,
children,
...props
}) => (
<TabsPrimitive.Root {...props}>
<TabsPrimitive.List
className={classNames(styles.tabsList, headerClassName)}
>
{tabs.map(tab => (
<TabsPrimitive.Trigger
key={tab.key}
value={tab.key}
className={styles.tabsTrigger}
>
{tab.label}
</TabsPrimitive.Trigger>
))}
</TabsPrimitive.List>
{children}
</TabsPrimitive.Root>
);

export default Tabs;
32 changes: 31 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default {
900: '#411526',
},
white: '#FFFFFF',
transparent: 'transparent',
},
fontSize: {
xs: ['0.75rem', '1rem'],
Expand Down

0 comments on commit bf13056

Please sign in to comment.