Skip to content

Commit

Permalink
feat(components): add DropZone (#1489)
Browse files Browse the repository at this point in the history
* feat(components): add `DropZone`

* chore: add changeset

* fix: add gap
  • Loading branch information
Niznikr authored Nov 25, 2024
1 parent 21eb723 commit b7083bd
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/moody-bobcats-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@launchpad-ui/components": patch
---

Add `DropZone`
11 changes: 11 additions & 0 deletions packages/components/__tests__/DropZone.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';

import { render, screen } from '../../../test/utils';
import { DropZone } from '../src';

describe('DropZone', () => {
it('renders', () => {
render(<DropZone />);
expect(screen.getByRole('button')).toBeVisible();
});
});
24 changes: 24 additions & 0 deletions packages/components/src/DropZone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ForwardedRef } from 'react';
import type { DropZoneProps } from 'react-aria-components';

import { cva } from 'class-variance-authority';
import { forwardRef } from 'react';
import { DropZone as AriaDropZone } from 'react-aria-components';

import styles from './styles/DropZone.module.css';

const zone = cva(styles.zone);

const _DropZone = ({ className, ...props }: DropZoneProps, ref: ForwardedRef<HTMLDivElement>) => {
return <AriaDropZone {...props} ref={ref} className={zone({ className })} />;
};

/**
* A drop zone is an area into which one or multiple objects can be dragged and dropped.
*
* https://react-spectrum.adobe.com/react-aria/DropZone.html
*/
const DropZone = forwardRef(_DropZone);

export { DropZone };
export type { DropZoneProps };
2 changes: 2 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type { DatePickerProps, DateRangePickerProps } from './DatePicker';
export type { DialogProps, DialogTriggerProps } from './Dialog';
export type { DisclosureProps, DisclosurePanelProps } from './Disclosure';
export type { DropIndicatorProps } from './DropIndicator';
export type { DropZoneProps } from './DropZone';
export type { FieldErrorProps } from './FieldError';
export type { FieldGroupProps } from './FieldGroup';
export type { FileTriggerProps } from './FileTrigger';
Expand Down Expand Up @@ -99,6 +100,7 @@ export { DatePicker, DateRangePicker } from './DatePicker';
export { Dialog, DialogTrigger } from './Dialog';
export { Disclosure, DisclosurePanel } from './Disclosure';
export { DropIndicator } from './DropIndicator';
export { DropZone } from './DropZone';
export { FieldError } from './FieldError';
export { FieldGroup } from './FieldGroup';
export { FileTrigger } from './FileTrigger';
Expand Down
28 changes: 28 additions & 0 deletions packages/components/src/styles/DropZone.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.zone {
background-color: var(--lp-color-bg-ui-secondary);
border: 1px solid var(--lp-color-border-ui-primary);
border-radius: var(--lp-border-radius-medium);
align-items: center;
justify-content: center;
margin: 0;
outline: none;
padding: var(--lp-spacing-500) var(--lp-spacing-700);
display: inline-flex;
flex-direction: column;
gap: var(--lp-spacing-400);

&[data-focus-visible],
&[data-drop-target] {
outline: 2px solid var(--lp-color-shadow-interactive-focus);
outline-offset: -2px;
z-index: 2;
}

&[data-drop-target] {
background-color: var(--lp-color-bg-interactive-selected);
}

&[data-disabled] {
opacity: 0.64;
}
}
71 changes: 71 additions & 0 deletions packages/components/stories/DropZone.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Meta, StoryObj } from '@storybook/react';
import type { FileDropItem } from 'react-aria';

import { useState } from 'react';

import { Button, DropZone, FileTrigger, Text } from '../src';

const meta: Meta<typeof DropZone> = {
component: DropZone,
title: 'Components/Drag and drop/DropZone',
parameters: {
status: {
type: import.meta.env.STORYBOOK_PACKAGE_STATUS__COMPONENTS,
},
},
};

export default meta;

type Story = StoryObj<typeof DropZone>;

export const Example: Story = {
render: (args) => {
const [dropped, setDropped] = useState(false);

return (
<DropZone
onDrop={() => {
setDropped(true);
}}
{...args}
>
<Text slot="label">{dropped ? 'You dropped something' : 'Drop object here'}</Text>
</DropZone>
);
},
};

export const WithFileTrigger: Story = {
render: (args) => {
const [files, setFiles] = useState<string | null>(null);

return (
<DropZone
onDrop={(e) => {
const files = e.items.filter((file) => file.kind === 'file') as FileDropItem[];
const filenames = files.map((file) => file.name);
setFiles(filenames.join(', '));
}}
{...args}
>
<FileTrigger
allowsMultiple
onSelect={(e) => {
if (e) {
const files = Array.from(e);
const filenames = files.map((file) => file.name);
setFiles(filenames.join(', '));
}
}}
>
<Button>Select files</Button>
</FileTrigger>
<Text slot="label" style={{ display: 'block' }}>
{files || 'Drop files here'}
</Text>
</DropZone>
);
},
name: 'FileTrigger',
};

0 comments on commit b7083bd

Please sign in to comment.