preview.mjs (5688B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { setCustomElementsManifest } from "@storybook/web-components"; 6 import { withActions } from "@storybook/addon-actions/decorator"; 7 import { css, html } from "lit.all.mjs"; 8 import { MozLitElement } from "toolkit/content/widgets/lit-utils.mjs"; 9 import customElementsManifest from "../custom-elements.json"; 10 import { insertFTLIfNeeded, connectFluent } from "./fluent-utils.mjs"; 11 import chromeMap from "./chrome-map.js"; 12 import DocsContainer from "./DocsContainer.mjs"; 13 14 // Base Fluent set up. 15 connectFluent(); 16 17 // Any fluent imports should go through MozXULElement.insertFTLIfNeeded. 18 window.MozXULElement = { 19 insertFTLIfNeeded, 20 }; 21 22 // Used to set prefs in unprivileged contexts. 23 window.RPMSetPref = () => { 24 /* NOOP */ 25 }; 26 window.RPMGetFormatURLPref = () => { 27 /* NOOP */ 28 }; 29 30 /** 31 * Function to automatically import reusable components into all stories. This 32 * helps ensure that components composed of multiple `moz-` elements will render 33 * correctly, since these elements would otherwise be lazily imported. 34 */ 35 function importReusableComponents() { 36 let sourceMap = chromeMap[2]; 37 let mozElements = new Set(); 38 for (let key of Object.keys(sourceMap)) { 39 if ( 40 key.startsWith("dist/bin/chrome/toolkit/content/global/elements/moz-") && 41 key.endsWith(".mjs") 42 ) { 43 mozElements.add(key.split("/").pop().replace(".mjs", "")); 44 } 45 } 46 mozElements.forEach(elementName => { 47 // eslint-disable-next-line no-unsanitized/method 48 import(`toolkit/content/widgets/${elementName}/${elementName}.mjs`); 49 }); 50 51 // Manually import the two components that don't follow our naming conventions. 52 import("toolkit/content/widgets/panel-list/panel-list.js"); 53 import("toolkit/content/widgets/named-deck.js"); 54 } 55 importReusableComponents(); 56 57 /** 58 * Wrapper component used to decorate all of our stories by providing access to 59 * `in-content/common.css` without leaking styles that conflict Storybook's CSS. 60 * 61 * More information on decorators can be found at: 62 * https://storybook.js.org/docs/web-components/writing-stories/decorators 63 * 64 * @property {Function} story 65 * Storybook uses this internally to render stories. We call `story` in our 66 * render function so that the story contents have the same shadow root as 67 * `with-common-styles` and styles from `in-content/common` get applied. 68 * @property {object} context 69 * Another Storybook provided property containing additional data stories use 70 * to render. If we don't make this a reactive property Lit seems to optimize 71 * away any re-rendering of components inside `with-common-styles`. 72 */ 73 class WithCommonStyles extends MozLitElement { 74 static styles = css` 75 :host { 76 display: block; 77 height: 100%; 78 padding: 1rem; 79 box-sizing: border-box; 80 } 81 82 :host, 83 :root { 84 font: message-box; 85 font-size: var(--font-size-root); 86 appearance: none; 87 background-color: var(--background-color-canvas); 88 color: var(--text-color); 89 -moz-box-layout: flex; 90 } 91 92 :host { 93 font-size: var(--font-size-root); 94 } 95 96 :host([theme="light"]) { 97 color-scheme: light; 98 } 99 100 :host([theme="dark"]) { 101 color-scheme: dark; 102 } 103 `; 104 105 static properties = { 106 story: { type: Function }, 107 context: { type: Object }, 108 }; 109 110 connectedCallback() { 111 super.connectedCallback(); 112 this.classList.add("anonymous-content-host"); 113 } 114 115 storyContent() { 116 if (this.story) { 117 return this.story(); 118 } 119 return html` <slot></slot> `; 120 } 121 122 render() { 123 return html` 124 <link 125 rel="stylesheet" 126 href="chrome://global/skin/in-content/common.css" 127 /> 128 ${this.storyContent()} 129 `; 130 } 131 } 132 customElements.define("with-common-styles", WithCommonStyles); 133 134 // ---- theme helpers used by the decorator (hoisted once) ---- 135 const mql = window.matchMedia("(prefers-color-scheme: dark)"); 136 const resolveTheme = (selected /* 'light'|'dark'|'system' */) => { 137 if (selected === "dark") { 138 return "dark"; 139 } 140 if (selected === "light") { 141 return "light"; 142 } 143 return mql.matches ? "dark" : "light"; // system 144 }; 145 146 // Wrap all stories in `with-common-styles`. 147 export default { 148 decorators: [ 149 (story, context) => { 150 const selectedTheme = context?.globals?.theme ?? "system"; 151 const resolvedTheme = resolveTheme(selectedTheme); 152 153 return html` 154 <with-common-styles 155 .story=${story} 156 .context=${context} 157 theme=${resolvedTheme} 158 ></with-common-styles> 159 `; 160 }, 161 withActions, 162 ], 163 parameters: { 164 docs: { 165 toc: { 166 disable: false, 167 headingSelector: "h2, h3", 168 ignoreSelector: "h2.text-truncated-ellipsis, .toc-ignore", 169 title: "On this page", 170 }, 171 container: DocsContainer, // updates /docs iframe 172 }, 173 options: { showPanel: true }, 174 }, 175 globalTypes: { 176 theme: { 177 description: "Global theme", 178 defaultValue: "system", 179 toolbar: { 180 title: "Theme toggle", 181 items: [ 182 { 183 value: "light", 184 title: "Light", 185 icon: "circlehollow", 186 }, 187 { 188 value: "dark", 189 title: "Dark", 190 icon: "circle", 191 }, 192 { 193 value: "system", 194 title: "System", 195 icon: "mirror", 196 }, 197 ], 198 dynamicTitle: true, 199 }, 200 }, 201 }, 202 }; 203 204 // Enable props tables documentation. 205 setCustomElementsManifest(customElementsManifest);