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.mjs100
-rw-r--r--browser/components/storybook/stories/fxview-category-navigation.stories.mjs113
-rw-r--r--browser/components/storybook/stories/fxview-tab-list.stories.md103
-rw-r--r--browser/components/storybook/stories/fxview-tab-list.stories.mjs213
-rw-r--r--browser/components/storybook/stories/letter-grade.stories.mjs55
-rw-r--r--browser/components/storybook/stories/login-command-button.stories.mjs90
-rw-r--r--browser/components/storybook/stories/login-timeline.stories.mjs45
-rw-r--r--browser/components/storybook/stories/migration-wizard.stories.mjs553
-rw-r--r--browser/components/storybook/stories/named-deck.stories.mjs165
-rw-r--r--browser/components/storybook/stories/shopping-card.stories.mjs91
-rw-r--r--browser/components/storybook/stories/shopping-container.stories.mjs301
-rw-r--r--browser/components/storybook/stories/shopping-message-bar.stories.mjs54
12 files changed, 1883 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..ad800c83ec
--- /dev/null
+++ b/browser/components/storybook/stories/button.stories.mjs
@@ -0,0 +1,100 @@
+/* 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",
+ argTypes: {
+ size: {
+ options: ["default", "small"],
+ control: { type: "radio" },
+ },
+ },
+ parameters: {
+ status: "stable",
+ fluent: `
+button-regular = Regular
+button-primary = Primary
+button-disabled = Disabled
+button-danger = Danger
+button-small = Small
+ `,
+ },
+};
+
+const Template = ({
+ disabled,
+ primary,
+ l10nId,
+ ghostButton,
+ icon,
+ dangerButton,
+ size,
+}) =>
+ html`
+ <style>
+ .icon-button {
+ background-image: url("${icon}");
+ }
+ </style>
+ <button
+ ?disabled=${disabled}
+ class=${classMap({
+ primary,
+ "ghost-button": ghostButton,
+ "icon-button": icon,
+ "danger-button": dangerButton,
+ "small-button": size === "small",
+ })}
+ data-l10n-id=${l10nId}
+ ></button>
+ `;
+
+export const RegularButton = Template.bind({});
+RegularButton.args = {
+ l10nId: "button-regular",
+ primary: false,
+ disabled: false,
+ size: "default",
+};
+export const PrimaryButton = Template.bind({});
+PrimaryButton.args = {
+ l10nId: "button-primary",
+ primary: true,
+ disabled: false,
+ size: "default",
+};
+export const DisabledButton = Template.bind({});
+DisabledButton.args = {
+ l10nId: "button-disabled",
+ primary: false,
+ disabled: true,
+ size: "default",
+};
+
+export const DangerButton = Template.bind({});
+DangerButton.args = {
+ l10nId: "button-danger",
+ primary: true,
+ disabled: false,
+ dangerButton: true,
+ size: "default",
+};
+
+export const GhostIconButton = Template.bind({});
+GhostIconButton.args = {
+ icon: "chrome://browser/skin/login.svg",
+ disabled: false,
+ ghostButton: true,
+ size: "default",
+};
+export const SmallButton = Template.bind({});
+SmallButton.args = {
+ l10nId: "button-small",
+ primary: true,
+ disabled: false,
+ size: "small",
+};
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..9e6c59a89d
--- /dev/null
+++ b/browser/components/storybook/stories/fxview-tab-list.stories.md
@@ -0,0 +1,103 @@
+# 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}
+ .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}
+ .tabItems=${this.tabItems}
+ @fxview-tab-list-secondary-action=${this.onSecondaryAction}
+ @fxview-tab-list-primary-action=${this.onPrimaryAction}
+></fxview-tab-list>
+```
+
+### Properties
+
+You'll need to pass along some of the following properties:
+* `compactRows` (**Optional**): If `true`, displays shorter rows by omitting the URL and date/time. The default value is `false`.
+* `dateTimeFormat` (**Optional**): 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` (**Optional**): The optional aria-haspopup attribute for the secondary action, if required
+* `maxTabsLength` (**Optional**): 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. You may use any negative number such as `-1` to indicate no max.
+* `tabItems` (**Required**): 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). If you just pass a title and an icon, it creates a header row that is not clickable.
+ * `closedId` (**Optional**) - For a closed tab, this ID is used by SessionStore to retrieve the tab data for forgetting/re-opening the tab.
+ * `icon` (**Required**) - The location string for the favicon. Will fallback to default favicon if none is found.
+ * `primaryL10nId` (**Optional**) - The l10n id to be used for the primary action element. This fluent string should ONLY define a `.title` attribute to describe the link element in each row.
+ * `primaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the primary action element
+ * `secondaryL10nId` (**Optional**) - The l10n id to be used for the secondary action button. This fluent string should ONLY define a `.title` attribute to describe the secondary button in each row.
+ * `secondaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the secondary action button
+ * `tabElement` (**Optional**) - The MozTabbrowserTab element for the tab item.
+ * `sourceClosedId` (**Optional**) - The closedId of the closed window the tab is from if applicable.
+ * `sourceWindowId` (**Optional**) - The SessionStore id of the window the tab is from if applicable.
+ * `tabid` (**Optional**) - Optional property expected for Recently Closed tab data
+ * `time` (**Optional**) - The time in milliseconds for expected last interaction with the tab (Ex: `lastUsed` for SyncedTabs tabs, `closedAt` for RecentlyClosed tabs, etc.)
+ * `title` (**Required**) - The title for the tab
+ * `url` (**Optional**) - The full URL for the tab
+* `searchQuery` (**Optional**) - Highlights matches of the query string for titles of each row.
+
+
+### Notes
+
+* In order to keep this as generic as possible, the icon for the secondary action button will NOT have a default. You can supply a `class` attribute to an instance of `fxview-tab-list` in order to apply styles to things like the icon for the secondary action button. In the above example, I added a class `"with-context-menu"` to `fxview-tab-list`, so I can update the button's icon by using:
+```css
+ fxview-tab-list.with-context-menu::part(secondary-button) {
+ background-image: url("chrome://global/skin/icons/more.svg");
+ }
+```
+* You'll also need to define functions for the `fxview-tab-list-primary-action` and `fxview-tab-list-secondary-action` listeners in order to add functionality to the primary element and the secondary button.
+ * For the both handler functions, you can reference the `fxview-tab-row` associated with the interaction by using `event.originalTarget`. You can also reference the original event by using `event.detail.originalEvent`.
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..c8e2328c44
--- /dev/null
+++ b/browser/components/storybook/stories/fxview-tab-list.stories.mjs
@@ -0,0 +1,213 @@
+/* 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-options-menu-button",
+ secondaryL10nArgs: JSON.stringify({ tabTitle: "Example Domain" }),
+ },
+ {
+ 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-options-menu-button",
+ secondaryL10nArgs: JSON.stringify({ tabTitle: "Example Domain" }),
+ },
+ {
+ 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-options-menu-button",
+ secondaryL10nArgs: JSON.stringify({ tabTitle: "Example Domain" }),
+ },
+];
+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/letter-grade.stories.mjs b/browser/components/storybook/stories/letter-grade.stories.mjs
new file mode 100644
index 0000000000..cb270d72cc
--- /dev/null
+++ b/browser/components/storybook/stories/letter-grade.stories.mjs
@@ -0,0 +1,55 @@
+/* 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-unresolved
+import { html, ifDefined } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/shopping/content/letter-grade.mjs";
+
+export default {
+ title: "Domain-specific UI Widgets/Shopping/Letter Grade",
+ component: "letter-grade",
+ argTypes: {
+ letter: {
+ control: {
+ type: "select",
+ options: ["A", "B", "C", "D", "F"],
+ },
+ },
+ showdescription: {
+ control: {
+ type: "boolean",
+ },
+ },
+ },
+ parameters: {
+ status: "in-development",
+ fluent: `
+shopping-letter-grade-description-ab = Reliable reviews
+shopping-letter-grade-description-c = Only some reliable reviews
+shopping-letter-grade-description-df = Unreliable reviews
+shopping-letter-grade-tooltip =
+ .title = this is tooltip
+ `,
+ },
+};
+
+const Template = ({ letter, showdescription }) => html`
+ <letter-grade
+ letter=${ifDefined(letter)}
+ ?showdescription=${ifDefined(showdescription)}
+ ></letter-grade>
+`;
+
+export const DefaultLetterGrade = Template.bind({});
+DefaultLetterGrade.args = {
+ letter: "A",
+ showdescription: null,
+};
+
+export const LetterGradeWithDescription = Template.bind({});
+LetterGradeWithDescription.args = {
+ ...DefaultLetterGrade.args,
+ showdescription: true,
+};
diff --git a/browser/components/storybook/stories/login-command-button.stories.mjs b/browser/components/storybook/stories/login-command-button.stories.mjs
new file mode 100644
index 0000000000..7ebdd23142
--- /dev/null
+++ b/browser/components/storybook/stories/login-command-button.stories.mjs
@@ -0,0 +1,90 @@
+/* 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-command-button.mjs";
+
+const BUTTON_TYPES = {
+ Edit: "login-item-edit-button",
+ "Copy Username": "login-item-copy-username-button-text",
+ "Copy Password": "login-item-copy-password-button-text",
+ Remove: "about-logins-login-item-remove-button",
+};
+
+const BUTTON_ICONS = {
+ Edit: "chrome://global/skin/icons/edit.svg",
+ "Copy Username": "chrome://global/skin/icons/edit-copy.svg",
+ "Copy Password": "chrome://global/skin/icons/edit-copy.svg",
+ Remove: "chrome://global/skin/icons/delete.svg",
+};
+
+const BUTTON_VARIANT = {
+ Regular: "",
+ Danger: "primary danger-button",
+ Primary: "primary",
+ Icon: "ghost-button icon-button",
+};
+
+export default {
+ title: "Domain-specific UI Widgets/Credential Management/Command Button",
+ component: "login-command-button",
+ argTypes: {
+ l10nId: {
+ options: Object.keys(BUTTON_TYPES),
+ mapping: BUTTON_TYPES,
+ control: { type: "select" },
+ defaultValue: "Edit",
+ },
+ icon: {
+ options: Object.keys(BUTTON_ICONS),
+ mapping: BUTTON_ICONS,
+ control: { type: "select" },
+ defaultValue: "Edit",
+ },
+ variant: {
+ options: Object.keys(BUTTON_VARIANT),
+ mapping: BUTTON_VARIANT,
+ control: { type: "select" },
+ defaultValue: "Regular",
+ },
+ },
+};
+
+window.MozXULElement.insertFTLIfNeeded("browser/aboutLogins.ftl");
+
+const Template = ({ onClick, l10nId, icon, variant, disabled, tooltip }) => {
+ return html`
+ <login-command-button
+ .onClick=${onClick}
+ .l10nId=${l10nId}
+ .icon=${icon}
+ .variant=${variant}
+ .disabled=${disabled}
+ .tooltip=${tooltip}
+ >
+ </login-command-button>
+ `;
+};
+
+export const WorkingButton = Template.bind({});
+WorkingButton.args = {
+ onClick: () => alert("I work Woohoo!!"),
+ l10nId: "login-item-edit-button",
+ icon: "chrome://global/skin/icons/edit.svg",
+ variant: "",
+ disabled: false,
+ tooltip: "Lorem ipsum dolor",
+};
+
+export const DisabledButton = Template.bind({});
+DisabledButton.args = {
+ onClick: () => alert("I shouldn't be working..."),
+ l10nId: "login-item-edit-button",
+ icon: "chrome://global/skin/icons/edit.svg",
+ variant: "",
+ disabled: true,
+ tooltip: "Lorem ipsum dolor",
+};
diff --git a/browser/components/storybook/stories/login-timeline.stories.mjs b/browser/components/storybook/stories/login-timeline.stories.mjs
new file mode 100644
index 0000000000..4632da5d02
--- /dev/null
+++ b/browser/components/storybook/stories/login-timeline.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/migration-wizard.stories.mjs b/browser/components/storybook/stories/migration-wizard.stories.mjs
new file mode 100644
index 0000000000..64ce519a66
--- /dev/null
+++ b/browser/components/storybook/stories/migration-wizard.stories.mjs
@@ -0,0 +1,553 @@
+/* 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 "chrome://global/content/elements/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",
+ "EXTENSIONS",
+ ],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/chrome.png",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "chrome",
+ displayName: "Chrome",
+ resourceTypes: [
+ "HISTORY",
+ "FORMDATA",
+ "PASSWORDS",
+ "BOOKMARKS",
+ "PAYMENT_METHODS",
+ "EXTENSIONS",
+ ],
+ profile: { id: "person-2", name: "Person 2" },
+ brandImage: "chrome://browser/content/migration/brands/chrome.png",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "ie",
+ displayName: "Microsoft Internet Explorer",
+ resourceTypes: ["HISTORY", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://global/skin/icons/defaultFavicon.svg",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "edge",
+ displayName: "Microsoft Edge Legacy",
+ resourceTypes: ["HISTORY", "FORMDATA", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://global/skin/icons/defaultFavicon.svg",
+ hasPermissions: true,
+ },
+ {
+ 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",
+ hasPermissions: true,
+ },
+ {
+ 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",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "internal-testing",
+ displayName: "Internal Testing Migrator",
+ resourceTypes: ["HISTORY", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://global/skin/icons/defaultFavicon.svg",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "safari",
+ displayName: "Safari",
+ resourceTypes: ["HISTORY", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://browser/content/migration/brands/safari.png",
+ hasPermissions: true,
+ },
+ {
+ 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",
+ hasPermissions: true,
+ },
+ {
+ 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",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "vivaldi",
+ displayName: "Vivaldi",
+ resourceTypes: ["HISTORY"],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/vivaldi.png",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "no-resources-browser",
+ displayName: "Browser with no resources",
+ resourceTypes: [],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://global/skin/icons/defaultFavicon.svg",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.FILE,
+ key: "file-password-csv",
+ displayName: "Passwords from CSV file",
+ brandImage: "chrome://branding/content/document.ico",
+ resourceTypes: [],
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.FILE,
+ key: "file-bookmarks",
+ displayName: "Bookmarks from file",
+ brandImage: "chrome://branding/content/document.ico",
+ resourceTypes: [],
+ hasPermissions: true,
+ },
+];
+
+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}
+ ></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 NoPermissionMessage = Template.bind({});
+NoPermissionMessage.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.SELECTION,
+ migrators: [
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "chromium",
+ displayName: "Chromium",
+ resourceTypes: [],
+ profile: null,
+ brandImage: "chrome://browser/content/migration/brands/chromium.png",
+ hasPermissions: false,
+ permissionsPath: "/home/user/snap/chromium/common/chromium",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "safari",
+ displayName: "Safari",
+ resourceTypes: ["HISTORY", "PASSWORDS", "BOOKMARKS"],
+ profile: null,
+ brandImage: "chrome://browser/content/migration/brands/safari.png",
+ hasPermissions: false,
+ permissionsPath: "/Users/user/Library/Safari",
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
+ key: "vivaldi",
+ displayName: "Vivaldi",
+ resourceTypes: ["HISTORY"],
+ profile: { id: "Default", name: "Default" },
+ brandImage: "chrome://browser/content/migration/brands/vivaldi.png",
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.FILE,
+ key: "file-password-csv",
+ displayName: "Passwords from CSV file",
+ brandImage: "chrome://branding/content/document.ico",
+ resourceTypes: [],
+ hasPermissions: true,
+ },
+ {
+ type: MigrationWizardConstants.MIGRATOR_TYPES.FILE,
+ key: "file-bookmarks",
+ displayName: "Bookmarks from file",
+ brandImage: "chrome://branding/content/document.ico",
+ resourceTypes: [],
+ hasPermissions: true,
+ },
+ ],
+ showImportAll: false,
+ },
+};
+
+export const Progress = Template.bind({});
+Progress.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ },
+ },
+};
+
+export const PartialProgress = Template.bind({});
+PartialProgress.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 logins and passwords",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "10 extensions",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ 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]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 bookmarks",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 logins and passwords",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "From the last 180 days",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "1 extension",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "6 payment methods",
+ },
+ },
+ },
+};
+
+export const SuccessWithWarnings = Template.bind({});
+SuccessWithWarnings.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 bookmarks",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.WARNING,
+ message: "Something didn't work correctly.",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "From the last 180 days",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "1 extension",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "6 payment methods",
+ },
+ },
+ },
+};
+
+export const ExtensionsPartialSuccess = Template.bind({});
+ExtensionsPartialSuccess.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 bookmarks",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 logins and passwords",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "From the last 180 days",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.INFO,
+ message: "5 of 10 extensions",
+ linkText: "Learn how Firefox matches extensions",
+ linkURL:
+ "https://support.mozilla.org/kb/import-data-another-browser#w_import-extensions-from-chrome/",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "6 payment methods",
+ },
+ },
+ },
+};
+
+export const ExtensionsImportFailure = Template.bind({});
+ExtensionsImportFailure.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.PROGRESS,
+ key: "chrome",
+ progress: {
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 bookmarks",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "14 logins and passwords",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "From the last 180 days",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.WARNING,
+ message: "No matching extensions",
+ linkText: "Browse extensions for Firefox",
+ linkURL: "https://addons.mozilla.org/",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "Addresses, credit cards, form history",
+ },
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ 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]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.LOADING,
+ },
+ },
+ },
+};
+
+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]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ message: "2 added",
+ },
+ [MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES
+ .PASSWORDS_UPDATED]: {
+ value: MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
+ 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,
+ },
+};
+
+export const FileImportError = Template.bind({});
+FileImportError.args = {
+ dialogMode: true,
+ state: {
+ page: MigrationWizardConstants.PAGES.SELECTION,
+ migrators: FAKE_MIGRATOR_LIST,
+ showImportAll: false,
+ migratorKey: "file-password-csv",
+ fileImportErrorMessage: "Some file import error message",
+ },
+};
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/shopping-card.stories.mjs b/browser/components/storybook/stories/shopping-card.stories.mjs
new file mode 100644
index 0000000000..14986b361a
--- /dev/null
+++ b/browser/components/storybook/stories/shopping-card.stories.mjs
@@ -0,0 +1,91 @@
+/* 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-unresolved
+import { html, ifDefined } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/shopping/content/shopping-card.mjs";
+
+export default {
+ title: "Domain-specific UI Widgets/Shopping/Shopping Card",
+ component: "shopping-card",
+ parameters: {
+ status: "in-development",
+ fluent: `
+shopping-card-label =
+ .label = This the label
+shopping-show-more-button = Show more
+shopping-show-less-button = Show less
+ `,
+ },
+};
+
+const Template = ({ l10nId, rating, content, type }) => html`
+ <main style="max-width: 400px">
+ <shopping-card
+ type=${ifDefined(type)}
+ data-l10n-id=${ifDefined(l10nId)}
+ data-l10n-attrs="label"
+ >
+ <div slot="rating">
+ ${rating ? html`<moz-five-star rating="${rating}" />` : ""}
+ </div>
+ <div slot="content">${ifDefined(content)}</div>
+ </shopping-card>
+ </main>
+`;
+
+export const DefaultCard = Template.bind({});
+DefaultCard.args = {
+ l10nId: "shopping-card-label",
+ content: "This is the content",
+};
+
+export const CardWithRating = Template.bind({});
+CardWithRating.args = {
+ ...DefaultCard.args,
+ rating: 3,
+};
+
+export const CardOnlyContent = Template.bind({});
+CardOnlyContent.args = {
+ content: "This card only contains content",
+};
+
+export const CardTypeAccordion = Template.bind({});
+CardTypeAccordion.args = {
+ ...DefaultCard.args,
+ content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Nunc velit turpis, mollis a ultricies vitae, accumsan ut augue.
+ In a eros ac dolor hendrerit varius et at mauris.`,
+ type: "accordion",
+};
+
+export const CardTypeShowMoreButtonDisabled = Template.bind({});
+CardTypeShowMoreButtonDisabled.args = {
+ ...DefaultCard.args,
+ content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Nunc velit turpis, mollis a ultricies vitae, accumsan ut augue.
+ In a eros ac dolor hendrerit varius et at mauris.`,
+ type: "show-more",
+};
+
+export const CardTypeShowMore = Template.bind({});
+CardTypeShowMore.args = {
+ ...DefaultCard.args,
+ content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+ Posuere morbi leo urna molestie at elementum.
+ Felis donec et odio pellentesque.
+ Malesuada fames ac turpis egestas maecenas pharetra convallis posuere morbi. Varius duis at consectetur lorem donec massa sapien faucibus et.
+ Et tortor consequat id porta nibh venenatis.
+ Adipiscing diam donec adipiscing tristique risus nec feugiat in fermentum.
+ Viverra accumsan in nisl nisi scelerisque eu ultrices vitae.
+ Gravida neque convallis a cras.
+ Fringilla est ullamcorper eget nulla.
+ Habitant morbi tristique senectus et netus.
+ Quam vulputate dignissim suspendisse in est ante in.
+ Feugiat vivamus at augue eget arcu dictum varius duis.
+ Est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Ultricies integer quis auctor elit.`,
+ type: "show-more",
+};
diff --git a/browser/components/storybook/stories/shopping-container.stories.mjs b/browser/components/storybook/stories/shopping-container.stories.mjs
new file mode 100644
index 0000000000..79ade4222e
--- /dev/null
+++ b/browser/components/storybook/stories/shopping-container.stories.mjs
@@ -0,0 +1,301 @@
+/* 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-unresolved
+import { html, ifDefined } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/shopping/content/shopping-container.mjs";
+// Bug 1845737: These should get imported by ShoppingMessageBar
+window.MozXULElement.insertFTLIfNeeded("browser/shopping.ftl");
+window.MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
+// Mock for Glean
+if (typeof window.Glean == "undefined") {
+ window.Glean = new Proxy(() => {}, { get: () => window.Glean });
+}
+
+export default {
+ title: "Domain-specific UI Widgets/Shopping/Shopping Container",
+ component: "shopping-container",
+ parameters: {
+ status: "in-development",
+ },
+};
+
+const MOCK_HIGHLIGHTS = {
+ price: {
+ positive: ["This watch is great and the price was even better."],
+ negative: [],
+ neutral: [],
+ },
+ quality: {
+ positive: [
+ "Other than that, I am very impressed with the watch and it’s capabilities.",
+ "This watch performs above expectations in every way with the exception of the heart rate monitor.",
+ ],
+ negative: [
+ "Battery life is no better than the 3 even with the solar gimmick, probably worse.",
+ ],
+ neutral: [
+ "I have small wrists and still went with the 6X and glad I did.",
+ "I can deal with the looks, as Im now retired.",
+ ],
+ },
+ competitiveness: {
+ positive: [
+ "Bought this to replace my vivoactive 3.",
+ "I like that this watch has so many features, especially those that monitor health like SP02, respiration, sleep, HRV status, stress, and heart rate.",
+ ],
+ negative: [
+ "I do not use it for sleep or heartrate monitoring so not sure how accurate they are.",
+ ],
+ neutral: [
+ "I've avoided getting a smartwatch for so long due to short battery life on most of them.",
+ ],
+ },
+ "packaging/appearance": {
+ positive: ["Great cardboard box."],
+ negative: [],
+ neutral: [],
+ },
+ shipping: {
+ positive: [],
+ negative: [],
+ neutral: [],
+ },
+};
+
+const MOCK_RECOMMENDED_ADS = [
+ {
+ name: "VIVO Electric 60 x 24 inch Stand Up Desk | Black Table Top, Black Frame, Height Adjustable Standing Workstation with Memory Preset Controller (DESK-KIT-1B6B)",
+ url: "www.example.com",
+ price: "249.99",
+ currency: "USD",
+ grade: "A",
+ adjusted_rating: 4.6,
+ sponsored: true,
+ image_blob: new Blob(new Uint8Array(), { type: "image/jpeg" }),
+ },
+];
+
+const Template = ({
+ data,
+ isOffline,
+ isAnalysisInProgress,
+ analysisEvent,
+ productUrl,
+ userReportedAvailable,
+ adsEnabled,
+ adsEnabledByUser,
+ recommendationData,
+ analysisProgress,
+}) => html`
+ <style>
+ main {
+ /**
+ --shopping-header-background and sidebar background-color should
+ come from shopping.page.css, but they are not loaded when
+ viewing the sidebar in Storybook. Hardcode them here
+ */
+ --shopping-header-background: light-dark(#f9f9fb, #2b2a33);
+ background-color: var(--shopping-header-background);
+ width: 320px;
+ }
+ </style>
+
+ <main>
+ <shopping-container
+ .data=${data}
+ ?isOffline=${isOffline}
+ ?isAnalysisInProgress=${isAnalysisInProgress}
+ .analysisEvent=${analysisEvent}
+ productUrl=${ifDefined(productUrl)}
+ ?userReportedAvailable=${userReportedAvailable}
+ ?adsEnabled=${adsEnabled}
+ ?adsEnabledByUser=${adsEnabledByUser}
+ .recommendationData=${recommendationData}
+ analysisProgress=${analysisProgress}
+ >
+ </shopping-container>
+ </main>
+`;
+
+export const DefaultShoppingContainer = Template.bind({});
+DefaultShoppingContainer.args = {
+ data: {
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+};
+
+export const NoHighlights = Template.bind({});
+NoHighlights.args = {
+ data: {
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ },
+};
+
+/**
+ * There will be no animation if prefers-reduced-motion is enabled.
+ */
+export const Loading = Template.bind({});
+
+export const UnanalyzedProduct = Template.bind({});
+UnanalyzedProduct.args = {
+ data: {
+ adjusted_rating: null,
+ grade: null,
+ highlights: null,
+ product_id: null,
+ needs_analysis: true,
+ },
+};
+
+export const NewAnalysisInProgress = Template.bind({});
+NewAnalysisInProgress.args = {
+ isAnalysisInProgress: true,
+ analysisProgress: 15,
+};
+
+export const StaleProduct = Template.bind({});
+StaleProduct.args = {
+ data: {
+ product_id: "ABCD123",
+ needs_analysis: true,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+};
+
+export const ReanalysisInProgress = Template.bind({});
+ReanalysisInProgress.args = {
+ data: {
+ product_id: "ABCD123",
+ needs_analysis: true,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+ isAnalysisInProgress: true,
+ analysisProgress: 15,
+ analysisEvent: {
+ type: "ReanalysisRequested",
+ productUrl: "https://example.com/ABCD123",
+ },
+ productUrl: "https://example.com/ABCD123",
+};
+
+/**
+ * When ad functionality is enabled and the user wants the ad
+ * component visible.
+ */
+export const AdVisibleByUser = Template.bind({});
+AdVisibleByUser.args = {
+ data: {
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+ adsEnabled: true,
+ adsEnabledByUser: true,
+ recommendationData: MOCK_RECOMMENDED_ADS,
+};
+
+/**
+ * When ad functionality is enabled, but the user wants the ad
+ * component hidden.
+ */
+export const AdHiddenByUser = Template.bind({});
+AdHiddenByUser.args = {
+ data: {
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+ adsEnabled: true,
+ adsEnabledByUser: false,
+ recommendationData: MOCK_RECOMMENDED_ADS,
+};
+
+export const NotEnoughReviews = Template.bind({});
+NotEnoughReviews.args = {
+ data: {
+ adjusted_rating: null,
+ grade: null,
+ highlights: null,
+ product_id: "ABCD123",
+ needs_analysis: true,
+ },
+};
+
+export const ProductNotAvailable = Template.bind({});
+ProductNotAvailable.args = {
+ data: {
+ deleted_product: true,
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+};
+
+export const ThanksForReporting = Template.bind({});
+ThanksForReporting.args = {
+ data: {
+ deleted_product: true,
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+ userReportedAvailable: true,
+};
+
+export const UnavailableProductReported = Template.bind({});
+UnavailableProductReported.args = {
+ data: {
+ deleted_product: true,
+ deleted_product_reported: true,
+ product_id: "ABCD123",
+ needs_analysis: false,
+ adjusted_rating: 5,
+ grade: "B",
+ highlights: MOCK_HIGHLIGHTS,
+ },
+};
+
+export const PageNotSupported = Template.bind({});
+PageNotSupported.args = {
+ data: {
+ product_id: null,
+ adjusted_rating: null,
+ grade: null,
+ page_not_supported: true,
+ },
+};
+
+export const GenericError = Template.bind({});
+GenericError.args = {
+ data: {
+ status: 422,
+ error: "Unprocessable entity",
+ },
+};
+
+export const Offline = Template.bind({});
+Offline.args = {
+ isOffline: true,
+};
diff --git a/browser/components/storybook/stories/shopping-message-bar.stories.mjs b/browser/components/storybook/stories/shopping-message-bar.stories.mjs
new file mode 100644
index 0000000000..7bc0895fd0
--- /dev/null
+++ b/browser/components/storybook/stories/shopping-message-bar.stories.mjs
@@ -0,0 +1,54 @@
+/* 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-unresolved
+import { html, ifDefined } from "lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "browser/components/shopping/content/shopping-message-bar.mjs";
+
+window.MozXULElement.insertFTLIfNeeded("browser/shopping.ftl");
+window.MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
+
+export default {
+ title: "Domain-specific UI Widgets/Shopping/Shopping Message Bar",
+ component: "shopping-message-bar",
+ argTypes: {
+ type: {
+ control: {
+ type: "select",
+ options: [
+ "stale",
+ "generic-error",
+ "not-enough-reviews",
+ "product-not-available",
+ "product-not-available-reported",
+ "thanks-for-reporting",
+ "analysis-in-progress",
+ "reanalysis-in-progress",
+ "page-not-supported",
+ "thank-you-for-feedback",
+ ],
+ },
+ },
+ },
+ parameters: {
+ status: "in-development",
+ actions: {
+ handles: ["click"],
+ },
+ },
+};
+
+const Template = ({ type, progress }) => html`
+ <shopping-message-bar
+ type=${ifDefined(type)}
+ progress=${ifDefined(progress)}
+ ></shopping-message-bar>
+`;
+
+export const DefaultShoppingMessageBar = Template.bind({});
+DefaultShoppingMessageBar.args = {
+ type: "stale",
+ progress: 0,
+};