From 6cf337c0fae982fbef33de54851619d336fca8a3 Mon Sep 17 00:00:00 2001
From: Daniele Cammareri <daniele.cammareri@hasura.io>
Date: Thu, 1 Sep 2022 23:23:23 +0200
Subject: [PATCH] console: add tabs component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

## Description 🔖

[VIDEO](https://www.loom.com/share/5844476e527f4e1d9ec296bc3a4bb932)

This PR creates a Tabs component that wraps the Radix UI component.

## Solution and Design 🎨

The component accepts all the props of the Radix UI tab root component and a list of items that specify the label and the content of the tabs. The component can be used in a controlled way (e.g., the state of the tab is stored in the URL) or uncontrolled.

For styling radix ui states I've added this library: https://github.com/ecklf/tailwindcss-radix

## Review Setup 💻

Run storybook and look for Tabs component

## Review checklist. 📋

- [ ] stories work
- [ ] doc is clear

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5667
GitOrigin-RevId: 0e73ef1026d6673c3c2dd413b986fa8395abb1f6
---
 console/package-lock.json                     | 11 +++
 console/package.json                          |  1 +
 .../src/new-components/Tabs/Tabs.stories.mdx  | 97 +++++++++++++++++++
 console/src/new-components/Tabs/Tabs.tsx      | 41 ++++++++
 console/src/new-components/Tabs/index.tsx     |  1 +
 console/tailwind.config.js                    |  6 +-
 6 files changed, 156 insertions(+), 1 deletion(-)
 create mode 100644 console/src/new-components/Tabs/Tabs.stories.mdx
 create mode 100644 console/src/new-components/Tabs/Tabs.tsx
 create mode 100644 console/src/new-components/Tabs/index.tsx

diff --git a/console/package-lock.json b/console/package-lock.json
index 369a01cc0e7..141a304119d 100644
--- a/console/package-lock.json
+++ b/console/package-lock.json
@@ -87,6 +87,7 @@
         "styled-components": "5.0.1",
         "styled-system": "5.1.5",
         "subscriptions-transport-ws": "0.9.16",
+        "tailwindcss-radix": "^2.5.0",
         "ts-essentials": "7.0.3",
         "uuid": "8.3.2",
         "xstate": "^4.30.1",
@@ -38429,6 +38430,11 @@
         "postcss": "^8.0.9"
       }
     },
+    "node_modules/tailwindcss-radix": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tailwindcss-radix/-/tailwindcss-radix-2.5.0.tgz",
+      "integrity": "sha512-vPIvolTmb0L6DlvIKBJRVGo+gB7ZU2jZbO8EpZw/5gBSTHx6PezRgQhR68kOuteh3F1q2PV3oBQgaIN4FxVmrw=="
+    },
     "node_modules/tailwindcss/node_modules/glob-parent": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -71770,6 +71776,11 @@
         }
       }
     },
+    "tailwindcss-radix": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tailwindcss-radix/-/tailwindcss-radix-2.5.0.tgz",
+      "integrity": "sha512-vPIvolTmb0L6DlvIKBJRVGo+gB7ZU2jZbO8EpZw/5gBSTHx6PezRgQhR68kOuteh3F1q2PV3oBQgaIN4FxVmrw=="
+    },
     "tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
diff --git a/console/package.json b/console/package.json
index 2f2e351652e..b0d3caf76d8 100644
--- a/console/package.json
+++ b/console/package.json
@@ -140,6 +140,7 @@
     "styled-components": "5.0.1",
     "styled-system": "5.1.5",
     "subscriptions-transport-ws": "0.9.16",
+    "tailwindcss-radix": "^2.5.0",
     "ts-essentials": "7.0.3",
     "uuid": "8.3.2",
     "xstate": "^4.30.1",
diff --git a/console/src/new-components/Tabs/Tabs.stories.mdx b/console/src/new-components/Tabs/Tabs.stories.mdx
new file mode 100644
index 00000000000..98fb3e84d05
--- /dev/null
+++ b/console/src/new-components/Tabs/Tabs.stories.mdx
@@ -0,0 +1,97 @@
+import { Canvas, Meta, Story } from '@storybook/addon-docs';
+import { action } from '@storybook/addon-actions';
+
+import { TemplateStoriesFactory } from '@/utils/StoryUtils';
+import { Tabs } from '@/new-components/Tabs';
+
+<Meta
+  title="components/Tabs 🧬"
+  component={Tabs}
+  parameters={{
+    docs: { source: { type: 'code' } },
+    chromatic: { disableSnapshot: true },
+  }}
+  decorators={[Story => <div className={'p-4'}>{Story()}</div>]}
+  argTypes={{
+    value: {
+      defaultValue: 'tab-1',
+      control: {
+        type: 'select',
+        options: ['tab-1', 'tab-2', 'tab-3'],
+      },
+    },
+    onValueChange: { action: 'onValueChange' },
+  }}
+/>
+
+# Tabs 🧬
+
+- [🧰 Overview](#-overview)
+- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/Tabs/Tabs.tsx)
+
+## 🧰 Overview
+
+A component that display tabbed content. It is a wrapper around [radix-ui](https://www.radix-ui.com/docs/primitives/components/tabs) component. It can be used either in a controlled or uncontrolled way.
+
+### Basic usage
+
+```ts
+import { Tabs } from '@/new-components/Tabs';
+```
+
+```tsx
+<Tabs
+  value={value}
+  onValueChange={value => setValue(value)}
+  items={[
+    { value: 'tab-1', label: 'Tab 1', content: 'Content 1' },
+    { value: 'tab-2', label: 'Tab 2', content: 'Content 2' },
+    { value: 'tab-3', label: 'Tab 3', content: 'Content 3' },
+  ]}
+/>
+```
+
+export const Template = ({ value, onValueChange, ...rest }) => (
+  <Tabs
+    items={[
+      { value: 'tab-1', label: 'Tab 1', content: 'Content 1' },
+      { value: 'tab-2', label: 'Tab 2', content: 'Content 2' },
+      { value: 'tab-3', label: 'Tab 3', content: 'Content 3' },
+    ]}
+    value={value}
+    onValueChange={onValueChange}
+    {...rest}
+  />
+);
+
+<Canvas>
+  <Story name="Controlled">{Template.bind({})}</Story>
+</Canvas>
+
+<Canvas>
+  <Story name="Unontrolled">
+    <Tabs
+      items={[
+        { value: 'tab-1', label: 'Tab 1', content: 'Content 1' },
+        { value: 'tab-2', label: 'Tab 2', content: 'Content 2' },
+        { value: 'tab-3', label: 'Tab 3', content: 'Content 3' },
+      ]}
+    />
+  </Story>
+</Canvas>
+
+#### 🚦 Usage
+
+The component accepts all the props of the radix-ui [Tabs](https://www.radix-ui.com/docs/primitives/components/tabs) root component.
+
+- If used in a controlled way (e.g. the state of tabs navigation is stored in the URL), you should pass:
+  - `value`: the id of the selected tab
+  - `onValueChange`: the callback to be called when the tab is changed
+- The `items` prop is an array of objects that contain the following properties:
+  - `value`: the id of the tab
+  - `label`: the label of the tab
+  - `content`: the content of the tab
+
+```
+
+```
diff --git a/console/src/new-components/Tabs/Tabs.tsx b/console/src/new-components/Tabs/Tabs.tsx
new file mode 100644
index 00000000000..2eb3449ded7
--- /dev/null
+++ b/console/src/new-components/Tabs/Tabs.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import * as RadixTabs from '@radix-ui/react-tabs';
+import clsx from 'clsx';
+
+interface TabsItem {
+  value: string;
+  label: string;
+  content: React.ReactNode;
+}
+
+interface TabsProps extends React.ComponentProps<typeof RadixTabs.Root> {
+  items: TabsItem[];
+}
+
+export const Tabs: React.FC<TabsProps> = props => {
+  const { items, ...rest } = props;
+  return (
+    <RadixTabs.Root defaultValue={items[0]?.value} {...rest}>
+      <RadixTabs.List aria-label="Tabs">
+        <div className="border-b border-gray-200 mb-lg bg-legacybg flex space-x-4">
+          {items.map(({ value: itemValue, label }) => (
+            <RadixTabs.Trigger value={itemValue} asChild>
+              <button
+                className={clsx(
+                  'whitespace-nowrap py-xs px-sm border-b-2 font-semibold',
+                  'hover:border-gray-300 border-transparent text-muted',
+                  'radix-state-active:hover:border-yellow-500 radix-state-active:border-yellow-500 radix-state-active:text-yellow-500'
+                )}
+              >
+                {label}
+              </button>
+            </RadixTabs.Trigger>
+          ))}
+        </div>
+      </RadixTabs.List>
+      {items.map(({ value: itemValue, content }) => (
+        <RadixTabs.Content value={itemValue}>{content}</RadixTabs.Content>
+      ))}
+    </RadixTabs.Root>
+  );
+};
diff --git a/console/src/new-components/Tabs/index.tsx b/console/src/new-components/Tabs/index.tsx
new file mode 100644
index 00000000000..856dbbb347c
--- /dev/null
+++ b/console/src/new-components/Tabs/index.tsx
@@ -0,0 +1 @@
+export * from './Tabs';
diff --git a/console/tailwind.config.js b/console/tailwind.config.js
index eb4dfd32780..e5dc691f990 100644
--- a/console/tailwind.config.js
+++ b/console/tailwind.config.js
@@ -82,5 +82,9 @@ module.exports = {
       },
     },
   },
-  plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
+  plugins: [
+    require('@tailwindcss/typography'),
+    require('@tailwindcss/forms'),
+    require('tailwindcss-radix')(),
+  ],
 };