/* 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, ifDefined } from "../vendor/lit.all.mjs";
import { GROUP_TYPES } from "./moz-box-group.mjs";
// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
import "chrome://browser/content/preferences/widgets/setting-control.mjs";
export default {
title: "UI Widgets/Box Group",
component: "moz-box-group",
argTypes: {
type: {
options: Object.keys(GROUP_TYPES),
mapping: GROUP_TYPES,
control: "select",
},
},
parameters: {
status: "in-development",
fluent: `
moz-box-item =
.label = I'm a box item
.description = I'm part of a group
moz-box-button-1 =
.label = I'm a box button in a group
moz-box-button-2 =
.label = Delete this box button from a group
moz-box-link =
.label = I'm a box link in a group
moz-box-delete-action =
.title = Delete I'm a box item
moz-box-edit-action =
.title = Edit I'm a box item
moz-box-toggle-action =
.aria-label = Toggle I'm a box item
moz-box-more-action =
.title = More options, I'm a box item
moz-box-item-reorderable-1 =
.label = I'm box item number 1
moz-box-item-reorderable-2 =
.label = I'm box item number 2
moz-box-item-reorderable-3 =
.label = I'm box item number 3
moz-box-item-reorderable-4 =
.label = I'm box item number 4
moz-box-item-reorderable-5 =
.label = I'm box item number 5
moz-box-item-header =
.label = I'm a header box item
moz-box-button-footer =
.label = I'm a footer box button
`,
},
};
function basicTemplate({ type, hasHeader, hasFooter, hasStatic }) {
return html`
${hasHeader
? html``
: ""}
${getInnerElements(type, hasStatic)}
${hasFooter
? html``
: ""}
${type == "list"
? html`
Add an item
`
: ""}`;
}
function getInnerElements(type, hasStatic) {
if (type == GROUP_TYPES.reorderable) {
return reorderableElements(hasStatic);
}
return basicElements();
}
function reorderableElements(hasStatic) {
const createItems = (length, slot, startIndex = 0) =>
Array.from({ length }).map(
(_, i) =>
html`
`
);
if (hasStatic) {
return html`${createItems(3)}${createItems(2, "static", 3)}`;
}
return html`${createItems(5)}`;
}
function basicElements() {
return html`
`;
}
const deleteItem = event => {
event.target.remove();
};
const appendItem = event => {
let group = event.target.getRootNode().querySelector("moz-box-group");
let boxItem = document.createElement("moz-box-item");
boxItem.label = "New box item";
boxItem.description = "New items are added to the list";
let actionButton = document.createElement("moz-button");
actionButton.addEventListener("click", () => boxItem.remove());
actionButton.iconSrc = "chrome://global/skin/icons/delete.svg";
actionButton.slot = "actions";
actionButton.setAttribute("data-l10n-id", "moz-box-delete-action");
boxItem.append(actionButton);
group.prepend(boxItem);
};
/**
* Handles the reorder event from moz-box-group. Since we're not using
* Lit for updates in this case, we need to manually reorder the elements.
*
* @param {CustomEvent} event - The reorder event.
* @param {object} event.detail - Detail object containing reorder information.
* @param {Element} event.detail.draggedElement - The element being reordered.
* @param {Element} event.detail.targetElement - The target element to reorder relative to.
* @param {number} event.detail.position - Position relative to target (-1 for before, 0 for after).
*/
const handleReorderEvent = event => {
let group = event.target.getRootNode().querySelector("moz-box-group");
let { draggedElement, targetElement, position } = event.detail;
let moveBefore = position === -1;
if (moveBefore) {
group.insertBefore(draggedElement, targetElement);
} else {
group.insertBefore(draggedElement, targetElement.nextElementSibling);
}
draggedElement.focus();
group.updateItems();
};
// Example with all child elements wrapped in setting-control/setting-group,
// which is the most common use case in Firefox preferences.
function wrappedTemplate({ type, hasHeader, hasFooter }) {
return html``;
}
const getConfig = ({ type, hasHeader, hasFooter }) => ({
id: "exampleWrapped",
control: "moz-box-group",
controlAttrs: {
type,
},
items: [
...(hasHeader
? [
{
id: "header",
control: "moz-box-item",
l10nId: "moz-box-item-header",
controlAttrs: { slot: "header " },
},
]
: []),
{
id: "item1",
control: "moz-box-item",
l10nId: "moz-box-item",
options: [
{
id: "slotted-button",
control: "moz-button",
l10nId: "moz-box-edit-action",
iconSrc: "chrome://global/skin/icons/edit-outline.svg",
controlAttrs: {
type: "ghost",
slot: "actions",
},
},
{
id: "slotted-toggle",
control: "moz-toggle",
l10nId: "moz-box-toggle-action",
controlAttrs: {
slot: "actions",
},
},
{
id: "slotted-icon-button",
control: "moz-button",
l10nId: "moz-box-more-action",
iconSrc: "chrome://global/skin/icons/more.svg",
controlAttrs: {
slot: "actions",
},
},
],
},
{
id: "link1",
control: "moz-box-link",
l10nId: "moz-box-link",
},
{
id: "button1",
control: "moz-box-button",
l10nId: "moz-box-button-1",
},
{
id: "item2",
control: "moz-box-item",
l10nId: "moz-box-item",
options: [
{
id: "slotted-button-start",
control: "moz-button",
l10nId: "moz-box-edit-action",
iconSrc: "chrome://global/skin/icons/edit-outline.svg",
controlAttrs: {
type: "ghost",
slot: "actions-start",
},
},
{
id: "slotted-icon-button-start",
control: "moz-button",
l10nId: "moz-box-more-action",
iconSrc: "chrome://global/skin/icons/more.svg",
controlAttrs: {
slot: "actions-start",
},
},
],
},
{
id: "button2",
control: "moz-box-button",
l10nId: "moz-box-button-2",
},
...(hasFooter
? [
{
id: "footer",
control: "moz-box-button",
l10nId: "moz-box-button-footer",
controlAttrs: { slot: "footer " },
},
]
: []),
],
});
const DEFAULT_SETTING = {
value: 1,
on() {},
off() {},
userChange() {},
getControlConfig: c => c,
controllingExtensionInfo: {},
visible: true,
};
function getSetting() {
return {
value: true,
on() {},
off() {},
userChange() {},
visible: () => true,
getControlConfig: c => c,
controllingExtensionInfo: {},
};
}
const standardTemplateHtml = ({ scrollable }) => html`
`;
const Template = ({
type,
hasHeader,
hasFooter,
scrollable,
wrapped,
hasStatic,
}) => html`
${standardTemplateHtml({ scrollable })}
${wrapped
? wrappedTemplate({ type, hasHeader, hasFooter })
: basicTemplate({ type, hasHeader, hasFooter, hasStatic })}
`;
const ReorderableTemplate = ({
type,
hasHeader,
hasFooter,
scrollable,
hasStatic,
}) => html`
${standardTemplateHtml({ scrollable })}
${basicTemplate({ type, hasHeader, hasFooter, hasStatic })}
`;
export const Default = Template.bind({});
Default.args = {
type: "default",
hasHeader: false,
hasFooter: false,
scrollable: false,
wrapped: false,
hasStatic: false,
};
export const List = Template.bind({});
List.args = {
...Default.args,
type: "list",
};
export const Reorderable = ReorderableTemplate.bind({});
Reorderable.args = {
type: "reorderable",
hasHeader: false,
hasFooter: false,
scrollable: false,
};
export const ReorderableWithStatic = ReorderableTemplate.bind({});
ReorderableWithStatic.args = {
...Reorderable.args,
hasStatic: true,
};
export const ListWithHeaderAndFooter = Template.bind({});
ListWithHeaderAndFooter.args = {
...List.args,
hasHeader: true,
hasFooter: true,
};
export const Scrollable = Template.bind({});
Scrollable.args = {
...ListWithHeaderAndFooter.args,
scrollable: true,
};
export const Wrapped = Template.bind({});
Wrapped.args = {
...ListWithHeaderAndFooter.args,
wrapped: true,
};