{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "assignment-submission-dropzone",
  "title": "Assignment Submission Dropzone",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/assignment-submission-dropzone/assignment-submission-dropzone.component.ts",
      "content": "// University coursework submission portal with plagiarism check indicator, grading rubric,\n// and deadline timer. Port of the React block 1:1.\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  DestroyRef,\n  ElementRef,\n  Input,\n  ViewChild,\n  inject,\n  signal,\n} from '@angular/core'\nimport {\n  Award,\n  BookOpen,\n  CheckCircle2,\n  Clock,\n  FileArchive,\n  GraduationCap,\n  Info,\n  LucideAngularModule,\n  RotateCcw,\n  Save,\n  Send,\n  ShieldAlert,\n  ShieldCheck,\n  Timer,\n  Trash2,\n  UploadCloud,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport interface AttachedFile {\n  name: string\n  size: string\n  uploadedAt: string\n  similarity: number\n  status: string\n}\n\nexport interface RubricCriterion {\n  label: string\n  points: number\n}\n\nexport interface RubricCategory {\n  id: string\n  title: string\n  points: number\n  percentage: number\n  criteria: RubricCriterion[]\n}\n\nconst defaultRubricCategories: RubricCategory[] = [\n  {\n    id: 'architecture',\n    title: 'Architecture & Fault Tolerance',\n    points: 40,\n    percentage: 40,\n    criteria: [\n      { label: 'Consensus algorithm implementation (Raft / Paxos) & partition resilience', points: 20 },\n      { label: 'Service discovery, dynamic load balancing, & circuit breaking', points: 15 },\n      { label: 'Data replication & linearizable consistency guarantees', points: 5 },\n    ],\n  },\n  {\n    id: 'code-quality',\n    title: 'Code Quality & Test Coverage',\n    points: 30,\n    percentage: 30,\n    criteria: [\n      { label: 'Automated unit, fuzz, & integration test suite (>85% coverage)', points: 15 },\n      { label: 'Idiomatic concurrency patterns, memory safety, clean modularity', points: 10 },\n      { label: 'Reproducible CI/CD pipeline automation & static analysis pass', points: 5 },\n    ],\n  },\n  {\n    id: 'performance',\n    title: 'Performance Benchmarks',\n    points: 20,\n    percentage: 20,\n    criteria: [\n      { label: 'Sustained throughput under load (>15,000 req/sec benchmark)', points: 10 },\n      { label: 'P99 latency SLA (<25ms under 50% simulated node failure)', points: 10 },\n    ],\n  },\n  {\n    id: 'documentation',\n    title: 'Documentation & API Specs',\n    points: 10,\n    percentage: 10,\n    criteria: [\n      { label: 'OpenAPI 3.1 & gRPC Protobuf schema specifications', points: 5 },\n      { label: 'System architecture design doc & production deployment runbook', points: 5 },\n    ],\n  },\n]\n\nconst DEFAULT_FILE: AttachedFile = {\n  name: 'distributed_system_v1.zip',\n  size: '18.4 MB',\n  uploadedAt: 'Aug 24, 14:22 PST',\n  similarity: 0.8,\n  status: 'Ready to submit',\n}\n\nconst DEFAULT_NOTES =\n  'Included benchmark logs in the /docs folder. All 12 distributed Raft consensus nodes passed the Chaos Mesh partition tests under 500ms latency simulation.'\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-assignment-submission-dropzone, [ui-assignment-submission-dropzone]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiProgressComponent,\n    UiSeparatorComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"assignment-submission-dropzone\" [class]=\"rootClass\">\n      <!-- Assignment Header Card -->\n      <div ui-card class=\"shadow-xs\">\n        <div ui-card-content class=\"p-6\">\n          <div class=\"flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between\">\n            <div class=\"space-y-2\">\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <span ui-badge wrap variant=\"outline\" class=\"gap-1.5 font-medium\">\n                  <lucide-icon [img]=\"GraduationCap\" class=\"text-primary size-3.5\" />\n                  CS 401 · Advanced Distributed Systems\n                </span>\n                <span ui-badge wrap variant=\"secondary\" class=\"gap-1 text-xs\">\n                  <lucide-icon [img]=\"Award\" class=\"text-muted-foreground size-3\" />\n                  100 Points Possible\n                </span>\n              </div>\n\n              <h1 class=\"text-foreground text-xl font-semibold tracking-tight sm:text-2xl\">\n                Final Project: Distributed Microservices Architecture\n              </h1>\n\n              <p class=\"text-muted-foreground text-sm\">\n                Term: Fall 2026 · Instructor: Prof. Elena Rostova · Stanford School of Engineering\n              </p>\n            </div>\n\n            <!-- Deadline Timer & Badges -->\n            <div class=\"flex flex-col items-start gap-3 sm:flex-row sm:items-center lg:flex-col lg:items-end\">\n              <span ui-badge wrap variant=\"warning\" class=\"gap-1.5 px-3 py-1 text-xs font-medium\">\n                <lucide-icon [img]=\"Clock\" class=\"size-3.5\" />\n                Due Friday, Aug 28 at 23:59 PST · 4 Days Left\n              </span>\n\n              <div class=\"border-border bg-muted/40 flex items-center gap-2 rounded-lg border px-3 py-1.5 shadow-xs\">\n                <lucide-icon [img]=\"Timer\" class=\"text-muted-foreground size-4 shrink-0\" />\n                <div class=\"flex items-center gap-1.5 text-xs font-medium\">\n                  <div class=\"flex items-baseline gap-0.5\">\n                    <span class=\"text-foreground font-semibold\">04</span>\n                    <span class=\"text-muted-foreground text-xs\">d</span>\n                  </div>\n                  <span class=\"text-muted-foreground\">:</span>\n                  <div class=\"flex items-baseline gap-0.5\">\n                    <span class=\"text-foreground font-semibold\">07</span>\n                    <span class=\"text-muted-foreground text-xs\">h</span>\n                  </div>\n                  <span class=\"text-muted-foreground\">:</span>\n                  <div class=\"flex items-baseline gap-0.5\">\n                    <span class=\"text-foreground font-semibold\">32</span>\n                    <span class=\"text-muted-foreground text-xs\">m</span>\n                  </div>\n                  <span class=\"text-muted-foreground\">:</span>\n                  <div class=\"flex items-baseline gap-0.5\">\n                    <span class=\"text-foreground font-semibold\">15</span>\n                    <span class=\"text-muted-foreground text-xs\">s</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Submitted Confirmation Banner -->\n      @if (isSubmitted()) {\n        <div\n          class=\"border-success/30 bg-success/10 text-foreground flex flex-col gap-4 rounded-xl border p-5 sm:flex-row sm:items-center sm:justify-between\"\n          role=\"status\"\n        >\n          <div class=\"flex items-start gap-3\">\n            <div class=\"bg-success/20 text-success flex size-9 shrink-0 items-center justify-center rounded-full\">\n              <lucide-icon [img]=\"CheckCircle2\" class=\"size-5\" />\n            </div>\n            <div class=\"space-y-1\">\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <h2 class=\"text-base font-semibold\">Coursework Submitted Successfully</h2>\n                <span ui-badge wrap variant=\"success\" class=\"text-xs\">Receipt #SUB-CS401-2026-98124</span>\n              </div>\n              <p class=\"text-muted-foreground text-xs sm:text-sm\">\n                Submission timestamp: August 24, 2026 at 14:25 PST · Hash:\n                <code class=\"font-mono text-xs\">sha256:4a8b...7f12</code>\n              </p>\n            </div>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <button ui-button variant=\"outline\" size=\"sm\" (click)=\"handleResetSubmission()\">\n              <lucide-icon [img]=\"RotateCcw\" class=\"size-3.5\" />\n              Edit Submission\n            </button>\n          </div>\n        </div>\n      }\n\n      <!-- 2-Column Submission Layout -->\n      <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n        <!-- Left Column: Submission & Dropzone Form -->\n        <div class=\"space-y-6 lg:col-span-7 xl:col-span-7\">\n          <div ui-card class=\"shadow-xs\">\n            <div ui-card-header>\n              <div class=\"flex items-center justify-between\">\n                <div class=\"space-y-1\">\n                  <h3 ui-card-title class=\"text-lg font-semibold\">Submission & File Upload</h3>\n                  <p ui-card-description>\n                    Upload your completed project archive, code repository bundle, and accompanying documentation.\n                  </p>\n                </div>\n                <span ui-badge wrap variant=\"outline\" class=\"text-xs\">Attempt 1 of 3</span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-5\">\n              <!-- Submission Instructions & Constraints -->\n              <div class=\"border-border bg-muted/40 flex items-start gap-3 rounded-lg border p-3.5 text-xs shadow-xs\">\n                <lucide-icon [img]=\"Info\" class=\"text-primary mt-0.5 size-4 shrink-0\" />\n                <div class=\"space-y-0.5\">\n                  <p class=\"text-foreground font-medium\">Accepted formats: PDF, ZIP, TAR.GZ · Max file size: 50 MB</p>\n                  <p class=\"text-muted-foreground\">\n                    Ensure all Docker Compose manifests, benchmarking scripts, and unit tests are included in the\n                    archive root.\n                  </p>\n                </div>\n              </div>\n\n              <!-- File Dropzone -->\n              <div>\n                <input\n                  #fileInput\n                  type=\"file\"\n                  class=\"hidden\"\n                  accept=\".zip,.tar.gz,.tar,.pdf\"\n                  (change)=\"handleFileInput($event)\"\n                />\n\n                <div\n                  [class]=\"dropzoneClass\"\n                  (dragover)=\"handleDragOver($event)\"\n                  (dragleave)=\"handleDragLeave($event)\"\n                  (drop)=\"handleDrop($event)\"\n                >\n                  <div\n                    class=\"bg-primary/10 text-primary flex size-12 items-center justify-center rounded-full transition-transform duration-200 group-hover:scale-105\"\n                  >\n                    <lucide-icon [img]=\"UploadCloud\" class=\"size-6\" />\n                  </div>\n\n                  <div class=\"mt-3 space-y-1\">\n                    <p class=\"text-foreground text-sm font-medium\">Drag and drop your project archive here</p>\n                    <p class=\"text-muted-foreground text-xs\">or select a file directly from your local filesystem</p>\n                  </div>\n\n                  <div class=\"mt-4 flex items-center gap-2\">\n                    <button ui-button variant=\"outline\" size=\"sm\" type=\"button\" (click)=\"triggerBrowse()\">\n                      Browse files\n                    </button>\n                  </div>\n\n                  <div class=\"text-muted-foreground mt-4 flex items-center gap-1.5 text-xs\">\n                    <span class=\"border-border bg-muted rounded px-1.5 py-0.5 font-mono text-xs\">.zip</span>\n                    <span class=\"border-border bg-muted rounded px-1.5 py-0.5 font-mono text-xs\">.tar.gz</span>\n                    <span class=\"border-border bg-muted rounded px-1.5 py-0.5 font-mono text-xs\">.pdf</span>\n                  </div>\n                </div>\n              </div>\n\n              <!-- Attached Files List -->\n              <div class=\"space-y-2\">\n                <div class=\"flex items-center justify-between\">\n                  <span class=\"text-foreground text-xs font-semibold tracking-wider uppercase\">\n                    Attached Files ({{ file() ? '1' : '0' }} / 1)\n                  </span>\n                  @if (!file()) {\n                    <span class=\"text-muted-foreground text-xs\">No file attached</span>\n                  }\n                </div>\n\n                @if (file(); as attached) {\n                  <div\n                    class=\"border-border bg-card flex flex-col gap-3 rounded-lg border p-3.5 shadow-xs transition-colors sm:flex-row sm:items-center sm:justify-between\"\n                  >\n                    <div class=\"flex min-w-0 items-center gap-3\">\n                      <div\n                        class=\"bg-primary/10 text-primary flex size-10 shrink-0 items-center justify-center rounded-md\"\n                      >\n                        <lucide-icon [img]=\"FileArchive\" class=\"size-5\" />\n                      </div>\n                      <div class=\"min-w-0 space-y-0.5\">\n                        <p class=\"text-foreground truncate text-sm font-medium\">{{ attached.name }}</p>\n                        <p class=\"text-muted-foreground text-xs\">{{ attached.size }} · {{ attached.uploadedAt }}</p>\n                      </div>\n                    </div>\n\n                    <div class=\"flex items-center gap-2 self-end sm:self-center\">\n                      <span ui-badge wrap variant=\"secondary\" class=\"text-xs\">{{ attached.size }}</span>\n                      <span ui-badge wrap variant=\"outline\" class=\"border-success/30 text-success text-xs font-medium\">\n                        {{ attached.status }}\n                      </span>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"icon-sm\"\n                        class=\"text-muted-foreground hover:text-destructive\"\n                        title=\"Remove file\"\n                        aria-label=\"Remove attached file\"\n                        (click)=\"removeFile()\"\n                      >\n                        <lucide-icon [img]=\"Trash2\" class=\"size-4\" />\n                      </button>\n                    </div>\n                  </div>\n                } @else {\n                  <div\n                    class=\"border-border bg-muted/20 flex items-center justify-between rounded-lg border border-dashed p-3 text-xs\"\n                  >\n                    <span class=\"text-muted-foreground\">Sample archive cleared.</span>\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"xs\"\n                      class=\"text-primary text-xs\"\n                      (click)=\"restoreSampleFile()\"\n                    >\n                      Restore default file\n                    </button>\n                  </div>\n                }\n              </div>\n\n              <!-- Plagiarism & Similarity Pre-scan Indicator -->\n              @if (file(); as scanned) {\n                <div [class]=\"scanPanelClass\">\n                  <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n                    <div class=\"flex items-center gap-2\">\n                      @if (isCleanSimilarity()) {\n                        <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-5 shrink-0\" />\n                      } @else {\n                        <lucide-icon [img]=\"ShieldAlert\" class=\"text-warning size-5 shrink-0\" />\n                      }\n                      <div>\n                        <h3 class=\"text-foreground text-sm font-semibold\">Academic Integrity & Plagiarism Pre-Scan</h3>\n                        <p class=\"text-muted-foreground text-xs\">\n                          Scanned against 14.2M academic repositories & public open-source codebases\n                        </p>\n                      </div>\n                    </div>\n\n                    <span\n                      ui-badge\n                      wrap\n                      [variant]=\"isCleanSimilarity() ? 'success' : 'warning'\"\n                      class=\"gap-1 self-start text-xs font-medium sm:self-auto\"\n                    >\n                      <lucide-icon [img]=\"ShieldCheck\" class=\"size-3\" />\n                      {{ scanned.similarity }}% Similarity · {{ isCleanSimilarity() ? 'Clean' : 'Needs Review' }}\n                    </span>\n                  </div>\n\n                  <div class=\"space-y-1.5 pt-1\">\n                    <div class=\"flex items-center justify-between text-xs\">\n                      <span class=\"text-muted-foreground\">Overall similarity index</span>\n                      <span class=\"text-foreground font-mono font-medium\">\n                        {{ scanned.similarity }}% (Max threshold: 15.0%)\n                      </span>\n                    </div>\n                    <ui-progress [value]=\"scanned.similarity * 6.66\" class=\"h-1.5\" />\n                  </div>\n\n                  <div class=\"grid grid-cols-1 gap-2 pt-1 text-xs sm:grid-cols-3\">\n                    <div class=\"border-border/60 bg-card/60 rounded border p-2\">\n                      <span class=\"text-muted-foreground block text-xs\">Internet Sources</span>\n                      <span class=\"text-foreground font-medium\">0.0% match</span>\n                    </div>\n                    <div class=\"border-border/60 bg-card/60 rounded border p-2\">\n                      <span class=\"text-muted-foreground block text-xs\">Peer Submissions</span>\n                      <span class=\"text-foreground font-medium\">0.0% match</span>\n                    </div>\n                    <div class=\"border-border/60 bg-card/60 rounded border p-2\">\n                      <span class=\"text-muted-foreground block text-xs\">Standard Boilerplate</span>\n                      <span class=\"text-foreground font-medium\">{{ scanned.similarity }}% (Apache 2.0)</span>\n                    </div>\n                  </div>\n                </div>\n              }\n\n              <!-- Student Submission Notes Textarea -->\n              <div class=\"space-y-2\">\n                <label for=\"angular-submission-notes\" class=\"text-foreground text-sm font-medium\">\n                  Student Submission Notes & Execution Instructions\n                </label>\n                <ui-textarea\n                  id=\"angular-submission-notes\"\n                  [value]=\"notes()\"\n                  (valueChange)=\"notes.set($event)\"\n                  [rows]=\"3\"\n                  placeholder=\"Included benchmark logs in the /docs folder...\"\n                  class=\"text-sm\"\n                />\n                <p class=\"text-muted-foreground text-xs\">\n                  Provide notes on environment configurations, docker flags, or benchmark reproducibility steps.\n                </p>\n              </div>\n            </div>\n\n            <div\n              ui-card-footer\n              class=\"border-border flex flex-col gap-3 border-t pt-4 sm:flex-row sm:items-center sm:justify-between\"\n            >\n              <div class=\"text-muted-foreground flex items-center gap-2 text-xs\">\n                <span class=\"bg-success size-2 rounded-full\"></span>\n                @if (isDraftSaved()) {\n                  <span class=\"text-success font-medium\">Draft saved successfully!</span>\n                } @else {\n                  <span>Draft auto-saved 2 mins ago · rev 3</span>\n                }\n              </div>\n\n              <div class=\"flex w-full flex-wrap items-center gap-2.5 sm:w-auto\">\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"flex-1 sm:flex-initial\"\n                  [disabled]=\"isSubmitted()\"\n                  (click)=\"handleSaveDraft()\"\n                >\n                  <lucide-icon [img]=\"Save\" class=\"size-3.5\" />\n                  Save Draft\n                </button>\n                <button\n                  ui-button\n                  variant=\"default\"\n                  size=\"sm\"\n                  class=\"flex-1 sm:flex-initial\"\n                  [disabled]=\"!file() || isSubmitted()\"\n                  (click)=\"handleSubmit()\"\n                >\n                  <lucide-icon [img]=\"Send\" class=\"size-3.5\" />\n                  Submit Assignment\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- Right Column: Grading Rubric & Assessment Criteria -->\n        <div class=\"space-y-6 lg:col-span-5 xl:col-span-5\">\n          <div ui-card class=\"shadow-xs\">\n            <div ui-card-header>\n              <div class=\"flex items-center justify-between\">\n                <div class=\"space-y-1\">\n                  <h3 ui-card-title class=\"flex items-center gap-2 text-lg font-semibold\">\n                    <lucide-icon [img]=\"BookOpen\" class=\"text-primary size-5\" />\n                    Grading Rubric\n                  </h3>\n                  <p ui-card-description>100 Points Total · Evaluated against course criteria</p>\n                </div>\n                <span ui-badge wrap variant=\"outline\" class=\"font-mono text-xs\">Pass: 70 pts</span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-5\">\n              @for (category of rubricCategories; track category.id; let last = $last) {\n                <div class=\"space-y-3\">\n                  <div class=\"space-y-1.5\">\n                    <div class=\"flex items-center justify-between\">\n                      <h4 class=\"text-foreground text-sm font-medium\">{{ category.title }}</h4>\n                      <span ui-badge wrap variant=\"secondary\" class=\"font-mono text-xs\">\n                        {{ category.points }} pts ({{ category.percentage }}%)\n                      </span>\n                    </div>\n                    <ui-progress [value]=\"category.percentage\" class=\"h-1.5\" />\n                  </div>\n\n                  <ul class=\"border-border/60 bg-muted/20 space-y-2 rounded-lg border p-2.5 text-xs\">\n                    @for (criterion of category.criteria; track criterion.label) {\n                      <li class=\"flex items-start justify-between gap-2\">\n                        <div class=\"flex min-w-0 items-start gap-1.5\">\n                          <span class=\"text-primary font-bold\">·</span>\n                          <span class=\"text-muted-foreground\">{{ criterion.label }}</span>\n                        </div>\n                        <span class=\"text-foreground shrink-0 font-mono font-medium\">{{ criterion.points }} pts</span>\n                      </li>\n                    }\n                  </ul>\n\n                  @if (!last) {\n                    <div ui-separator class=\"mt-4\"></div>\n                  }\n                </div>\n              }\n            </div>\n\n            <div\n              ui-card-footer\n              class=\"border-border bg-muted/30 text-muted-foreground flex flex-col gap-2 rounded-b-xl border-t p-4 text-xs\"\n            >\n              <div class=\"flex items-start gap-2\">\n                <lucide-icon [img]=\"Info\" class=\"text-muted-foreground mt-0.5 size-4 shrink-0\" />\n                <p>\n                  <strong class=\"text-foreground font-medium\">Late Submission Policy:</strong> Deductions of 5% apply\n                  per 24 hours delayed up to a maximum 48-hour grace period.\n                </p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiAssignmentSubmissionDropzoneComponent {\n  protected readonly Award = Award\n  protected readonly BookOpen = BookOpen\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Clock = Clock\n  protected readonly FileArchive = FileArchive\n  protected readonly GraduationCap = GraduationCap\n  protected readonly Info = Info\n  protected readonly RotateCcw = RotateCcw\n  protected readonly Save = Save\n  protected readonly Send = Send\n  protected readonly ShieldAlert = ShieldAlert\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Timer = Timer\n  protected readonly Trash2 = Trash2\n  protected readonly UploadCloud = UploadCloud\n\n  protected readonly rubricCategories = defaultRubricCategories\n\n  @ViewChild('fileInput') private fileInput?: ElementRef<HTMLInputElement>\n\n  @Input() set initialFile(v: AttachedFile | null | undefined) {\n    this.file.set(v === undefined ? { ...DEFAULT_FILE } : v ? { ...v } : null)\n  }\n  @Input() set initialNotes(v: string) {\n    if (v !== undefined) this.notes.set(v)\n  }\n  @Input() set initialSubmitted(v: boolean) {\n    this.isSubmitted.set(!!v)\n  }\n  @Input('class') className?: string\n\n  readonly file = signal<AttachedFile | null>({ ...DEFAULT_FILE })\n  readonly notes = signal(DEFAULT_NOTES)\n  readonly isSubmitted = signal(false)\n  readonly isDraftSaved = signal(false)\n  readonly isDragging = signal(false)\n\n  private draftTimer: ReturnType<typeof setTimeout> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => clearTimeout(this.draftTimer))\n  }\n\n  get rootClass(): string {\n    return cn('space-y-6', this.className)\n  }\n\n  get dropzoneClass(): string {\n    return cn(\n      'group relative flex flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors duration-200',\n      this.isDragging()\n        ? 'border-primary bg-primary/5'\n        : 'border-border bg-muted/10 hover:border-primary/50 hover:bg-muted/20',\n    )\n  }\n\n  get scanPanelClass(): string {\n    return cn(\n      'space-y-3 rounded-lg border p-4 shadow-xs transition-colors',\n      this.isCleanSimilarity()\n        ? 'border-success/30 bg-success/5 dark:bg-success/10'\n        : 'border-warning/30 bg-warning/5 dark:bg-warning/10',\n    )\n  }\n\n  isCleanSimilarity(): boolean {\n    const f = this.file()\n    return !f || f.similarity < 10\n  }\n\n  triggerBrowse(): void {\n    this.fileInput?.nativeElement.click()\n  }\n\n  handleFileInput(event: Event): void {\n    const input = event.target as HTMLInputElement\n    const picked = input.files?.[0]\n    if (picked) this.attachPickedFile(picked.name, picked.size)\n  }\n\n  handleDrop(event: DragEvent): void {\n    event.preventDefault()\n    this.isDragging.set(false)\n    const picked = event.dataTransfer?.files?.[0]\n    if (picked) this.attachPickedFile(picked.name, picked.size)\n  }\n\n  handleDragOver(event: DragEvent): void {\n    event.preventDefault()\n    this.isDragging.set(true)\n  }\n\n  handleDragLeave(event: DragEvent): void {\n    event.preventDefault()\n    this.isDragging.set(false)\n  }\n\n  removeFile(): void {\n    this.file.set(null)\n  }\n\n  restoreSampleFile(): void {\n    this.file.set({ ...DEFAULT_FILE })\n  }\n\n  handleSaveDraft(): void {\n    this.isDraftSaved.set(true)\n    clearTimeout(this.draftTimer)\n    this.draftTimer = setTimeout(() => this.isDraftSaved.set(false), 3000)\n  }\n\n  handleSubmit(): void {\n    if (!this.file()) return\n    this.isSubmitted.set(true)\n  }\n\n  handleResetSubmission(): void {\n    this.isSubmitted.set(false)\n  }\n\n  private attachPickedFile(name: string, sizeBytes: number): void {\n    this.file.set({\n      name,\n      size: `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`,\n      uploadedAt: 'Just now',\n      similarity: 0.8,\n      status: 'Ready to submit',\n    })\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/assignment-submission-dropzone/assignment-submission-dropzone.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/assignment-submission-dropzone/index.ts",
      "content": "export {\n  UiAssignmentSubmissionDropzoneComponent,\n  type AttachedFile,\n  type RubricCategory,\n  type RubricCriterion,\n} from './assignment-submission-dropzone.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/assignment-submission-dropzone/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "University coursework submission portal with plagiarism check indicator, grading rubric, and deadline timer.",
  "categories": [
    "education",
    "app",
    "forms",
    "dashboard"
  ]
}