import type { Meta, StoryObj } from '@storybook/nextjs-vite'; import { useState } from 'react'; import { fn } from 'storybook/test'; import { Button } from '../components/ui/button'; import { Input } from '../components/ui/input'; import { ModalV2, ModalV2Content, ModalV2Footer, ModalV2Header, ModalV2Title, ModalV2TwoColumn, } from '../components/ui/modal-v2'; import { Textarea } from '../components/ui/textarea'; type ModalV2StoryMeta = Meta; const meta: ModalV2StoryMeta = { title: 'UI/ModalV2', component: ModalV2, parameters: { layout: 'fullscreen', docs: { description: { component: 'The current modal. `size` picks the panel width (`default` 28rem / `medium` 42rem / `wide` 1400px); `wide` also FIXES the desktop panel height so it stops jumping as content hydrates in. Use `ModalV2TwoColumn` instead of `ModalV2Content` for two-column editors — below `lg` it is one column with a single outer scroll, from `lg` up each column scrolls independently while header and footer stay pinned.', }, }, }, decorators: [ Story => (
), ], argTypes: { isOpen: { control: 'boolean', description: 'Whether the modal is open', }, size: { control: 'radio', options: ['default', 'medium', 'wide'], description: 'Panel width. `default` = confirms, `medium` = single-column forms, `wide` = two-column editors (also fixes the desktop height)', }, className: { control: 'text', description: 'Custom className for the modal panel', }, }, }; export default meta; type Story = StoryObj; /* ------------------------------------------------------------------ * * Story content — deliberately long so the scroll model is reviewable. * ------------------------------------------------------------------ */ const VENDOR_FIELDS: { label: string; value: string }[] = [ { label: 'Vendor name', value: 'Acme Endpoint Security' }, { label: 'Slug', value: 'acme-endpoint-security' }, { label: 'Website', value: 'https://acme-endpoint.example' }, { label: 'Support email', value: 'support@acme-endpoint.example' }, { label: 'Headquarters', value: 'Austin, TX' }, { label: 'Founded', value: '2014' }, { label: 'Employees', value: '240' }, { label: 'Category', value: 'Endpoint protection' }, { label: 'Sub-category', value: 'EDR' }, { label: 'Deployment', value: 'Cloud, on-premise' }, { label: 'Agent platforms', value: 'Windows, macOS, Linux' }, { label: 'Compliance', value: 'SOC 2 Type II, ISO 27001' }, { label: 'Integration partner', value: 'OpenFrame' }, { label: 'Account manager', value: 'Dana Whitfield' }, { label: 'Renewal date', value: '2026-11-30' }, { label: 'Contract owner', value: 'Procurement' }, ]; const ACTIVITY_ROWS: { title: string; meta: string; body: string }[] = [ { title: 'Pricing refreshed', meta: 'Today · 09:12', body: 'Per-device tier moved from $4.20 to $4.60. Annual commitment unchanged.', }, { title: 'Feature matrix synced', meta: 'Yesterday · 17:40', body: 'Ransomware rollback and USB device control marked as generally available.', }, { title: 'Case study published', meta: '3 days ago', body: 'Regional MSP consolidated three agents onto one console after migration.', }, { title: 'Security review passed', meta: '6 days ago', body: 'SOC 2 Type II report re-issued; no exceptions carried forward.', }, { title: 'Support SLA updated', meta: '2 weeks ago', body: 'P1 response tightened to 30 minutes for partners on the managed plan.', }, { title: 'Partner tier changed', meta: '3 weeks ago', body: 'Promoted to Gold. Deal registration discount now applies automatically.', }, { title: 'Console redesign shipped', meta: '1 month ago', body: 'Alert triage moved into a single queue with saved views per technician.', }, { title: 'API v3 announced', meta: '1 month ago', body: 'Webhook payloads gain device tags; v2 stays supported through next year.', }, { title: 'Outage postmortem', meta: '2 months ago', body: 'Ingest backlog cleared in 90 minutes; regional failover added since.', }, { title: 'Onboarding docs rewritten', meta: '2 months ago', body: 'Silent-install flags documented for every supported RMM deployment tool.', }, ]; function VendorFields({ count = VENDOR_FIELDS.length }: { count?: number }) { return ( <>

Vendor profile

{VENDOR_FIELDS.slice(0, count).map(field => ( ))}