diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /browser/components/storybook/stories | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/storybook/stories')
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, +}; |