summaryrefslogtreecommitdiffstats
path: root/browser/components/storybook/stories
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/storybook/stories')
-rw-r--r--browser/components/storybook/stories/button.stories.mjs79
-rw-r--r--browser/components/storybook/stories/credential-management.stories.mjs45
-rw-r--r--browser/components/storybook/stories/fxview-category-navigation.stories.mjs113
-rw-r--r--browser/components/storybook/stories/fxview-tab-list.stories.md99
-rw-r--r--browser/components/storybook/stories/fxview-tab-list.stories.mjs210
-rw-r--r--browser/components/storybook/stories/message-bar.stories.mjs48
-rw-r--r--browser/components/storybook/stories/migration-wizard.stories.mjs344
-rw-r--r--browser/components/storybook/stories/named-deck.stories.mjs165
-rw-r--r--browser/components/storybook/stories/panel-list.stories.mjs133
9 files changed, 1236 insertions, 0 deletions
diff --git a/browser/components/storybook/stories/button.stories.mjs b/browser/components/storybook/stories/button.stories.mjs
new file mode 100644
index 0000000000..5a6ec2cac5
--- /dev/null
+++ b/browser/components/storybook/stories/button.stories.mjs
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { classMap, html } from "lit.all.mjs";
+
+export default {
+ title: "UI Widgets/Button",
+ component: "button",
+ parameters: {
+ status: "stable",
+ fluent: `
+button-regular = Regular
+button-primary = Primary
+button-disabled = Disabled
+button-danger = Danger
+ `,
+ },
+};
+
+const Template = ({
+ disabled,
+ primary,
+ l10nId,
+ ghostButton,
+ icon,
+ dangerButton,
+}) =>
+ html`
+ <style>
+ .icon-button {
+ background-image: url("${icon}");
+ }
+ </style>
+ <button
+ ?disabled=${disabled}
+ class=${classMap({
+ primary,
+ "ghost-button": ghostButton,
+ "icon-button": icon,
+ "danger-button": dangerButton,
+ })}
+ data-l10n-id=${l10nId}
+ ></button>
+ `;
+
+export const RegularButton = Template.bind({});
+RegularButton.args = {
+ l10nId: "button-regular",
+ primary: false,
+ disabled: false,
+};
+export const PrimaryButton = Template.bind({});
+PrimaryButton.args = {
+ l10nId: "button-primary",
+ primary: true,
+ disabled: false,
+};
+export const DisabledButton = Template.bind({});
+DisabledButton.args = {
+ l10nId: "button-disabled",
+ primary: false,
+ disabled: true,
+};
+
+export const DangerButton = Template.bind({});
+DangerButton.args = {
+ l10nId: "button-danger",
+ primary: true,
+ disabled: false,
+ dangerButton: true,
+};
+
+export const GhostIconButton = Template.bind({});
+GhostIconButton.args = {
+ icon: "chrome://browser/skin/login.svg",
+ disabled: false,
+ ghostButton: true,
+};
diff --git a/browser/components/storybook/stories/credential-management.stories.mjs b/browser/components/storybook/stories/credential-management.stories.mjs
new file mode 100644
index 0000000000..4632da5d02
--- /dev/null
+++ b/browser/components/storybook/stories/credential-management.stories.mjs
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "lit.all.mjs";
+// Imported for side-effects.
+// eslint-disable-next-line import/no-unassigned-import
+import "../../aboutlogins/content/components/login-timeline.mjs";
+
+export default {
+ title: "Domain-specific UI Widgets/Credential Management/Timeline",
+ component: "login-timeline",
+};
+
+window.MozXULElement.insertFTLIfNeeded("browser/aboutLogins.ftl");
+
+const Template = ({ historyItems }) =>
+ html` <login-timeline .history=${historyItems}></login-timeline> `;
+
+const ACTION_ID_CREATED = "login-item-timeline-action-created";
+const ACTION_ID_UPDATED = "login-item-timeline-action-updated";
+const ACTION_ID_USED = "login-item-timeline-action-used";
+
+export const EmptyTimeline = Template.bind({});
+EmptyTimeline.args = {
+ historyItems: [],
+};
+
+export const TypicalTimeline = Template.bind({});
+TypicalTimeline.args = {
+ historyItems: [
+ { actionId: ACTION_ID_CREATED, time: 1463526500267 },
+ { actionId: ACTION_ID_UPDATED, time: 1653621219569 },
+ { actionId: ACTION_ID_USED, time: 1561813190300 },
+ ],
+};
+
+export const AllSameDayTimeline = Template.bind({});
+AllSameDayTimeline.args = {
+ historyItems: [
+ { actionId: ACTION_ID_CREATED, time: 1463526500267 },
+ { actionId: ACTION_ID_UPDATED, time: 1463526500267 },
+ { actionId: ACTION_ID_USED, time: 1463526500267 },
+ ],
+};
diff --git a/browser/components/storybook/stories/fxview-category-navigation.stories.mjs b/browser/components/storybook/stories/fxview-category-navigation.stories.mjs
new file mode 100644
index 0000000000..74a379a5a8
--- /dev/null
+++ b/browser/components/storybook/stories/fxview-category-navigation.stories.mjs
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/firefoxview/fxview-category-navigation.mjs";
+
+export default {
+ title: "Domain-specific UI Widgets/Firefox View/Category Navigation",
+ component: "fxview-category-navigation",
+ parameters: {
+ status: "in-development",
+ actions: {
+ handles: ["change-category"],
+ },
+ fluent: `
+fxview-category-button-one = Category 1
+ .title = Category 1
+fxview-category-button-two = Category 2
+ .title = Category 2
+fxview-category-button-three = Category 3
+ .title = Category 3
+fxview-category-footer-button = Settings
+ .title = Settings
+ `,
+ },
+};
+
+const Template = () => html`
+ <style>
+ #page {
+ height: 100%;
+ display: grid;
+ grid-template-columns: var(--in-content-sidebar-width) 1fr;
+ }
+ fxview-category-navigation {
+ margin-inline-start: 10px;
+ }
+ fxview-category-button[name="category-one"]::part(icon) {
+ background-image: url("chrome://browser/skin/preferences/category-general.svg");
+ }
+ fxview-category-button[name="category-two"]::part(icon) {
+ background-image: url("chrome://browser/skin/preferences/category-general.svg");
+ }
+ fxview-category-button[name="category-three"]::part(icon) {
+ background-image: url("chrome://browser/skin/preferences/category-general.svg");
+ }
+ .footer-button {
+ display: flex;
+ gap: 12px;
+ font-weight: normal;
+ min-width: unset;
+ padding: 8px;
+ margin: 0;
+ }
+ .footer-button .cat-icon {
+ background-image: url("chrome://browser/skin/preferences/category-general.svg");
+ background-color: initial;
+ background-size: 20px;
+ background-repeat: no-repeat;
+ background-position: center;
+ height: 20px;
+ width: 20px;
+ display: inline-block;
+ -moz-context-properties: fill;
+ fill: currentColor;
+ }
+ @media (max-width: 52rem) {
+ #page {
+ grid-template-columns: 82px 1fr;
+ }
+ .cat-name {
+ display: none;
+ }
+ }
+ </style>
+ <div id="page">
+ <fxview-category-navigation>
+ <h2 slot="category-nav-header">Header</h2>
+ <fxview-category-button
+ slot="category-button"
+ name="category-one"
+ data-l10n-id="fxview-category-button-one"
+ >
+ </fxview-category-button>
+ <fxview-category-button
+ slot="category-button"
+ name="category-two"
+ data-l10n-id="fxview-category-button-two"
+ >
+ </fxview-category-button>
+ <fxview-category-button
+ slot="category-button"
+ name="category-three"
+ data-l10n-id="fxview-category-button-three"
+ >
+ </fxview-category-button>
+ <div slot="category-nav-footer" class="category-nav-footer">
+ <button class="footer-button ghost-button">
+ <span class="cat-icon"></span>
+ <span
+ class="cat-name"
+ data-l10n-id="fxview-category-footer-button"
+ ></span>
+ </button>
+ </div>
+ </fxview-category-navigation>
+ </div>
+`;
+
+export const Default = Template.bind({});
+Default.args = {};
diff --git a/browser/components/storybook/stories/fxview-tab-list.stories.md b/browser/components/storybook/stories/fxview-tab-list.stories.md
new file mode 100644
index 0000000000..f8317f5605
--- /dev/null
+++ b/browser/components/storybook/stories/fxview-tab-list.stories.md
@@ -0,0 +1,99 @@
+# FxviewTabList
+
+`fxview-tab-list` is a list of `fxview-tab-row` elements that display tab info such as:
+* A link containing:
+ * Favicon
+ * Title
+ * Domain
+ * Time when tab was last accessed (can be formatted as `relative`, `date and time`, `date only`, and `time only`)
+* Secondary action button
+
+## When to use
+
+* Use `fxview-tab-list` anywhere you want to display a list of tabs with the above info displayed.
+
+## Code
+
+The source for `fxview-tab-list` can be found under
+[browser/components/firefoxview/fxview-tab-list](https://searchfox.org/mozilla-central/source/browser/components/firefoxview/fxview-tab-list.mjs).
+
+`fxview-tab-list` can be imported into `.html`/`.xhtml` files:
+
+```html
+<script type="module" src="chrome://content/browser/firefoxview/fxview-tab-list.mjs"></script>
+```
+
+And used as follows:
+
+With context menu:
+```html
+<fxview-tab-list
+ class="with-context-menu"
+ .dateTimeFormat=${"relative"}
+ .hasPopup=${"menu"}
+ .maxTabsLength=${this.maxTabsLength}
+ .secondaryL10nId=${"fxviewtabrow-open-menu"}
+ .tabItems=${this.tabItems}
+ @fxview-tab-list-secondary-action=${this.onSecondaryAction}
+ @fxview-tab-list-primary-action=${this.onPrimaryAction}
+>
+ <panel-list slot="menu" @hide=${this.menuClosed}>
+ <panel-item
+ @click=${...}
+ data-l10n-id="fxviewtabrow-delete"
+ ></panel-item>
+ <panel-item
+ @click=${...}
+ data-l10n-id="fxviewtabrow-forget-about-this-site"
+ ></panel-item>
+ ...
+ </panel-list>
+</fxview-tab-list>
+```
+With dismiss button:
+```html
+<fxview-tab-list
+ class="with-dismiss-button"
+ .dateTimeFormat=${"relative"}
+ .maxTabsLength=${this.maxTabsLength}
+ .secondaryL10nId=${"fxviewtabrow-dismiss-tab-button"}
+ .tabItems=${this.tabItems}
+ @fxview-tab-list-secondary-action=${this.onSecondaryAction}
+ @fxview-tab-list-primary-action=${this.onPrimaryAction}
+></fxview-tab-list>
+```
+
+### Notes
+
+* You'll need to defines function for `this.onPrimaryAction` and `this.onSecondaryAction` in order to add functionality to the primary element and the secondary button
+* You can also supply a `class` attribute to the instance on `fxview-tab-list` in order to apply styles to things like the icon for the secondary action button:
+For example:
+```css
+ fxview-tab-list.with-context-menu::part(secondary-button) {
+ background-image: url("chrome://global/skin/icons/more.svg");
+ }
+```
+
+### Properties
+
+You'll need to pass along some of the following properties:
+* `dateTimeFormat`: A string to indicate the expected format/options for the date and/or time displayed on each tab item in the list. The default for this if not given is `"relative"`.
+ * Options include:
+ * `relative` (Ex: "Just now", "2m ago", etc.)
+ * `date` (Ex: "4/1/23", "01/04/23", etc. - Will be formatted based on locale)
+ * `time` (Ex: "4:00 PM", "16:00", etc - Will be formatted based on locale)
+ * `dateTime` (Ex: "4/1/23 4:00PM", "01/04/23 16:00", etc. - Will be formatted based on locale)
+* `hasPopup`: The optional aria-haspopup attribute for the secondary action, if required
+* `maxTabsLength`: The max number of tabs you want to display in the tabs list. The default value will be `25` if no max value is given.
+* `secondaryL10nId`: The l10n ID of the secondary action button
+* `tabItems`: An array of tab data such as History nodes, Bookmark nodes, Synced Tabs, etc.
+ * The component is expecting to receive the following properties within each `tabItems` object (you may need to do some normalizing for this):
+ * `icon` - The location string for the favicon. Will fallback to default favicon if none is found.
+ * `primaryL10nId` - The l10n id to be used for the primary action element
+ * `primaryL10nArgs` - The l10n args you can optionally pass for the primary action element
+ * `secondaryL10nId` - The l10n id to be used for the secondary action button
+ * `secondaryL10nArgs` - The l10n args you can optionally pass for the secondary action button
+ * `tabid` - Optional property expected for Recently Closed tab data
+ * `time` - The time in milliseconds for expected last interaction with the tab (Ex: `lastUsed` for SyncedTabs tabs, `closedAt` for RecentlyClosed tabs, etc.)
+ * `title` - The title for the tab
+ * `url` - The full URL for the tab
diff --git a/browser/components/storybook/stories/fxview-tab-list.stories.mjs b/browser/components/storybook/stories/fxview-tab-list.stories.mjs
new file mode 100644
index 0000000000..692577d378
--- /dev/null
+++ b/browser/components/storybook/stories/fxview-tab-list.stories.mjs
@@ -0,0 +1,210 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/firefoxview/fxview-tab-list.mjs";
+
+const DATE_TIME_FORMATS = {
+ relative: "relative",
+ dateTime: "dateTime",
+ date: "date",
+ time: "time",
+};
+
+export default {
+ title: "Domain-specific UI Widgets/Firefox View/Tab List",
+ component: "fxview-tab-list",
+ argTypes: {
+ dateTimeFormat: {
+ options: Object.keys(DATE_TIME_FORMATS),
+ mapping: DATE_TIME_FORMATS,
+ control: { type: "select" },
+ },
+ },
+};
+
+const Template = ({
+ listClass,
+ dateTimeFormat,
+ hasPopup,
+ maxTabsLength,
+ primaryAction,
+ secondaryAction,
+ tabItems,
+}) => html`
+ <style>
+ main {
+ max-width: 750px;
+ }
+ fxview-tab-list.menu::part(secondary-button) {
+ background-image: url("chrome://global/skin/icons/more.svg");
+ }
+ fxview-tab-list.dismiss::part(secondary-button) {
+ background-image: url("chrome://global/skin/icons/close.svg");
+ }
+ :host panel-item::part(button) {
+ padding-inline-start: 12px;
+ cursor: pointer;
+ }
+ </style>
+ <main>
+ <fxview-tab-list
+ class=${listClass}
+ .hasPopup=${hasPopup}
+ .dateTimeFormat=${dateTimeFormat}
+ .maxTabsLength=${maxTabsLength}
+ .tabItems=${tabItems}
+ @fxview-tab-list-secondary-action=${secondaryAction}
+ @fxview-tab-list-primary-action=${primaryAction}
+ >
+ <panel-list slot="menu">
+ <panel-item data-l10n-id="fxviewtabrow-delete"></panel-item>
+ <panel-item
+ data-l10n-id="fxviewtabrow-forget-about-this-site"
+ ></panel-item>
+ <hr />
+ <panel-item data-l10n-id="fxviewtabrow-open-in-window"></panel-item>
+ <panel-item
+ data-l10n-id="fxviewtabrow-open-in-private-window"
+ ></panel-item>
+ <hr />
+ <panel-item data-l10n-id="fxviewtabrow-add-bookmark"></panel-item>
+ <panel-item data-l10n-id="fxviewtabrow-save-to-pocket"></panel-item>
+ <panel-item data-l10n-id="fxviewtabrow-copy-link"></panel-item>
+ </panel-list>
+ </fxview-tab-list>
+ </main>
+`;
+
+const MAX_TABS_LENGTH = 25;
+
+let secondaryAction = e => {
+ e.target.querySelector("panel-list").toggle(e.detail.originalEvent);
+};
+
+let primaryAction = e => {
+ // Open in new tab
+};
+
+const tabItems = [
+ {
+ icon: "chrome://global/skin/icons/defaultFavicon.svg",
+ title: "Example Domain",
+ url: "example.net",
+ time: 1678141738136,
+ primaryL10nId: "fxviewtabrow-tabs-list-tab",
+ primaryL10nArgs: JSON.stringify({ targetURI: "example.net" }),
+ secondaryL10nId: "fxviewtabrow-open-menu-button",
+ },
+ {
+ icon: "chrome://global/skin/icons/defaultFavicon.svg",
+ title: "Example Domain",
+ url: "example.org",
+ time: 1678141738136,
+ primaryL10nId: "fxviewtabrow-tabs-list-tab",
+ primaryL10nArgs: JSON.stringify({ targetURI: "example.org" }),
+ secondaryL10nId: "fxviewtabrow-open-menu-button",
+ },
+ {
+ icon: "chrome://global/skin/icons/defaultFavicon.svg",
+ title: "Example Domain",
+ url: "example.com",
+ time: 1678141738136,
+ primaryL10nId: "fxviewtabrow-tabs-list-tab",
+ primaryL10nArgs: JSON.stringify({ targetURI: "example.com" }),
+ secondaryL10nId: "fxviewtabrow-open-menu-button",
+ },
+];
+const recentlyClosedItems = [
+ {
+ icon: "chrome://global/skin/icons/defaultFavicon.svg",
+ title: "Example Domain",
+ url: "example.net",
+ tabid: 1,
+ time: 1678141738136,
+ primaryL10nId: "fxviewtabrow-tabs-list-tab",
+ primaryL10nArgs: JSON.stringify({ targetURI: "example.net" }),
+ secondaryL10nId: "fxviewtabrow-dismiss-tab-button",
+ secondaryL10nArgs: JSON.stringify({
+ tabTitle: "Example Domain",
+ }),
+ },
+ {
+ icon: "chrome://global/skin/icons/defaultFavicon.svg",
+ title: "Example Domain",
+ url: "example.org",
+ tabid: 2,
+ time: 1678141738136,
+ primaryL10nId: "fxviewtabrow-tabs-list-tab",
+ primaryL10nArgs: JSON.stringify({ targetURI: "example.net" }),
+ secondaryL10nId: "fxviewtabrow-dismiss-tab-button",
+ secondaryL10nArgs: JSON.stringify({
+ tabTitle: "Example Domain",
+ }),
+ },
+ {
+ icon: "chrome://global/skin/icons/defaultFavicon.svg",
+ title: "Example Domain",
+ url: "example.com",
+ tabid: 3,
+ time: 1678141738136,
+ primaryL10nId: "fxviewtabrow-tabs-list-tab",
+ primaryL10nArgs: JSON.stringify({ targetURI: "example.net" }),
+ secondaryL10nId: "fxviewtabrow-dismiss-tab-button",
+ secondaryL10nArgs: JSON.stringify({
+ tabTitle: "Example Domain",
+ }),
+ },
+];
+
+export const RelativeTime = Template.bind({});
+RelativeTime.args = {
+ listClass: "menu",
+ dateTimeFormat: "relative",
+ hasPopup: "menu",
+ maxTabsLength: MAX_TABS_LENGTH,
+ primaryAction,
+ secondaryAction,
+ tabItems,
+};
+export const DateAndTime = Template.bind({});
+DateAndTime.args = {
+ listClass: "menu",
+ dateTimeFormat: "dateTime",
+ maxTabsLength: MAX_TABS_LENGTH,
+ primaryAction,
+ secondaryAction,
+ tabItems,
+};
+export const DateOnly = Template.bind({});
+DateOnly.args = {
+ listClass: "menu",
+ dateTimeFormat: "date",
+ hasPopup: "menu",
+ maxTabsLength: MAX_TABS_LENGTH,
+ primaryAction,
+ secondaryAction,
+ tabItems,
+};
+export const TimeOnly = Template.bind({});
+TimeOnly.args = {
+ listClass: "menu",
+ dateTimeFormat: "time",
+ hasPopup: "menu",
+ maxTabsLength: MAX_TABS_LENGTH,
+ primaryAction,
+ secondaryAction,
+ tabItems,
+};
+export const RecentlyClosed = Template.bind({});
+RecentlyClosed.args = {
+ listClass: "dismiss",
+ dateTimeFormat: "relative",
+ hasPopup: null,
+ maxTabsLength: MAX_TABS_LENGTH,
+ primaryAction,
+ secondaryAction: () => {},
+ tabItems: recentlyClosedItems,
+};
diff --git a/browser/components/storybook/stories/message-bar.stories.mjs b/browser/components/storybook/stories/message-bar.stories.mjs
new file mode 100644
index 0000000000..a8a7b7ddc7
--- /dev/null
+++ b/browser/components/storybook/stories/message-bar.stories.mjs
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "lit.all.mjs";
+// Imported for side-effects.
+// eslint-disable-next-line import/no-unassigned-import
+import "toolkit-widgets/message-bar.js";
+
+const MESSAGE_TYPES = {
+ default: "",
+ success: "success",
+ error: "error",
+ warning: "warning",
+};
+
+export default {
+ title: "UI Widgets/Message Bar",
+ component: "message-bar",
+ argTypes: {
+ type: {
+ options: Object.keys(MESSAGE_TYPES),
+ mapping: MESSAGE_TYPES,
+ control: { type: "select" },
+ },
+ },
+ parameters: {
+ status: "stable",
+ fluent: `
+message-bar-text = A very expressive and slightly whimsical message goes here.
+message-bar-button = Click me, please!
+ `,
+ },
+};
+
+const Template = ({ dismissable, type }) =>
+ html`
+ <message-bar type=${type} ?dismissable=${dismissable}>
+ <span data-l10n-id="message-bar-text"></span>
+ <button data-l10n-id="message-bar-button"></button>
+ </message-bar>
+ `;
+
+export const Basic = Template.bind({});
+Basic.args = { type: "", dismissable: false };
+
+export const Dismissable = Template.bind({});
+Dismissable.args = { type: "", dismissable: true };
diff --git a/browser/components/storybook/stories/migration-wizard.stories.mjs b/browser/components/storybook/stories/migration-wizard.stories.mjs
new file mode 100644
index 0000000000..f525c44a33
--- /dev/null
+++ b/browser/components/storybook/stories/migration-wizard.stories.mjs
@@ -0,0 +1,344 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Imported for side-effects.
+import { html } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "toolkit-widgets/panel-list.js";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/migration/content/migration-wizard.mjs";
+import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";
+
+// Imported for side-effects.
+// eslint-disable-next-line import/no-unassigned-import
+import "toolkit-widgets/named-deck.js";
+
+export default {
+ title: "Domain-specific UI Widgets/Migration Wizard",
+ component: "migration-wizard",
+};
+
+const FAKE_MIGRATOR_LIST = [
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "chrome",
+ displayName: "Chrome",
+ resourceTypes: [
+ "HISTORY",
+ "FORMDATA",
+ "PASSWORDS",
+ "BOOKMARKS",
+ "PAYMENT_METHODS",
+ ],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/chrome.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "chrome",
+ displayName: "Chrome",
+ resourceTypes: [
+ "HISTORY",
+ "FORMDATA",
+ "PASSWORDS",
+ "BOOKMARKS",
+ "PAYMENT_METHODS",
+ ],
+ profile: { id: "person-2", name: "Person 2" },
+ brandImage: "chrome://browser/content/migration/brands/chrome.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "ie",
+ displayName: "Microsoft Internet Explorer",
+ resourceTypes: ["HISTORY", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://browser/content/migration/brands/ie.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "edge",
+ displayName: "Microsoft Edge Legacy",
+ resourceTypes: ["HISTORY", "FORMDATA", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "chromium-edge",
+ displayName: "Microsoft Edge",
+ resourceTypes: [
+ "HISTORY",
+ "FORMDATA",
+ "PASSWORDS",
+ "BOOKMARKS",
+ "PAYMENT_METHODS",
+ ],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/edge.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "brave",
+ displayName: "Brave",
+ resourceTypes: [
+ "HISTORY",
+ "FORMDATA",
+ "PASSWORDS",
+ "BOOKMARKS",
+ "PAYMENT_METHODS",
+ ],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/brave.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "internal-testing",
+ displayName: "Internal Testing Migrator",
+ resourceTypes: ["HISTORY", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "safari",
+ displayName: "Safari",
+ resourceTypes: ["HISTORY", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://browser/content/migration/brands/safari.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "opera",
+ displayName: "Opera",
+ resourceTypes: ["HISTORY", "FORMDATA", "PASSWORDS", "BOOKMARKS"],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/opera.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "opera-gx",
+ displayName: "Opera GX",
+ resourceTypes: ["HISTORY", "FORMDATA", "PASSWORDS", "BOOKMARKS"],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/operagx.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "vivaldi",
+ displayName: "Vivaldi",
+ resourceTypes: ["HISTORY"],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/vivaldi.png",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "no-resources-browser",
+ displayName: "Browser with no resources",
+ resourceTypes: [],
+ profile: { id: "Default", name: "Default" },
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.FILE,
+ key: "file-password-csv",
+ displayName: "Passwords from CSV file",
+ brandImage: "chrome://branding/content/document.ico",
+ resourceTypes: [],
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.FILE,
+ key: "file-bookmarks",
+ displayName: "Bookmarks from file",
+ brandImage: "chrome://branding/content/document.ico",
+ resourceTypes: [],
+ },
+];
+
+const Template = ({ state, dialogMode }) => html`
+ <style>
+ @media (prefers-reduced-motion: no-preference) {
+ migration-wizard::part(progress-spinner) {
+ mask: url(./migration/progress-mask.svg);
+ }
+ }
+ </style>
+
+ <div class="card card-no-hover" style="width: fit-content">
+ <migration-wizard ?dialog-mode=${dialogMode} .state=${state}>
+ <panel-list></panel-list>
+ </migration-wizard>
+ </div>
+`;
+
+export const LoadingSkeleton = Template.bind({});
+LoadingSkeleton.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.LOADING,
+ },
+};
+
+export const MainSelectorVariant1 = Template.bind({});
+MainSelectorVariant1.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.SELECTION,
+ migrators: FAKE_MIGRATOR_LIST,
+ showImportAll: false,
+ },
+};
+
+export const MainSelectorVariant2 = Template.bind({});
+MainSelectorVariant2.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.SELECTION,
+ migrators: FAKE_MIGRATOR_LIST,
+ showImportAll: true,
+ },
+};
+
+export const Progress = Template.bind({});
+Progress.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ inProgress: true,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ inProgress: true,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ inProgress: true,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ inProgress: true,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ inProgress: true,
+ },
+ },
+ },
+};
+
+export const PartialProgress = Template.bind({});
+PartialProgress.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ inProgress: true,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ inProgress: false,
+ message: "14 logins and passwords",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ inProgress: true,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ inProgress: false,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ inProgress: false,
+ message: "6 payment methods",
+ },
+ },
+ },
+};
+
+export const Success = Template.bind({});
+Success.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ inProgress: false,
+ message: "14 bookmarks",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ inProgress: false,
+ message: "14 logins and passwords",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ inProgress: false,
+ message: "From the last 180 days",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ inProgress: false,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ inProgress: false,
+ message: "6 payment methods",
+ },
+ },
+ },
+};
+
+export const FileImportProgress = Template.bind({});
+FileImportProgress.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS,
+ title: "Importing Passwords",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES
+ .PASSWORDS_FROM_FILE]: {
+ inProgress: true,
+ },
+ },
+ },
+};
+
+export const FileImportSuccess = Template.bind({});
+FileImportSuccess.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS,
+ title: "Passwords Imported Successfully",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES.PASSWORDS_NEW]: {
+ inProgress: false,
+ message: "2 added",
+ },
+ [MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES
+ .PASSWORDS_UPDATED]: {
+ inProgress: false,
+ message: "14 updated",
+ },
+ },
+ },
+};
+
+export const SafariPermissions = Template.bind({});
+SafariPermissions.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.SAFARI_PERMISSION,
+ },
+};
+
+export const SafariPasswordPermissions = Template.bind({});
+SafariPasswordPermissions.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION,
+ },
+};
+
+export const NoBrowsersFound = Template.bind({});
+NoBrowsersFound.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND,
+ hasFileMigrators: true,
+ },
+};
diff --git a/browser/components/storybook/stories/named-deck.stories.mjs b/browser/components/storybook/stories/named-deck.stories.mjs
new file mode 100644
index 0000000000..21c9d62e3e
--- /dev/null
+++ b/browser/components/storybook/stories/named-deck.stories.mjs
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "lit.all.mjs";
+// Imported for side-effects.
+// eslint-disable-next-line import/no-unassigned-import
+import "toolkit-widgets/named-deck.js";
+
+export default {
+ title: "UI Widgets/Named Deck",
+ component: "named-deck",
+ parameters: {
+ status: "stable",
+ fluent: `
+named-deck-tab-one = Tab 1
+named-deck-tab-two = Tab 2
+named-deck-tab-three = Tab 3
+named-deck-content-one = This is tab 1
+named-deck-content-two = This is tab 2
+named-deck-content-three = This is tab 3
+button-group-one = One
+button-group-two = Two
+button-group-three = Three
+button-group-four = Four
+ `,
+ },
+};
+
+export const Tabs = () => html`
+ <style>
+ button[selected] {
+ outline: 2px dashed var(--in-content-primary-button-background);
+ }
+ </style>
+ <button-group>
+ <button is="named-deck-button" deck="tabs-deck" name="tab-1" data-l10n-id="named-deck-tab-one"></button>
+ <button is="named-deck-button" deck="tabs-deck" name="tab-2" data-l10n-id="named-deck-tab-two"></button>
+ <button is="named-deck-button" deck="tabs-deck" name="tab-3" data-l10n-id="named-deck-tab-three"></button>
+ </button-group>
+ <named-deck id="tabs-deck" is-tabbed>
+ <p name="tab-1" data-l10n-id="named-deck-content-one"></p>
+ <p name="tab-2" data-l10n-id="named-deck-content-two"></p>
+ <p name="tab-3" data-l10n-id="named-deck-content-three"></p>
+ </named-deck>
+
+ <hr>
+
+ <p>
+ The dashed outline is added for emphasis here. It applies to the button with
+ the <code>selected</code> attribute, but matches the deck's
+ <code>selected-view</code> name.
+ </p>
+
+ <p>
+ These tabs are a combination of <code>button-group</code>,
+ <code>named-deck-button</code>, and <code>named-deck</code>.
+ <ul>
+ <li>
+ <code>button-group</code> makes the tabs a single focusable group,
+ using left/right to switch between focused buttons.
+ </li>
+ <li>
+ <code>named-deck-button</code>s are <code>button</code> subclasses
+ that are used to control the <code>named-deck</code>.
+ </li>
+ <li>
+ <code>named-deck</code> show only one selected child at a time.
+ </li>
+ </ul>
+ </p>
+`;
+
+export const ListMenu = () => html`
+ <style>
+ .icon-button {
+ background-image: url("chrome://global/skin/icons/arrow-left.svg");
+ }
+
+ .vertical-group {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ }
+ </style>
+ <named-deck id="list-deck" is-tabbed>
+ <section name="list">
+ <button-group orientation="vertical" class="vertical-group">
+ <button is="named-deck-button" deck="list-deck" name="tab-1">
+ Tab 1
+ </button>
+ <button is="named-deck-button" deck="list-deck" name="tab-2">
+ Tab 2
+ </button>
+ <button is="named-deck-button" deck="list-deck" name="tab-3">
+ Tab 3
+ </button>
+ </button-group>
+ </section>
+ <section name="tab-1">
+ <button
+ class="icon-button ghost-button"
+ is="named-deck-button"
+ deck="list-deck"
+ name="list"
+ ></button>
+ <p>This is tab 1</p>
+ </section>
+ <section name="tab-2">
+ <button
+ class="icon-button ghost-button"
+ is="named-deck-button"
+ deck="list-deck"
+ name="list"
+ ></button>
+ <p>This is tab 2</p>
+ </section>
+ <section name="tab-3">
+ <button
+ class="icon-button ghost-button"
+ is="named-deck-button"
+ deck="list-deck"
+ name="list"
+ ></button>
+ <p>This is tab 3</p>
+ </section>
+ </named-deck>
+
+ <hr />
+
+ <p>
+ This is an alternate layout for creating a menu navigation. In this case,
+ the first view in the <code>named-deck</code> is the list view which
+ contains the <code>named-deck-button</code>s to link to the other views.
+ Each view then includes a back <code>named-deck-button</code> which is used
+ to navigate back to the first view.
+ </p>
+`;
+
+const FocusGroupTemplate = ({ orientation }) => html`
+ <button-group orientation=${orientation}>
+ <button data-l10n-id="button-group-one"></button>
+ <button data-l10n-id="button-group-two"></button>
+ <button data-l10n-id="button-group-three"></button>
+ <button data-l10n-id="button-group-four"></button>
+ </button-group>
+
+ <p>
+ The <code>button-group</code> element will group focus to the buttons,
+ requiring left/right or up/down to switch focus between its child elements.
+ It accepts an <code>orientation</code> property, which determines if
+ left/right or up/down are used to change the focused button.
+ </p>
+`;
+
+export const FocusGroup = FocusGroupTemplate.bind({});
+FocusGroup.args = {
+ orientation: "horizontal",
+};
+FocusGroup.argTypes = {
+ orientation: {
+ options: ["horizontal", "vertical"],
+ control: { type: "radio" },
+ },
+};
diff --git a/browser/components/storybook/stories/panel-list.stories.mjs b/browser/components/storybook/stories/panel-list.stories.mjs
new file mode 100644
index 0000000000..af1678b1e7
--- /dev/null
+++ b/browser/components/storybook/stories/panel-list.stories.mjs
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// eslint-disable-next-line import/no-unassigned-import
+import "toolkit-widgets/panel-list.js";
+import { html, ifDefined } from "lit.all.mjs";
+
+export default {
+ title: "UI Widgets/Panel Menu",
+ component: "panel-list",
+ parameters: {
+ status: "stable",
+ actions: {
+ handles: ["click"],
+ },
+ fluent: `
+panel-list-item-one = Item One
+panel-list-item-two = Item Two (accesskey w)
+panel-list-item-three = Item Three
+panel-list-checked = Checked
+panel-list-badged = Badged, look at me
+panel-list-passwords = Passwords
+panel-list-settings = Settings
+ `,
+ },
+};
+
+const openMenu = e => {
+ e.target.getRootNode().querySelector("panel-list").toggle(e);
+};
+
+const Template = ({ isOpen, items, wideAnchor }) =>
+ html`
+ <style>
+ panel-item[icon="passwords"]::part(button) {
+ background-image: url("chrome://browser/skin/login.svg");
+ }
+ panel-item[icon="settings"]::part(button) {
+ background-image: url("chrome://global/skin/icons/settings.svg");
+ }
+ button {
+ position: absolute;
+ background-image: url("chrome://global/skin/icons/more.svg");
+ }
+ button[wide] {
+ width: 400px !important;
+ }
+ .end {
+ inset-inline-end: 30px;
+ }
+
+ .bottom {
+ inset-block-end: 30px;
+ }
+ </style>
+ <button
+ class="ghost-button icon-button"
+ @click=${openMenu}
+ ?wide="${wideAnchor}"
+ ></button>
+ <button
+ class="ghost-button icon-button end"
+ @click=${openMenu}
+ ?wide="${wideAnchor}"
+ ></button>
+ <button
+ class="ghost-button icon-button bottom"
+ @click=${openMenu}
+ ?wide="${wideAnchor}"
+ ></button>
+ <button
+ class="ghost-button icon-button bottom end"
+ @click=${openMenu}
+ ?wide="${wideAnchor}"
+ ></button>
+ <panel-list
+ ?stay-open=${isOpen}
+ ?open=${isOpen}
+ ?min-width-from-anchor=${wideAnchor}
+ >
+ ${items.map(i =>
+ i == "<hr>"
+ ? html` <hr /> `
+ : html`
+ <panel-item
+ icon=${i.icon ?? ""}
+ ?checked=${i.checked}
+ ?badged=${i.badged}
+ accesskey=${ifDefined(i.accesskey)}
+ data-l10n-id=${i.l10nId ?? i}
+ ></panel-item>
+ `
+ )}
+ </panel-list>
+ `;
+
+export const Simple = Template.bind({});
+Simple.args = {
+ isOpen: false,
+ wideAnchor: false,
+ items: [
+ "panel-list-item-one",
+ { l10nId: "panel-list-item-two", accesskey: "w" },
+ "panel-list-item-three",
+ "<hr>",
+ { l10nId: "panel-list-checked", checked: true },
+ { l10nId: "panel-list-badged", badged: true, icon: "settings" },
+ ],
+};
+
+export const Icons = Template.bind({});
+Icons.args = {
+ isOpen: false,
+ wideAnchor: false,
+ items: [
+ { l10nId: "panel-list-passwords", icon: "passwords" },
+ { l10nId: "panel-list-settings", icon: "settings" },
+ ],
+};
+
+export const Open = Template.bind({});
+Open.args = {
+ ...Simple.args,
+ wideAnchor: false,
+ isOpen: true,
+};
+
+export const Wide = Template.bind({});
+Wide.args = {
+ ...Simple.args,
+ wideAnchor: true,
+};