trisquel-icecat/icecat/browser/components/aboutwelcome/content-src/components/ContentTiles.jsx
2025-10-06 02:35:48 -06:00

238 lines
7.8 KiB
JavaScript

/* 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 React, { useState, useEffect } from "react";
import { Localized } from "./MSLocalized";
import { AddonsPicker } from "./AddonsPicker";
import { SingleSelect } from "./SingleSelect";
import { MobileDownloads } from "./MobileDownloads";
import { MultiSelect } from "./MultiSelect";
import { EmbeddedMigrationWizard } from "./EmbeddedMigrationWizard";
import { ActionChecklist } from "./ActionChecklist";
import { EmbeddedBrowser } from "./EmbeddedBrowser";
import { AboutWelcomeUtils } from "../lib/aboutwelcome-utils.mjs";
const HEADER_STYLES = [
"backgroundColor",
"border",
"padding",
"margin",
"width",
"height",
];
const TILE_STYLES = [
"marginBlock",
"marginInline",
"paddingBlock",
"paddingInline",
];
export const ContentTiles = props => {
const { content } = props;
const [expandedTileIndex, setExpandedTileIndex] = useState(null);
// State for header that toggles showing and hiding all tiles, if applicable
const [tilesHeaderExpanded, setTilesHeaderExpanded] = useState(false);
const { tiles } = content;
if (!tiles) {
return null;
}
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
// Run once when ContentTiles mounts to prefill activeMultiSelect
if (!props.activeMultiSelect) {
const tilesArray = Array.isArray(tiles) ? tiles : [tiles];
tilesArray.forEach((tile, index) => {
if (tile.type !== "multiselect" || !tile.data) {
return;
}
const multiSelectId = `tile-${index}`;
const newActiveMultiSelect = [];
tile.data.forEach(({ id, defaultValue }) => {
if (defaultValue && id) {
newActiveMultiSelect.push(id);
}
});
if (newActiveMultiSelect.length) {
props.setActiveMultiSelect(newActiveMultiSelect, multiSelectId);
}
});
}
}, [tiles]); // eslint-disable-line react-hooks/exhaustive-deps
const toggleTile = (index, tile) => {
const tileId = `${tile.type}${tile.id ? "_" : ""}${tile.id ?? ""}_header`;
setExpandedTileIndex(prevIndex => (prevIndex === index ? null : index));
AboutWelcomeUtils.sendActionTelemetry(props.messageId, tileId);
};
const toggleTiles = () => {
setTilesHeaderExpanded(prev => !prev);
AboutWelcomeUtils.sendActionTelemetry(
props.messageId,
"content_tiles_header"
);
};
function getTileMultiSelects(screenMultiSelects, index) {
return screenMultiSelects?.[`tile-${index}`];
}
function getTileActiveMultiSelect(activeMultiSelect, index) {
return activeMultiSelect?.[`tile-${index}`];
}
const renderContentTile = (tile, index = 0) => {
const isExpanded = expandedTileIndex === index;
const { header, title, subtitle } = tile;
return (
<div
key={index}
className={`content-tile ${header ? "has-header" : ""}`}
style={AboutWelcomeUtils.getValidStyle(tile.style, TILE_STYLES)}
>
{header?.title && (
<button
className="tile-header secondary"
onClick={() => toggleTile(index, tile)}
aria-expanded={isExpanded}
aria-controls={`tile-content-${index}`}
style={AboutWelcomeUtils.getValidStyle(header.style, HEADER_STYLES)}
>
<div className="header-text-container">
<Localized text={header.title}>
<span className="header-title" />
</Localized>
{header.subtitle && (
<Localized text={header.subtitle}>
<span className="header-subtitle" />
</Localized>
)}
</div>
<div className="arrow-icon"></div>
</button>
)}
{(title || subtitle) && (
<div
className="tile-title-container"
id={`tile-title-container-${index}`}
>
{title && (
<Localized text={title}>
{/* H1 content is provided by Localized */}
{/* eslint-disable-next-line jsx-a11y/heading-has-content */}
<h1 className="tile-title" id={`content-tile-title-${index}`} />
</Localized>
)}
{subtitle && (
<Localized text={subtitle}>
<p className="tile-subtitle" />
</Localized>
)}
</div>
)}
{isExpanded || !header ? (
<div className="tile-content" id={`tile-content-${index}`}>
{tile.type === "addons-picker" && tile.data && (
<AddonsPicker
content={{ tiles: tile }}
installedAddons={props.installedAddons}
message_id={props.messageId}
handleAction={props.handleAction}
layout={content.position}
/>
)}
{["theme", "single-select"].includes(tile.type) && tile.data && (
<SingleSelect
content={{ tiles: tile }}
activeTheme={props.activeTheme}
handleAction={props.handleAction}
activeSingleSelectSelections={
props.activeSingleSelectSelections
}
setActiveSingleSelectSelection={
props.setActiveSingleSelectSelection
}
singleSelectId={`single-select-${index}`}
/>
)}
{tile.type === "mobile_downloads" && tile.data && (
<MobileDownloads
data={tile.data}
handleAction={props.handleAction}
/>
)}
{tile.type === "multiselect" && tile.data && (
<MultiSelect
content={{ tiles: tile }}
screenMultiSelects={getTileMultiSelects(
props.screenMultiSelects,
index
)}
setScreenMultiSelects={props.setScreenMultiSelects}
activeMultiSelect={getTileActiveMultiSelect(
props.activeMultiSelect,
index
)}
setActiveMultiSelect={props.setActiveMultiSelect}
multiSelectId={`tile-${index}`}
/>
)}
{tile.type === "migration-wizard" && (
<EmbeddedMigrationWizard
handleAction={props.handleAction}
content={{ tiles: tile }}
/>
)}
{tile.type === "action_checklist" && tile.data && (
<ActionChecklist content={content} message_id={props.messageId} />
)}
{tile.type === "embedded_browser" && tile.data?.url && (
<EmbeddedBrowser url={tile.data.url} style={tile.data.style} />
)}
</div>
) : null}
</div>
);
};
const renderContentTiles = () => {
if (Array.isArray(tiles)) {
return (
<div id="content-tiles-container">
{tiles.map((tile, index) => renderContentTile(tile, index))}
</div>
);
}
// If tiles is not an array render the tile alone without a container
return renderContentTile(tiles, 0);
};
if (content.tiles_header) {
return (
<React.Fragment>
<button
className="content-tiles-header secondary"
onClick={toggleTiles}
aria-expanded={tilesHeaderExpanded}
aria-controls={`content-tiles-container`}
>
<Localized text={content.tiles_header.title}>
<span className="header-title" />
</Localized>
<div className="arrow-icon"></div>
</button>
{tilesHeaderExpanded && renderContentTiles()}
</React.Fragment>
);
}
return renderContentTiles(tiles);
};