{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "feature-drag-drop-organizer",
  "title": "Feature Drag Drop Organizer",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/feature-drag-drop-organizer/feature-drag-drop-organizer.component.ts",
      "content": "// Composable dashboard layout workbench: widget hierarchy with up/down reordering, visibility\n// toggles, a live canvas preview that follows the order, and a generated JSON schema with copy.\n// Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  Activity,\n  ArrowDown,\n  ArrowRight,\n  ArrowUp,\n  Check,\n  Copy,\n  Eye,\n  EyeOff,\n  Layers,\n  LucideAngularModule,\n  RotateCcw,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\n\nexport interface LayoutWidget {\n  id: string\n  name: string\n  category: string\n  visible: boolean\n  colSpan: number\n  previewSnippet: string\n}\n\nexport const INITIAL_WIDGETS: LayoutWidget[] = [\n  {\n    id: 'widget-kpi',\n    name: 'Real-time KPI Metrics',\n    category: 'Analytics',\n    visible: true,\n    colSpan: 12,\n    previewSnippet: '3 Cards: $124.5k MRR • 18ms Latency • 99.99% Uptime',\n  },\n  {\n    id: 'widget-chart',\n    name: 'Traffic Sparkline Chart',\n    category: 'Telemetry',\n    visible: true,\n    colSpan: 8,\n    previewSnippet: 'SVG Real-time stream (35,000 req/s live)',\n  },\n  {\n    id: 'widget-nodes',\n    name: 'Edge PoP Status',\n    category: 'Infrastructure',\n    visible: true,\n    colSpan: 4,\n    previewSnippet: '8 global edge regions active (optimal)',\n  },\n  {\n    id: 'widget-feed',\n    name: 'Deployment Activity Feed',\n    category: 'Workflows',\n    visible: true,\n    colSpan: 12,\n    previewSnippet: 'sha-a81f9c deployed to edge via CLI (2 mins ago)',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-feature-drag-drop-organizer, [ui-feature-drag-drop-organizer]',\n  standalone: true,\n  // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent],\n  template: `\n    <section data-slot=\"feature-drag-drop-organizer\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\">\n        <!-- Section Header -->\n        <div class=\"mx-auto max-w-3xl space-y-4 text-center\">\n          <a\n            href=\"#layout-organizer\"\n            class=\"group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors\"\n          >\n            <lucide-icon [img]=\"Layers\" class=\"text-primary size-3.5\" />\n            <span>Composable Layout Workbench</span>\n            <lucide-icon\n              [img]=\"ArrowRight\"\n              class=\"text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5\"\n            />\n          </a>\n\n          <h2 class=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl\">{{ title }}</h2>\n\n          <p class=\"text-muted-foreground text-base sm:text-lg\">{{ description }}</p>\n        </div>\n\n        <!-- Workbench Grid (2 Columns: Controls & Live Canvas) -->\n        <div class=\"mt-12 grid grid-cols-1 gap-6 lg:grid-cols-12\">\n          <!-- Left: Widget Re-ordering Controls (6 Cols) -->\n          <div ui-card class=\"border-border bg-card flex flex-col justify-between shadow-xs lg:col-span-6\">\n            <div ui-card-content class=\"space-y-5 p-6\">\n              <div class=\"border-border flex items-center justify-between border-b pb-3\">\n                <div class=\"flex items-center gap-2\">\n                  <lucide-icon [img]=\"Layers\" class=\"text-primary size-4\" />\n                  <span class=\"text-foreground text-sm font-semibold\">Widget Hierarchy</span>\n                </div>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  class=\"text-muted-foreground hover:text-foreground h-7 gap-1 px-2 font-mono text-xs active:scale-95\"\n                  (click)=\"resetLayout()\"\n                >\n                  <lucide-icon [img]=\"RotateCcw\" class=\"size-3\" />\n                  <span>Reset</span>\n                </button>\n              </div>\n\n              <!-- Reorderable List of Widgets -->\n              <div class=\"space-y-2\">\n                @for (widget of widgets(); track widget.id; let idx = $index) {\n                  <div [class]=\"widgetRowClass(widget)\">\n                    <div class=\"flex min-w-0 items-center gap-3\">\n                      <div class=\"text-muted-foreground flex items-center gap-1\">\n                        <button\n                          type=\"button\"\n                          [disabled]=\"idx === 0\"\n                          [attr.aria-label]=\"'Move ' + widget.name + ' up'\"\n                          class=\"hover:bg-muted rounded p-1 transition-[colors,transform] active:scale-90 disabled:opacity-30\"\n                          (click)=\"moveWidget(idx, 'up')\"\n                        >\n                          <lucide-icon [img]=\"ArrowUp\" class=\"size-3.5\" />\n                        </button>\n                        <button\n                          type=\"button\"\n                          [disabled]=\"idx === widgets().length - 1\"\n                          [attr.aria-label]=\"'Move ' + widget.name + ' down'\"\n                          class=\"hover:bg-muted rounded p-1 transition-[colors,transform] active:scale-90 disabled:opacity-30\"\n                          (click)=\"moveWidget(idx, 'down')\"\n                        >\n                          <lucide-icon [img]=\"ArrowDown\" class=\"size-3.5\" />\n                        </button>\n                      </div>\n\n                      <div class=\"min-w-0 space-y-0.5\">\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"text-foreground text-xs font-bold\">{{ widget.name }}</span>\n                          <span ui-badge variant=\"outline\" class=\"border-border text-muted-foreground text-xs\">\n                            {{ widget.category }}\n                          </span>\n                        </div>\n                        <div class=\"text-muted-foreground truncate text-xs\">{{ widget.previewSnippet }}</div>\n                      </div>\n                    </div>\n\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      class=\"text-muted-foreground hover:text-foreground size-8 shrink-0 active:scale-95\"\n                      [attr.aria-label]=\"(widget.visible ? 'Hide ' : 'Show ') + widget.name\"\n                      (click)=\"toggleVisibility(idx)\"\n                    >\n                      @if (widget.visible) {\n                        <lucide-icon [img]=\"Eye\" class=\"text-primary size-3.5\" />\n                      } @else {\n                        <lucide-icon [img]=\"EyeOff\" class=\"size-3.5\" />\n                      }\n                    </button>\n                  </div>\n                }\n              </div>\n\n              <!-- Export JSON Box -->\n              <div class=\"border-border space-y-2 border-t pt-2\">\n                <div class=\"flex items-center justify-between text-xs\">\n                  <span class=\"text-foreground font-medium\">Generated Layout JSON</span>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"h-6 gap-1 px-2 font-mono text-xs active:scale-95\"\n                    (click)=\"copyJson()\"\n                  >\n                    @if (copied()) {\n                      <lucide-icon [img]=\"Check\" class=\"text-primary size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                    }\n                    <span>{{ copied() ? 'Copied' : 'Copy JSON' }}</span>\n                  </button>\n                </div>\n                <pre\n                  class=\"border-border bg-muted/40 text-muted-foreground max-h-32 overflow-y-auto rounded-lg border p-2.5 font-mono text-xs\"\n                ><code>{{ exportJson() }}</code></pre>\n              </div>\n            </div>\n          </div>\n\n          <!-- Right: Live Canvas Preview (6 Cols) -->\n          <div ui-card class=\"border-border bg-card flex flex-col justify-between shadow-xs lg:col-span-6\">\n            <div ui-card-content class=\"space-y-4 p-6\">\n              <div class=\"border-border flex items-center justify-between border-b pb-3\">\n                <div class=\"flex items-center gap-2\">\n                  <lucide-icon [img]=\"Activity\" class=\"text-success size-4\" />\n                  <span class=\"text-foreground text-sm font-semibold\">Rendered Canvas Preview</span>\n                </div>\n                <span class=\"text-success font-mono text-xs\">Live Synchronized</span>\n              </div>\n\n              <!-- Simulated Dashboard Canvas with Reordered Components -->\n              <div\n                data-testid=\"canvas-preview\"\n                class=\"border-border/80 bg-background/80 space-y-3 rounded-lg border p-4\"\n              >\n                @for (widget of widgets(); track widget.id) {\n                  @if (widget.visible) {\n                    <div class=\"border-border bg-muted/30 space-y-1 rounded-lg border p-3 transition-colors\">\n                      <div class=\"text-foreground flex items-center justify-between text-xs font-semibold\">\n                        <span>{{ widget.name }}</span>\n                        <span class=\"text-muted-foreground font-mono text-xs\">col-span-{{ widget.colSpan }}</span>\n                      </div>\n                      <div class=\"text-muted-foreground text-xs\">{{ widget.previewSnippet }}</div>\n                    </div>\n                  }\n                }\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiFeatureDragDropOrganizerComponent {\n  protected readonly Activity = Activity\n  protected readonly ArrowDown = ArrowDown\n  protected readonly ArrowRight = ArrowRight\n  protected readonly ArrowUp = ArrowUp\n  protected readonly Check = Check\n  protected readonly Copy = Copy\n  protected readonly Eye = Eye\n  protected readonly EyeOff = EyeOff\n  protected readonly Layers = Layers\n  protected readonly RotateCcw = RotateCcw\n\n  @Input() title = 'Arrange dashboard layouts intuitively with zero boilerplate.'\n  @Input() description =\n    'Customize widget order, toggle component visibility, and export clean declarative JSON layout schemas for your application.'\n  @Input() set initialWidgets(v: LayoutWidget[] | undefined) {\n    this.widgets.set((v ?? INITIAL_WIDGETS).map((w) => ({ ...w })))\n  }\n  @Input('class') className?: string\n\n  readonly widgets = signal<LayoutWidget[]>(INITIAL_WIDGETS.map((w) => ({ ...w })))\n  readonly copied = signal(false)\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n\n  readonly exportJson = computed(() =>\n    JSON.stringify(\n      this.widgets().map((w, idx) => ({ order: idx, id: w.id, name: w.name, visible: w.visible, colSpan: w.colSpan })),\n      null,\n      2,\n    ),\n  )\n\n  get rootClass(): string {\n    return cn('bg-background relative overflow-hidden py-16 sm:py-24', this.className)\n  }\n\n  widgetRowClass(widget: LayoutWidget): string {\n    return cn(\n      'flex items-center justify-between rounded-lg border p-3 transition-colors',\n      widget.visible ? 'border-border bg-background' : 'border-border/60 bg-muted/30 opacity-60',\n    )\n  }\n\n  moveWidget(index: number, direction: 'up' | 'down'): void {\n    const targetIndex = direction === 'up' ? index - 1 : index + 1\n    if (targetIndex < 0 || targetIndex >= this.widgets().length) return\n    const updated = [...this.widgets()]\n    const [removed] = updated.splice(index, 1)\n    updated.splice(targetIndex, 0, removed!)\n    this.widgets.set(updated)\n  }\n\n  toggleVisibility(index: number): void {\n    this.widgets.update((prev) => {\n      const next = [...prev]\n      next[index] = { ...next[index]!, visible: !next[index]!.visible }\n      return next\n    })\n  }\n\n  resetLayout(): void {\n    this.widgets.set(INITIAL_WIDGETS.map((w) => ({ ...w })))\n  }\n\n  async copyJson(): Promise<void> {\n    try {\n      await navigator.clipboard.writeText(this.exportJson())\n      this.copied.set(true)\n      clearTimeout(this.copyTimer)\n      this.copyTimer = setTimeout(() => this.copied.set(false), 2000)\n    } catch {\n      // Clipboard unavailable (insecure context / jsdom): leave the label unchanged.\n    }\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/feature-drag-drop-organizer/feature-drag-drop-organizer.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/feature-drag-drop-organizer/index.ts",
      "content": "export {\n  UiFeatureDragDropOrganizerComponent,\n  INITIAL_WIDGETS,\n  type LayoutWidget,\n} from './feature-drag-drop-organizer.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/feature-drag-drop-organizer/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"
  ],
  "description": "Interactive dashboard block layout organizer with position reordering, visibility toggling, and JSON schema export.",
  "categories": [
    "feature",
    "marketing"
  ]
}