| 1 | package gallery |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "html/template" |
| 6 | ) |
| 7 | |
| 8 | // The preview heights: how tall each example's own document is, so the |
| 9 | // box the widget draws it in has its height before the frame arrives, |
| 10 | // scripts or no scripts, and a wrong number is a test failure rather |
| 11 | // than a jump on screen. Measured, not guessed: the two height drives |
| 12 | // in heights_browser_test.go read every frame's content off a real |
| 13 | // engine and name the number to write here; FIESTA_SWEEP=full prints |
| 14 | // the whole table. Keyed <example>/<state>, <example>/row for a |
| 15 | // grouped widget, and demo for the demo application. |
| 16 | // |
| 17 | // A box holds what a reader opens in it as well as what is there at |
| 18 | // rest: a menu, the enhanced select's list, the date and time fields' |
| 19 | // calendar or clock. The drives open each in turn in a box made tall |
| 20 | // enough that a menu's dvh cap is not the frame, and the number here is |
| 21 | // the larger of the two readings, so a box sized for the closed state |
| 22 | // is a short box. Rastrillo reserves the same room by hand. The one |
| 23 | // exception is a frame in previewRestHeights, whose open menus need far |
| 24 | // more than its page: there the box rests at the closed reading and |
| 25 | // view.js grows it to this one while a menu is open. |
| 26 | // |
| 27 | // Seeded once by the widget task to prove the measurement; calibrated |
| 28 | // once the shell, the frame padding, the stage and the demo were all |
| 29 | // in, which is when the drives started failing a short box; and swept |
| 30 | // again over all twelve locales once the samples were translated, which |
| 31 | // is where previewMobileLocaleHeights comes from: the languages whose |
| 32 | // samples wrap taller than English's on a phone. |
| 33 | |
| 34 | // previewHeight is what an example gets when the table has nothing to |
| 35 | // say about it. |
| 36 | const previewHeight = 220 |
| 37 | |
| 38 | // previewHeights is each key's Desktop height, in CSS pixels at its |
| 39 | // class's virtual width: the tallest of its content at rest and with |
| 40 | // each thing in it open, over the twelve locales, with slack. |
| 41 | var previewHeights = map[string]int{ |
| 42 | "actor/a-person": 80, |
| 43 | "actor/an-agent-acting-for-a-person": 80, |
| 44 | "api-tokens/four-tokens-two-of-them-live": 990, |
| 45 | "api-tokens/just-created-the-secret-shown-once": 1120, |
| 46 | "api-tokens/no-tokens-yet": 670, |
| 47 | "back-nav/back-to-a-record": 100, |
| 48 | "badge/row": 100, |
| 49 | "box/default": 220, |
| 50 | "bulk-bar/three-rows-selected": 210, |
| 51 | "button/default": 270, |
| 52 | "callout/info": 140, |
| 53 | "callout/negative": 160, |
| 54 | "callout/negative-announced-as-an-alert": 160, |
| 55 | "callout/positive": 160, |
| 56 | "callout/warning": 160, |
| 57 | "choice-field/pick-any": 280, |
| 58 | "choice-field/pick-one": 280, |
| 59 | "confirm-form/destructive": 170, |
| 60 | "confirm-form/ordinary-default-cancel-wording": 170, |
| 61 | "demo": 760, |
| 62 | "detail-list/four-facts-one-machine-ish": 230, |
| 63 | "dropdown/a-filter-menu-with-one-choice-applied": 200, |
| 64 | "dropdown/menu-idiom": 210, |
| 65 | "empty-state/sentence-only": 160, |
| 66 | "empty-state/with-a-post-call-to-action": 240, |
| 67 | "error-page/403-forbidden": 350, |
| 68 | "error-page/404-not-found": 350, |
| 69 | "error-page/422-unprocessable": 330, |
| 70 | "error-page/500-server-error": 400, |
| 71 | "error-page/503-unavailable": 350, |
| 72 | "error-page/anything-else-the-generic-pair": 330, |
| 73 | "field-check/disabled": 160, |
| 74 | "field-check/off": 160, |
| 75 | "field-check/on": 160, |
| 76 | "field-date/bounded": 430, |
| 77 | "field-date/enhanced-input-type-tomorrow-or-in-3-weeks": 430, |
| 78 | "field-date/plain-the-bare-native-input": 190, |
| 79 | "field-date/required-with-an-error": 430, |
| 80 | "field-daterange/date-and-time-seeded": 460, |
| 81 | "field-daterange/dates-only-with-an-error-on-the-end": 460, |
| 82 | "field-datetime/bounded": 430, |
| 83 | "field-datetime/enhanced-input-type-tomorrow-or-in-3-weeks": 430, |
| 84 | "field-datetime/plain-the-bare-native-input": 190, |
| 85 | "field-datetime/required-with-an-error": 430, |
| 86 | "field-money/bare": 190, |
| 87 | "field-money/with-a-hint": 210, |
| 88 | "field-money/with-an-error": 210, |
| 89 | "field-select/hand-written-with-optgroups": 400, |
| 90 | "field-select/required-with-an-error": 210, |
| 91 | "field-select/three-options-a-native-select": 180, |
| 92 | "field-select/twelve-options-plain-the-enhancement-opted-out": 180, |
| 93 | "field-select/twelve-options-the-filterable-combobox": 400, |
| 94 | "field-text/bare": 190, |
| 95 | "field-text/required": 190, |
| 96 | "field-text/the-headline-field": 190, |
| 97 | "field-text/with-a-hint": 210, |
| 98 | "field-text/with-an-error": 210, |
| 99 | "field-textarea/bare": 230, |
| 100 | "field-textarea/required": 230, |
| 101 | "field-textarea/with-a-hint": 260, |
| 102 | "field-textarea/with-an-error": 260, |
| 103 | "field-time/bounded": 440, |
| 104 | "field-time/enhanced-input-type-half-seven-or-noon": 440, |
| 105 | "field-time/plain-the-bare-native-input": 190, |
| 106 | "field-time/required-with-an-error": 440, |
| 107 | "field-url/bare": 190, |
| 108 | "field-url/with-a-hint": 210, |
| 109 | "field-url/with-an-error": 210, |
| 110 | "field/bare": 190, |
| 111 | "field/required": 190, |
| 112 | "field/short": 190, |
| 113 | "field/the-headline-field": 190, |
| 114 | "field/with-a-hint": 190, |
| 115 | "field/with-an-error": 210, |
| 116 | "field/with-help-under-the-control": 210, |
| 117 | "form-error/a-rejected-submission": 100, |
| 118 | "form-foot/idle-the-button-before-anything-happens": 180, |
| 119 | "form-foot/save-and-cancel": 180, |
| 120 | "form-foot/submit-only": 180, |
| 121 | "form-foot/working-what-busy-js-writes-on-the-way-out": 180, |
| 122 | "form-layout/default": 380, |
| 123 | "format-abbr/default": 170, |
| 124 | "format-addresses/default": 220, |
| 125 | "format-dates/default": 150, |
| 126 | "format-durations/default": 150, |
| 127 | "format-filesizes/default": 150, |
| 128 | "format-identifiers/default": 170, |
| 129 | "format-money/default": 210, |
| 130 | "format-numbers/default": 140, |
| 131 | "format-output/default": 230, |
| 132 | "format-people/default": 120, |
| 133 | "format-ratios/default": 280, |
| 134 | "help/default": 100, |
| 135 | "job-status/failed": 80, |
| 136 | "job-status/finished": 80, |
| 137 | "job-status/running": 90, |
| 138 | "layout-column/default": 920, |
| 139 | "layout-console-page/default": 680, |
| 140 | "layout-console/default": 680, |
| 141 | "layout-sidebar-page/default": 560, |
| 142 | "layout-sidebar/default": 560, |
| 143 | "layout-stage/default": 460, |
| 144 | "layout-topbar/default": 660, |
| 145 | "list-bar-search/empty-accessible-name-from-the-catalog": 120, |
| 146 | "list-bar-search/with-a-query": 120, |
| 147 | "list-bar-search/with-a-query-on-page-4": 120, |
| 148 | "list-bar/search-with-a-filter-dropdown": 210, |
| 149 | "list-grid/default": 280, |
| 150 | "list-row-action/a-short-status-beside-the-name": 120, |
| 151 | "list-row-action/lead-marker-status-and-an-action": 120, |
| 152 | "list-row-action/name-and-meta-line-only": 120, |
| 153 | "list-search-submit/default-label": 130, |
| 154 | "locale-menu/twelve-languages-current-highlighted": 440, |
| 155 | "media-grid/screenshots-a-logo-and-a-banner": 720, |
| 156 | "media-grid/select-mode-one-tile-selected": 400, |
| 157 | "meter/row": 100, |
| 158 | "modal/default": 250, |
| 159 | "notice/a-saved-message": 120, |
| 160 | "page-header/title-only": 120, |
| 161 | "page-header/title-subhead-and-a-primary-action": 150, |
| 162 | "pagination/first-page-default-accessible-name": 120, |
| 163 | "pagination/middle-of-a-long-list": 120, |
| 164 | "person/large-the-show-header-size": 100, |
| 165 | "person/name-and-email": 90, |
| 166 | "person/no-initial": 90, |
| 167 | "row-menu/links-a-form-and-a-destructive-item": 210, |
| 168 | "row-menu/links-only-for-a-list-inside-a-selection-form": 180, |
| 169 | "scheme-menu/standalone": 210, |
| 170 | "scheme-menu/three-choices-dark-current": 210, |
| 171 | "seg-tabs/buttons-that-switch-in-place": 110, |
| 172 | "seg-tabs/two-sections": 110, |
| 173 | "side-nav/four-sections-the-first-open": 210, |
| 174 | "selbox/default": 90, |
| 175 | "shell-sidebar/default": 260, |
| 176 | "shell-topbar/default": 210, |
| 177 | "signin/a-passkey": 470, |
| 178 | "signin/after-the-link-is-sent": 390, |
| 179 | "signin/after-the-link-is-sent-in-another-browser": 390, |
| 180 | "signin/an-address-that-does-not-look-right": 390, |
| 181 | "signin/asking-for-an-address": 420, |
| 182 | "signin/coming-back-with-an-email-link": 390, |
| 183 | "signin/coming-back-with-keymail": 390, |
| 184 | "signin/email-and-password": 390, |
| 185 | "signin/google-apple-and-the-rest": 310, |
| 186 | "signin/on-the-way-to-keymail": 390, |
| 187 | "signin/when-keymail-could-not-confirm-it": 410, |
| 188 | "signin/when-keymail-was-offered-and-a-link-went-out": 390, |
| 189 | "snippet/a-line-to-copy": 110, |
| 190 | "stat-band/default": 190, |
| 191 | "stat-band/two-to-a-line-on-a-phone": 200, |
| 192 | "stat/a-shortened-number": 140, |
| 193 | "stat/a-smaller-number": 140, |
| 194 | "stat/the-big-number": 190, |
| 195 | "stat/when-going-down-is-good": 180, |
| 196 | "stat/when-going-up-is-bad": 180, |
| 197 | "stat/when-you-cannot-say": 180, |
| 198 | "status-pill/row": 100, |
| 199 | "tblock/default": 270, |
| 200 | } |
| 201 | |
| 202 | // previewMobileHeights overrides the 1.25× factor for the examples |
| 203 | // whose Mobile layout is taller than the factor allows: one that |
| 204 | // reflows onto a different axis at 390px, and, since Mobile lays out |
| 205 | // at the stage's own width below 390px, one whose text wraps taller at |
| 206 | // the narrowest phone frame, a 320px viewport. Seeded from the sweep at |
| 207 | // 1500px with Mobile chosen and at a 320px viewport, over the twelve |
| 208 | // locales, whose samples are all English until they are translated. |
| 209 | var previewMobileHeights = map[string]int{ |
| 210 | "api-tokens/four-tokens-two-of-them-live": 1850, |
| 211 | "api-tokens/just-created-the-secret-shown-once": 2080, |
| 212 | "badge/row": 252, |
| 213 | "button/default": 410, |
| 214 | "callout/negative": 240, |
| 215 | "callout/warning": 240, |
| 216 | "format-abbr/default": 220, |
| 217 | "format-numbers/default": 220, |
| 218 | "layout-column/default": 1330, |
| 219 | "layout-console-page/default": 1130, |
| 220 | "layout-sidebar-page/default": 1000, |
| 221 | "layout-stage/default": 640, |
| 222 | "layout-topbar/default": 1070, |
| 223 | "list-bar/search-with-a-filter-dropdown": 310, |
| 224 | "list-row-action/a-short-status-beside-the-name": 300, |
| 225 | "list-row-action/lead-marker-status-and-an-action": 240, |
| 226 | "media-grid/screenshots-a-logo-and-a-banner": 1590, |
| 227 | "media-grid/select-mode-one-tile-selected": 850, |
| 228 | "meter/row": 370, |
| 229 | "page-header/title-subhead-and-a-primary-action": 220, |
| 230 | "pagination/middle-of-a-long-list": 180, |
| 231 | "side-nav/four-sections-the-first-open": 300, |
| 232 | "signin/a-passkey": 730, |
| 233 | "signin/after-the-link-is-sent": 600, |
| 234 | "signin/after-the-link-is-sent-in-another-browser": 570, |
| 235 | "signin/an-address-that-does-not-look-right": 610, |
| 236 | "signin/asking-for-an-address": 660, |
| 237 | "signin/coming-back-with-an-email-link": 560, |
| 238 | "signin/coming-back-with-keymail": 580, |
| 239 | "signin/on-the-way-to-keymail": 550, |
| 240 | "signin/when-keymail-could-not-confirm-it": 660, |
| 241 | "signin/when-keymail-was-offered-and-a-link-went-out": 660, |
| 242 | "snippet/a-line-to-copy": 180, |
| 243 | "stat-band/default": 460, |
| 244 | "stat-band/two-to-a-line-on-a-phone": 310, |
| 245 | "status-pill/row": 180, |
| 246 | } |
| 247 | |
| 248 | // previewMobileLocaleHeights is a language's own Mobile height for a |
| 249 | // frame whose content wraps taller in that language than |
| 250 | // previewMobileHeights allows, read for that language's pages only. |
| 251 | var previewMobileLocaleHeights = map[string]map[string]int{ |
| 252 | "ar": { |
| 253 | "api-tokens/four-tokens-two-of-them-live": 1880, |
| 254 | "list-row-action/a-short-status-beside-the-name": 320, |
| 255 | "stat-band/two-to-a-line-on-a-phone": 330, |
| 256 | }, |
| 257 | "bn": { |
| 258 | "form-layout/default": 510, |
| 259 | }, |
| 260 | "es": { |
| 261 | "api-tokens/four-tokens-two-of-them-live": 1880, |
| 262 | "api-tokens/just-created-the-secret-shown-once": 2110, |
| 263 | "box/default": 310, |
| 264 | "callout/info": 210, |
| 265 | "callout/negative-announced-as-an-alert": 240, |
| 266 | "empty-state/with-a-post-call-to-action": 350, |
| 267 | "error-page/403-forbidden": 470, |
| 268 | "error-page/404-not-found": 440, |
| 269 | "error-page/422-unprocessable": 470, |
| 270 | "error-page/500-server-error": 520, |
| 271 | "form-layout/default": 570, |
| 272 | "format-abbr/default": 250, |
| 273 | "list-row-action/a-short-status-beside-the-name": 410, |
| 274 | "signin/after-the-link-is-sent-in-another-browser": 620, |
| 275 | "signin/on-the-way-to-keymail": 580, |
| 276 | "signin/when-keymail-could-not-confirm-it": 700, |
| 277 | "signin/when-keymail-was-offered-and-a-link-went-out": 710, |
| 278 | }, |
| 279 | "ga": { |
| 280 | "api-tokens/four-tokens-two-of-them-live": 2040, |
| 281 | "api-tokens/just-created-the-secret-shown-once": 2290, |
| 282 | "box/default": 320, |
| 283 | "button/default": 450, |
| 284 | "callout/positive": 240, |
| 285 | "empty-state/with-a-post-call-to-action": 350, |
| 286 | "error-page/403-forbidden": 500, |
| 287 | "error-page/404-not-found": 450, |
| 288 | "error-page/422-unprocessable": 440, |
| 289 | "error-page/500-server-error": 520, |
| 290 | "form-layout/default": 510, |
| 291 | "format-abbr/default": 270, |
| 292 | "format-addresses/default": 320, |
| 293 | "layout-column/default": 1390, |
| 294 | "layout-console-page/default": 1240, |
| 295 | "layout-topbar/default": 1180, |
| 296 | "list-row-action/lead-marker-status-and-an-action": 310, |
| 297 | "modal/default": 350, |
| 298 | "side-nav/four-sections-the-first-open": 320, |
| 299 | "signin/after-the-link-is-sent": 630, |
| 300 | "signin/after-the-link-is-sent-in-another-browser": 630, |
| 301 | "signin/google-apple-and-the-rest": 430, |
| 302 | "signin/on-the-way-to-keymail": 590, |
| 303 | "signin/when-keymail-could-not-confirm-it": 730, |
| 304 | "signin/when-keymail-was-offered-and-a-link-went-out": 720, |
| 305 | "snippet/a-line-to-copy": 200, |
| 306 | "stat-band/two-to-a-line-on-a-phone": 330, |
| 307 | }, |
| 308 | "hi": { |
| 309 | "list-row-action/a-short-status-beside-the-name": 350, |
| 310 | }, |
| 311 | "ja": { |
| 312 | "api-tokens/four-tokens-two-of-them-live": 1900, |
| 313 | "api-tokens/just-created-the-secret-shown-once": 2130, |
| 314 | "callout/negative-announced-as-an-alert": 240, |
| 315 | "error-page/403-forbidden": 470, |
| 316 | "error-page/500-server-error": 520, |
| 317 | "form-layout/default": 530, |
| 318 | "format-abbr/default": 250, |
| 319 | "list-row-action/a-short-status-beside-the-name": 530, |
| 320 | }, |
| 321 | "pt-PT": { |
| 322 | "api-tokens/four-tokens-two-of-them-live": 1900, |
| 323 | "api-tokens/just-created-the-secret-shown-once": 2130, |
| 324 | "button/default": 450, |
| 325 | "callout/positive": 240, |
| 326 | "callout/warning": 290, |
| 327 | "empty-state/with-a-post-call-to-action": 350, |
| 328 | "error-page/404-not-found": 440, |
| 329 | "error-page/500-server-error": 520, |
| 330 | "form-layout/default": 570, |
| 331 | "format-abbr/default": 250, |
| 332 | "list-row-action/a-short-status-beside-the-name": 390, |
| 333 | "signin/after-the-link-is-sent": 630, |
| 334 | "signin/after-the-link-is-sent-in-another-browser": 630, |
| 335 | "signin/on-the-way-to-keymail": 590, |
| 336 | "signin/when-keymail-could-not-confirm-it": 730, |
| 337 | "signin/when-keymail-was-offered-and-a-link-went-out": 690, |
| 338 | "tblock/default": 360, |
| 339 | }, |
| 340 | "ru": { |
| 341 | "api-tokens/four-tokens-two-of-them-live": 2020, |
| 342 | "api-tokens/just-created-the-secret-shown-once": 2270, |
| 343 | "button/default": 450, |
| 344 | "callout/info": 210, |
| 345 | "callout/positive": 240, |
| 346 | "callout/warning": 290, |
| 347 | "error-page/403-forbidden": 520, |
| 348 | "error-page/404-not-found": 440, |
| 349 | "error-page/422-unprocessable": 500, |
| 350 | "error-page/500-server-error": 550, |
| 351 | "form-layout/default": 510, |
| 352 | "format-abbr/default": 270, |
| 353 | "format-addresses/default": 350, |
| 354 | "job-status/failed": 130, |
| 355 | "layout-column/default": 1400, |
| 356 | "layout-console-page/default": 1200, |
| 357 | "layout-topbar/default": 1140, |
| 358 | "list-row-action/a-short-status-beside-the-name": 330, |
| 359 | "list-row-action/lead-marker-status-and-an-action": 290, |
| 360 | "modal/default": 350, |
| 361 | "seg-tabs/buttons-that-switch-in-place": 170, |
| 362 | "seg-tabs/two-sections": 170, |
| 363 | "side-nav/four-sections-the-first-open": 320, |
| 364 | "signin/after-the-link-is-sent": 640, |
| 365 | "signin/after-the-link-is-sent-in-another-browser": 640, |
| 366 | "signin/on-the-way-to-keymail": 590, |
| 367 | "signin/when-keymail-could-not-confirm-it": 700, |
| 368 | "signin/when-keymail-was-offered-and-a-link-went-out": 730, |
| 369 | "snippet/a-line-to-copy": 220, |
| 370 | "stat-band/two-to-a-line-on-a-phone": 330, |
| 371 | "status-pill/row": 310, |
| 372 | }, |
| 373 | "vi": { |
| 374 | "api-tokens/four-tokens-two-of-them-live": 1990, |
| 375 | "api-tokens/just-created-the-secret-shown-once": 2230, |
| 376 | "callout/info": 210, |
| 377 | "error-page/403-forbidden": 440, |
| 378 | "job-status/failed": 130, |
| 379 | "list-row-action/a-short-status-beside-the-name": 420, |
| 380 | "signin/after-the-link-is-sent-in-another-browser": 600, |
| 381 | "signin/when-keymail-was-offered-and-a-link-went-out": 690, |
| 382 | }, |
| 383 | } |
| 384 | |
| 385 | // previewReserved is the room a box is given beyond what any sweep |
| 386 | // measures, written by hand with its reason, and never printed by a |
| 387 | // sweep: a floor under the measured table. The modal's is Rastrillo's |
| 388 | // 620px: a modal wants a window to be modal over, and its panel is |
| 389 | // sized from the window (85vh, at most 640px), so a box that only just |
| 390 | // holds the panel shows a modal in a letterbox rather than one over a |
| 391 | // page. The drives still read the panel for clipping at whatever the |
| 392 | // box comes to. |
| 393 | var previewReserved = map[string]int{ |
| 394 | "modal/default": 620, |
| 395 | } |
| 396 | |
| 397 | // restHeights is a box's Desktop and Mobile heights while nothing in |
| 398 | // its frame is open. |
| 399 | type restHeights struct{ Desktop, Mobile int } |
| 400 | |
| 401 | // previewRestHeights is for a frame whose open menu needs far more room |
| 402 | // than its page: the column's footer menus open downward under the |
| 403 | // page, so a box sized for them open is mostly empty at rest. The main |
| 404 | // tables keep the open height; view.js shows these while no menu in |
| 405 | // the frame is open and the open height while one is. With scripts |
| 406 | // off the box keeps the open height, since nothing could grow it. |
| 407 | var previewRestHeights = map[string]restHeights{ |
| 408 | "layout-column/default": {Desktop: 650, Mobile: 1110}, |
| 409 | } |
| 410 | |
| 411 | // heightOf is one key's Desktop height: the measured table's, or the |
| 412 | // default, raised to its reservation where it has one. |
| 413 | func heightOf(key string) int { |
| 414 | h, ok := previewHeights[key] |
| 415 | if !ok { |
| 416 | h = previewHeight |
| 417 | } |
| 418 | if r := previewReserved[key]; r > h { |
| 419 | h = r |
| 420 | } |
| 421 | return h |
| 422 | } |
| 423 | |
| 424 | // previewStyle writes one widget's two virtual heights. Mobile is a |
| 425 | // quarter taller than Desktop unless the tables say otherwise: one |
| 426 | // factor rather than a second table, since a component this small has |
| 427 | // one column either way, and the exceptions are the ones whose whole |
| 428 | // shape is a row that becomes a column. |
| 429 | func previewStyle(locale, key string) template.CSS { |
| 430 | h, m := previewHeightsFor(locale, key) |
| 431 | css := fmt.Sprintf("--ds-h: %dpx; --ds-hm: %dpx", h, m) |
| 432 | if r, ok := previewRestHeights[key]; ok { |
| 433 | css += fmt.Sprintf("; --ds-hr: %dpx; --ds-hmr: %dpx", r.Desktop, r.Mobile) |
| 434 | } |
| 435 | return template.CSS(css) |
| 436 | } |
| 437 | |
| 438 | // previewHeightsFor is one widget's Desktop and Mobile heights in |
| 439 | // locale, as previewStyle writes them. |
| 440 | func previewHeightsFor(locale, key string) (desktop, mobile int) { |
| 441 | h := heightOf(key) |
| 442 | m, ok := previewMobileHeights[key] |
| 443 | if !ok { |
| 444 | m = h * 5 / 4 |
| 445 | } |
| 446 | if l, ok := previewMobileLocaleHeights[locale][key]; ok && l > m { |
| 447 | m = l |
| 448 | } |
| 449 | return h, m |
| 450 | } |
| 451 | |