0
# Viewport Control
1
2
Viewport control system for testing components across different screen sizes and device configurations. Provides pre-defined device viewports and the ability to create custom viewport configurations for responsive design testing.
3
4
## Capabilities
5
6
### Viewport Configuration
7
8
Core interfaces for defining viewport dimensions and device characteristics.
9
10
```typescript { .api }
11
interface Viewport {
12
/** Display name for the viewport */
13
name: string;
14
/** CSS styles defining viewport dimensions */
15
styles: ViewportStyles;
16
/** Optional device category for organization */
17
type?: 'desktop' | 'mobile' | 'tablet' | 'other';
18
}
19
20
interface ViewportStyles {
21
/** Viewport height (CSS value) */
22
height: string;
23
/** Viewport width (CSS value) */
24
width: string;
25
}
26
27
/** Collection of viewport configurations indexed by key */
28
type ViewportMap = Record<string, Viewport>;
29
```
30
31
**Usage Example:**
32
33
```typescript
34
// Define custom viewports
35
const customViewports: ViewportMap = {
36
small: {
37
name: 'Small Mobile',
38
styles: {
39
width: '320px',
40
height: '568px',
41
},
42
type: 'mobile',
43
},
44
medium: {
45
name: 'Medium Tablet',
46
styles: {
47
width: '768px',
48
height: '1024px',
49
},
50
type: 'tablet',
51
},
52
large: {
53
name: 'Large Desktop',
54
styles: {
55
width: '1440px',
56
height: '900px',
57
},
58
type: 'desktop',
59
},
60
};
61
62
// Use in Storybook configuration
63
export const parameters = {
64
viewport: {
65
viewports: customViewports,
66
defaultViewport: 'medium',
67
},
68
};
69
```
70
71
### Global Viewport State
72
73
State management interface for viewport selection and rotation.
74
75
```typescript { .api }
76
interface GlobalState {
77
/** Currently selected viewport key */
78
value: string | undefined;
79
/** Whether the viewport is rotated 90 degrees */
80
isRotated?: boolean;
81
}
82
```
83
84
### Story-Level Viewport Parameters
85
86
Configure viewport behavior for individual stories or components.
87
88
```typescript { .api }
89
interface ViewportParameters {
90
viewport?: {
91
/** Disable viewport controls for this story */
92
disable?: boolean;
93
/** Available viewport options (overrides global) */
94
options?: ViewportMap;
95
/** Default viewport for this story */
96
defaultViewport?: string;
97
};
98
}
99
```
100
101
**Usage Example:**
102
103
```typescript
104
export const MobileOnlyComponent: Story = {
105
parameters: {
106
viewport: {
107
options: {
108
mobile: {
109
name: 'iPhone SE',
110
styles: { width: '375px', height: '667px' },
111
type: 'mobile',
112
},
113
mobileLandscape: {
114
name: 'iPhone SE Landscape',
115
styles: { width: '667px', height: '375px' },
116
type: 'mobile',
117
},
118
},
119
defaultViewport: 'mobile',
120
},
121
},
122
render: () => (
123
<div style={{
124
padding: '16px',
125
backgroundColor: '#f0f0f0',
126
minHeight: '100vh'
127
}}>
128
<h1>Mobile-Optimized Component</h1>
129
<p>This component is designed specifically for mobile viewports.</p>
130
</div>
131
),
132
};
133
134
export const ResponsiveComponent: Story = {
135
render: () => (
136
<div style={{
137
display: 'grid',
138
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
139
gap: '16px',
140
padding: '16px'
141
}}>
142
<div style={{
143
backgroundColor: '#e3f2fd',
144
padding: '20px',
145
borderRadius: '8px'
146
}}>
147
Card 1
148
</div>
149
<div style={{
150
backgroundColor: '#f3e5f5',
151
padding: '20px',
152
borderRadius: '8px'
153
}}>
154
Card 2
155
</div>
156
<div style={{
157
backgroundColor: '#e8f5e8',
158
padding: '20px',
159
borderRadius: '8px'
160
}}>
161
Card 3
162
</div>
163
</div>
164
),
165
};
166
```
167
168
## Pre-defined Viewport Collections
169
170
Storybook provides common device viewport configurations out of the box.
171
172
### Standard Device Viewports
173
174
```typescript
175
// Available as part of @storybook/addon-viewport
176
const INITIAL_VIEWPORTS = {
177
iphone5: {
178
name: 'iPhone 5',
179
styles: { width: '320px', height: '568px' },
180
type: 'mobile',
181
},
182
iphone6: {
183
name: 'iPhone 6',
184
styles: { width: '375px', height: '667px' },
185
type: 'mobile',
186
},
187
iphone6p: {
188
name: 'iPhone 6 Plus',
189
styles: { width: '414px', height: '736px' },
190
type: 'mobile',
191
},
192
iphonex: {
193
name: 'iPhone X',
194
styles: { width: '375px', height: '812px' },
195
type: 'mobile',
196
},
197
ipad: {
198
name: 'iPad',
199
styles: { width: '768px', height: '1024px' },
200
type: 'tablet',
201
},
202
ipadpro: {
203
name: 'iPad Pro',
204
styles: { width: '1024px', height: '1366px' },
205
type: 'tablet',
206
},
207
// ... additional devices
208
};
209
```
210
211
**Usage in Configuration:**
212
213
```typescript
214
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
215
216
export const parameters = {
217
viewport: {
218
viewports: {
219
...INITIAL_VIEWPORTS,
220
// Add custom viewports
221
ultrawide: {
222
name: 'Ultrawide Monitor',
223
styles: { width: '3440px', height: '1440px' },
224
type: 'desktop',
225
},
226
},
227
},
228
};
229
```
230
231
## Advanced Viewport Usage
232
233
### Viewport-Specific Stories
234
235
Create variants of stories optimized for different viewport sizes.
236
237
```typescript
238
export const DesktopLayout: Story = {
239
parameters: {
240
viewport: {
241
defaultViewport: 'desktop',
242
},
243
},
244
render: () => (
245
<div style={{
246
display: 'flex',
247
maxWidth: '1200px',
248
margin: '0 auto',
249
padding: '20px'
250
}}>
251
<aside style={{
252
width: '250px',
253
marginRight: '20px',
254
backgroundColor: '#f5f5f5',
255
padding: '16px',
256
borderRadius: '8px'
257
}}>
258
<h3>Sidebar</h3>
259
<nav>
260
<ul>
261
<li>Navigation Item 1</li>
262
<li>Navigation Item 2</li>
263
<li>Navigation Item 3</li>
264
</ul>
265
</nav>
266
</aside>
267
<main style={{ flex: 1 }}>
268
<h1>Main Content Area</h1>
269
<p>This layout is optimized for desktop viewing with a sidebar.</p>
270
</main>
271
</div>
272
),
273
};
274
275
export const MobileLayout: Story = {
276
parameters: {
277
viewport: {
278
defaultViewport: 'iphone6',
279
},
280
},
281
render: () => (
282
<div style={{ padding: '16px' }}>
283
<header style={{
284
backgroundColor: '#2196f3',
285
color: 'white',
286
padding: '12px',
287
marginBottom: '16px',
288
borderRadius: '4px'
289
}}>
290
<h1 style={{ margin: 0, fontSize: '18px' }}>Mobile Header</h1>
291
</header>
292
<main>
293
<h2>Main Content</h2>
294
<p>This layout is optimized for mobile viewing with a stacked design.</p>
295
</main>
296
<nav style={{
297
position: 'fixed',
298
bottom: 0,
299
left: 0,
300
right: 0,
301
backgroundColor: '#f5f5f5',
302
padding: '12px',
303
display: 'flex',
304
justifyContent: 'space-around'
305
}}>
306
<button>Home</button>
307
<button>Search</button>
308
<button>Profile</button>
309
</nav>
310
</div>
311
),
312
};
313
```
314
315
### Responsive Behavior Testing
316
317
Test how components adapt to different screen sizes.
318
319
```typescript
320
export const ResponsiveGrid: Story = {
321
render: () => {
322
const items = Array.from({ length: 12 }, (_, i) => ({
323
id: i + 1,
324
title: `Item ${i + 1}`,
325
description: `Description for item ${i + 1}`,
326
}));
327
328
return (
329
<div style={{
330
display: 'grid',
331
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
332
gap: '16px',
333
padding: '16px',
334
// Responsive adjustments
335
'@media (max-width: 768px)': {
336
gridTemplateColumns: '1fr',
337
gap: '12px',
338
padding: '12px',
339
},
340
}}>
341
{items.map((item) => (
342
<div
343
key={item.id}
344
style={{
345
backgroundColor: '#ffffff',
346
border: '1px solid #e0e0e0',
347
borderRadius: '8px',
348
padding: '16px',
349
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
350
}}
351
>
352
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px' }}>
353
{item.title}
354
</h3>
355
<p style={{ margin: 0, color: '#666', fontSize: '14px' }}>
356
{item.description}
357
</p>
358
</div>
359
))}
360
</div>
361
);
362
},
363
};
364
```
365
366
### Viewport Integration with Controls
367
368
Combine viewport controls with component props for comprehensive testing.
369
370
```typescript
371
export const ViewportAwareComponent: Story = {
372
args: {
373
compact: false,
374
showSidebar: true,
375
title: 'Sample Content',
376
},
377
argTypes: {
378
compact: {
379
control: 'boolean',
380
description: 'Use compact layout for smaller screens',
381
},
382
showSidebar: {
383
control: 'boolean',
384
description: 'Show/hide sidebar navigation',
385
},
386
title: {
387
control: 'text',
388
description: 'Page title',
389
},
390
},
391
render: (args) => {
392
const isMobile = window.innerWidth < 768;
393
const shouldUseCompact = args.compact || isMobile;
394
const shouldShowSidebar = args.showSidebar && !isMobile;
395
396
return (
397
<div style={{
398
display: 'flex',
399
flexDirection: shouldUseCompact ? 'column' : 'row',
400
minHeight: '100vh',
401
}}>
402
{shouldShowSidebar && (
403
<aside style={{
404
width: shouldUseCompact ? '100%' : '200px',
405
backgroundColor: '#f0f0f0',
406
padding: '16px',
407
order: shouldUseCompact ? 2 : 1,
408
}}>
409
<h3>Sidebar</h3>
410
<ul>
411
<li>Menu Item 1</li>
412
<li>Menu Item 2</li>
413
<li>Menu Item 3</li>
414
</ul>
415
</aside>
416
)}
417
<main style={{
418
flex: 1,
419
padding: '16px',
420
order: shouldUseCompact ? 1 : 2,
421
}}>
422
<h1>{args.title}</h1>
423
<p>
424
Current layout: {shouldUseCompact ? 'Compact' : 'Standard'}<br />
425
Sidebar: {shouldShowSidebar ? 'Visible' : 'Hidden'}
426
</p>
427
</main>
428
</div>
429
);
430
},
431
};
432
```
433
434
## Configuration Examples
435
436
### Global Viewport Setup
437
438
```typescript
439
// .storybook/preview.js
440
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
441
442
const customViewports = {
443
kindleFire2: {
444
name: 'Kindle Fire 2',
445
styles: {
446
width: '600px',
447
height: '963px',
448
},
449
type: 'tablet',
450
},
451
kindleFireHD: {
452
name: 'Kindle Fire HD',
453
styles: {
454
width: '533px',
455
height: '801px',
456
},
457
type: 'tablet',
458
},
459
};
460
461
export const parameters = {
462
viewport: {
463
viewports: {
464
...INITIAL_VIEWPORTS,
465
...customViewports,
466
},
467
defaultViewport: 'responsive',
468
},
469
};
470
```
471
472
### Story-Specific Viewport Restrictions
473
474
```typescript
475
export const TabletOnlyFeature: Story = {
476
parameters: {
477
viewport: {
478
viewports: {
479
ipad: INITIAL_VIEWPORTS.ipad,
480
ipadpro: INITIAL_VIEWPORTS.ipadpro,
481
},
482
defaultViewport: 'ipad',
483
},
484
},
485
render: () => (
486
<div>This feature is only available on tablet devices</div>
487
),
488
};
489
```