Skip to main content
Glama
keyboardScreenAdapter.stories.tsx21.5 kB
import type { Meta, StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; import { KeyboardScreenAdapter } from '.'; const meta: Meta<typeof KeyboardScreenAdapter> = { title: 'Components/KeyboardScreenAdapter', component: KeyboardScreenAdapter, tags: ['autodocs'], argTypes: { children: { description: 'Content to render within the adaptive container', control: false, }, className: { description: 'Additional CSS classes for the container', control: 'text', }, style: { description: 'Inline styles (note: maxHeight may be overridden by keyboard detection)', control: 'object', }, }, parameters: { docs: { description: { component: 'A smart wrapper component that automatically adapts viewport height when virtual keyboards appear on mobile devices. Prevents content from being hidden behind the keyboard.', }, }, layout: 'fullscreen', }, } satisfies Meta<typeof KeyboardScreenAdapter>; export default meta; type Story = StoryObj<typeof meta>; // Basic Examples export const Default: Story = { args: { children: ( <div className="flex h-full items-center justify-center bg-linear-to-br from-blue-50 to-indigo-100"> <div className="p-6 text-center"> <h1 className="mb-4 font-bold text-2xl text-gray-800"> KeyboardScreenAdapter Demo </h1> <p className="mb-6 text-gray-600"> This component adapts to virtual keyboard visibility on mobile devices. </p> <input type="text" placeholder="Try typing here on a mobile device" className="w-full max-w-md rounded-lg border border-gray-300 px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> </div> ), }, parameters: { docs: { description: { story: 'Basic usage with a simple input field that demonstrates keyboard adaptation.', }, }, }, }; export const FormExample: Story = { args: { children: ( <div className="min-h-screen bg-gray-50 py-8"> <div className="mx-auto max-w-md rounded-lg bg-white p-6 shadow-md"> <h2 className="mb-6 font-semibold text-gray-800 text-xl"> Contact Form </h2> <form className="space-y-4"> <div> <label htmlFor="name" className="mb-2 block font-medium text-gray-700 text-sm" > Full Name </label> <input id="name" type="text" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter your full name" /> </div> <div> <label htmlFor="email" className="mb-2 block font-medium text-gray-700 text-sm" > Email Address </label> <input id="email" type="email" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter your email" /> </div> <div> <label htmlFor="phone" className="mb-2 block font-medium text-gray-700 text-sm" > Phone Number </label> <input id="phone" type="tel" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter your phone number" /> </div> <div> <label htmlFor="message" className="mb-2 block font-medium text-gray-700 text-sm" > Message </label> <textarea id="message" rows={4} className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter your message" /> </div> <button type="submit" className="w-full rounded-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500" > Send Message </button> </form> </div> </div> ), }, parameters: { docs: { description: { story: 'A complete contact form that demonstrates how KeyboardScreenAdapter prevents form fields from being hidden when the virtual keyboard appears.', }, }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Test form elements are present expect(canvas.getByLabelText('Full Name')).toBeInTheDocument(); expect(canvas.getByLabelText('Email Address')).toBeInTheDocument(); expect(canvas.getByLabelText('Phone Number')).toBeInTheDocument(); expect(canvas.getByLabelText('Message')).toBeInTheDocument(); // Test form submission button const submitButton = canvas.getByRole('button', { name: /send message/i }); expect(submitButton).toBeInTheDocument(); }, }; export const ChatInterface: Story = { args: { children: ( <div className="flex h-screen flex-col bg-gray-100"> {/* Header */} <div className="bg-blue-600 p-4 text-white"> <h2 className="font-semibold text-lg">Chat Support</h2> <p className="text-blue-100 text-sm"> Online • Typically replies instantly </p> </div> {/* Messages */} <div className="flex-1 space-y-4 overflow-auto p-4"> <div className="flex"> <div className="max-w-xs rounded-lg bg-white p-3 shadow-sm"> <p className="text-sm">Hello! How can we help you today?</p> <p className="mt-1 text-gray-500 text-xs">10:30 AM</p> </div> </div> <div className="flex justify-end"> <div className="max-w-xs rounded-lg bg-blue-600 p-3 text-white"> <p className="text-sm">I need help with my account settings</p> <p className="mt-1 text-blue-100 text-xs">10:32 AM</p> </div> </div> <div className="flex"> <div className="max-w-xs rounded-lg bg-white p-3 shadow-sm"> <p className="text-sm"> I'd be happy to help you with that! What specific setting would you like to change? </p> <p className="mt-1 text-gray-500 text-xs">10:33 AM</p> </div> </div> </div> {/* Message Input */} <div className="border-t bg-white p-4"> <div className="flex space-x-2"> <input type="text" placeholder="Type your message..." className="flex-1 rounded-full border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> <button className="rounded-full bg-blue-600 px-6 py-2 text-white transition-colors hover:bg-blue-700"> Send </button> </div> </div> </div> ), }, parameters: { docs: { description: { story: 'A chat interface that demonstrates how KeyboardScreenAdapter ensures the message input remains visible and accessible when typing on mobile devices.', }, }, }, }; export const SearchInterface: Story = { args: { children: ( <div className="min-h-screen bg-white"> {/* Search Header */} <div className="sticky top-0 border-b bg-white p-4"> <div className="mx-auto max-w-2xl"> <div className="relative"> <input type="search" placeholder="Search for anything..." className="w-full rounded-full border border-gray-300 px-4 py-3 pr-4 pl-12 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" /> <div className="-translate-y-1/2 absolute top-1/2 left-4 transform"> <svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> </svg> </div> </div> </div> </div> {/* Search Results */} <div className="mx-auto max-w-2xl p-4"> <div className="space-y-4"> {[ 'Getting started with KeyboardScreenAdapter', 'Mobile-first responsive design patterns', 'Handling virtual keyboards in web applications', 'Accessibility best practices for mobile forms', 'Progressive Web App development guide', ].map((result, index) => ( <div key={index} className="rounded-lg border border-gray-200 p-4 hover:bg-gray-50" > <h3 className="font-medium text-gray-900">{result}</h3> <p className="mt-1 text-gray-600 text-sm"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. </p> <div className="mt-2 flex items-center text-gray-500 text-xs"> <span>example.com</span> <span className="mx-2">•</span> <span>2 min read</span> </div> </div> ))} </div> </div> </div> ), }, parameters: { docs: { description: { story: 'A search interface showing how the component maintains usability during search input on mobile devices.', }, }, }, }; // Styling Variations export const CustomStyling: Story = { args: { className: 'bg-linear-to-br from-purple-50 to-pink-50', children: ( <div className="flex h-full items-center justify-center p-8"> <div className="w-full max-w-md rounded-xl bg-white/80 p-8 shadow-xl backdrop-blur-sm"> <h2 className="mb-6 text-center font-bold text-2xl text-gray-800"> Custom Styled Container </h2> <div className="space-y-4"> <input type="text" placeholder="Username" className="w-full rounded-lg border border-gray-200 bg-white/50 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500" /> <input type="password" placeholder="Password" className="w-full rounded-lg border border-gray-200 bg-white/50 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500" /> <button className="w-full rounded-lg bg-gradient-to-r from-purple-600 to-pink-600 py-3 font-medium text-white transition-all hover:from-purple-700 hover:to-pink-700"> Sign In </button> </div> </div> </div> ), }, parameters: { docs: { description: { story: 'KeyboardScreenAdapter with custom styling including gradients and glassmorphism effects.', }, }, }, }; export const MinimalHeight: Story = { args: { className: 'min-h-[400px] max-w-md mx-auto bg-gray-50', children: ( <div className="h-full p-6"> <h3 className="mb-4 font-semibold text-lg">Compact Form</h3> <div className="space-y-3"> <input type="email" placeholder="Email address" className="w-full rounded border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> <input type="password" placeholder="Password" className="w-full rounded border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> <button className="w-full rounded bg-blue-600 py-2 text-white hover:bg-blue-700"> Login </button> </div> </div> ), }, parameters: { docs: { description: { story: 'Compact version with custom height constraints, useful for modal or sidebar implementations.', }, }, }, }; // Accessibility Testing export const AccessibilityTest: Story = { args: { role: 'main', 'aria-label': 'Main application content with keyboard adaptation', children: ( <div className="mx-auto max-w-lg p-6"> <h1 className="mb-4 font-bold text-xl">Accessible Form</h1> <form className="space-y-4"> <div> <label htmlFor="acc-name" className="mb-1 block font-medium text-sm" > Name (required) </label> <input id="acc-name" type="text" required aria-describedby="name-help" className="w-full rounded border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> <p id="name-help" className="mt-1 text-gray-600 text-sm"> Enter your full legal name </p> </div> <div> <label htmlFor="acc-email" className="mb-1 block font-medium text-sm" > Email (required) </label> <input id="acc-email" type="email" required aria-describedby="email-help" className="w-full rounded border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> <p id="email-help" className="mt-1 text-gray-600 text-sm"> We'll use this to contact you </p> </div> <button type="submit" className="w-full rounded bg-green-600 py-2 text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500" > Submit Form </button> </form> </div> ), }, parameters: { docs: { description: { story: 'Form with comprehensive accessibility features including proper labels, ARIA attributes, and keyboard navigation support.', }, }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Test accessibility attributes on the container const main = canvas.getByRole('main'); expect(main).toHaveAttribute( 'aria-label', 'Main application content with keyboard adaptation' ); // Test form accessibility const nameInput = canvas.getByLabelText(/name \(required\)/i); const emailInput = canvas.getByLabelText(/email \(required\)/i); expect(nameInput).toHaveAttribute('required'); expect(nameInput).toHaveAttribute('aria-describedby', 'name-help'); expect(emailInput).toHaveAttribute('required'); expect(emailInput).toHaveAttribute('aria-describedby', 'email-help'); // Test help text association expect(canvas.getByText('Enter your full legal name')).toHaveAttribute( 'id', 'name-help' ); expect(canvas.getByText("We'll use this to contact you")).toHaveAttribute( 'id', 'email-help' ); }, }; // Edge Cases and Real-world Examples export const LongContent: Story = { args: { children: ( <div className="p-6"> <h1 className="mb-4 font-bold text-2xl">Long Scrollable Content</h1> <div className="space-y-6"> {Array.from({ length: 10 }, (_, i) => ( <div key={i} className="rounded-lg bg-white p-4 shadow"> <h3 className="mb-2 font-semibold">Section {i + 1}</h3> <p className="mb-4 text-gray-600"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. </p> {i === 5 && ( <input type="text" placeholder="Input in the middle of long content" className="w-full rounded border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> )} </div> ))} <div className="rounded-lg bg-blue-50 p-4"> <h3 className="mb-2 font-semibold">Final Input</h3> <input type="text" placeholder="Try typing here after scrolling" className="w-full rounded border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> </div> </div> ), }, parameters: { docs: { description: { story: 'Long scrollable content with inputs at various positions to test keyboard adaptation with scrolling.', }, }, }, }; export const MultiStepForm: Story = { args: { children: ( <div className="min-h-screen bg-gray-50 py-8"> <div className="mx-auto max-w-md"> {/* Progress Steps */} <div className="mb-8"> <div className="flex items-center justify-center space-x-4"> {[1, 2, 3].map((step, index) => ( <div key={step} className="flex items-center"> <div className={`flex h-8 w-8 items-center justify-center rounded-full font-medium text-sm ${ index === 0 ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600' }`} > {step} </div> {index < 2 && <div className="mx-2 h-px w-12 bg-gray-300" />} </div> ))} </div> <p className="mt-2 text-center text-gray-600 text-sm"> Step 1 of 3: Personal Information </p> </div> {/* Form Content */} <div className="rounded-lg bg-white p-6 shadow-md"> <h2 className="mb-6 font-semibold text-xl">Personal Information</h2> <div className="space-y-4"> <div className="grid grid-cols-2 gap-4"> <div> <label className="mb-1 block font-medium text-gray-700 text-sm"> First Name </label> <input type="text" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="John" /> </div> <div> <label className="mb-1 block font-medium text-gray-700 text-sm"> Last Name </label> <input type="text" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Doe" /> </div> </div> <div> <label className="mb-1 block font-medium text-gray-700 text-sm"> Date of Birth </label> <input type="date" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="mb-1 block font-medium text-gray-700 text-sm"> Phone Number </label> <input type="tel" className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="+1 (555) 123-4567" /> </div> <div className="flex justify-between pt-6"> <button className="rounded-md border border-gray-300 px-6 py-2 text-gray-700 hover:bg-gray-50"> Back </button> <button className="rounded-md bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"> Next Step </button> </div> </div> </div> </div> </div> ), }, parameters: { docs: { description: { story: 'Multi-step form wizard that demonstrates keyboard adaptation across different form sections and input types.', }, }, }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server