UIPackage
Menu

Framework

Change language

Boilerplate repo

Floor Plan Explorer

blockreal-estate

Architectural unit floor plan inspector and residential layout explorer with 2D CAD-styled blueprint canvas, interactive room highlight pins, dimensional callouts, room breakdown specs, and building unit availability table.

Also available for Vue ->

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/floor-plan-explorer.json
Named registry:npx shadcn@latest add @uipkge-react/floor-plan-explorerInstalls to:components/blocks/floor-plan-explorer/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
classNamestringoptional

Schema

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

RoomSpec
interface RoomSpec {
  id: string
  name: string
  shortLabel: string
  dimensionsImperial: string
  dimensionsMetric: string
  areaSqFt: number
  areaSqM: number
  flooring: string
  features: string[]
  pinX: number
  pinY: number
  rect: {
    x: number
    y: number
    w: number
    h: number
  }
}
AvailableUnit
interface AvailableUnit {
  unitNumber: string
  floor: number
  view: string
  monthlyRent: number
  sqFt: number
  status: 'available-now' | 'available-soon' | 'leased' | 'under-contract'
  statusLabel: string
  moveInDate: string
  exposure: string
}
FloorPlanData
interface FloorPlanData {
  id: string
  tabKey: string
  tabLabel: string
  marketingName: string
  tier: string
  beds: number
  baths: number
  totalAreaSqFt: number
  totalAreaSqM: number
  interiorSqFt: number
  exteriorSqFt: number
  ceilingHeight: string
  exposure: string
  startingRent: number
  description: string
  highlights: string[]
  rooms: RoomSpec[]
  availableUnits: AvailableUnit[]
}

npm dependencies

Files installed (4)

  • components/blocks/floor-plan-explorer/FloorPlanExplorer.tsx28.4 kB
    'use client'
    
    import * as React from 'react'
    import { useMemo } from 'react'
    import { Bath, Bed, Building2, Check, Download } 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 { AvailableUnitsTable } from './AvailableUnitsTable'
    import { FloorPlanCanvas } from './FloorPlanCanvas'
    import { type AvailableUnit, type FloorPlanData, type RoomSpec } from './floor-plan-explorer-types'
    
    export type { AvailableUnit, FloorPlanData, RoomSpec } from './floor-plan-explorer-types'
    import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
    
    export interface FloorPlanExplorerProps {
      className?: string
    }
    
    const floorPlans: Record<string, FloorPlanData> = {
      'plan-a': {
        id: 'plan-a',
        tabKey: 'plan-a',
        tabLabel: 'Plan A - 1 Bed / 1 Bath',
        marketingName: 'Plan A - The Urban Haven',
        tier: 'Urban Gallery Residence',
        beds: 1,
        baths: 1,
        totalAreaSqFt: 780,
        totalAreaSqM: 72.5,
        interiorSqFt: 735,
        exteriorSqFt: 45,
        ceilingHeight: `10' 0" Finished`,
        exposure: 'East · Garden & Sunrise',
        startingRent: 3450,
        description:
          'Artfully designed open-concept layout featuring oversized east-facing windows, gourmet galley kitchen with quartz surfaces, secluded bedroom suite with walk-in closet, and a private sunrise terrace.',
        highlights: [
          'Floor-to-ceiling glass wall',
          'European quartz kitchen island',
          'Deep soaking tub & frameless shower',
          'Private sunrise terrace',
        ],
        rooms: [
          {
            id: 'living',
            name: 'Living & Dining Room',
            shortLabel: 'Living / Dining',
            dimensionsImperial: `16' × 12'`,
            dimensionsMetric: '4.9m × 3.7m',
            areaSqFt: 192,
            areaSqM: 17.8,
            flooring: 'Wide Plank Natural White Oak',
            features: ['East Sunrise Exposure', '9-Foot Glazing Wall', 'Concealed Media Wiring'],
            pinX: 280,
            pinY: 175,
            rect: { x: 170, y: 90, w: 230, h: 170 },
          },
          {
            id: 'master-bed',
            name: 'Master Bedroom',
            shortLabel: 'Master Bed',
            dimensionsImperial: `13' × 11'`,
            dimensionsMetric: '4.0m × 3.4m',
            areaSqFt: 143,
            areaSqM: 13.3,
            flooring: 'Plush Organic Wool Loop',
            features: ['Acoustic Insulation', 'Custom Closet Built-ins', 'Blackout Shade Pocket'],
            pinX: 505,
            pinY: 175,
            rect: { x: 410, y: 90, w: 190, h: 170 },
          },
          {
            id: 'kitchen',
            name: 'Gourmet Galley Kitchen',
            shortLabel: 'Kitchen',
            dimensionsImperial: `11' × 9'`,
            dimensionsMetric: '3.4m × 2.7m',
            areaSqFt: 99,
            areaSqM: 9.2,
            flooring: 'Calacatta Honed Quartzite',
            features: ['Integrated Bosch Appliances', 'Undermount LED Lights', 'Soft-Close Walnut Joinery'],
            pinX: 280,
            pinY: 330,
            rect: { x: 170, y: 270, w: 230, h: 120 },
          },
          {
            id: 'master-bath',
            name: 'Primary Full Bath',
            shortLabel: 'Full Bath',
            dimensionsImperial: `9' × 6'`,
            dimensionsMetric: '2.7m × 1.8m',
            areaSqFt: 54,
            areaSqM: 5.0,
            flooring: 'Matte Slate & Terrazzo',
            features: ['Kohler Undermount Sink', 'Frameless Glass Shower', 'Backlit Anti-Fog Vanity'],
            pinX: 505,
            pinY: 330,
            rect: { x: 410, y: 270, w: 190, h: 120 },
          },
          {
            id: 'balcony',
            name: 'Private Terrace',
            shortLabel: 'Terrace',
            dimensionsImperial: `9' × 5'`,
            dimensionsMetric: '2.7m × 1.5m',
            areaSqFt: 45,
            areaSqM: 4.2,
            flooring: 'Weathered Teak Decking',
            features: ['Architectural Steel Railing', 'Exterior Power Outlet', 'Garden Courtyard Vistas'],
            pinX: 105,
            pinY: 175,
            rect: { x: 50, y: 90, w: 110, h: 170 },
          },
        ],
        availableUnits: [
          {
            unitNumber: '304',
            floor: 3,
            view: 'Courtyard & Garden',
            monthlyRent: 3450,
            sqFt: 780,
            status: 'available-now',
            statusLabel: 'Available Now',
            moveInDate: 'Immediate',
            exposure: 'East',
          },
          {
            unitNumber: '604',
            floor: 6,
            view: 'Skyline Sunrise View',
            monthlyRent: 3650,
            sqFt: 780,
            status: 'available-soon',
            statusLabel: 'Available Oct 15',
            moveInDate: 'Oct 15, 2026',
            exposure: 'East / North',
          },
          {
            unitNumber: '904',
            floor: 9,
            view: 'High-Floor Park View',
            monthlyRent: 3800,
            sqFt: 780,
            status: 'leased',
            statusLabel: 'Leased',
            moveInDate: 'Occupied',
            exposure: 'East',
          },
        ],
      },
      'plan-b': {
        id: 'plan-b',
        tabKey: 'plan-b',
        tabLabel: 'Plan B - 2 Bed / 2 Bath',
        marketingName: 'Plan B - The Skyline Suite',
        tier: 'Signature Corner Residence',
        beds: 2,
        baths: 2,
        totalAreaSqFt: 1420,
        totalAreaSqM: 132.0,
        interiorSqFt: 1360,
        exteriorSqFt: 60,
        ceilingHeight: `10' 6" Finished`,
        exposure: 'Southwest · Bay & City Skyline',
        startingRent: 4850,
        description:
          'Expansive dual-exposure residence centered around a dramatic living and entertaining salon with floor-to-ceiling panoramic glass, chef-grade kitchen island, split master and guest wings, and a sheltered private terrace.',
        highlights: [
          'Dual-aspect corner positioning with 270° light',
          'Chef island with 10-foot marble waterfall',
          'Primary wing with dual walk-in closets',
          'Dedicated foyer vestibule & utility pantry',
        ],
        rooms: [
          {
            id: 'living',
            name: 'Living Room',
            shortLabel: 'Living Room',
            dimensionsImperial: `18' × 14'`,
            dimensionsMetric: '5.5m × 4.3m',
            areaSqFt: 252,
            areaSqM: 23.4,
            flooring: 'Wide Plank European White Oak',
            features: ['Floor-to-Ceiling Corner Glazing', 'Motorized Solar Shades', 'Recessed Architectural Lighting'],
            pinX: 255,
            pinY: 160,
            rect: { x: 150, y: 75, w: 220, h: 175 },
          },
          {
            id: 'master-bed',
            name: 'Master Bedroom',
            shortLabel: 'Master Bed',
            dimensionsImperial: `15' × 12'`,
            dimensionsMetric: '4.6m × 3.7m',
            areaSqFt: 180,
            areaSqM: 16.7,
            flooring: 'New Zealand Wool Weave',
            features: ['South Bay Views', 'Direct En-Suite Access', 'Custom Millwork Wardrobe'],
            pinX: 455,
            pinY: 145,
            rect: { x: 380, y: 75, w: 155, h: 150 },
          },
          {
            id: 'kitchen',
            name: 'Chef Kitchen & Dining',
            shortLabel: 'Kitchen',
            dimensionsImperial: `12' × 10'`,
            dimensionsMetric: '3.7m × 3.0m',
            areaSqFt: 120,
            areaSqM: 11.1,
            flooring: 'Calacatta Honed Quartzite',
            features: ['Waterfall Marble Island', 'Sub-Zero & Wolf Appliances', 'Pantry Cabinetry'],
            pinX: 255,
            pinY: 325,
            rect: { x: 150, y: 260, w: 220, h: 135 },
          },
          {
            id: 'master-bath',
            name: 'Master Bath',
            shortLabel: 'Master Bath',
            dimensionsImperial: `10' × 8'`,
            dimensionsMetric: '3.0m × 2.4m',
            areaSqFt: 80,
            areaSqM: 7.4,
            flooring: 'Bianco Carrara Marble',
            features: ['Dual Floating Vanities', 'Walk-in Rain Shower', 'Acoustic Privacy Door'],
            pinX: 590,
            pinY: 145,
            rect: { x: 545, y: 75, w: 90, h: 150 },
          },
          {
            id: 'guest-bed',
            name: 'Guest Bedroom / Study',
            shortLabel: 'Guest Bed',
            dimensionsImperial: `13' × 11'`,
            dimensionsMetric: '4.0m × 3.4m',
            areaSqFt: 143,
            areaSqM: 13.3,
            flooring: 'Wide Plank European White Oak',
            features: ['Multi-Function Home Office', 'South Window Exposure', 'Built-in Closet System'],
            pinX: 565,
            pinY: 320,
            rect: { x: 490, y: 245, w: 145, h: 150 },
          },
          {
            id: 'guest-bath',
            name: 'Guest Full Bath',
            shortLabel: 'Guest Bath',
            dimensionsImperial: `8' × 6'`,
            dimensionsMetric: '2.4m × 1.8m',
            areaSqFt: 48,
            areaSqM: 4.5,
            flooring: 'Smoked Gray Terrazzo',
            features: ['Full Soaking Tub', 'Modern Matte Black Fixtures', 'Linen Storage'],
            pinX: 430,
            pinY: 320,
            rect: { x: 380, y: 245, w: 100, h: 150 },
          },
          {
            id: 'balcony',
            name: 'Private Terrace',
            shortLabel: 'Terrace',
            dimensionsImperial: `10' × 6'`,
            dimensionsMetric: '3.0m × 1.8m',
            areaSqFt: 60,
            areaSqM: 5.6,
            flooring: 'Architectural Ipe Wood',
            features: ['Panoramic Bay Overlook', 'Weatherproof Recessed Sconces', 'Glass Wind Guard'],
            pinX: 90,
            pinY: 160,
            rect: { x: 40, y: 75, w: 100, h: 175 },
          },
        ],
        availableUnits: [
          {
            unitNumber: '402',
            floor: 4,
            view: 'South City View',
            monthlyRent: 4850,
            sqFt: 1420,
            status: 'available-now',
            statusLabel: 'Available Now',
            moveInDate: 'Immediate',
            exposure: 'South / West',
          },
          {
            unitNumber: '708',
            floor: 7,
            view: 'Bay & Marina View',
            monthlyRent: 5200,
            sqFt: 1420,
            status: 'available-soon',
            statusLabel: 'Available Sep 1',
            moveInDate: 'Sep 1, 2026',
            exposure: 'Bay View / South',
          },
          {
            unitNumber: '1102',
            floor: 11,
            view: 'Penthouse Level Panoramic',
            monthlyRent: 5600,
            sqFt: 1420,
            status: 'leased',
            statusLabel: 'Leased',
            moveInDate: 'Occupied',
            exposure: 'Panoramic South',
          },
        ],
      },
      'plan-c': {
        id: 'plan-c',
        tabKey: 'plan-c',
        tabLabel: 'Plan C - 3 Bed Penthouse',
        marketingName: 'Plan C - The Grandview Penthouse',
        tier: 'Crown Estate Penthouse',
        beds: 3,
        baths: 3.5,
        totalAreaSqFt: 2250,
        totalAreaSqM: 209.0,
        interiorSqFt: 2090,
        exteriorSqFt: 160,
        ceilingHeight: `11' 8" Finished`,
        exposure: '360° Panoramic · Harbor, Bay & Bridges',
        startingRent: 9400,
        description:
          'A true architectural masterpiece occupying the top levels of the tower. Direct key-lock private elevator access opens into a sweeping grand salon with 11.5-foot ceilings, full culinary kitchen with butler pantry, spa sanctuary primary suite, and wraparound sky terrace.',
        highlights: [
          'Private elevator vestibule with secure biometric access',
          'Wraparound sky terrace with built-in gas fire table',
          'Primary retreat with double walk-in dressing galleries',
          'Butler service pantry with climate-controlled wine vault',
        ],
        rooms: [
          {
            id: 'grand-salon',
            name: 'Grand Salon & Living',
            shortLabel: 'Grand Salon',
            dimensionsImperial: `24' × 16'`,
            dimensionsMetric: '7.3m × 4.9m',
            areaSqFt: 384,
            areaSqM: 35.7,
            flooring: 'Herringbone European Oak',
            features: ['360° Bay Vista Glazing', 'Gas Linear Fireplace', 'Custom Architectural Coving'],
            pinX: 275,
            pinY: 155,
            rect: { x: 160, y: 85, w: 240, h: 150 },
          },
          {
            id: 'primary-suite',
            name: 'Primary Penthouse Suite',
            shortLabel: 'Primary Suite',
            dimensionsImperial: `18' × 14'`,
            dimensionsMetric: '5.5m × 4.3m',
            areaSqFt: 252,
            areaSqM: 23.4,
            flooring: 'Custom Silk-Wool Blend Weave',
            features: ['Harbor Sunrise Exposure', 'Sitting Lounge Area', 'Dual Walk-in Closets'],
            pinX: 480,
            pinY: 155,
            rect: { x: 410, y: 85, w: 140, h: 150 },
          },
          {
            id: 'spa-bath',
            name: 'Spa Sanctuary Primary Bath',
            shortLabel: 'Spa Bath',
            dimensionsImperial: `12' × 9'`,
            dimensionsMetric: '3.7m × 2.7m',
            areaSqFt: 108,
            areaSqM: 10.0,
            flooring: 'Bookmatched Calacatta Marble',
            features: ['Freestanding Oval Soaking Tub', 'Dual Steam Shower', 'Radiant Heated Flooring'],
            pinX: 595,
            pinY: 155,
            rect: { x: 560, y: 85, w: 80, h: 150 },
          },
          {
            id: 'dining',
            name: 'Formal Dining Gallery',
            shortLabel: 'Dining Room',
            dimensionsImperial: `15' × 12'`,
            dimensionsMetric: '4.6m × 3.7m',
            areaSqFt: 180,
            areaSqM: 16.7,
            flooring: 'Herringbone European Oak',
            features: ['Seating for 10-12 Guests', 'Integrated Bar Cabinetry', 'Designer Chandelier Anchor'],
            pinX: 235,
            pinY: 315,
            rect: { x: 160, y: 245, w: 150, h: 145 },
          },
          {
            id: 'kitchen',
            name: 'Culinary Kitchen & Pantry',
            shortLabel: 'Kitchen',
            dimensionsImperial: `16' × 11'`,
            dimensionsMetric: '4.9m × 3.4m',
            areaSqFt: 176,
            areaSqM: 16.3,
            flooring: 'Taj Mahal Quartzite',
            features: ['12-Foot Monolithic Island', 'Gaggenau 400 Series Suite', 'Butler Prep Scullery'],
            pinX: 95,
            pinY: 315,
            rect: { x: 40, y: 245, w: 110, h: 145 },
          },
          {
            id: 'bed-2',
            name: 'Bedroom Suite 2',
            shortLabel: 'Bed 2 Suite',
            dimensionsImperial: `14' × 12'`,
            dimensionsMetric: '4.3m × 3.7m',
            areaSqFt: 168,
            areaSqM: 15.6,
            flooring: 'Wide Plank European Oak',
            features: ['En-Suite Private Bath', 'South Window Bank', 'Deep Built-in Wardrobe'],
            pinX: 395,
            pinY: 315,
            rect: { x: 320, y: 245, w: 150, h: 145 },
          },
          {
            id: 'bed-3',
            name: 'Bedroom 3 / Executive Study',
            shortLabel: 'Bed 3 / Study',
            dimensionsImperial: `13' × 11'`,
            dimensionsMetric: '4.0m × 3.4m',
            areaSqFt: 143,
            areaSqM: 13.3,
            flooring: 'Custom Walnut Parquetry',
            features: ['Integrated Library Shelving', 'Acoustic Pocket Doors', 'Corner View Light'],
            pinX: 560,
            pinY: 315,
            rect: { x: 480, y: 245, w: 160, h: 145 },
          },
          {
            id: 'sky-terrace',
            name: 'Wraparound Sky Terrace',
            shortLabel: 'Sky Terrace',
            dimensionsImperial: `20' × 8'`,
            dimensionsMetric: '6.1m × 2.4m',
            areaSqFt: 160,
            areaSqM: 14.9,
            flooring: 'Porcelain Pavers & Teak',
            features: ['Integrated Linear Gas Hearth', 'Outdoor Audio Pre-Wire', 'Frameless Wind Barrier'],
            pinX: 340,
            pinY: 45,
            rect: { x: 160, y: 25, w: 360, h: 50 },
          },
        ],
        availableUnits: [
          {
            unitNumber: 'PH-01',
            floor: 14,
            view: '360° Bay & City Skyline',
            monthlyRent: 9400,
            sqFt: 2250,
            status: 'available-now',
            statusLabel: 'Available Now',
            moveInDate: 'Immediate',
            exposure: '360° Panoramic',
          },
          {
            unitNumber: 'PH-02',
            floor: 14,
            view: 'Harbor Sunrise & Marina',
            monthlyRent: 9100,
            sqFt: 2250,
            status: 'under-contract',
            statusLabel: 'Under Contract',
            moveInDate: 'Pending',
            exposure: 'East / South',
          },
          {
            unitNumber: 'PH-03',
            floor: 15,
            view: 'Crown Sky Penthouse with Spa',
            monthlyRent: 10500,
            sqFt: 2250,
            status: 'available-soon',
            statusLabel: 'Available Nov 1',
            moveInDate: 'Nov 1, 2026',
            exposure: 'Crown Panoramic',
          },
        ],
      },
    }
    
    export function FloorPlanExplorer({ className }: FloorPlanExplorerProps) {
      const [activeTab, setActiveTab] = React.useState('plan-b')
      const [selectedRoomId, setSelectedRoomId] = React.useState<string | null>('living')
      const [hoveredRoomId, setHoveredRoomId] = React.useState<string | null>(null)
      const [selectedUnitNumber, setSelectedUnitNumber] = React.useState<string>('402')
      const [showDimensions, setShowDimensions] = React.useState(true)
      const [showFurniture, setShowFurniture] = React.useState(true)
      const [showPins, setShowPins] = React.useState(true)
      const [unitSystem, setUnitSystem] = React.useState<'imperial' | 'metric'>('imperial')
      const [isDownloadingPdf, setIsDownloadingPdf] = React.useState(false)
      const [downloadSuccess, setDownloadSuccess] = React.useState(false)
      const [showTourSuccess, setShowTourSuccess] = React.useState(false)
    
      const currentPlan = useMemo(() => floorPlans[activeTab] || floorPlans['plan-b'], [activeTab])
    
      const activeRoom = useMemo(() => {
        if (hoveredRoomId) {
          return currentPlan.rooms.find((r) => r.id === hoveredRoomId) || null
        }
        if (selectedRoomId) {
          return currentPlan.rooms.find((r) => r.id === selectedRoomId) || null
        }
        return currentPlan.rooms[0] || null
      }, [hoveredRoomId, selectedRoomId, currentPlan])
    
      const handleSelectTab = (key: string) => {
        setActiveTab(key)
        const plan = floorPlans[key]
        if (plan) {
          setSelectedRoomId(plan.rooms[0]?.id || null)
          setHoveredRoomId(null)
          setSelectedUnitNumber(plan.availableUnits[0]?.unitNumber || '')
        }
      }
    
      const handleSelectRoom = (roomId: string) => {
        setSelectedRoomId((prev) => (prev === roomId ? null : roomId))
      }
    
      const handleDownloadPdf = () => {
        setIsDownloadingPdf(true)
        setDownloadSuccess(false)
        setTimeout(() => {
          setIsDownloadingPdf(false)
          setDownloadSuccess(true)
          setTimeout(() => {
            setDownloadSuccess(false)
          }, 4000)
        }, 900)
      }
    
      const handleScheduleTour = () => {
        setShowTourSuccess(true)
        setTimeout(() => {
          setShowTourSuccess(false)
        }, 4500)
      }
    
      return (
        <div data-slot="floor-plan-explorer" className={cn('bg-background text-foreground w-full space-y-6', className)}>
          {/* Header: Building Title, Plan Switcher Tabs, Action Button */}
          <header className="border-border flex flex-col gap-4 border-b pb-6 lg:flex-row lg:items-center lg:justify-between">
            <div className="space-y-1.5">
              <div className="flex items-center gap-2">
                <Badge variant="outline" className="text-muted-foreground gap-1 px-2 py-0.5 font-mono text-xs">
                  <Building2 className="text-primary size-3" />
                  The Grandview Collection
                </Badge>
                <span className="text-muted-foreground text-xs">Tower West · Residences</span>
              </div>
              <h1 className="text-xl font-semibold tracking-tight sm:text-2xl">
                The Grandview Penthouse Collection · Floor Plan Explorer
              </h1>
              <p className="text-muted-foreground text-xs sm:text-sm">
                Interactive architectural layout inspector, dimensional specifications, and real-time residential unit
                availability.
              </p>
            </div>
    
            <div className="flex flex-wrap items-center gap-3">
              <Button
                aria-label="Download attachment"
                variant="outline"
                size="sm"
                className="h-9 gap-2 text-xs"
                disabled={isDownloadingPdf}
                onClick={handleDownloadPdf}
              >
                {!downloadSuccess ? (
                  <Download className="text-muted-foreground size-3.5" />
                ) : (
                  <Check className="text-success size-3.5" />
                )}
                <span>
                  {isDownloadingPdf ? 'Generating PDF...' : downloadSuccess ? 'PDF Downloaded' : 'Download PDF Floor Plan'}
                </span>
              </Button>
            </div>
          </header>
    
          {/* Plan Selection Tabs Bar */}
          <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
            <Tabs value={activeTab} className="w-full sm:w-auto" onValueChange={handleSelectTab}>
              <TabsList className="bg-muted/60 grid h-10 w-full grid-cols-3 gap-1 p-1 sm:w-auto">
                <TabsTrigger
                  value="plan-a"
                  className="data-[state=active]:bg-background px-3 text-xs font-medium data-[state=active]:shadow-xs sm:px-4 sm:text-sm"
                >
                  Plan A - 1 Bed / 1 Bath
                </TabsTrigger>
                <TabsTrigger
                  value="plan-b"
                  className="data-[state=active]:bg-background px-3 text-xs font-medium data-[state=active]:shadow-xs sm:px-4 sm:text-sm"
                >
                  Plan B - 2 Bed / 2 Bath
                </TabsTrigger>
                <TabsTrigger
                  value="plan-c"
                  className="data-[state=active]:bg-background px-3 text-xs font-medium data-[state=active]:shadow-xs sm:px-4 sm:text-sm"
                >
                  Plan C - 3 Bed Penthouse
                </TabsTrigger>
              </TabsList>
            </Tabs>
    
            <div className="flex items-center gap-2">
              <span className="text-muted-foreground text-xs">Units:</span>
              <div className="border-border bg-muted/30 inline-flex rounded-md border p-0.5">
                <button
                  type="button"
                  className={cn(
                    'focus-visible:ring-ring rounded px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
                    unitSystem === 'imperial'
                      ? 'bg-background text-foreground shadow-xs'
                      : 'text-muted-foreground hover:text-foreground',
                  )}
                  onClick={() => setUnitSystem('imperial')}
                >
                  Sq Ft / ft
                </button>
                <button
                  type="button"
                  className={cn(
                    'focus-visible:ring-ring rounded px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
                    unitSystem === 'metric'
                      ? 'bg-background text-foreground shadow-xs'
                      : 'text-muted-foreground hover:text-foreground',
                  )}
                  onClick={() => setUnitSystem('metric')}
                >
                  m² / m
                </button>
              </div>
            </div>
          </div>
    
          {/* 2-Column Inspector: Left 2D CAD Blueprint Canvas, Right Specs & Availability */}
          <div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
            {/* Left Column: 2D Architectural Layout Canvas (7 cols) */}
            <FloorPlanCanvas
              plan={currentPlan}
              activeRoom={activeRoom}
              unitSystem={unitSystem}
              showDimensions={showDimensions}
              showFurniture={showFurniture}
              showPins={showPins}
              selectedRoomId={selectedRoomId}
              hoveredRoomId={hoveredRoomId}
              onShowDimensionsChange={setShowDimensions}
              onShowFurnitureChange={setShowFurniture}
              onShowPinsChange={setShowPins}
              onHoverRoom={setHoveredRoomId}
              onSelectRoom={handleSelectRoom}
            />
    
            {/* Right Column: Unit Specs, Room Breakdown & Available Units Table (5 cols) */}
            <div className="space-y-6 lg:col-span-5">
              {/* Selected Plan Header Card with Total Area */}
              <Card className="border-border bg-card shadow-xs">
                <CardHeader className="pb-3">
                  <div className="flex flex-wrap items-center justify-between">
                    <Badge variant="secondary" className="font-mono text-xs">
                      {currentPlan.tier}
                    </Badge>
                    <div className="text-muted-foreground flex items-center gap-1.5 text-xs">
                      <Bed className="size-3.5" />
                      <span>{currentPlan.beds} Bed</span>
                      <span>·</span>
                      <Bath className="size-3.5" />
                      <span>{currentPlan.baths} Bath</span>
                    </div>
                  </div>
                  <CardTitle className="mt-1 text-lg font-semibold tracking-tight sm:text-xl">
                    {currentPlan.marketingName}
                  </CardTitle>
                  <CardDescription className="text-xs leading-relaxed sm:text-sm">
                    {currentPlan.description}
                  </CardDescription>
                </CardHeader>
    
                <CardContent className="space-y-4">
                  {/* Total Area Hero Metric Box */}
                  <div className="border-border bg-muted/40 rounded-lg border p-4">
                    <p className="text-muted-foreground text-xs font-medium">Total Architectural Living Area</p>
                    <div className="mt-1 flex items-baseline gap-2">
                      <span className="text-foreground font-mono text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                        {currentPlan.totalAreaSqFt.toLocaleString()}
                      </span>
                      <span className="text-muted-foreground text-sm font-medium">Sq Ft</span>
                      <span className="text-muted-foreground/60">/</span>
                      <span className="text-muted-foreground font-mono text-lg font-semibold tabular-nums">
                        {currentPlan.totalAreaSqM.toFixed(1)}
                      </span>
                      <span className="text-muted-foreground text-xs font-medium"></span>
                    </div>
                    <div className="border-border/60 text-muted-foreground mt-2 flex flex-wrap items-center justify-between border-t pt-2 text-xs">
                      <span>
                        Interior:{' '}
                        <strong className="text-foreground font-mono tabular-nums">{currentPlan.interiorSqFt} sq ft</strong>
                      </span>
                      <span>
                        Terrace:{' '}
                        <strong className="text-foreground font-mono tabular-nums">{currentPlan.exteriorSqFt} sq ft</strong>
                      </span>
                    </div>
                  </div>
    
                  {/* Room Breakdown List with Interactive Highlights */}
                  <div>
                    <div className="mb-2 flex flex-wrap items-center justify-between">
                      <h4 className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                        Room Breakdown & Dimensions
                      </h4>
                      <span className="text-muted-foreground text-xs">{currentPlan.rooms.length} Spaces</span>
                    </div>
    
                    <div className="space-y-1.5">
                      {currentPlan.rooms.map((room) => (
                        <div
                          key={room.id}
                          className={cn(
                            'flex cursor-pointer items-center justify-between rounded-md border p-2.5 transition-colors duration-150',
                            selectedRoomId === room.id
                              ? 'border-primary bg-primary/5 shadow-xs'
                              : hoveredRoomId === room.id
                                ? 'border-border bg-muted/60'
                                : 'border-border/60 bg-card hover:bg-muted/30',
                          )}
                          onMouseEnter={() => setHoveredRoomId(room.id)}
                          onMouseLeave={() => setHoveredRoomId(null)}
                          onClick={() => handleSelectRoom(room.id)}
                        >
                          <div className="flex min-w-0 items-center gap-2">
                            <span
                              className={cn(
                                'size-2 rounded-full transition-colors',
                                selectedRoomId === room.id
                                  ? 'bg-primary'
                                  : hoveredRoomId === room.id
                                    ? 'bg-primary/70'
                                    : 'bg-muted-foreground/40',
                              )}
                            />
                            <div>
                              <p className="text-foreground text-xs font-medium">{room.name}</p>
                              <p className="text-muted-foreground text-xs">{room.flooring}</p>
                            </div>
                          </div>
    
                          <div className="shrink-0 text-right">
                            <p className="text-foreground font-mono text-xs font-semibold tabular-nums">
                              {unitSystem === 'imperial' ? room.dimensionsImperial : room.dimensionsMetric}
                            </p>
                            <p className="text-muted-foreground font-mono text-xs tabular-nums">
                              {unitSystem === 'imperial' ? `${room.areaSqFt} sq ft` : `${room.areaSqM}`}
                            </p>
                          </div>
                        </div>
                      ))}
                    </div>
                  </div>
                </CardContent>
              </Card>
    
              {/* Available Units in Building Table */}
              <AvailableUnitsTable
                plan={currentPlan}
                selectedUnitNumber={selectedUnitNumber}
                tourSuccess={showTourSuccess}
                onSelectUnit={setSelectedUnitNumber}
                onScheduleTour={handleScheduleTour}
              />
            </div>
          </div>
        </div>
      )
    }
    
    export default FloorPlanExplorer
    
  • components/blocks/floor-plan-explorer/FloorPlanCanvas.tsx21.8 kB
  • components/blocks/floor-plan-explorer/AvailableUnitsTable.tsx5.8 kB
  • components/blocks/floor-plan-explorer/floor-plan-explorer-types.ts1 kB

Raw manifest:https://uipkge.dev/r/react/floor-plan-explorer.json