mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
console: add tabs component
## 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
This commit is contained in:
parent
6e578f229a
commit
6cf337c0fa
11
console/package-lock.json
generated
11
console/package-lock.json
generated
@ -87,6 +87,7 @@
|
|||||||
"styled-components": "5.0.1",
|
"styled-components": "5.0.1",
|
||||||
"styled-system": "5.1.5",
|
"styled-system": "5.1.5",
|
||||||
"subscriptions-transport-ws": "0.9.16",
|
"subscriptions-transport-ws": "0.9.16",
|
||||||
|
"tailwindcss-radix": "^2.5.0",
|
||||||
"ts-essentials": "7.0.3",
|
"ts-essentials": "7.0.3",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"xstate": "^4.30.1",
|
"xstate": "^4.30.1",
|
||||||
@ -38429,6 +38430,11 @@
|
|||||||
"postcss": "^8.0.9"
|
"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": {
|
"node_modules/tailwindcss/node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"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": {
|
"tapable": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||||
|
@ -140,6 +140,7 @@
|
|||||||
"styled-components": "5.0.1",
|
"styled-components": "5.0.1",
|
||||||
"styled-system": "5.1.5",
|
"styled-system": "5.1.5",
|
||||||
"subscriptions-transport-ws": "0.9.16",
|
"subscriptions-transport-ws": "0.9.16",
|
||||||
|
"tailwindcss-radix": "^2.5.0",
|
||||||
"ts-essentials": "7.0.3",
|
"ts-essentials": "7.0.3",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"xstate": "^4.30.1",
|
"xstate": "^4.30.1",
|
||||||
|
97
console/src/new-components/Tabs/Tabs.stories.mdx
Normal file
97
console/src/new-components/Tabs/Tabs.stories.mdx
Normal file
@ -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
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
41
console/src/new-components/Tabs/Tabs.tsx
Normal file
41
console/src/new-components/Tabs/Tabs.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
};
|
1
console/src/new-components/Tabs/index.tsx
Normal file
1
console/src/new-components/Tabs/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Tabs';
|
@ -82,5 +82,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
|
plugins: [
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
require('@tailwindcss/forms'),
|
||||||
|
require('tailwindcss-radix')(),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user