All blocks
Block
Attachments above input
Multimodal compose row. Drag a file anywhere on the preview or click the paperclip — chips appear in the attachment-preview row above the textarea.
Drop-anywhere multimodal compose
<pk-file-upload
#fu
accept="image/*,application/pdf"
(filesAdded)="onFilesAdded($event)"
>
@if (attachments().length) {
<pk-attachment-preview
[attachments]="attachments()"
(removed)="onRemove($event)"
/>
}
<!-- spartan input-group: textarea plus a block-end addon -->
<div hlmInputGroup class="rounded-3xl">
<textarea
hlmInputGroupTextarea
placeholder="Describe what you want or drop files..."
[value]="value()"
(input)="value.set($any($event.target).value)"
(keydown.enter)="onEnter($event)"
></textarea>
<div hlmInputGroupAddon align="block-end" class="justify-between">
<button hlmBtn size="icon-sm" variant="ghost" hlmTooltip="Attach files"
(click)="fu.openPicker(); $event.stopPropagation()">
<ng-icon name="lucidePaperclip" class="text-[length:--spacing(4)]" />
</button>
<button hlmBtn size="icon-sm" class="rounded-full" hlmTooltip="Send" (click)="onSubmit()">
<ng-icon name="lucideArrowUp" class="text-[length:--spacing(3)]" />
</button>
</div>
</div>
<pk-file-upload-content>
<!-- drag-state overlay -->
</pk-file-upload-content>
</pk-file-upload>
// Component
protected readonly value = signal('');
protected readonly attachments = signal<Attachment[]>([]);
protected onFilesAdded(files: File[]): void {
const next: Attachment[] = files.map((f, i) => ({
id: `${Date.now()}-${i}`,
name: f.name,
type: f.type.startsWith('image/') ? 'image' : 'file',
size: f.size,
mimeType: f.type,
}));
this.attachments.update(list => [...list, ...next]);
}
protected onRemove(id: string): void {
this.attachments.update(list => list.filter(a => a.id !== id));
}