Feat: Add Copy URL button and reusable toast notification system
Detail page now has a "Copy URL" button that copies the image's direct file URL to the clipboard. A toast service (BehaviorSubject-backed, auto-dismissing after 3s) confirms success or failure. ToastComponent is registered at the app root and available to all future features. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
50
specs/016-copy-url-toast/contracts/toast-service.md
Normal file
50
specs/016-copy-url-toast/contracts/toast-service.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Contract: ToastService
|
||||
|
||||
**Location**: `ui/src/app/services/toast.service.ts`
|
||||
**Provided in**: `root` (singleton)
|
||||
|
||||
## Interface
|
||||
|
||||
```typescript
|
||||
interface Toast {
|
||||
message: string;
|
||||
type: 'success' | 'error';
|
||||
}
|
||||
|
||||
class ToastService {
|
||||
// Observable — emits a Toast when one is active, null when none.
|
||||
readonly current$: Observable<Toast | null>;
|
||||
|
||||
// Show a toast. Replaces any currently-visible toast.
|
||||
// duration defaults to 3000ms.
|
||||
show(message: string, type?: 'success' | 'error', duration?: number): void;
|
||||
}
|
||||
```
|
||||
|
||||
## Behaviour
|
||||
|
||||
- `show()` emits the toast immediately on `current$`.
|
||||
- After `duration` ms, emits `null` to dismiss.
|
||||
- Calling `show()` again before the timer expires resets the timer (new toast replaces old).
|
||||
- `type` defaults to `'success'`.
|
||||
- `duration` defaults to `3000`.
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
// In any component:
|
||||
constructor(private toast: ToastService) {}
|
||||
|
||||
async copyUrl() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
this.toast.show('URL copied!');
|
||||
} catch {
|
||||
this.toast.show('Failed to copy URL', 'error');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Consumer: ToastComponent
|
||||
|
||||
`ToastComponent` subscribes to `current$` via the `async` pipe and renders/hides based on the emitted value. It is placed once in `AppComponent` and is always present in the DOM.
|
||||
Reference in New Issue
Block a user