1import type {
2 PreviewAutomationResizeInput,
3 PreviewViewportPresetId,
4 PreviewViewportSetting,
5} from "@t3tools/contracts";
6import { PREVIEW_VIEWPORT_PRESET_IDS } from "@t3tools/contracts";
7
8export interface PreviewViewportPreset {
9 readonly id: PreviewViewportPresetId;
10 readonly label: string;
11 readonly category: "Desktop" | "Tablet" | "Phone";
12 readonly detail: string;
13 readonly width: number;
14 readonly height: number;
15}
16
17type PreviewViewportPresetDefinition = Omit<PreviewViewportPreset, "id">;
18
19// Keep this in Chrome DevTools' default-device order. Dimensions are CSS
20// viewport sizes from Chromium's EmulatedDevices.ts standard catalog.
21const PREVIEW_VIEWPORT_PRESET_DEFINITIONS = {
22 "iphone-se": {
23 label: "iPhone SE",
24 category: "Phone",
25 detail: "375 × 667",
26 width: 375,
27 height: 667,
28 },
29 "iphone-xr": {
30 label: "iPhone XR",
31 category: "Phone",
32 detail: "414 × 896",
33 width: 414,
34 height: 896,
35 },
36 "iphone-12-pro": {
37 label: "iPhone 12 Pro",
38 category: "Phone",
39 detail: "390 × 844",
40 width: 390,
41 height: 844,
42 },
43 "iphone-14-pro-max": {
44 label: "iPhone 14 Pro Max",
45 category: "Phone",
46 detail: "430 × 932",
47 width: 430,
48 height: 932,
49 },
50 "pixel-7": {
51 label: "Pixel 7",
52 category: "Phone",
53 detail: "412 × 915",
54 width: 412,
55 height: 915,
56 },
57 "samsung-galaxy-s8-plus": {
58 label: "Samsung Galaxy S8+",
59 category: "Phone",
60 detail: "360 × 740",
61 width: 360,
62 height: 740,
63 },
64 "samsung-galaxy-s20-ultra": {
65 label: "Samsung Galaxy S20 Ultra",
66 category: "Phone",
67 detail: "412 × 915",
68 width: 412,
69 height: 915,
70 },
71 "ipad-mini": {
72 label: "iPad Mini",
73 category: "Tablet",
74 detail: "768 × 1024",
75 width: 768,
76 height: 1024,
77 },
78 "ipad-air": {
79 label: "iPad Air",
80 category: "Tablet",
81 detail: "820 × 1180",
82 width: 820,
83 height: 1180,
84 },
85 "ipad-pro": {
86 label: "iPad Pro",
87 category: "Tablet",
88 detail: "1024 × 1366",
89 width: 1024,
90 height: 1366,
91 },
92 "surface-pro-7": {
93 label: "Surface Pro 7",
94 category: "Tablet",
95 detail: "912 × 1368",
96 width: 912,
97 height: 1368,
98 },
99 "surface-duo": {
100 label: "Surface Duo",
101 category: "Phone",
102 detail: "540 × 720",
103 width: 540,
104 height: 720,
105 },
106 "galaxy-z-fold-5": {
107 label: "Galaxy Z Fold 5",
108 category: "Phone",
109 detail: "344 × 882",
110 width: 344,
111 height: 882,
112 },
113 "asus-zenbook-fold": {
114 label: "Asus Zenbook Fold",
115 category: "Tablet",
116 detail: "853 × 1280",
117 width: 853,
118 height: 1280,
119 },
120 "samsung-galaxy-a51-71": {
121 label: "Samsung Galaxy A51/71",
122 category: "Phone",
123 detail: "412 × 914",
124 width: 412,
125 height: 914,
126 },
127 "nest-hub": {
128 label: "Nest Hub",
129 category: "Tablet",
130 detail: "1024 × 600",
131 width: 1024,
132 height: 600,
133 },
134 "nest-hub-max": {
135 label: "Nest Hub Max",
136 category: "Tablet",
137 detail: "1280 × 800",
138 width: 1280,
139 height: 800,
140 },
141} as const satisfies Record<PreviewViewportPresetId, PreviewViewportPresetDefinition>;
142
143export const PREVIEW_VIEWPORT_PRESETS: ReadonlyArray<PreviewViewportPreset> =
144 PREVIEW_VIEWPORT_PRESET_IDS.map((id) => ({
145 id,
146 ...PREVIEW_VIEWPORT_PRESET_DEFINITIONS[id],
147 }));
148
149export function resolvePreviewViewport(
150 input: PreviewAutomationResizeInput,
151): PreviewViewportSetting {
152 if (input.mode === "fill") return { _tag: "fill" };
153 if (input.mode === "preset" && input.preset !== undefined) {
154 const preset = PREVIEW_VIEWPORT_PRESETS.find((candidate) => candidate.id === input.preset);
155 if (!preset) throw new Error(`Unknown preview viewport preset: ${input.preset}`);
156 const landscape = input.orientation === "landscape";
157 const portrait = input.orientation === "portrait";
158 const nativePortrait = preset.height >= preset.width;
159 const shouldSwap = (landscape && nativePortrait) || (portrait && !nativePortrait);
160 return {
161 _tag: "preset",
162 width: shouldSwap ? preset.height : preset.width,
163 height: shouldSwap ? preset.width : preset.height,
164 presetId: preset.id,
165 };
166 }
167 if (input.width === undefined || input.height === undefined) {
168 throw new Error("Custom preview viewport requires width and height");
169 }
170 return {
171 _tag: "freeform",
172 width: input.width,
173 height: input.height,
174 };
175}