Menu

Change language

GitHub repo

Matrix Swot Analysis

blockproductivity

Strategic planning 2x2 SWOT analysis matrix (Strengths, Weaknesses, Opportunities, Threats) with dynamic card management, quadrant counters, TOWS strategic initiatives, and PDF/share toolbar.

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/matrix-swot-analysis.json
Named registry:npx shadcn@latest add @uipkge-react/matrix-swot-analysisInstalls to:components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
titlestring—optional
projectstring—optional
lastUpdatedstring—optional
initialStrengthsSwotItem[]—optional
initialWeaknessesSwotItem[]—optional
initialOpportunitiesSwotItem[]—optional
initialThreatsSwotItem[]—optional
initiativesStrategicInitiative[]—optional
readonlyboolean—optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

SwotItem
interface SwotItem {
  id: string
  title: string
  description: string
  impact: 'high' | 'medium' | 'low'
  tag: string
}
StrategicInitiative
interface 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 (1)

  • components/blocks/MatrixSwotAnalysis.tsx36.5 kB
    'use client'
    
    import * as React from 'react'
    import {
      AlertTriangle,
      Calendar,
      Check,
      Clock,
      Download,
      Layers,
      Plus,
      Share2,
      ShieldAlert,
      ShieldCheck,
      Trash2,
      TrendingUp,
      Zap,
    } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
    import { Input } from '@/components/ui/input'
    
    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
    }
    
    export interface MatrixSwotAnalysisProps extends React.HTMLAttributes<HTMLDivElement> {
      title?: string
      project?: string
      lastUpdated?: string
      initialStrengths?: SwotItem[]
      initialWeaknesses?: SwotItem[]
      initialOpportunities?: SwotItem[]
      initialThreats?: SwotItem[]
      initiatives?: StrategicInitiative[]
      readonly?: boolean
    }
    
    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',
      },
    ]
    
    export function MatrixSwotAnalysis({
      title = 'SWOT Strategic Analysis Canvas',
      project = 'UIPKGE Unbundled UI Registry Strategy · Q3 2026',
      lastUpdated = 'Aug 21, 2026',
      initialStrengths = defaultStrengths,
      initialWeaknesses = defaultWeaknesses,
      initialOpportunities = defaultOpportunities,
      initialThreats = defaultThreats,
      initiatives = defaultInitiatives,
      readonly = false,
      className,
      ...props
    }: MatrixSwotAnalysisProps) {
      const [strengths, setStrengths] = React.useState<SwotItem[]>(initialStrengths)
      const [weaknesses, setWeaknesses] = React.useState<SwotItem[]>(initialWeaknesses)
      const [opportunities, setOpportunities] = React.useState<SwotItem[]>(initialOpportunities)
      const [threats, setThreats] = React.useState<SwotItem[]>(initialThreats)
    
      const [inputStrength, setInputStrength] = React.useState('')
      const [inputWeakness, setInputWeakness] = React.useState('')
      const [inputOpportunity, setInputOpportunity] = React.useState('')
      const [inputThreat, setInputThreat] = React.useState('')
    
      const [isCopied, setIsCopied] = React.useState(false)
      const [isExporting, setIsExporting] = React.useState(false)
    
      const totalCards = strengths.length + weaknesses.length + opportunities.length + threats.length
    
      function handleAddStrength(e: React.FormEvent) {
        e.preventDefault()
        const text = inputStrength.trim()
        if (!text) return
        setStrengths((prev) => [
          ...prev,
          {
            id: `s-${Date.now()}`,
            title: text,
            description: 'Custom strategic strength added during canvas session.',
            impact: 'high',
            tag: 'Internal Strategic Advantage',
          },
        ])
        setInputStrength('')
      }
    
      function handleRemoveStrength(id: string) {
        setStrengths((prev) => prev.filter((item) => item.id !== id))
      }
    
      function handleAddWeakness(e: React.FormEvent) {
        e.preventDefault()
        const text = inputWeakness.trim()
        if (!text) return
        setWeaknesses((prev) => [
          ...prev,
          {
            id: `w-${Date.now()}`,
            title: text,
            description: 'Custom strategic vulnerability identified for remediation.',
            impact: 'medium',
            tag: 'Internal Remediation',
          },
        ])
        setInputWeakness('')
      }
    
      function handleRemoveWeakness(id: string) {
        setWeaknesses((prev) => prev.filter((item) => item.id !== id))
      }
    
      function handleAddOpportunity(e: React.FormEvent) {
        e.preventDefault()
        const text = inputOpportunity.trim()
        if (!text) return
        setOpportunities((prev) => [
          ...prev,
          {
            id: `o-${Date.now()}`,
            title: text,
            description: 'External market dynamic or technological growth tailwind.',
            impact: 'high',
            tag: 'External Opportunity',
          },
        ])
        setInputOpportunity('')
      }
    
      function handleRemoveOpportunity(id: string) {
        setOpportunities((prev) => prev.filter((item) => item.id !== id))
      }
    
      function handleAddThreat(e: React.FormEvent) {
        e.preventDefault()
        const text = inputThreat.trim()
        if (!text) return
        setThreats((prev) => [
          ...prev,
          {
            id: `t-${Date.now()}`,
            title: text,
            description: 'External industry risk factor requiring proactive defensive mitigation.',
            impact: 'high',
            tag: 'External Threat',
          },
        ])
        setInputThreat('')
      }
    
      function handleRemoveThreat(id: string) {
        setThreats((prev) => prev.filter((item) => item.id !== id))
      }
    
      function handleShare() {
        if (typeof navigator !== 'undefined' && navigator.clipboard) {
          navigator.clipboard.writeText(window.location.href)
          setIsCopied(true)
          setTimeout(() => setIsCopied(false), 2000)
        }
      }
    
      function handleExportPdf() {
        setIsExporting(true)
        setTimeout(() => setIsExporting(false), 1500)
      }
    
      return (
        <div data-slot="matrix-swot-analysis" className={cn('w-full space-y-6', className)} {...props}>
          {/* Header Surface */}
          <div className="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 className="space-y-1.5">
              <div className="flex flex-wrap items-center gap-2">
                <h2 className="text-xl font-bold tracking-tight sm:text-2xl">{title}</h2>
                <Badge wrap variant="outline" className="bg-primary/5 text-primary border-primary/20 text-xs font-medium">
                  2×2 TOWS Matrix
                </Badge>
                <Badge wrap variant="secondary" className="text-xs font-normal">
                  {totalCards} Strategic Factors
                </Badge>
              </div>
    
              <div className="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-1 text-xs sm:text-sm">
                <div className="flex items-center gap-1.5">
                  <Layers className="size-3.5" />
                  <span className="text-foreground font-medium">{project}</span>
                </div>
                <span className="hidden sm:inline">·</span>
                <div className="flex items-center gap-1.5">
                  <Calendar className="size-3.5" />
                  <span>Updated {lastUpdated}</span>
                </div>
              </div>
            </div>
    
            {/* Action Toolbar */}
            <div className="flex flex-wrap items-center gap-2.5">
              <Button
                aria-label="Download attachment"
                variant="outline"
                size="sm"
                className="h-9 gap-2 text-xs shadow-xs"
                disabled={isExporting}
                onClick={handleExportPdf}
              >
                {!isExporting ? <Download className="size-3.5" /> : <Clock className="size-3.5 animate-spin" />}
                <span>{isExporting ? 'Generating PDF...' : 'Export Canvas PDF'}</span>
              </Button>
    
              <Button variant="default" size="sm" className="h-9 gap-2 text-xs shadow-xs" onClick={handleShare}>
                {isCopied ? <Check className="size-3.5" /> : <Share2 className="size-3.5" />}
                <span>{isCopied ? 'Link Copied!' : 'Share Strategy'}</span>
              </Button>
            </div>
          </div>
    
          {/* 2x2 Matrix Axis Guide Legend */}
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
            <div className="border-success/20 bg-success/5 text-success text-success flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs">
              <div className="flex items-center gap-2">
                <ShieldCheck className="text-success size-4 shrink-0" />
                <span>Internal Attributes</span>
              </div>
              <Badge wrap variant="outline" className="border-success/30 bg-success/10 text-xs">
                Helpful (+)
              </Badge>
            </div>
    
            <div className="border-destructive/20 bg-destructive/5 text-destructive text-destructive flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs">
              <div className="flex items-center gap-2">
                <AlertTriangle className="text-destructive size-4 shrink-0" />
                <span>Internal Attributes</span>
              </div>
              <Badge wrap variant="outline" className="border-destructive/30 bg-destructive/10 text-xs">
                Harmful (−)
              </Badge>
            </div>
    
            <div className="border-info/20 bg-info/5 text-info text-info flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs">
              <div className="flex items-center gap-2">
                <TrendingUp className="text-info size-4 shrink-0" />
                <span>External Dynamics</span>
              </div>
              <Badge wrap variant="outline" className="border-info/30 bg-info/10 text-xs">
                Helpful (+)
              </Badge>
            </div>
    
            <div className="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 className="flex items-center gap-2">
                <ShieldAlert className="text-warning size-4 shrink-0" />
                <span>External Dynamics</span>
              </div>
              <Badge wrap variant="outline" className="border-warning/30 bg-warning/10 text-warning text-xs">
                Harmful (−)
              </Badge>
            </div>
          </div>
    
          {/* 2x2 SWOT Grid */}
          <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
            {/* QUADRANT 1: STRENGTHS (Internal / Positive) */}
            <Card className="border-success/30 border-success/20 flex flex-col justify-between shadow-xs">
              <CardHeader className="border-border/60 bg-success/5 dark:bg-success/10 border-b pb-4">
                <div className="flex items-start justify-between gap-3">
                  <div className="flex items-center gap-3">
                    <div className="border-success/20 bg-success/10 text-success text-success flex size-9 items-center justify-center rounded-lg border shadow-xs">
                      <ShieldCheck className="size-5" />
                    </div>
                    <div>
                      <div className="flex items-center gap-2">
                        <CardTitle className="text-base font-bold sm:text-lg">Strengths</CardTitle>
                        <Badge wrap className="border-success/20 bg-success/15 text-success text-xs font-semibold">
                          {strengths.length}
                        </Badge>
                      </div>
                      <CardDescription className="text-xs">
                        Internal attributes &amp; distinct technical capabilities
                      </CardDescription>
                    </div>
                  </div>
    
                  <div className="flex items-center gap-1.5">
                    <Badge
                      wrap
                      variant="outline"
                      className="border-success/30 text-success text-success hidden text-xs sm:inline-flex"
                    >
                      Internal
                    </Badge>
                    <Badge wrap variant="outline" className="border-success/30 text-success text-xs">
                      Positive
                    </Badge>
                  </div>
                </div>
              </CardHeader>
    
              <CardContent className="flex-1 space-y-3 p-4 sm:p-5">
                {strengths.map((item) => (
                  <div
                    key={item.id}
                    className="group bg-card hover:border-success/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
                  >
                    <div className="flex items-start justify-between gap-2">
                      <div className="space-y-1">
                        <div className="flex flex-wrap items-center gap-2">
                          <h4 className="text-foreground text-sm leading-snug font-semibold">{item.title}</h4>
                          <Badge
                            wrap
                            variant="outline"
                            className="border-success/20 bg-success/10 text-success text-xs font-normal"
                          >
                            {item.tag}
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs leading-relaxed">{item.description}</p>
                      </div>
    
                      {!readonly && (
                        <Button
                          variant="ghost"
                          size="sm"
                          className="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"
                          onClick={() => handleRemoveStrength(item.id)}
                        >
                          <Trash2 className="size-3.5" />
                        </Button>
                      )}
                    </div>
                  </div>
                ))}
    
                {strengths.length === 0 && (
                  <div className="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs">
                    No strength items listed. Add a core advantage below.
                  </div>
                )}
              </CardContent>
    
              {!readonly && (
                <CardFooter className="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
                  <form className="flex w-full items-center gap-2" onSubmit={handleAddStrength}>
                    <Input
                      value={inputStrength}
                      type="text"
                      placeholder="Add key strength... (Press Enter)"
                      className="h-8.5 text-xs shadow-xs"
                      onChange={(e) => setInputStrength(e.target.value)}
                    />
                    <Button
                      type="submit"
                      size="sm"
                      className="bg-success hover:bg-success/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs"
                    >
                      <Plus className="size-3.5" />
                      <span>Add</span>
                    </Button>
                  </form>
                </CardFooter>
              )}
            </Card>
    
            {/* QUADRANT 2: WEAKNESSES (Internal / Negative) */}
            <Card className="border-destructive/30 border-destructive/20 flex flex-col justify-between shadow-xs">
              <CardHeader className="border-border/60 bg-destructive/5 dark:bg-destructive/10 border-b pb-4">
                <div className="flex items-start justify-between gap-3">
                  <div className="flex items-center gap-3">
                    <div className="border-destructive/20 bg-destructive/10 text-destructive text-destructive flex size-9 items-center justify-center rounded-lg border shadow-xs">
                      <AlertTriangle className="size-5" />
                    </div>
                    <div>
                      <div className="flex items-center gap-2">
                        <CardTitle className="text-base font-bold sm:text-lg">Weaknesses</CardTitle>
                        <Badge
                          wrap
                          className="border-destructive/20 bg-destructive/15 text-destructive text-xs font-semibold"
                        >
                          {weaknesses.length}
                        </Badge>
                      </div>
                      <CardDescription className="text-xs">
                        Internal limitations &amp; operational friction points
                      </CardDescription>
                    </div>
                  </div>
    
                  <div className="flex items-center gap-1.5">
                    <Badge
                      wrap
                      variant="outline"
                      className="border-destructive/30 text-destructive text-destructive hidden text-xs sm:inline-flex"
                    >
                      Internal
                    </Badge>
                    <Badge wrap variant="outline" className="border-destructive/30 text-destructive text-xs">
                      Negative
                    </Badge>
                  </div>
                </div>
              </CardHeader>
    
              <CardContent className="flex-1 space-y-3 p-4 sm:p-5">
                {weaknesses.map((item) => (
                  <div
                    key={item.id}
                    className="group bg-card hover:border-destructive/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
                  >
                    <div className="flex items-start justify-between gap-2">
                      <div className="space-y-1">
                        <div className="flex flex-wrap items-center gap-2">
                          <h4 className="text-foreground text-sm leading-snug font-semibold">{item.title}</h4>
                          <Badge
                            wrap
                            variant="outline"
                            className="border-destructive/20 bg-destructive/10 text-destructive text-xs font-normal"
                          >
                            {item.tag}
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs leading-relaxed">{item.description}</p>
                      </div>
    
                      {!readonly && (
                        <Button
                          variant="ghost"
                          size="sm"
                          className="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"
                          onClick={() => handleRemoveWeakness(item.id)}
                        >
                          <Trash2 className="size-3.5" />
                        </Button>
                      )}
                    </div>
                  </div>
                ))}
    
                {weaknesses.length === 0 && (
                  <div className="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>
                )}
              </CardContent>
    
              {!readonly && (
                <CardFooter className="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
                  <form className="flex w-full items-center gap-2" onSubmit={handleAddWeakness}>
                    <Input
                      value={inputWeakness}
                      type="text"
                      placeholder="Add key weakness... (Press Enter)"
                      className="h-8.5 text-xs shadow-xs"
                      onChange={(e) => setInputWeakness(e.target.value)}
                    />
                    <Button
                      type="submit"
                      size="sm"
                      className="bg-destructive hover:bg-destructive/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs"
                    >
                      <Plus className="size-3.5" />
                      <span>Add</span>
                    </Button>
                  </form>
                </CardFooter>
              )}
            </Card>
    
            {/* QUADRANT 3: OPPORTUNITIES (External / Positive) */}
            <Card className="border-info/30 border-info/20 flex flex-col justify-between shadow-xs">
              <CardHeader className="border-border/60 bg-info/5 dark:bg-info/10 border-b pb-4">
                <div className="flex items-start justify-between gap-3">
                  <div className="flex items-center gap-3">
                    <div className="border-info/20 bg-info/10 text-info text-info flex size-9 items-center justify-center rounded-lg border shadow-xs">
                      <TrendingUp className="size-5" />
                    </div>
                    <div>
                      <div className="flex items-center gap-2">
                        <CardTitle className="text-base font-bold sm:text-lg">Opportunities</CardTitle>
                        <Badge wrap className="border-info/20 bg-info/15 text-info text-xs font-semibold">
                          {opportunities.length}
                        </Badge>
                      </div>
                      <CardDescription className="text-xs">
                        External tailwinds &amp; strategic expansion opportunities
                      </CardDescription>
                    </div>
                  </div>
    
                  <div className="flex items-center gap-1.5">
                    <Badge
                      wrap
                      variant="outline"
                      className="border-info/30 text-info text-info hidden text-xs sm:inline-flex"
                    >
                      External
                    </Badge>
                    <Badge wrap variant="outline" className="border-info/30 text-info text-xs">
                      Positive
                    </Badge>
                  </div>
                </div>
              </CardHeader>
    
              <CardContent className="flex-1 space-y-3 p-4 sm:p-5">
                {opportunities.map((item) => (
                  <div
                    key={item.id}
                    className="group bg-card hover:border-info/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
                  >
                    <div className="flex items-start justify-between gap-2">
                      <div className="space-y-1">
                        <div className="flex flex-wrap items-center gap-2">
                          <h4 className="text-foreground text-sm leading-snug font-semibold">{item.title}</h4>
                          <Badge wrap variant="outline" className="border-info/20 bg-info/10 text-info text-xs font-normal">
                            {item.tag}
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs leading-relaxed">{item.description}</p>
                      </div>
    
                      {!readonly && (
                        <Button
                          variant="ghost"
                          size="sm"
                          className="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"
                          onClick={() => handleRemoveOpportunity(item.id)}
                        >
                          <Trash2 className="size-3.5" />
                        </Button>
                      )}
                    </div>
                  </div>
                ))}
    
                {opportunities.length === 0 && (
                  <div className="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>
                )}
              </CardContent>
    
              {!readonly && (
                <CardFooter className="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
                  <form className="flex w-full items-center gap-2" onSubmit={handleAddOpportunity}>
                    <Input
                      value={inputOpportunity}
                      type="text"
                      placeholder="Add key opportunity... (Press Enter)"
                      className="h-8.5 text-xs shadow-xs"
                      onChange={(e) => setInputOpportunity(e.target.value)}
                    />
                    <Button
                      type="submit"
                      size="sm"
                      className="bg-info hover:bg-info/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs"
                    >
                      <Plus className="size-3.5" />
                      <span>Add</span>
                    </Button>
                  </form>
                </CardFooter>
              )}
            </Card>
    
            {/* QUADRANT 4: THREATS (External / Negative) */}
            <Card className="border-warning/30 dark:border-warning/20 flex flex-col justify-between shadow-xs">
              <CardHeader className="border-border/60 bg-warning/5 dark:bg-warning/10 border-b pb-4">
                <div className="flex items-start justify-between gap-3">
                  <div className="flex items-center gap-3">
                    <div className="border-warning/20 bg-warning/10 text-warning flex size-9 items-center justify-center rounded-lg border shadow-xs">
                      <ShieldAlert className="size-5" />
                    </div>
                    <div>
                      <div className="flex items-center gap-2">
                        <CardTitle className="text-base font-bold sm:text-lg">Threats</CardTitle>
                        <Badge wrap className="border-warning/20 bg-warning/15 text-warning text-xs font-semibold">
                          {threats.length}
                        </Badge>
                      </div>
                      <CardDescription className="text-xs">
                        External risks, competitive forces &amp; ecosystem shifts
                      </CardDescription>
                    </div>
                  </div>
    
                  <div className="flex items-center gap-1.5">
                    <Badge wrap variant="outline" className="border-warning/30 text-warning hidden text-xs sm:inline-flex">
                      External
                    </Badge>
                    <Badge wrap variant="outline" className="border-warning/30 text-warning text-xs">
                      Negative
                    </Badge>
                  </div>
                </div>
              </CardHeader>
    
              <CardContent className="flex-1 space-y-3 p-4 sm:p-5">
                {threats.map((item) => (
                  <div
                    key={item.id}
                    className="bg-card hover:border-warning/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
                  >
                    <div className="flex items-start justify-between gap-2">
                      <div className="space-y-1">
                        <div className="flex flex-wrap items-center gap-2">
                          <h4 className="text-foreground text-sm leading-snug font-semibold">{item.title}</h4>
                          <Badge
                            wrap
                            variant="outline"
                            className="border-warning/20 bg-warning/10 text-warning text-xs font-normal"
                          >
                            {item.tag}
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs leading-relaxed">{item.description}</p>
                      </div>
    
                      {!readonly && (
                        <Button
                          variant="ghost"
                          size="sm"
                          className="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"
                          onClick={() => handleRemoveThreat(item.id)}
                        >
                          <Trash2 className="size-3.5" />
                        </Button>
                      )}
                    </div>
                  </div>
                ))}
    
                {threats.length === 0 && (
                  <div className="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>
                )}
              </CardContent>
    
              {!readonly && (
                <CardFooter className="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
                  <form className="flex w-full items-center gap-2" onSubmit={handleAddThreat}>
                    <Input
                      value={inputThreat}
                      type="text"
                      placeholder="Add key threat... (Press Enter)"
                      className="h-8.5 text-xs shadow-xs"
                      onChange={(e) => setInputThreat(e.target.value)}
                    />
                    <Button
                      type="submit"
                      size="sm"
                      className="bg-warning text-warning-foreground hover:bg-warning/90 h-8.5 shrink-0 gap-1.5 text-xs shadow-xs"
                    >
                      <Plus className="size-3.5" />
                      <span>Add</span>
                    </Button>
                  </form>
                </CardFooter>
              )}
            </Card>
          </div>
    
          {/* Strategic Synthesis & Key Next Actions Card (TOWS Strategy) */}
          <Card className="shadow-xs">
            <CardHeader className="border-border/60 bg-muted/15 border-b pb-4">
              <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div className="space-y-1">
                  <div className="flex items-center gap-2">
                    <Zap className="text-primary size-4" />
                    <CardTitle className="text-base font-bold sm:text-lg">
                      Strategic Synthesis &amp; Key Next Actions
                    </CardTitle>
                  </div>
                  <CardDescription className="text-xs">
                    Actionable strategic initiatives bridging internal capabilities with external market dynamics (TOWS
                    matrix formulation).
                  </CardDescription>
                </div>
    
                <Badge wrap variant="outline" className="w-fit text-xs font-medium">
                  {initiatives.length} Active Initiatives
                </Badge>
              </div>
            </CardHeader>
    
            <CardContent className="p-4 sm:p-6">
              <div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
                {initiatives.map((initiative) => (
                  <div
                    key={initiative.id}
                    className="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 className="space-y-3">
                      <div className="flex items-center justify-between gap-2">
                        <Badge
                          variant="outline"
                          className={cn(
                            'text-xs font-semibold',
                            initiative.strategyType === 'SO' && 'border-success/20 bg-success/10 text-success',
                            initiative.strategyType === 'WO' && 'border-info/20 bg-info/10 text-info',
                            initiative.strategyType === 'ST' && 'border-warning/20 bg-warning/10 text-warning',
                            initiative.strategyType === 'WT' && 'border-destructive/20 bg-destructive/10 text-destructive',
                          )}
                        >
                          {initiative.strategyType} Strategy
                        </Badge>
    
                        <Badge
                          wrap
                          variant={initiative.priority === 'Critical' ? 'destructive' : 'secondary'}
                          className="text-xs font-medium"
                        >
                          {initiative.priority}
                        </Badge>
                      </div>
    
                      <div className="space-y-1.5">
                        <h4 className="text-foreground text-sm leading-snug font-semibold">{initiative.title}</h4>
                        <p className="text-muted-foreground text-xs leading-relaxed">{initiative.description}</p>
                      </div>
                    </div>
    
                    <div className="border-border/50 mt-4 flex items-center justify-between gap-2 border-t pt-3 text-xs">
                      <div className="text-muted-foreground flex items-center gap-1.5">
                        <Clock className="size-3.5" />
                        <span>{initiative.timeframe}</span>
                      </div>
                      <span className="bg-primary/10 text-primary rounded px-2 py-0.5 text-xs font-medium">
                        {initiative.targetMetric}
                      </span>
                    </div>
                  </div>
                ))}
              </div>
            </CardContent>
          </Card>
        </div>
      )
    }
    
    export default MatrixSwotAnalysis
    

Raw manifest:https://uipkge.dev/r/react/matrix-swot-analysis.json