mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
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:
parent
c83897b82f
commit
bce4ed6f3f
@ -117,7 +117,7 @@ const SchemasDetails: React.VFC<{
|
||||
<Tabs
|
||||
value={tabState}
|
||||
onValueChange={state => setTabState(state)}
|
||||
headerTabBackgroundColor="bg-white"
|
||||
headerTabBackgroundColor="white"
|
||||
items={[
|
||||
{
|
||||
value: 'graphql',
|
||||
|
@ -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
|
||||
|
||||
```
|
||||
|
||||
```
|
@ -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>
|
||||
);
|
||||
},
|
||||
};
|
@ -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 ? (
|
||||
|
Loading…
Reference in New Issue
Block a user