Files
rbondesson b0ee6f5323 Add CSS cascade layers for Compound, shared components, and app/web styles (#33302)
* Layer Compound and shared component CSS

* Layer app theme CSS

* Remove !important flags from ActionBarView

* Remove unnecessary !important statements from shared components

* Avoid dead code errors for *.pcss just because layer is specified after @import url

* Remove unnecessary !important styling

* Override Banner defaults in RoomStatusBarView

* Updated snaps

* Updated snaps

* Fix styling of media body in app/web

* Fix styling for Compound anchors

* Fix styling issues in app/web

* More styling fixes

* Fix a problem extracting css for HTMLExport

* Revert changes

* Fix for theme styling

* Add test to improve coverage

* Prettier

* Fix styling issues

* Add data-kind attribute to avoid global styling override

* Update screenshot that now is correct

* Revert data-kind attribute

* Handle LinkPreview styling in .pcss

* Fix flaky test: Avoid racing the lazy-loaded ManageEventIndexDialog

* Take care of review comments

* Updated snaps

* Updated snaps again after merge

* Remove !important from RoomStatusBar
2026-04-30 11:54:49 +00:00

129 lines
5.0 KiB
TypeScript

/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import type { StorybookConfig } from "@storybook/react-vite";
import fs from "node:fs";
import { nodePolyfills } from "vite-plugin-node-polyfills";
import { mergeConfig, normalizePath, type Plugin } from "vite";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const srcRoot = normalizePath(join(__dirname, "..", "src"));
const sharedComponentsLayer = "shared-components";
// Get a list of available languages so the language selector can display them at runtime
const languageFiles = fs.readdirSync(join(__dirname, "..", "src", "i18n", "strings")).map((f) => f.slice(0, -5));
const languages: Record<string, string> = {};
for (const lang of languageFiles) {
const normalizedLanguage = lang.toLowerCase().replace("_", "-");
const languageParts = normalizedLanguage.split("-");
if (languageParts.length === 2 && languageParts[0] === languageParts[1]) {
languages[languageParts[0]] = `${lang}.json`;
} else {
languages[normalizedLanguage] = `${lang}.json`;
}
}
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
}
function layerSharedComponentCssModules(): Plugin {
return {
name: "element-web-shared-components-storybook-css-layer",
enforce: "pre",
transform(code, id) {
const cssPath = normalizePath(id.split("?")[0]);
if (!cssPath.startsWith(srcRoot) || !cssPath.endsWith(".module.css")) {
return;
}
return {
code: `@layer ${sharedComponentsLayer} {\n${code}\n}\n`,
map: null,
};
},
};
}
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-docs",
"@storybook/addon-designs",
"@storybook/addon-a11y",
"@storybook/addon-vitest",
getAbsolutePath("storybook-addon-vis"),
],
framework: "@storybook/react-vite",
core: {
disableTelemetry: true,
},
typescript: {
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
// The default exclude is ["**/**.stories.tsx"] which prevents
// docgen from extracting snapshot field descriptions from wrapper
// components defined in story files.
exclude: [],
},
},
async viteFinal(config) {
return mergeConfig(config, {
plugins: [
layerSharedComponentCssModules(),
// Needed for counterpart to work
nodePolyfills({ include: ["util"], globals: { global: false } }),
{
name: "language-middleware",
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === "/i18n/languages.json") {
// Dynamically generate a languages.json file based on what files are available
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(languages));
} else if (req.url === "/usercontent/" || req.url === "/usercontent") {
// Mock usercontent endpoint used by encrypted download iframes.
res.end("This is where /usercontent/ is loaded.");
} else if (req.url?.startsWith("/i18n/")) {
// Serve the individual language files, which annoyingly can't be a simple
// static dir because the directory structure in src doesn't match what
// the app requests.
const langFile = req.url.split("/").pop();
res.setHeader("Content-Type", "application/json");
fs.createReadStream(`src/i18n/strings/${langFile}`).pipe(res);
} else {
next();
}
});
},
},
],
server: {
allowedHosts: ["localhost", ".docker.internal"],
},
});
},
refs: {
"compound-web": {
title: "Compound Web",
url: "https://element-hq.github.io/compound-web/",
},
},
env: (config) => ({
...config,
STORYBOOK_LANGUAGES: JSON.stringify(Object.keys(languages)),
}),
};
export default config;