Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# `<ShareDialog />`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`<ShareDialog />` renders the modal sharing UI for a partition. It
exposes a recipients list, an add-recipient form (text input plus
permission select), and a revoke button per row. The component is
controlled — `open`, `recipients`, `loading`, and `errorMessage` are
all owned by the parent. Wire it to `useSharing` for live engine
integration or hand it static props in Storybook.
## Features
- Controlled `open` prop — renders `null` when closed so callers can
conditionally mount.
- Focus moves to the recipient input on open.
- ESC key closes the dialog. Backdrop click also closes; clicks
inside the dialog do NOT close (stopPropagation).
- ARIA: `role="dialog"`, `aria-modal="true"`, `aria-labelledby` tied to
the title heading. Error region uses `role="alert"`.
- Recipient list reuses the same `data-grant-id` row contract as
`<FileAccessList />`.
- Localisable: pass `labels` to override every string. Permission
options localise individually.
## Usage: Basic Example
```tsx
import { ShareDialog } from "@suaveplan/drive-react-sharing";
import { useState } from "react";
function Trigger() {
const sharing = useSharing(/* ... */);
const [open, setOpen] = useState(false);
return (
<>
<button type="button" onClick={() => setOpen(true)}>Share</button>
<ShareDialog
open={open}
recipients={sharing.recipients}
onAdd={(id, p) => sharing.addRecipient(id, p)}
onRemove={(id) => sharing.removeRecipient(id)}
onClose={() => setOpen(false)}
/>
</>
);
}
```
## Usage: Advanced Example
Custom labels for a localised application:
```tsx
<ShareDialog
open={open}
recipients={sharing.recipients}
loading={sharing.loading}
errorMessage={sharing.error?.message}
defaultPermission="write"
onAdd={(id, p) => sharing.addRecipient(id, p)}
onRemove={(id) => sharing.removeRecipient(id)}
onClose={() => setOpen(false)}
labels={{
title: "Partager",
add: "Ajouter",
revoke: "Révoquer",
empty: "Aucun destinataire",
permissionOptions: {
read: "Lecture",
write: "Écriture",
delete: "Suppression",
share: "Partage",
admin: "Administrateur",
},
}}
/>;
```
## API Reference
- `ShareDialogProps`:
- `open: boolean` — required.
- `recipients: readonly ShareRecipient[]` — required.
- `onAdd(recipientId, permission): void | Promise<void>` — required.
- `onRemove(grantId): void | Promise<void>` — required.
- `onClose(): void` — required.
- `title?: string` — overrides `labels.title`.
- `loading?: boolean` — disables interactive controls.
- `errorMessage?: string` — renders `role="alert"` banner.
- `defaultPermission?: SharePermission` — initial select value.
- `labels?: Partial<ShareDialogLabels>` — string overrides.
- `ShareDialogLabels` — exported interface; permission options
override individually.
## Implementation Notes
The dialog is intentionally a controlled component because the open
state is usually tied to other UI in the parent (an avatar stack, a
"Share" button, a routing flag). Storybook can drive the entire surface
with mocked callbacks. The error region is rendered only when
`errorMessage` is a non-empty string — passing an undefined or
zero-length string hides it. The submit button is disabled when the
recipient draft is empty so users cannot accidentally fire `onAdd`
with whitespace.