Skip to content

Commit

Permalink
Breadcrumbs component
Browse files Browse the repository at this point in the history
  • Loading branch information
MrFlashAccount committed Jan 27, 2025
1 parent a64dd4a commit 0c4fd3d
Show file tree
Hide file tree
Showing 29 changed files with 1,775 additions and 363 deletions.
2 changes: 1 addition & 1 deletion app/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@
"@types/node": "^20.11.21",
"lib0": "^0.2.99",
"react": "^18.3.1",
"vitest": "3.0.0-beta.3"
"vitest": "3.0.3"
}
}
1 change: 1 addition & 0 deletions app/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"submit": "Submit",
"retry": "Retry",
"hide": "Hide",
"more": "More",

"arbitraryFetchError": "An error occurred while fetching data",
"arbitraryFetchImageError": "An error occurred while fetching an image",
Expand Down
26 changes: 13 additions & 13 deletions app/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
"test-dev:unit": "vitest",
"test-dev:integration": "cross-env NODE_ENV=production playwright test --ui",
"test-dev-dashboard:integration": "cross-env NODE_ENV=production playwright test ./integration-test/dashboard/ --ui",
"storybook:react": "cross-env FRAMEWORK=react storybook dev",
"storybook:vue": "cross-env FRAMEWORK=vue storybook dev",
"storybook:react": "cross-env FRAMEWORK=react storybook dev --port 6006 --no-open",
"storybook:vue": "cross-env FRAMEWORK=vue storybook dev --port 7007 --no-open",
"build-storybook:react": "cross-env FRAMEWORK=react storybook build",
"build-storybook:vue": "cross-env FRAMEWORK=vue storybook build",
"chromatic:react": "cross-env FRAMEWORK=react chromatic deploy",
Expand Down Expand Up @@ -147,15 +147,15 @@
"@open-rpc/server-js": "^1.9.5",
"@playwright/test": "^1.49.1",
"@react-types/shared": "3.27.0",
"@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-interactions": "^8.4.7",
"@storybook/addon-onboarding": "^8.4.7",
"@storybook/blocks": "^8.4.7",
"@storybook/react": "^8.4.7",
"@storybook/react-vite": "^8.4.7",
"@storybook/test": "^8.4.7",
"@storybook/vue3": "^8.4.7",
"@storybook/vue3-vite": "^8.4.7",
"@storybook/addon-essentials": "8.5.0",
"@storybook/addon-interactions": "8.5.0",
"@storybook/addon-onboarding": "8.5.0",
"@storybook/blocks": "8.5.0",
"@storybook/react": "8.5.0",
"@storybook/react-vite": "8.5.0",
"@storybook/test": "8.5.0",
"@storybook/vue3": "8.5.0",
"@storybook/vue3-vite": "8.5.0",
"@tanstack/react-query-devtools": "5.59.20",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1",
Expand Down Expand Up @@ -200,7 +200,7 @@
"resize-observer-polyfill": "1.5.1",
"shuffle-seed": "^1.1.6",
"sql-formatter": "^13.1.0",
"storybook": "^8.4.7",
"storybook": "8.5.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "1.0.7",
"tailwindcss-react-aria-components": "1.2.0",
Expand All @@ -209,7 +209,7 @@
"vite": "^6.0.7",
"vite-plugin-vue-devtools": "7.6.8",
"vite-plugin-wasm": "^3.4.1",
"vitest": "3.0.0-beta.3",
"vitest": "3.0.3",
"vue-react-wrapper": "^0.3.1",
"vue-tsc": "^2.2.0",
"yaml": "^2.7.0",
Expand Down
3 changes: 3 additions & 0 deletions app/gui/src/dashboard/assets/expand_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const STYLES = tv({
width: { auto: 'w-auto', full: 'w-full', min: 'w-min', max: 'w-max' },
gap: {
custom: '',
none: 'gap-0',
joined: 'gap-0',
large: 'gap-3.5',
medium: 'gap-2',
Expand Down
28 changes: 6 additions & 22 deletions app/gui/src/dashboard/components/AriaComponents/Button/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import type * as aria from '#/components/aria'
import type { ExtractFunction } from '#/utilities/tailwindVariants'
import type { ReactElement, ReactNode } from 'react'
import type { Addon, IconProp, TestIdProps } from '../types'
import type { BUTTON_STYLES, ButtonVariants } from './variants'

/**
Expand Down Expand Up @@ -61,19 +62,15 @@ interface PropsWithoutHref {

/** Base props for a button. */
export interface BaseButtonProps<Render>
extends Omit<ButtonVariants, 'iconOnly' | 'isJoined' | 'position'> {
extends Omit<ButtonVariants, 'iconOnly' | 'isJoined' | 'position'>,
TestIdProps {
/** If `true`, the loader will not be shown. */
readonly hideLoader?: boolean
/** Falls back to `aria-label`. Pass `false` to explicitly disable the tooltip. */
readonly tooltip?: ReactElement | string | false | null
readonly tooltipPlacement?: aria.Placement
/** The icon to display in the button */
readonly icon?:
| ReactElement
| string
| ((render: Render) => ReactElement | string | null)
| null
| undefined
readonly icon?: IconProp<Render>
/** When `true`, icon will be shown only when hovered. */
readonly showIconOnHover?: boolean
/**
Expand All @@ -82,7 +79,6 @@ export interface BaseButtonProps<Render>
*/
readonly onPress?: ((event: aria.PressEvent) => Promise<void> | void) | null | undefined
readonly contentClassName?: string
readonly testId?: string
readonly isDisabled?: boolean
readonly formnovalidate?: boolean
/**
Expand All @@ -94,20 +90,8 @@ export interface BaseButtonProps<Render>

readonly children?: ReactNode | ((render: Render) => ReactNode)

readonly addonStart?:
| ReactElement
| string
| false
| ((render: Render) => ReactElement | string | null)
| null
| undefined
readonly addonEnd?:
| ReactElement
| string
| false
| ((render: Render) => ReactElement | string | null)
| null
| undefined
readonly addonStart?: Addon<Render>
readonly addonEnd?: Addon<Render>
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export const BUTTON_STYLES = tv({
},
iconOnly: {
// Specified in the compoundVariants
true: '',
true: 'aspect-square',
},
rounded: {
full: 'rounded-full',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as twv from '#/utilities/tailwindVariants'

import { useEventCallback } from '#/hooks/eventCallbackHooks'
import { ResetButtonGroupContext } from '../Button'
import { Close } from './Close'
import * as dialogProvider from './DialogProvider'
import * as dialogStackProvider from './DialogStackProvider'
import { DialogTrigger } from './DialogTrigger'
Expand Down Expand Up @@ -86,7 +87,6 @@ export function Popover(props: PopoverProps) {
size,
rounded,
variant,
placement = 'bottom start',
isDismissable = true,
...ariaPopoverProps
} = props
Expand All @@ -110,7 +110,6 @@ export function Popover(props: PopoverProps) {
})
}
UNSTABLE_portalContainer={root}
placement={placement}
style={popoverStyle}
shouldCloseOnInteractOutside={() => false}
{...ariaPopoverProps}
Expand Down Expand Up @@ -209,3 +208,4 @@ function PopoverContent(props: PopoverContentProps) {
}

Popover.Trigger = DialogTrigger
Popover.Close = Close
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import EyeClosed from '#/assets/eye_crossed.svg'
import Folder from '#/assets/folder.svg'
import type { Meta, StoryObj } from '@storybook/react'

import { useText } from '#/providers/TextProvider'
import { expect, userEvent, within } from '@storybook/test'
import type { MenuProps } from '.'
import { Menu } from '.'
import { passwordSchema } from '../../../pages/authentication/schemas'
import { Button } from '../Button'
import { Popover } from '../Dialog'
import { Form } from '../Form'
import { Input } from '../Inputs'

const meta = {
title: 'Components/Menu',
component: Menu,
parameters: {
layout: 'centered',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const button = canvas.getByRole('button', { name: 'Open Menu' })
Expand Down Expand Up @@ -189,7 +191,7 @@ function MenuContentWithDescription() {
<Menu.Item icon={Folder} description="This is a description" shortcut="⌘O">
Open Submenu
</Menu.Item>
<Menu selectionMode="multiple" placement="right">
<Menu selectionMode="multiple">
<Menu.Item description="This is a description" icon={Eye}>
Submenu item
</Menu.Item>
Expand Down Expand Up @@ -268,3 +270,68 @@ export const DynamicContent: Story = {
await expect(canvas.getByRole('menu')).toBeInTheDocument()
},
}

export const WithPopover: Story = {
render: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { getText } = useText()
return (
<Menu.Trigger>
<Button>Open Menu</Button>

<Menu>
<Menu.Item>New File</Menu.Item>

<Menu.Separator />

<Menu.Item>Save</Menu.Item>
<Menu.Item>Cut</Menu.Item>
<Menu.Item>Copy</Menu.Item>
<Menu.Item>Paste</Menu.Item>
<Menu.Item>Delete</Menu.Item>
<Menu.Item>Rename</Menu.Item>
<Menu.Item>Move</Menu.Item>

<Menu.SubmenuTrigger>
<Menu.Item>Edit Secret</Menu.Item>

<Popover isDismissable={false}>
<Form
method="dialog"
schema={(z) => z.object({ name: z.string(), password: passwordSchema(getText) })}
onSubmit={() => new Promise((resolve) => setTimeout(resolve, 1000))}
>
<Input name="name" label="Name" />
<Input name="password" type="password" label="Password" testId="password" />

<Button.Group>
<Form.Submit>Save</Form.Submit>
<Popover.Close>Cancel</Popover.Close>
</Button.Group>
<Form.FormError />
</Form>
</Popover>
</Menu.SubmenuTrigger>
</Menu>
</Menu.Trigger>
)
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)

const button = canvas.getByRole('button', { name: 'Open Menu' })
await userEvent.click(button)

await userEvent.hover(canvas.getByRole('menuitem', { name: 'Edit Secret' }))

const nameInput = await canvas.findByRole('textbox', { name: 'Name' })
await userEvent.type(nameInput, 'John')

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const passwordInput = canvas.getByTestId('password').querySelector('input')!
await userEvent.type(passwordInput, 'abc123sadflmsdkf')

const saveButton = await canvas.findByRole('button', { name: 'Save' })
await userEvent.click(saveButton)
},
}
12 changes: 2 additions & 10 deletions app/gui/src/dashboard/components/AriaComponents/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { AnimatedBackground } from '../../AnimatedBackground'
import { Popover } from '../Dialog'
import { Separator, SEPARATOR_STYLES, type SeparatorProps } from '../Separator'
import { Text } from '../Text'
import type { Placement, TestIdProps } from '../types'
import type { TestIdProps } from '../types'
import { MenuItem } from './MenuItem'
import { MenuTrigger } from './MenuTrigger'

Expand Down Expand Up @@ -45,7 +45,6 @@ export interface MenuProps<T extends object>
TestIdProps {
readonly variant?: 'dark' | 'light'
readonly className?: string
readonly placement?: Placement
}

/** Props for {@link MenuSection} */
Expand Down Expand Up @@ -91,7 +90,6 @@ export const Menu = createHideableComponent(function Menu<T extends object>(prop
variant,
className,
children,
placement = 'bottom start',
variants = MENU_STYLES,
testId = 'menu',
...menuProps
Expand All @@ -100,13 +98,7 @@ export const Menu = createHideableComponent(function Menu<T extends object>(prop
const styles = variants()

return (
<Popover
variant={variant}
placement={placement}
className={styles.popover()}
size="xxsmall"
rounded="xxxlarge"
>
<Popover variant={variant} className={styles.popover()} size="xxsmall" rounded="xxxlarge">
{() => (
<AnimatedBackground>
<aria.Menu<T> data-testid={testId} className={styles.base({ className })} {...menuProps}>
Expand Down
22 changes: 8 additions & 14 deletions app/gui/src/dashboard/components/AriaComponents/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { memo, type ReactElement, type ReactNode } from 'react'
import type { MenuItemProps as AriaMenuItemProps, MenuItemRenderProps } from 'react-aria-components'
import { MenuItem as AriaMenuItem, Keyboard } from 'react-aria-components'
import { AnimatedBackground } from '../../AnimatedBackground'
import { Icon } from '../../Icon'
import SvgMask from '../../SvgMask'
import { Check } from '../Check'
import { Text, TEXT_STYLE } from '../Text'
import type { TestIdProps } from '../types'
import type { IconProp, TestIdProps } from '../types'

export const MENU_ITEM_STYLES = tv({
base: 'group flex w-full cursor-default gap-3 rounded-3xl px-[14px] py-1 outline-none transition-colors duration-75 text-left',
Expand Down Expand Up @@ -57,10 +58,7 @@ export type MenuItemProps<T extends object> = MenuItemBaseProps &
*/
export interface MenuItemBaseProps {
/** Icon to display before the menu item text. Can be a string (path to SVG), ReactElement, or a render function */
readonly icon?:
| ReactElement
| string
| ((renderProps: MenuItemRenderProps) => ReactElement | string)
readonly icon?: IconProp<MenuItemRenderProps>
/** Keyboard shortcut text to display */
readonly shortcut?: string
/** Additional class name */
Expand Down Expand Up @@ -166,15 +164,11 @@ interface MenuItemIconProps extends MenuItemRenderProps {
const MenuItemIcon = memo(function MenuItemIcon(props: MenuItemIconProps) {
const { icon, className, ...renderProps } = props

if (icon == null) return null

const iconContent = typeof icon === 'function' ? icon(renderProps) : icon

if (typeof iconContent === 'string') {
return <SvgMask src={iconContent} className={className} />
}

return iconContent
return (
<Icon color="current" renderProps={renderProps} className={className}>
{icon}
</Icon>
)
})

/** Renders the selection indicator for the menu item */
Expand Down
7 changes: 4 additions & 3 deletions app/gui/src/dashboard/components/AriaComponents/Text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,14 @@ const Heading = memo(
}),
)

Text.Heading = Heading

/** Text group component. It's used to visually group text elements together */
Text.Group = function TextGroup(props: React.PropsWithChildren) {
function TextGroup(props: React.PropsWithChildren) {
return (
<textProvider.TextProvider value={{ isInsideTextComponent: true }}>
{props.children}
</textProvider.TextProvider>
)
}

Text.Heading = Heading
Text.Group = TextGroup
Loading

0 comments on commit 0c4fd3d

Please sign in to comment.