or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions.mdcli-commands.mdframework-support.mdhighlighting.mdindex.mdmanager-api.mdstory-composition.mdtesting.mdtheming.mdviewport.md

viewport.mddocs/

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

```