diff --git a/.storybook/preview.js b/.storybook/preview.js index 6e5642a9a16..45e279530f3 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -10,6 +10,7 @@ import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtim import { Toaster } from '../components/ui/Toaster'; import { TooltipProvider } from '../components/ui/Tooltip'; import UserProvider from '../components/UserProvider'; +import { ModalProvider } from '../components/ModalContext'; import 'react-pdf/dist/esm/Page/TextLayer.css'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; @@ -33,12 +34,14 @@ export const decorators = [ - - - - - - + + + + + + + + diff --git a/components/DrawerHeader.tsx b/components/DrawerHeader.tsx index dcfbf8bd4af..1e2ee2bf489 100644 --- a/components/DrawerHeader.tsx +++ b/components/DrawerHeader.tsx @@ -3,6 +3,7 @@ import clsx from 'clsx'; import { MoreHorizontal, X } from 'lucide-react'; import { FormattedMessage } from 'react-intl'; +import type { Action } from '../lib/actions/types'; import { useWindowResize, VIEWPORTS } from '../lib/hooks/useWindowResize'; import { DropdownActionItem } from './table/RowActionsMenu'; @@ -10,7 +11,18 @@ import { Button } from './ui/Button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuTrigger } from './ui/DropdownMenu'; import { SheetClose } from './ui/Sheet'; -export default function DrawerHeader({ actions, entityName, entityIdentifier, entityLabel, dropdownTriggerRef }) { +type DrawerHeaderProps = { + actions?: { + primary?: Action[]; + secondary?: Action[]; + }; + entity: React.ReactNode; + identifier: React.ReactNode; + label: React.ReactNode; + dropdownTriggerRef?: React.RefObject; +}; + +export default function DrawerHeader({ actions, entity, identifier, label, dropdownTriggerRef }: DrawerHeaderProps) { const { viewport } = useWindowResize(); const isMobile = viewport === VIEWPORTS.XSMALL; const { primary, secondary } = actions || {}; @@ -20,9 +32,9 @@ export default function DrawerHeader({ actions, entityName, entityIdentifier, en - {entityName} + {entity} - {entityIdentifier} + {identifier} @@ -37,14 +49,14 @@ export default function DrawerHeader({ actions, entityName, entityIdentifier, en - {entityLabel} + {label} {primary?.map(action => ( - {isMobile && primary?.map(action => )} + {isMobile && primary?.map(action => )} {isMobile && primary.length > 0 && secondary.length > 0 && } - {secondary?.map(action => )} + {secondary?.map(action => )} )} diff --git a/components/dashboard/sections/legal-documents/LegalDocumentDrawer.tsx b/components/dashboard/sections/legal-documents/LegalDocumentDrawer.tsx index 679d10e9df7..acd055e2e3d 100644 --- a/components/dashboard/sections/legal-documents/LegalDocumentDrawer.tsx +++ b/components/dashboard/sections/legal-documents/LegalDocumentDrawer.tsx @@ -84,9 +84,9 @@ export default function LegalDocumentDrawer({ {document.id}} - entityLabel={ + entity={intl.formatMessage({ defaultMessage: 'Tax form', id: 'TaxForm' })} + identifier={{document.id}} + label={ } - entityLabel={ + entity={} + label={ } dropdownTriggerRef={dropdownTriggerRef} - entityIdentifier={ + identifier={ } /> diff --git a/components/dashboard/sections/transactions/TransactionDrawer.tsx b/components/dashboard/sections/transactions/TransactionDrawer.tsx index babb082cc58..89fd1b4e55e 100644 --- a/components/dashboard/sections/transactions/TransactionDrawer.tsx +++ b/components/dashboard/sections/transactions/TransactionDrawer.tsx @@ -265,13 +265,13 @@ function TransactionDetails({ transactionId, getActions }: TransactionDetailsPro } - entityIdentifier={ + entity={} + identifier={ }> #{id} } - entityLabel={ + label={ {loading ? ( @@ -286,8 +286,7 @@ function TransactionDetails({ transactionId, getActions }: TransactionDetailsPro amountStyles={{ letterSpacing: 0 }} showCurrencyCode={false} /> - - + {' '} {transaction?.netAmount.currency} ) diff --git a/lib/actions/types.ts b/lib/actions/types.ts index d4f9109e003..22d5f1a7d32 100644 --- a/lib/actions/types.ts +++ b/lib/actions/types.ts @@ -1,11 +1,12 @@ import type { LucideIcon } from 'lucide-react'; import type React from 'react'; -type Action = { +export type Action = { label: React.ReactNode; onClick: () => void; Icon?: LucideIcon; isLoading?: boolean; + variant?: string; // TODO: use button variant disabled?: boolean; 'data-cy'?: string; key: string; diff --git a/stories/patterns/DrawerHeader.stories.tsx b/stories/patterns/DrawerHeader.stories.tsx new file mode 100644 index 00000000000..77d7f629308 --- /dev/null +++ b/stories/patterns/DrawerHeader.stories.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import { Delete, Pencil, Share, Trash } from 'lucide-react'; + +import DrawerHeader from '../../components/DrawerHeader'; +import { Button } from '../../components/ui/Button'; +import { Sheet, SheetContent, SheetTrigger } from '../../components/ui/Sheet'; +import { CopyID } from '../../components/CopyId'; +import { useModal } from '../../components/ModalContext'; +import { Input } from '../../components/ui/Input'; + +const meta = { + component: DrawerHeader, + parameters: { + docs: { + description: { + component: ` +The \`DrawerHeader\` component is used to display the header section within a drawer. +It includes an identifier, a name, a label, and a set of actions that can be taken. + +## Usage +The \`DrawerHeader\` component is typically used inside a \`Sheet\` component to provide a context-specific header with actions for entities like transactions or other data types. + +### Props +#### Entity +This is the name of the entity being displayed in the header, e.g. "Agreement", "Transaction", "Expense", etc. + +#### Identifier +This is the unique identifier of the entity being displayed in the header, e.g. the ID of the agreement, the transaction ID, etc. + +#### Label +This is the label/title of the entity being displayed in the header, which might be different things depending on the enitity e.g. "Coworking at Kolgruvan" for an Agreement, "$43.21 USD" for a Transaction, Avatar + "BackYourStack" for a Collective + +## Example +This example demonstrates how to use \`DrawerHeader\` with a primary action to edit the entity. + +\`\`\`jsx + + + Open drawer + + + #{account.id}} + entity="Agreement" + label="Test Collective" + actions={{ + primary: [ + { + key: 'edit', + label: 'Edit', + onClick: () => {}, + Icon: Pencil, + }, + ], + secondary: [ + { + key: 'share', + label: 'Share', + onClick: () => {}, + Icon: Share, + }, + { + key: 'delete', + label: 'Delete', + onClick: () => {}, + Icon: Trash, + }, + ], + }} + /> + {/* Drawer contents */} + + +\`\`\` + `, + }, + }, + }, +}; + +export default meta; + +const ExampleComponent: React.FC = () => { + const account = { + id: '1234', + }; + const { showConfirmationModal } = useModal(); + const [isEditing, setEditing] = React.useState(false); + const defaultActions = { + primary: [ + { + key: 'edit', + label: 'Edit', + onClick: () => setEditing(true), + Icon: Pencil, + }, + ], + secondary: [ + { + key: 'share', + label: 'Share', + onClick: () => {}, + Icon: Share, + }, + { + key: 'delete', + label: 'Delete', + onClick: () => + showConfirmationModal({ + title: 'Delete agreement?', + description: 'This will permanently delete the agreement and all attachments and comments.', + confirmLabel: 'Delete', + variant: 'destructive', + onConfirm: () => {}, + }), + Icon: Trash, + }, + ], + }; + const editingActions = { + primary: [ + { + key: 'cancel', + label: 'Cancel', + variant: 'outline', + onClick: () => { + setEditing(false); + }, + }, + { + key: 'save', + label: 'Save', + variant: 'default', + onClick: () => setEditing(false), + // Icon: Check, + }, + ], + }; + const label = 'Coworking at Kolgruvan'; + return ( + + + Open drawer + + + #{account.id}} + entity="Agreement" + label={label} + actions={isEditing ? editingActions : defaultActions} + /> + + + ); +}; +export const Example = { + render: () => , +};