Skip to content

Commit

Permalink
Add Drawer header story
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavlrsn committed Jul 4, 2024
1 parent 4c67f73 commit 95fcd10
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 26 deletions.
15 changes: 9 additions & 6 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -33,12 +34,14 @@ export const decorators = [
<ApolloProvider client={initClient()}>
<ThemeProvider theme={theme}>
<IntlProvider locale="en">
<UserProvider>
<TooltipProvider>
<Story />
<Toaster />
</TooltipProvider>
</UserProvider>
<ModalProvider>
<UserProvider>
<TooltipProvider>
<Story />
<Toaster />
</TooltipProvider>
</UserProvider>
</ModalProvider>
</IntlProvider>
</ThemeProvider>
</ApolloProvider>
Expand Down
28 changes: 20 additions & 8 deletions components/DrawerHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@ 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';
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<HTMLButtonElement>;
};

export default function DrawerHeader({ actions, entity, identifier, label, dropdownTriggerRef }: DrawerHeaderProps) {
const { viewport } = useWindowResize();
const isMobile = viewport === VIEWPORTS.XSMALL;
const { primary, secondary } = actions || {};
Expand All @@ -20,9 +32,9 @@ export default function DrawerHeader({ actions, entityName, entityIdentifier, en
<div className="flex flex-col gap-1 border-b px-6 py-4">
<div className={clsx('flex items-center justify-between gap-4')}>
<div className="flex shrink grow items-center gap-1 text-sm text-muted-foreground">
<span className="whitespace-nowrap">{entityName}</span>
<span className="whitespace-nowrap">{entity}</span>

<div className="w-0 max-w-fit flex-1">{entityIdentifier}</div>
<div className="w-0 max-w-fit flex-1">{identifier}</div>
</div>

<div className="flex items-center gap-1">
Expand All @@ -37,14 +49,14 @@ export default function DrawerHeader({ actions, entityName, entityIdentifier, en
</div>
</div>
<div className="flex justify-between">
<div className="flex items-center gap-2">{entityLabel}</div>
<div className="flex items-center gap-2 text-xl font-semibold">{label}</div>

<div className="flex items-center gap-1">
<div className="hidden items-center gap-1 sm:flex">
{primary?.map(action => (
<Button
key={action.label}
variant="outline"
key={action.key}
variant={action.variant || 'outline'}

Check failure on line 59 in components/DrawerHeader.tsx

View workflow job for this annotation

GitHub Actions / typescript

Type 'string' is not assignable to type '"secondary" | "link" | "default" | "outline" | "destructive" | "outlineDestructive" | "ghost"'.
size="xs"
className="gap-1.5"
onClick={action.onClick}
Expand All @@ -67,11 +79,11 @@ export default function DrawerHeader({ actions, entityName, entityIdentifier, en
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{isMobile && primary?.map(action => <DropdownActionItem key={action.label} action={action} />)}
{isMobile && primary?.map(action => <DropdownActionItem key={action.key} action={action} />)}

{isMobile && primary.length > 0 && secondary.length > 0 && <DropdownMenuSeparator />}

{secondary?.map(action => <DropdownActionItem key={action.label} action={action} />)}
{secondary?.map(action => <DropdownActionItem key={action.key} action={action} />)}
</DropdownMenuContent>
</DropdownMenu>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ export default function LegalDocumentDrawer({
<DrawerHeader
dropdownTriggerRef={dropdownTriggerRef}
actions={getActions(document, dropdownTriggerRef)}
entityName={intl.formatMessage({ defaultMessage: 'Tax form', id: 'TaxForm' })}
entityIdentifier={<CopyID value={document.id}>{document.id}</CopyID>}
entityLabel={
entity={intl.formatMessage({ defaultMessage: 'Tax form', id: 'TaxForm' })}
identifier={<CopyID value={document.id}>{document.id}</CopyID>}
label={
<AccountHoverCard
account={document.account}
trigger={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const TransactionsImportRowDrawer = ({
<React.Fragment>
<DrawerHeader
actions={getActions(row, dropdownTriggerRef)}
entityName={<FormattedMessage defaultMessage="Transaction import row" id="qqPBY/" />}
entityLabel={
entity={<FormattedMessage defaultMessage="Transaction import row" id="qqPBY/" />}
label={
<div className="text-2xl">
<span className={'font-bold text-foreground'}>
<FormattedMoneyAmount
Expand All @@ -50,7 +50,7 @@ export const TransactionsImportRowDrawer = ({
</div>
}
dropdownTriggerRef={dropdownTriggerRef}
entityIdentifier={
identifier={
<FormattedMessage defaultMessage="No. {number}" id="rowNumber" values={{ number: rowIndex + 1 }} />
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,13 @@ function TransactionDetails({ transactionId, getActions }: TransactionDetailsPro
<DrawerHeader
actions={actions}
dropdownTriggerRef={dropdownTriggerRef}
entityName={<FormattedMessage defaultMessage="Transaction" id="1+ROfp" />}
entityIdentifier={
entity={<FormattedMessage defaultMessage="Transaction" id="1+ROfp" />}
identifier={
<CopyID value={id} tooltipLabel={<FormattedMessage defaultMessage="Copy transaction ID" id="zzd7ZI" />}>
#{id}
</CopyID>
}
entityLabel={
label={
<React.Fragment>
{loading ? (
<Skeleton className="h-8 w-48" />
Expand All @@ -286,8 +286,7 @@ function TransactionDetails({ transactionId, getActions }: TransactionDetailsPro
amountStyles={{ letterSpacing: 0 }}
showCurrencyCode={false}
/>
</span>

</span>{' '}
<span className="text-muted-foreground">{transaction?.netAmount.currency}</span>
</div>
)
Expand Down
3 changes: 2 additions & 1 deletion lib/actions/types.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
158 changes: 158 additions & 0 deletions stories/patterns/DrawerHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React from 'react';

Check failure on line 1 in stories/patterns/DrawerHeader.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

Run autofix to sort these imports!
import { Delete, Pencil, Share, Trash } from 'lucide-react';

Check failure on line 2 in stories/patterns/DrawerHeader.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

'Delete' is defined but never used

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';

Check failure on line 9 in stories/patterns/DrawerHeader.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

'Input' is defined but never used

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
<Sheet>
<SheetTrigger asChild>
<Button>Open drawer</Button>
</SheetTrigger>
<SheetContent>
<DrawerHeader
identifier={<CopyID value={account.id}>#{account.id}</CopyID>}
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 */}
</SheetContent>
</Sheet>
\`\`\`
`,
},
},
},
};

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 (
<Sheet>
<SheetTrigger asChild>
<Button>Open drawer</Button>
</SheetTrigger>
<SheetContent>
<DrawerHeader
identifier={<CopyID value={account.id}>#{account.id}</CopyID>}
entity="Agreement"
label={label}
actions={isEditing ? editingActions : defaultActions}
/>
</SheetContent>
</Sheet>
);
};
export const Example = {
render: () => <ExampleComponent />,
};

0 comments on commit 95fcd10

Please sign in to comment.