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')(), + ], };