Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor DropdownMenu #1108

Merged
merged 5 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 19 additions & 30 deletions apps/design-system/src/components/DropdownMenu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
import { DropdownMenu } from '@asyncapi/studio-ui'
import React from 'react';
import { Meta } from '@storybook/react';
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuItem, DropdownMenuSeparator } from '@asyncapi/studio-ui'

export default {
const meta: Meta = {
component: DropdownMenu,
parameters: {
layout: 'fullscreen',
backgrounds: {
default: 'dark'
}
},
}
}

const items = [
{
title: 'Import from URL',
onSelect: () => console.log('Import from URL')
},
{
title: 'Import from file',
onSelect: () => console.log('Import from file')
},
{
title: 'Import from Base64',
onSelect: () => console.log('Import from Base64')
},
{
type: 'separator'
},
{
title: 'Generate code/docs',
onSelect: () => console.log('Generate code/docs')
},
]
export default meta


export const Default = {
args: {
trigger: <button className="text-black bg-white rounded mx-3 my-3 px-3">Click me!</button>,
items,
side: 'bottom',
align: 'start'
}
render: () => (<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="text-black bg-white rounded mx-3 my-3 px-3">Click me!</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onSelect={(e) => console.log(e.target)}>Import from URL</DropdownMenuItem>
<DropdownMenuItem onSelect={(e) => console.log(e.target)}>Import from file</DropdownMenuItem>
<DropdownMenuItem onSelect={(e) => console.log(e.target)}>Import from Base64</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={(e) => console.log(e.target)}>Generate code/docs</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>)
}
36 changes: 28 additions & 8 deletions apps/design-system/src/styles/tailwind.output.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
*/

/*
Expand Down Expand Up @@ -32,9 +32,11 @@
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/

html {
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
Expand All @@ -48,6 +50,8 @@ html {
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}

/*
Expand Down Expand Up @@ -119,8 +123,10 @@ strong {
}

/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/

code,
Expand All @@ -129,8 +135,12 @@ samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-size: 1em;
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}

/*
Expand Down Expand Up @@ -199,6 +209,8 @@ textarea {
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
Expand All @@ -222,9 +234,9 @@ select {
*/

button,
[type='button'],
[type='reset'],
[type='submit'] {
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
Expand Down Expand Up @@ -473,6 +485,10 @@ video {
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}

::backdrop {
Expand Down Expand Up @@ -523,6 +539,10 @@ video {
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}

.m-3 {
Expand Down
129 changes: 72 additions & 57 deletions packages/ui/components/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,80 @@
import type { FunctionComponent, ReactNode } from 'react'
import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'
"use client"

interface DropdownMenuRegularItem {
type?: 'regular'
title: string
onSelect: () => void
}
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"

interface DropdownMenuSeparatorItem {
type: 'separator'
}
import { cn } from "@asyncapi/studio-utils"

export type DropdownMenuItem = DropdownMenuRegularItem | DropdownMenuSeparatorItem
const DropdownMenu = DropdownMenuPrimitive.Root

interface DropdownMenuProps {
trigger: ReactNode
items: DropdownMenuItem[]
side?: 'top' | 'right' | 'bottom' | 'left'
align?: 'start' | 'center' | 'end'
}
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger

interface DropdownMenuItemComponentProps {
item: DropdownMenuItem
}
const DropdownMenuGroup = DropdownMenuPrimitive.Group

const DropdownMenuPortal = DropdownMenuPrimitive.Portal

const DropdownMenuSub = DropdownMenuPrimitive.Sub

const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup


const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 5, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"min-w-[220px] bg-gray-950 rounded-md p-2.5 shadow",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName

const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"text-gray-200 text-sm leading-7 px-2.5 cursor-pointer rounded outline-none select-none hover:bg-gray-700 focus:bg-gray-700",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName


const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("w-full h-px bg-gray-700 my-2", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName

const DropdownMenuItemComponent: FunctionComponent<DropdownMenuItemComponentProps> = ({ item }) => {
return (
item.type === 'separator' ? (
<RadixDropdownMenu.Separator className="w-full h-px bg-gray-700 my-2" />
) : (
<RadixDropdownMenu.Item
className="text-gray-200 text-sm leading-7 px-2.5 cursor-pointer rounded outline-none select-none hover:bg-gray-700 focus:bg-gray-700"
onSelect={item.onSelect}
>
{item.title}
</RadixDropdownMenu.Item>
)
)
}

export const DropdownMenu: FunctionComponent<DropdownMenuProps> = ({
trigger,
items,
side,
align,
}) => {
return (
<RadixDropdownMenu.Root>
<RadixDropdownMenu.Trigger aria-label='Select an Option.' asChild>
{trigger}
</RadixDropdownMenu.Trigger>
<RadixDropdownMenu.Portal>
<RadixDropdownMenu.Content className="min-w-[220px] bg-gray-950 rounded-md p-2.5 shadow" sideOffset={5} side={side} align={align}>
{
items.map((item, index) => (
<DropdownMenuItemComponent key={index} item={item} />
))
}
<RadixDropdownMenu.Arrow className="fill-gray-950" />
</RadixDropdownMenu.Content>
</RadixDropdownMenu.Portal>
</RadixDropdownMenu.Root>
)
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuRadioGroup,
}
33 changes: 23 additions & 10 deletions packages/ui/components/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Tooltip from './Tooltip'
import * as RadixToolbar from '@radix-ui/react-toolbar'
import { FunctionComponent } from 'react'
import { DropdownMenu, DropdownMenuItem } from './DropdownMenu'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from './DropdownMenu'

interface ToolbarToggleItem {
type: 'toggle'
Expand Down Expand Up @@ -31,10 +31,21 @@ interface ToolbarSeparatorItem {
type: 'separator'
}

interface DropdownMenuRegularItem {
type?: 'regular'
title: string
onSelect: () => void
}

interface DropdownMenuSeparatorItem {
type: 'separator'
}

type DropdownMenuItemInterface = DropdownMenuRegularItem | DropdownMenuSeparatorItem
interface ToolbarDropdownMenuItem {
type: 'dropdownMenu',
icon: FunctionComponent<any>
items: DropdownMenuItem[]
items: DropdownMenuItemInterface[]
}

type ToolbarItem = ToolbarToggleItem | ToolbarToggleGroupSingleItem | ToolbarToggleGroupMultipleItem | ToolbarSeparatorItem | ToolbarDropdownMenuItem
Expand Down Expand Up @@ -117,17 +128,19 @@ const ToolbarItem: FunctionComponent<ToolbarItemProps> = ({ item }) => {
)
} else if (type === 'dropdownMenu') {
return (
<DropdownMenu
trigger={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex text-sm focus:outline-none border-box py-2 my-3.5 text-gray-500 hover:text-white focus:text-white rounded">
<item.icon className="w-6 h-6"/>
</button>
}
items={item.items}
side="bottom"
align="end"
/>

</DropdownMenuTrigger>
<DropdownMenuContent>
{item.items.map((menuItem) => (
menuItem.type === 'separator' ? <DropdownMenuSeparator key={menuItem.type} /> :
<DropdownMenuItem key={menuItem.title} onSelect={menuItem.onSelect}>{menuItem.title}</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
throw new Error(`Unsupported type of Toolbar item: ${type}`)
Expand Down
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"eslint-config-custom": "workspace:*",
"postcss": "^8.4.31",
"tailwind-config": "workspace:*",
"@asyncapi/studio-utils": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^8.0.2",
"typescript": "^4.9.4"
Expand Down
21 changes: 21 additions & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@asyncapi/studio-utils",
"version": "0.0.0",
"description": "Shared utilities for studio.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"private": true,
"scripts": {
"build": "tsup"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"clsx": "^2.1.1",
"tailwind-merge": "^2.3.0"
},
"devDependencies": {
"tsup": "^8.0.2"
}
}
6 changes: 6 additions & 0 deletions packages/utils/src/class-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { cn } from './class-names'
Loading
Loading