console: improve dropdown api

## Description 🔖

This PR improves the DropdownButton API to make it more generic.

- It has been renamed Dropdown.
- The children are rendered as the trigger. You can now use other elements than a Button
- an option prop has been added to control all the radix ui element
- a group class name has been added to the trigger so you can style it based on its state, using tailwindcss-radix plugin
- stories of dropdown button as styled in the Hasura design system has been added

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5733
GitOrigin-RevId: 4d292ffcbec93ebae525764431e92b5ed87cb5b5
This commit is contained in:
Daniele Cammareri 2022-09-02 12:46:52 +02:00 committed by hasura-bot
parent 71de2e0a35
commit bcd5851164
6 changed files with 68 additions and 37 deletions

View File

@ -1,7 +1,7 @@
import { getConfirmation } from '@/components/Common/utils/jsUtils';
import { QueryCollectionEntry } from '@/metadata/types';
import { Button } from '@/new-components/Button';
import { DropdownButton } from '@/new-components/DropdownButton';
import { DropdownMenu } from '@/new-components/DropdownMenu';
import React, { useState } from 'react';
import { FaEllipsisH, FaPlusCircle } from 'react-icons/fa';
import { useDeleteQueryCollections } from '../../../QueryCollections/hooks/useDeleteQueryCollections';
@ -35,7 +35,7 @@ export const QueryCollectionHeader: React.FC<QueryCollectionHeaderProps> =
</p>
</div>
<div className="relative ml-auto mr-sm">
<DropdownButton
<DropdownMenu
items={[
[
<div
@ -75,8 +75,10 @@ export const QueryCollectionHeader: React.FC<QueryCollectionHeaderProps> =
],
]}
>
<FaEllipsisH />
</DropdownButton>
<Button>
<FaEllipsisH />
</Button>
</DropdownMenu>
</div>
<Button mode="primary" icon={<FaPlusCircle />}>
Add Operation

View File

@ -120,7 +120,7 @@ export const Button = (props: ButtonProps) => {
? React.cloneElement(icon, {
className: `inline-flex ${children && 'mr-2'} ${
size === 'sm' ? 'w-4 h-4' : 'w-5 h-5'
}`,
} ${icon.props.className}`,
})
: null}
<span className="whitespace-nowrap max-w-full">{children}</span>
@ -128,7 +128,7 @@ export const Button = (props: ButtonProps) => {
? React.cloneElement(icon, {
className: `inline-flex ${children && 'ml-2'} ${
size === 'sm' ? 'w-4 h-4' : 'w-5 h-5'
}`,
} ${icon.props.className}`,
})
: null}
</>

View File

@ -1 +0,0 @@
export * from './DropdownButton';

View File

@ -2,11 +2,13 @@ import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
import { action } from '@storybook/addon-actions';
import { TemplateStoriesFactory } from '@/utils/StoryUtils';
import { DropdownButton } from '@/new-components/DropdownButton';
import { Button } from '@/new-components/Button';
import { DropdownMenu } from '@/new-components/DropdownMenu';
import { FaChevronUp } from 'react-icons/fa';
<Meta
title="components/Dropdown Button 🧬"
component={DropdownButton}
title="components/Dropdown Menu 🧬"
component={DropdownMenu}
parameters={{
docs: { source: { type: 'code' } },
chromatic: { disableSnapshot: true },
@ -14,57 +16,79 @@ import { DropdownButton } from '@/new-components/DropdownButton';
decorators={[Story => <div className={'p-4'}>{Story()}</div>]}
/>
# DropdownButton ⚛️
# DropdownMenu ⚛️
- [🧰 Overview](#-overview)
- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/DropdownButton/DropdownButton.tsx)
- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/DropdownMenu/DropdownMenu.tsx)
## 🧰 Overview
A component that display a button that opens a dropdown menu.
A component that display an element that opens a dropdownMenu menu.
### Basic usage
```ts
import { DropdownButton } from '@/new-components/DropdownButton';
import { DropdownMenu } from '@/new-components/DropdownMenu';
```
```tsx
<DropdownButton
<DropdownMenu
items={[
['Action', <span className="text-red-600">Destructive Action</span>],
['Another action'],
]}
>
The DropdownButton label
</DropdownButton>
The DropdownMenu label
</DropdownMenu>
```
<Canvas>
<Story name="Overview">
<div>
<DropdownButton
<DropdownMenu
items={[
['Action', <span className="text-red-600">Destructive Action</span>],
['Another action'],
]}
>
The DropdownButton label
</DropdownButton>
The DropdownMenu label
</DropdownMenu>
</div>
</Story>
</Canvas>
<Canvas>
<Story name="Dropdown Button">
<div>
<DropdownMenu
items={[
['Action', <span className="text-red-600">Destructive Action</span>],
['Another action'],
]}
>
<Button
iconPosition="end"
icon={
<FaChevronUp className="transition-transform group-radix-state-open:rotate-180" />
}
>
Dropdown Button
</Button>
</DropdownMenu>
</div>
</Story>
</Canvas>
#### 🚦 Usage
- The dropdown button is used for opening a contextual menu with list of actions
- The `items` prop is an array of array of react nodes (each array is a group of items) that will be rendered in the dropdown
- The Button prop is an object of props forwarded to the button component
- The children of this component are forwarded to the button component
- The dropdownMenu button is used for opening a contextual menu with list of actions
- The `items` prop is an array of array of react nodes (each array is a group of items) that will be rendered in the dropdownMenu
- The `children` of this component is the component (usually a button) that opens the menu
- the `options` is an object of props that will be passed to radix ui components
## ⚙️ API
export const Template = args => <DropdownButton {...args} />;
export const Template = args => <DropdownMenu {...args} />;
<Canvas>
<Story
@ -74,7 +98,7 @@ export const Template = args => <DropdownButton {...args} />;
['Action', <span className="text-red-600">Destructive</span>],
['Another action', 'Another action'],
],
children: 'Button label',
children: 'Label',
}}
>
{Template.bind({})}

View File

@ -1,28 +1,33 @@
import React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Button } from '@/new-components/Button';
interface DropdownButtonProps {
buttonProps?: React.ComponentProps<typeof Button>;
interface DropdownMenuProps {
options?: {
root?: React.ComponentProps<typeof DropdownMenuPrimitive.Root>;
trigger?: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>;
content?: React.ComponentProps<typeof DropdownMenuPrimitive.Content>;
item?: React.ComponentProps<typeof DropdownMenuPrimitive.Item>;
portal?: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>;
};
items: React.ReactNode[][];
}
export const DropdownButton: React.FC<DropdownButtonProps> = ({
export const DropdownMenu: React.FC<DropdownMenuProps> = ({
children,
buttonProps,
items,
options,
}) => (
<DropdownMenuPrimitive.Root>
<DropdownMenuPrimitive.Trigger>
<Button {...buttonProps}>{children}</Button>
<DropdownMenuPrimitive.Root {...options?.root}>
<DropdownMenuPrimitive.Trigger asChild {...options?.trigger}>
<div className="group">{children}</div>
</DropdownMenuPrimitive.Trigger>
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content align="start">
<DropdownMenuPrimitive.Portal {...options?.portal}>
<DropdownMenuPrimitive.Content align="start" {...options?.content}>
<div className="origin-top-left absolute left-0 z-10 mt-xs w-max max-w-xs rounded shadow-md bg-white ring-1 ring-gray-300 divide-y divide-gray-300 focus:outline-none">
{items.map(group => (
<div className="py-1">
{group.map(item => (
<DropdownMenuPrimitive.Item>
<DropdownMenuPrimitive.Item {...options?.item}>
<div className="cursor-pointer flex items-center mx-1 px-xs py-xs rounded whitespace-nowrap hover:bg-gray-100">
{item}
</div>

View File

@ -0,0 +1 @@
export * from './DropdownMenu';