console: add tab color and accent style options

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9795
GitOrigin-RevId: 7aba4c497ee08df413fbb3d0da30ff751acd0064
This commit is contained in:
Matthew Goodwin 2023-07-11 09:44:36 -05:00 committed by hasura-bot
parent c83897b82f
commit bce4ed6f3f
4 changed files with 119 additions and 107 deletions

View File

@ -117,7 +117,7 @@ const SchemasDetails: React.VFC<{
<Tabs
value={tabState}
onValueChange={state => setTabState(state)}
headerTabBackgroundColor="bg-white"
headerTabBackgroundColor="white"
items={[
{
value: 'graphql',

View File

@ -1,97 +0,0 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs';
import { action } from '@storybook/addon-actions';
import { TemplateStoriesFactory } from '../../utils/StoryUtils';
import { Tabs } from '.';
<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
```
```

View File

@ -0,0 +1,55 @@
import { Meta, StoryObj } from '@storybook/react';
import { Tabs } from './Tabs';
import randomWords from 'random-words';
import { useState } from 'react';
export default {
component: Tabs,
parameters: {
layout: 'fullscreen',
},
} satisfies Meta<typeof Tabs>;
const Content: React.FC = ({ children }) => (
<div data-chromatic="ignore" className="whitespace-pre-line break-words p-2">
{children}
</div>
);
export const Playground: StoryObj<typeof Tabs> = {
args: {
items: [
{
value: 'tab-1',
label: 'Tab 1',
content: <Content>{randomWords(100).join(' ')}</Content>,
},
{
value: 'tab-2',
label: 'Tab 2',
content: <Content>{randomWords(100).join(' ')}</Content>,
},
{
value: 'tab-3',
label: 'Tab 3',
content: <Content>{randomWords(100).join(' ')}</Content>,
},
],
},
render: args => (
<div className="m-2 max-w-xl">
<Tabs {...args} />
</div>
),
};
export const Controlled: StoryObj<typeof Tabs> = {
args: { ...Playground.args },
render: args => {
const [tab, setTab] = useState('tab-1');
return (
<div className="m-2 max-w-xl">
<Tabs {...args} value={tab} onValueChange={setTab} />
</div>
);
},
};

View File

@ -9,17 +9,70 @@ interface TabsItem {
content: React.ReactNode;
}
export type TabColor =
| 'green'
| 'red'
| 'yellow'
| 'indigo'
| 'gray'
| 'blue'
| 'purple';
type AccentStyle = 'underline' | 'background';
interface TabsCustomProps extends React.ComponentProps<typeof RadixTabs.Root> {
headerTabBackgroundColor?: string;
headerTabBackgroundColor?: 'white' | 'grey';
}
interface TabsProps extends TabsCustomProps {
items: TabsItem[];
color?: TabColor;
accentStyle?: AccentStyle;
}
export const Tabs: React.FC<TabsProps> = props => {
const { headerTabBackgroundColor, items, ...rest } = props;
const twThemes = {
underline: {
yellow: `radix-state-active:border-yellow-500 radix-state-active:text-yellow-500`,
blue: `radix-state-active:border-blue-500 radix-state-active:text-blue-800`,
green: `radix-state-active:border-green-500 radix-state-active:text-green-800`,
red: `radix-state-active:border-red-500 radix-state-active:text-red-800`,
gray: `radix-state-active:border-gray-500 radix-state-active:text-gray-800`,
indigo: `radix-state-active:border-indigo-500 radix-state-active:text-indigo-800`,
purple: `radix-state-active:border-purple-500 radix-state-active:text-purple-800`,
},
background: {
green: `radix-state-active:bg-green-100 radix-state-active:text-green-800`,
red: `radix-state-active:bg-red-100 radix-state-active:text-red-800`,
yellow: `radix-state-active:bg-primary-light/80 radix-state-active:!text-slate-700`,
gray: `radix-state-active:bg-gray-200 radix-state-active:text-gray-900`,
indigo: `radix-state-active:bg-indigo-100 radix-state-active:text-indigo-800`,
blue: `radix-state-active:bg-blue-100 radix-state-active:text-blue-800`,
purple: `radix-state-active:bg-purple-100 radix-state-active:text-purple-800`,
},
hoverStyle: {
yellow: `hover:border-yellow-300`,
blue: `hover:border-blue-300`,
green: `hover:border-green-300 `,
red: `hover:border-red-300 `,
gray: `hover:border-gray-300`,
indigo: `hover:border-indigo-300 `,
purple: `hover:border-purple-300 `,
},
};
const twTabBackgroundColors = {
white: `bg-white`,
grey: `bg-legacybg`,
};
export const Tabs = (props: TabsProps) => {
const {
headerTabBackgroundColor = 'grey',
items,
color = 'yellow',
accentStyle = 'underline',
...rest
} = props;
const backgroundColor = headerTabBackgroundColor ?? 'bg-legacybg';
return (
<RadixTabs.Root
defaultValue={rest?.defaultValue ?? items[0]?.value}
@ -27,17 +80,18 @@ export const Tabs: React.FC<TabsProps> = props => {
>
<RadixTabs.List aria-label="Tabs">
<div
className={`border-b border-gray-200 ${backgroundColor} flex space-x-4`}
className={`border-b border-gray-200 ${twTabBackgroundColors[headerTabBackgroundColor]} flex space-x-4`}
>
{items.map(({ value: itemValue, label, icon }) => (
<RadixTabs.Trigger key={itemValue} 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',
'border-transparent rounded-t text-muted whitespace-nowrap py-xs px-sm border-b-2 font-semibold tracking-[.015em] flex items-center gap-2',
twThemes.underline[color],
twThemes[accentStyle][color],
twThemes.hoverStyle[color],
// add focus visible outline for accessibility
'focus-visible:outline focus-visible:outline-2 outline-offset-2 focus-visible:outline-secondary'
'focus-visible:outline focus-visible:outline-2 outline-offset-4 focus-visible:outline-blue-400'
)}
>
{icon ? (