Matrix Swot Analysis
blockproductivityStrategic planning 2x2 SWOT analysis matrix (Strengths, Weaknesses, Opportunities, Threats) with dynamic card management, quadrant counters, TOWS strategic initiatives, and PDF/share toolbar.
Installation
$pnpm dlx uipkge-ng@latest add matrix-swot-analysis$npx uipkge-ng@latest add matrix-swot-analysis$yarn dlx uipkge-ng@latest add matrix-swot-analysis$bunx uipkge-ng@latest add matrix-swot-analysisManifest:
https://uipkge.dev/r/angular/matrix-swot-analysis.jsonInstalls to:src/app/components/blocks/matrix-swot-analysis/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
SwotIteminterface SwotItem {
id: string
title: string
description: string
impact: 'high' | 'medium' | 'low'
tag: string
}StrategicInitiativeinterface StrategicInitiative {
id: string
title: string
description: string
strategyType: 'SO' | 'WO' | 'ST' | 'WT'
strategyLabel: string
priority: 'Critical' | 'High' | 'Strategic'
timeframe: string
targetMetric: string
}npm dependencies
Files installed (2)
src/app/components/blocks/matrix-swot-analysis/matrix-swot-analysis.component.ts38.1 kB
// Strategic planning 2x2 SWOT analysis matrix (Strengths, Weaknesses, Opportunities, // Threats) with dynamic card management, quadrant counters, TOWS strategic initiatives, // and PDF/share toolbar. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { AlertTriangle, Calendar, Check, Clock, Download, Layers, LucideAngularModule, Plus, Share2, ShieldAlert, ShieldCheck, Trash2, TrendingUp, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiInputComponent } from '@/ui/input/input.component' export interface SwotItem { id: string title: string description: string impact: 'high' | 'medium' | 'low' tag: string } export interface StrategicInitiative { id: string title: string description: string strategyType: 'SO' | 'WO' | 'ST' | 'WT' strategyLabel: string priority: 'Critical' | 'High' | 'Strategic' timeframe: string targetMetric: string } const defaultStrengths: SwotItem[] = [ { id: 's-1', title: 'Zero npm dependency overhead', description: 'Components are copied directly into consumer project codebases, eliminating semver dependency locks, version skew, and transitive package bloat.', impact: 'high', tag: 'Core Architecture', }, { id: 's-2', title: '100% code ownership for consumers', description: 'Engineering teams retain complete control over markup, styles, accessibility props, and internal behaviors with zero upstream constraints.', impact: 'high', tag: 'Developer Autonomy', }, { id: 's-3', title: 'Dual-framework Vue & React parity', description: 'Strict visual, token, and functional symmetry between Vue 3 (Reka UI) and React (Radix UI) enables cross-stack design system alignment.', impact: 'high', tag: 'Market Breadth', }, { id: 's-4', title: 'Tailwind v4 OKLCH token architecture', description: 'CSS-first theme configuration using semantic surface variables, calibrated dark-mode layering, and WCAG AA compliant contrast ratios.', impact: 'medium', tag: 'Design System', }, ] const defaultWeaknesses: SwotItem[] = [ { id: 'w-1', title: 'Manual upgrade path without semver', description: 'Consuming codebases must manually pull code diffs or re-execute CLI add commands when library enhancements or bug fixes are published.', impact: 'high', tag: 'Maintenance Model', }, { id: 'w-2', title: 'Initial learning curve for monorepos', description: 'Junior developers accustomed to single-command npm package installs may face an initial cognitive hurdle managing raw unbundled components.', impact: 'medium', tag: 'Developer DX', }, { id: 'w-3', title: 'Build time for 1,300+ SSG pages', description: 'Synthesizing static island previews, AST code extraction, and multi-format LLM documentation across dual registries requires high memory in CI.', impact: 'medium', tag: 'Tooling Scale', }, ] const defaultOpportunities: SwotItem[] = [ { id: 'o-1', title: 'AI Copilot workflows demanding raw source code', description: 'LLM coding assistants (Claude, Cursor, Copilot) perform substantially better when refactoring transparent local SFCs than navigating opaque compiled dependencies.', impact: 'high', tag: 'AI Ergonomics', }, { id: 'o-2', title: 'Enterprise design system white-labeling', description: 'B2B organizations seeking unbranded, custom-styled design systems can adopt UIPKGE as their modular, open-source architectural baseline.', impact: 'high', tag: 'Enterprise B2B', }, { id: 'o-3', title: 'E-Commerce and Healthcare template verticals', description: 'Surging industry appetite for pre-built reference architectures (HRMS, Hospital Management, Logistics tracking) powered by composable blocks.', impact: 'high', tag: 'Vertical Growth', }, { id: 'o-4', title: 'Community contributions of domain blocks', description: 'Open-source contributors can rapidly publish specialized workflow blocks (radar charts, KYC wizards, audit logs) without semver packaging bottlenecks.', impact: 'medium', tag: 'Community', }, ] const defaultThreats: SwotItem[] = [ { id: 't-1', title: 'Official shadcn expanding into Vue ecosystem', description: 'Potential first-party Vue support by upstream shadcn could consolidate developer attention away from independent unbundled registries.', impact: 'high', tag: 'Competitive Risk', }, { id: 't-2', title: 'Upstream Reka UI headless breaking changes', description: 'Substantial architectural shifts or breaking release cycles in upstream headless libraries require immediate parity synchronization.', impact: 'high', tag: 'Upstream Drift', }, { id: 't-3', title: 'Browser CSS specification shifts', description: 'Rapid evolution in CSS @scope, anchor positioning, and native light-dark() CSS functions may require recurring baseline refactoring.', impact: 'medium', tag: 'Web Standards', }, ] const defaultInitiatives: StrategicInitiative[] = [ { id: 'init-1', title: 'Automate Upstream Sync & Breaking Change Detection', description: 'Implement automated CI regression monitoring against Reka UI and upstream shadcn tokens to maintain 100% runtime compatibility without breaking consumer projects.', strategyType: 'SO', strategyLabel: 'SO Strategy (Strengths × Opportunities)', priority: 'Critical', timeframe: 'Q4 2026', targetMetric: '< 24h Upstream Parity Turnaround', }, { id: 'init-2', title: 'AI-Native Context & CLI Smart Upgrade Engine', description: 'Ship an intelligent CLI diff command and fine-tuned llms.txt vector context to let AI coding agents auto-patch and upgrade local consumer components directly.', strategyType: 'WO', strategyLabel: 'WO Strategy (Weaknesses × Opportunities)', priority: 'High', timeframe: 'Q4 2026', targetMetric: 'Zero-Friction AST Upgrade Patches', }, { id: 'init-3', title: 'Curated Vertical Template Packs & Enterprise Baseline', description: 'Deliver production-grade Nuxt 4 and Next.js vertical boilerplate templates (HRMS, HMS, Logistics) to eliminate monorepo onboarding friction for enterprise builders.', strategyType: 'ST', strategyLabel: 'ST Strategy (Strengths × Threats)', priority: 'Strategic', timeframe: 'Q1 2027', targetMetric: '4 Turnkey Vertical Reference Apps', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-matrix-swot-analysis, [ui-matrix-swot-analysis]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiInputComponent, ], template: ` <div data-slot="matrix-swot-analysis" [class]="rootClass"> <!-- Header Surface --> <div class="bg-card text-card-foreground flex flex-col gap-5 rounded-xl border p-5 shadow-xs sm:p-6 lg:flex-row lg:items-center lg:justify-between" > <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <h2 class="text-xl font-bold tracking-tight sm:text-2xl">{{ titleText() }}</h2> <span ui-badge variant="outline" class="bg-primary/5 text-primary border-primary/20 text-xs font-medium"> 2×2 TOWS Matrix </span> <span ui-badge variant="secondary" class="text-xs font-normal"> {{ totalCards() }} Strategic Factors </span> </div> <div class="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-1 text-xs sm:text-sm"> <div class="flex items-center gap-1.5"> <lucide-icon [img]="Layers" class="size-3.5" /> <span class="text-foreground font-medium">{{ projectText() }}</span> </div> <span class="hidden sm:inline">·</span> <div class="flex items-center gap-1.5"> <lucide-icon [img]="Calendar" class="size-3.5" /> <span>Updated {{ lastUpdatedText() }}</span> </div> </div> </div> <!-- Action Toolbar --> <div class="flex flex-wrap items-center gap-2.5"> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="h-9 gap-2 text-xs shadow-xs" [disabled]="isExporting()" (click)="handleExportPdf()" > @if (!isExporting()) { <lucide-icon [img]="Download" class="size-3.5" /> } @else { <lucide-icon [img]="Clock" class="size-3.5 animate-spin" /> } <span>{{ isExporting() ? 'Generating PDF...' : 'Export Canvas PDF' }}</span> </button> <button ui-button variant="default" size="sm" class="h-9 gap-2 text-xs shadow-xs" (click)="handleShare()"> @if (isCopied()) { <lucide-icon [img]="Check" class="size-3.5" /> } @else { <lucide-icon [img]="Share2" class="size-3.5" /> } <span>{{ isCopied() ? 'Link Copied!' : 'Share Strategy' }}</span> </button> </div> </div> <!-- 2x2 Matrix Axis Guide Legend --> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div class="border-success/20 bg-success/5 text-success flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs" > <div class="flex items-center gap-2"> <lucide-icon [img]="ShieldCheck" class="text-success size-4 shrink-0" /> <span>Internal Attributes</span> </div> <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-xs">Helpful (+)</span> </div> <div class="border-destructive/20 bg-destructive/5 text-destructive flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs" > <div class="flex items-center gap-2"> <lucide-icon [img]="AlertTriangle" class="text-destructive size-4 shrink-0" /> <span>Internal Attributes</span> </div> <span ui-badge variant="outline" class="border-destructive/30 bg-destructive/10 text-xs">Harmful (−)</span> </div> <div class="border-info/20 bg-info/5 text-info flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs" > <div class="flex items-center gap-2"> <lucide-icon [img]="TrendingUp" class="text-info size-4 shrink-0" /> <span>External Dynamics</span> </div> <span ui-badge variant="outline" class="border-info/30 bg-info/10 text-xs">Helpful (+)</span> </div> <div class="border-warning/20 bg-warning/5 text-warning flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs" > <div class="flex items-center gap-2"> <lucide-icon [img]="ShieldAlert" class="text-warning size-4 shrink-0" /> <span>External Dynamics</span> </div> <span ui-badge variant="outline" class="border-warning/30 bg-warning/10 text-warning text-xs"> Harmful (−) </span> </div> </div> <!-- 2x2 SWOT Grid --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-2"> <!-- QUADRANT 1: STRENGTHS --> <div ui-card class="border-success/30 border-success/20 flex flex-col justify-between shadow-xs"> <div ui-card-header class="border-border/60 bg-success/5 dark:bg-success/10 border-b pb-4"> <div class="flex items-start justify-between gap-3"> <div class="flex items-center gap-3"> <div class="border-success/20 bg-success/10 text-success flex size-9 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="ShieldCheck" class="size-5" /> </div> <div> <div class="flex items-center gap-2"> <h3 ui-card-title class="text-base font-bold sm:text-lg">Strengths</h3> <span ui-badge class="border-success/20 bg-success/15 text-success text-xs font-semibold"> {{ strengths().length }} </span> </div> <p ui-card-description class="text-xs">Internal attributes & distinct technical capabilities</p> </div> </div> <div class="flex items-center gap-1.5"> <span ui-badge variant="outline" class="border-success/30 text-success hidden text-xs sm:inline-flex"> Internal </span> <span ui-badge variant="outline" class="border-success/30 text-success text-xs"> Positive </span> </div> </div> </div> <div ui-card-content class="flex-1 space-y-3 p-4 sm:p-5"> @for (item of strengths(); track item.id) { <div class="group bg-card hover:border-success/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]" > <div class="flex items-start justify-between gap-2"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4> <span ui-badge variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-normal" > {{ item.tag }} </span> </div> <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p> </div> @if (!isReadonly()) { <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100" aria-label="Remove strength item" (click)="removeStrength(item.id)" > <lucide-icon [img]="Trash2" class="size-3.5" /> </button> } </div> </div> } @if (strengths().length === 0) { <div class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"> No strength items listed. Add a core advantage below. </div> } </div> @if (!isReadonly()) { <div ui-card-footer class="border-border/60 bg-muted/20 border-t p-3 sm:p-4"> <form class="flex w-full items-center gap-2" (submit)="addStrength($event)"> <ui-input [value]="inputStrength()" (valueChange)="inputStrength.set($event)" type="text" placeholder="Add key strength... (Press Enter)" class="h-8.5 text-xs shadow-xs" /> <button ui-button type="submit" size="sm" class="bg-success hover:bg-success/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs" > <lucide-icon [img]="Plus" class="size-3.5" /> <span>Add</span> </button> </form> </div> } </div> <!-- QUADRANT 2: WEAKNESSES --> <div ui-card class="border-destructive/30 border-destructive/20 flex flex-col justify-between shadow-xs"> <div ui-card-header class="border-border/60 bg-destructive/5 dark:bg-destructive/10 border-b pb-4"> <div class="flex items-start justify-between gap-3"> <div class="flex items-center gap-3"> <div class="border-destructive/20 bg-destructive/10 text-destructive flex size-9 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="AlertTriangle" class="size-5" /> </div> <div> <div class="flex items-center gap-2"> <h3 ui-card-title class="text-base font-bold sm:text-lg">Weaknesses</h3> <span ui-badge class="border-destructive/20 bg-destructive/15 text-destructive text-xs font-semibold" > {{ weaknesses().length }} </span> </div> <p ui-card-description class="text-xs">Internal limitations & operational friction points</p> </div> </div> <div class="flex items-center gap-1.5"> <span ui-badge variant="outline" class="border-destructive/30 text-destructive hidden text-xs sm:inline-flex" > Internal </span> <span ui-badge variant="outline" class="border-destructive/30 text-destructive text-xs"> Negative </span> </div> </div> </div> <div ui-card-content class="flex-1 space-y-3 p-4 sm:p-5"> @for (item of weaknesses(); track item.id) { <div class="group bg-card hover:border-destructive/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]" > <div class="flex items-start justify-between gap-2"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4> <span ui-badge variant="outline" class="border-destructive/20 bg-destructive/10 text-destructive text-xs font-normal" > {{ item.tag }} </span> </div> <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p> </div> @if (!isReadonly()) { <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100" aria-label="Remove weakness item" (click)="removeWeakness(item.id)" > <lucide-icon [img]="Trash2" class="size-3.5" /> </button> } </div> </div> } @if (weaknesses().length === 0) { <div class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"> No weakness items listed. Add an area for improvement below. </div> } </div> @if (!isReadonly()) { <div ui-card-footer class="border-border/60 bg-muted/20 border-t p-3 sm:p-4"> <form class="flex w-full items-center gap-2" (submit)="addWeakness($event)"> <ui-input [value]="inputWeakness()" (valueChange)="inputWeakness.set($event)" type="text" placeholder="Add key weakness... (Press Enter)" class="h-8.5 text-xs shadow-xs" /> <button ui-button type="submit" size="sm" class="bg-destructive hover:bg-destructive/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs" > <lucide-icon [img]="Plus" class="size-3.5" /> <span>Add</span> </button> </form> </div> } </div> <!-- QUADRANT 3: OPPORTUNITIES --> <div ui-card class="border-info/30 border-info/20 flex flex-col justify-between shadow-xs"> <div ui-card-header class="border-border/60 bg-info/5 dark:bg-info/10 border-b pb-4"> <div class="flex items-start justify-between gap-3"> <div class="flex items-center gap-3"> <div class="border-info/20 bg-info/10 text-info flex size-9 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="TrendingUp" class="size-5" /> </div> <div> <div class="flex items-center gap-2"> <h3 ui-card-title class="text-base font-bold sm:text-lg">Opportunities</h3> <span ui-badge class="border-info/20 bg-info/15 text-info text-xs font-semibold"> {{ opportunities().length }} </span> </div> <p ui-card-description class="text-xs">External tailwinds & strategic expansion opportunities</p> </div> </div> <div class="flex items-center gap-1.5"> <span ui-badge variant="outline" class="border-info/30 text-info hidden text-xs sm:inline-flex"> External </span> <span ui-badge variant="outline" class="border-info/30 text-info text-xs"> Positive </span> </div> </div> </div> <div ui-card-content class="flex-1 space-y-3 p-4 sm:p-5"> @for (item of opportunities(); track item.id) { <div class="group bg-card hover:border-info/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]" > <div class="flex items-start justify-between gap-2"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4> <span ui-badge variant="outline" class="border-info/20 bg-info/10 text-info text-xs font-normal"> {{ item.tag }} </span> </div> <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p> </div> @if (!isReadonly()) { <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100" aria-label="Remove opportunity item" (click)="removeOpportunity(item.id)" > <lucide-icon [img]="Trash2" class="size-3.5" /> </button> } </div> </div> } @if (opportunities().length === 0) { <div class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"> No opportunity items listed. Add a market growth vector below. </div> } </div> @if (!isReadonly()) { <div ui-card-footer class="border-border/60 bg-muted/20 border-t p-3 sm:p-4"> <form class="flex w-full items-center gap-2" (submit)="addOpportunity($event)"> <ui-input [value]="inputOpportunity()" (valueChange)="inputOpportunity.set($event)" type="text" placeholder="Add key opportunity... (Press Enter)" class="h-8.5 text-xs shadow-xs" /> <button ui-button type="submit" size="sm" class="bg-info hover:bg-info/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs" > <lucide-icon [img]="Plus" class="size-3.5" /> <span>Add</span> </button> </form> </div> } </div> <!-- QUADRANT 4: THREATS --> <div ui-card class="border-warning/30 dark:border-warning/20 flex flex-col justify-between shadow-xs"> <div ui-card-header class="border-border/60 bg-warning/5 dark:bg-warning/10 border-b pb-4"> <div class="flex items-start justify-between gap-3"> <div class="flex items-center gap-3"> <div class="border-warning/20 bg-warning/10 text-warning flex size-9 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="ShieldAlert" class="size-5" /> </div> <div> <div class="flex items-center gap-2"> <h3 ui-card-title class="text-base font-bold sm:text-lg">Threats</h3> <span ui-badge class="border-warning/20 bg-warning/15 text-warning text-xs font-semibold"> {{ threats().length }} </span> </div> <p ui-card-description class="text-xs">External risks, competitive forces & ecosystem shifts</p> </div> </div> <div class="flex items-center gap-1.5"> <span ui-badge variant="outline" class="border-warning/30 text-warning hidden text-xs sm:inline-flex"> External </span> <span ui-badge variant="outline" class="border-warning/30 text-warning text-xs"> Negative </span> </div> </div> </div> <div ui-card-content class="flex-1 space-y-3 p-4 sm:p-5"> @for (item of threats(); track item.id) { <div class="bg-card hover:border-warning/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]" > <div class="flex items-start justify-between gap-2"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4> <span ui-badge variant="outline" class="border-warning/20 bg-warning/10 text-warning text-xs font-normal" > {{ item.tag }} </span> </div> <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p> </div> @if (!isReadonly()) { <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100" aria-label="Remove threat item" (click)="removeThreat(item.id)" > <lucide-icon [img]="Trash2" class="size-3.5" /> </button> } </div> </div> } @if (threats().length === 0) { <div class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"> No threat items listed. Add an external risk factor below. </div> } </div> @if (!isReadonly()) { <div ui-card-footer class="border-border/60 bg-muted/20 border-t p-3 sm:p-4"> <form class="flex w-full items-center gap-2" (submit)="addThreat($event)"> <ui-input [value]="inputThreat()" (valueChange)="inputThreat.set($event)" type="text" placeholder="Add key threat... (Press Enter)" class="h-8.5 text-xs shadow-xs" /> <button ui-button type="submit" size="sm" class="bg-warning text-warning-foreground hover:bg-warning/90 h-8.5 shrink-0 gap-1.5 text-xs shadow-xs" > <lucide-icon [img]="Plus" class="size-3.5" /> <span>Add</span> </button> </form> </div> } </div> </div> <!-- Strategic Synthesis & Key Next Actions Card (TOWS Strategy) --> <div ui-card class="shadow-xs"> <div ui-card-header class="border-border/60 bg-muted/15 border-b pb-4"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex items-center gap-2"> <lucide-icon [img]="Zap" class="text-primary size-4" /> <h3 ui-card-title class="text-base font-bold sm:text-lg">Strategic Synthesis & Key Next Actions</h3> </div> <p ui-card-description class="text-xs"> Actionable strategic initiatives bridging internal capabilities with external market dynamics (TOWS matrix formulation). </p> </div> <span ui-badge variant="outline" class="w-fit text-xs font-medium"> {{ strategicInitiatives().length }} Active Initiatives </span> </div> </div> <div ui-card-content class="p-4 sm:p-6"> <div class="grid grid-cols-1 gap-4 lg:grid-cols-3"> @for (initiative of strategicInitiatives(); track initiative.id) { <div class="bg-muted/30 hover:border-primary/40 flex flex-col justify-between rounded-xl border p-4 shadow-xs transition-[border-color,box-shadow]" > <div class="space-y-3"> <div class="flex items-center justify-between gap-2"> <span ui-badge variant="outline" [class]="strategyBadgeClass(initiative.strategyType)"> {{ initiative.strategyType }} Strategy </span> <span ui-badge [variant]="initiative.priority === 'Critical' ? 'destructive' : 'secondary'" class="text-xs font-medium whitespace-normal" > {{ initiative.priority }} </span> </div> <div class="space-y-1.5"> <h4 class="text-foreground text-sm leading-snug font-semibold">{{ initiative.title }}</h4> <p class="text-muted-foreground text-xs leading-relaxed">{{ initiative.description }}</p> </div> </div> <div class="border-border/50 mt-4 flex items-center justify-between gap-2 border-t pt-3 text-xs"> <div class="text-muted-foreground flex items-center gap-1.5"> <lucide-icon [img]="Clock" class="size-3.5" /> <span>{{ initiative.timeframe }}</span> </div> <span class="bg-primary/10 text-primary rounded px-2 py-0.5 text-xs font-medium"> {{ initiative.targetMetric }} </span> </div> </div> } </div> </div> </div> </div> `, }) export class UiMatrixSwotAnalysisComponent { protected readonly AlertTriangle = AlertTriangle protected readonly Calendar = Calendar protected readonly Check = Check protected readonly Clock = Clock protected readonly Download = Download protected readonly Layers = Layers protected readonly Plus = Plus protected readonly Share2 = Share2 protected readonly ShieldAlert = ShieldAlert protected readonly ShieldCheck = ShieldCheck protected readonly Trash2 = Trash2 protected readonly TrendingUp = TrendingUp protected readonly Zap = Zap @Input() set title(v: string | undefined) { this.titleText.set(v ?? 'SWOT Strategic Analysis Canvas') } @Input() set project(v: string | undefined) { this.projectText.set(v ?? 'UIPKGE Unbundled UI Registry Strategy · Q3 2026') } @Input() set lastUpdated(v: string | undefined) { this.lastUpdatedText.set(v ?? 'Aug 21, 2026') } @Input() set initialStrengths(v: SwotItem[] | undefined) { if (v) this.strengths.set([...v]) } @Input() set initialWeaknesses(v: SwotItem[] | undefined) { if (v) this.weaknesses.set([...v]) } @Input() set initialOpportunities(v: SwotItem[] | undefined) { if (v) this.opportunities.set([...v]) } @Input() set initialThreats(v: SwotItem[] | undefined) { if (v) this.threats.set([...v]) } @Input() set initiatives(v: StrategicInitiative[] | undefined) { if (v) this.strategicInitiatives.set([...v]) } @Input() set readonly(v: boolean) { this.isReadonly.set(!!v) } @Input('class') className?: string readonly titleText = signal('SWOT Strategic Analysis Canvas') readonly projectText = signal('UIPKGE Unbundled UI Registry Strategy · Q3 2026') readonly lastUpdatedText = signal('Aug 21, 2026') readonly isReadonly = signal(false) readonly strengths = signal<SwotItem[]>([...defaultStrengths]) readonly weaknesses = signal<SwotItem[]>([...defaultWeaknesses]) readonly opportunities = signal<SwotItem[]>([...defaultOpportunities]) readonly threats = signal<SwotItem[]>([...defaultThreats]) readonly strategicInitiatives = signal<StrategicInitiative[]>([...defaultInitiatives]) readonly inputStrength = signal('') readonly inputWeakness = signal('') readonly inputOpportunity = signal('') readonly inputThreat = signal('') readonly isCopied = signal(false) readonly isExporting = signal(false) readonly totalCards = computed( () => this.strengths().length + this.weaknesses().length + this.opportunities().length + this.threats().length, ) get rootClass(): string { return cn('w-full space-y-6', this.className) } strategyBadgeClass(t: StrategicInitiative['strategyType']): string { return cn( 'text-xs font-semibold', t === 'SO' && 'border-success/20 bg-success/10 text-success', t === 'WO' && 'border-info/20 bg-info/10 text-info', t === 'ST' && 'border-warning/20 bg-warning/10 text-warning', t === 'WT' && 'border-destructive/20 bg-destructive/10 text-destructive', ) } addStrength(e?: Event): void { e?.preventDefault() const text = this.inputStrength().trim() if (!text) return this.strengths.update((prev) => [ ...prev, { id: `s-${Date.now()}`, title: text, description: 'Custom strategic strength added during canvas session.', impact: 'high', tag: 'Internal Strategic Advantage', }, ]) this.inputStrength.set('') } removeStrength(id: string): void { this.strengths.update((prev) => prev.filter((item) => item.id !== id)) } addWeakness(e?: Event): void { e?.preventDefault() const text = this.inputWeakness().trim() if (!text) return this.weaknesses.update((prev) => [ ...prev, { id: `w-${Date.now()}`, title: text, description: 'Custom strategic vulnerability identified for remediation.', impact: 'medium', tag: 'Internal Remediation', }, ]) this.inputWeakness.set('') } removeWeakness(id: string): void { this.weaknesses.update((prev) => prev.filter((item) => item.id !== id)) } addOpportunity(e?: Event): void { e?.preventDefault() const text = this.inputOpportunity().trim() if (!text) return this.opportunities.update((prev) => [ ...prev, { id: `o-${Date.now()}`, title: text, description: 'External market dynamic or technological growth tailwind.', impact: 'high', tag: 'External Opportunity', }, ]) this.inputOpportunity.set('') } removeOpportunity(id: string): void { this.opportunities.update((prev) => prev.filter((item) => item.id !== id)) } addThreat(e?: Event): void { e?.preventDefault() const text = this.inputThreat().trim() if (!text) return this.threats.update((prev) => [ ...prev, { id: `t-${Date.now()}`, title: text, description: 'External industry risk factor requiring proactive defensive mitigation.', impact: 'high', tag: 'External Threat', }, ]) this.inputThreat.set('') } removeThreat(id: string): void { this.threats.update((prev) => prev.filter((item) => item.id !== id)) } handleShare(): void { if (typeof navigator !== 'undefined' && navigator.clipboard) { navigator.clipboard.writeText(window.location.href) this.isCopied.set(true) setTimeout(() => this.isCopied.set(false), 2000) } } handleExportPdf(): void { this.isExporting.set(true) setTimeout(() => this.isExporting.set(false), 1500) } }src/app/components/blocks/matrix-swot-analysis/index.ts0.1 kB
export { UiMatrixSwotAnalysisComponent, type SwotItem, type StrategicInitiative, } from './matrix-swot-analysis.component'
Raw manifest:https://uipkge.dev/r/angular/matrix-swot-analysis.json