snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
# @rocket.chat/storybook-dark-mode
## 4.2.1
### Patch Changes
- [#2060](https://github.com/RocketChat/fuselage/pull/2060) [`ee381bb`](https://github.com/RocketChat/fuselage/commit/ee381bb32982d0f4ed9ef26385b5016e0a2e4023) Thanks [@tassoevan](https://github.com/tassoevan)! - feat: Remove deprecations and keep documentation comments
## 4.2.0
### Minor Changes
- [#1999](https://github.com/RocketChat/fuselage/pull/1999) [`6e37f9d`](https://github.com/RocketChat/fuselage/commit/6e37f9d2fee4c9ebeb8ea2c1601bbd4c89905859) Thanks [@tassoevan](https://github.com/tassoevan)! - feat!: Upgrade to React 19
## 4.1.0
### Minor Changes
- [#1849](https://github.com/RocketChat/fuselage/pull/1849) [`99b319c`](https://github.com/RocketChat/fuselage/commit/99b319c6c2ef1015d1d1edb027068c23dc1e29d9) Thanks [@tassoevan](https://github.com/tassoevan)! - feat(storybook-dark-mode): Fork `storybook-dark-mode`

View File

@@ -0,0 +1,20 @@
Copyright (c) 2019 Andrew Lisowski <lisowski54@gmail.com>
Copyright (c) 2026 Rocket.Chat Technologies Corp.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,51 @@
{
"name": "@rocket.chat/storybook-dark-mode",
"version": "4.2.1",
"description": "Toggle between light and dark mode in Storybook (Rocket.Chat fork)",
"repository": {
"type": "git",
"url": "git+https://github.com/RocketChat/Rocket.Chat.git",
"directory": "packages/storybook-dark-mode"
},
"license": "MIT",
"author": "Andrew Lisowski <lisowski54@gmail.com>",
"contributors": [
"Rocket.Chat Technologies Corp."
],
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/ts/index.d.ts",
"files": [
"src",
"dist",
"preset.js"
],
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && tsc -p tsconfig.build.json",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@storybook/global": "~5.0.0",
"@storybook/icons": "~1.6.0",
"fast-deep-equal": "~3.1.3"
},
"devDependencies": {
"@rocket.chat/tsconfig": "workspace:~",
"@types/react": "~19.2.18",
"@types/react-dom": "~19.2.4",
"eslint": "~9.39.5",
"react": "~19.2.8",
"react-dom": "~19.2.8",
"storybook": "^9.1.20",
"typescript": "~5.9.3"
},
"peerDependencies": {
"react": "*",
"react-dom": "*",
"storybook": "^9.0.0"
},
"volta": {
"extends": "../../package.json"
}
}

View File

@@ -0,0 +1,7 @@
function managerEntries(entry = []) {
return [...entry, require.resolve('./dist/esm/preset/manager')];
}
module.exports = {
managerEntries,
};

View File

@@ -0,0 +1,245 @@
import { global } from '@storybook/global';
import { MoonIcon, SunIcon } from '@storybook/icons';
import equal from 'fast-deep-equal';
import * as React from 'react';
import { IconButton } from 'storybook/internal/components';
import { STORY_CHANGED, SET_STORIES, DOCS_RENDERED } from 'storybook/internal/core-events';
import type { API } from 'storybook/manager-api';
import { useParameter } from 'storybook/manager-api';
import type { ThemeVars } from 'storybook/theming';
import { themes } from 'storybook/theming';
import { DARK_MODE_EVENT_NAME, UPDATE_DARK_MODE_EVENT_NAME } from './constants';
const { document, window } = global as { document: Document; window: Window };
type Mode = 'light' | 'dark';
type DarkModeStore = {
/** The class target in the preview iframe */
classTarget: string;
/** The current mode the storybook is set to */
current: Mode;
/** The dark theme for storybook */
dark: ThemeVars;
/** The dark class name for the preview iframe */
darkClass: string | string[];
/** The light theme for storybook */
light: ThemeVars;
/** The light class name for the preview iframe */
lightClass: string | string[];
/** Apply mode to iframe */
stylePreview: boolean;
/** Persist if the user has set the theme */
userHasExplicitlySetTheTheme: boolean;
};
const STORAGE_KEY = 'sb-addon-themes-3';
export const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
const defaultParams: Required<Omit<DarkModeStore, 'current'>> = {
classTarget: 'body',
dark: themes.dark,
darkClass: ['dark'],
light: themes.light,
lightClass: ['light'],
stylePreview: false,
userHasExplicitlySetTheTheme: false,
};
/** Persist the dark mode settings in localStorage */
export const updateStore = (newStore: DarkModeStore) => {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newStore));
};
/** Add the light/dark class to an element */
const toggleDarkClass = (
el: Element,
{ current, darkClass = defaultParams.darkClass, lightClass = defaultParams.lightClass }: DarkModeStore,
) => {
if (current === 'dark') {
el.classList.remove(...arrayify(lightClass));
el.classList.add(...arrayify(darkClass));
} else {
el.classList.remove(...arrayify(darkClass));
el.classList.add(...arrayify(lightClass));
}
};
/** Coerce a string to a single item array, or return an array as-is */
const arrayify = (classes: string | string[]): string[] => {
const arr: string[] = [];
return arr.concat(classes).map((item) => item);
};
/** Update the preview iframe class */
const updatePreview = (store: DarkModeStore) => {
const iframe = document.getElementById('storybook-preview-iframe') as HTMLIFrameElement;
if (!iframe) {
return;
}
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
const target = iframeDocument?.querySelector<HTMLElement>(store.classTarget);
if (!target) {
return;
}
toggleDarkClass(target, store);
};
/** Update the manager iframe class */
const updateManager = (store: DarkModeStore) => {
const manager = document.querySelector(store.classTarget);
if (!manager) {
return;
}
toggleDarkClass(manager, store);
};
/** Update changed dark mode settings and persist to localStorage */
export const store = (userTheme: Partial<DarkModeStore> = {}): DarkModeStore => {
const storedItem = window.localStorage.getItem(STORAGE_KEY);
if (typeof storedItem === 'string') {
const stored = JSON.parse(storedItem) as DarkModeStore;
if (userTheme) {
if (userTheme.dark && !equal(stored.dark, userTheme.dark)) {
stored.dark = userTheme.dark;
updateStore(stored);
}
if (userTheme.light && !equal(stored.light, userTheme.light)) {
stored.light = userTheme.light;
updateStore(stored);
}
}
return stored;
}
return { ...defaultParams, ...userTheme } as DarkModeStore;
};
// On initial load, set the dark mode class on the manager
// This is needed if you're using mostly CSS overrides to styles the storybook
// Otherwise the default theme is set in src/preset/manager.tsx
updateManager(store());
type DarkModeProps = {
/** The storybook API */
api: API;
};
/** A toolbar icon to toggle between dark and light themes in storybook */
export function DarkMode({ api }: DarkModeProps) {
const [isDark, setDark] = React.useState(prefersDark.matches);
const darkModeParams = useParameter<Partial<DarkModeStore>>('darkMode', {});
const { current: defaultMode, stylePreview, ...params } = darkModeParams;
const channel = api.getChannel();
// Save custom themes on init
const userHasExplicitlySetTheTheme = React.useMemo(() => store(params).userHasExplicitlySetTheTheme, [params]);
/** Set the theme in storybook, update the local state, and emit an event */
const setMode = React.useCallback(
(mode: Mode) => {
const currentStore = store();
api.setOptions({ theme: currentStore[mode] });
setDark(mode === 'dark');
api.getChannel()?.emit(DARK_MODE_EVENT_NAME, mode === 'dark');
updateManager(currentStore);
if (stylePreview) {
updatePreview(currentStore);
}
},
[api, stylePreview],
);
/** Update the theme settings in localStorage, react, and storybook */
const updateMode = React.useCallback(
(mode?: Mode) => {
const currentStore = store();
const current = mode || (currentStore.current === 'dark' ? 'light' : 'dark');
updateStore({ ...currentStore, current });
setMode(current);
},
[setMode],
);
/** Update the theme based on the color preference */
function prefersDarkUpdate(event: MediaQueryListEvent) {
if (userHasExplicitlySetTheTheme || defaultMode) {
return;
}
updateMode(event.matches ? 'dark' : 'light');
}
/** Render the current theme */
const renderTheme = React.useCallback(() => {
const { current = 'light' } = store();
setMode(current);
}, [setMode]);
/** Handle the user event and side effects */
const handleIconClick = () => {
updateMode();
const currentStore = store();
updateStore({ ...currentStore, userHasExplicitlySetTheTheme: true });
};
/** When storybook params change update the stored themes */
React.useEffect(() => {
const currentStore = store();
// Ensure we use the stores `current` value first to persist
// themeing between page loads and story changes.
updateStore({
...currentStore,
...darkModeParams,
current: currentStore.current || darkModeParams.current,
});
renderTheme();
}, [darkModeParams, renderTheme]);
React.useEffect(() => {
channel?.on(STORY_CHANGED, renderTheme);
channel?.on(SET_STORIES, renderTheme);
channel?.on(DOCS_RENDERED, renderTheme);
prefersDark.addListener(prefersDarkUpdate);
return () => {
channel?.removeListener(STORY_CHANGED, renderTheme);
channel?.removeListener(SET_STORIES, renderTheme);
channel?.removeListener(DOCS_RENDERED, renderTheme);
prefersDark.removeListener(prefersDarkUpdate);
};
});
React.useEffect(() => {
channel?.on(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
return () => {
channel?.removeListener(UPDATE_DARK_MODE_EVENT_NAME, updateMode);
};
});
// Storybook's first render doesn't have the global user params loaded so we
// need the effect to run whenever defaultMode is updated
React.useEffect(() => {
// If a users has set the mode this is respected
if (userHasExplicitlySetTheTheme) {
return;
}
if (defaultMode) {
updateMode(defaultMode);
} else if (prefersDark.matches) {
updateMode('dark');
}
}, [defaultMode, updateMode, userHasExplicitlySetTheTheme]);
return (
<IconButton key='dark-mode' title={isDark ? 'Change theme to light mode' : 'Change theme to dark mode'} onClick={handleIconClick}>
{isDark ? <SunIcon aria-hidden='true' /> : <MoonIcon aria-hidden='true' />}
</IconButton>
);
}
export default DarkMode;

View File

@@ -0,0 +1,2 @@
export const DARK_MODE_EVENT_NAME = 'DARK_MODE';
export const UPDATE_DARK_MODE_EVENT_NAME = 'UPDATE_DARK_MODE';

View File

@@ -0,0 +1,21 @@
import { addons, useState, useEffect } from 'storybook/preview-api';
import { store } from './Tool';
import { DARK_MODE_EVENT_NAME } from './constants';
/**
* Returns the current state of storybook's dark-mode
*/
export function useDarkMode(): boolean {
const [isDark, setIsDark] = useState(store().current === 'dark');
useEffect(() => {
const chan = addons.getChannel();
chan.on(DARK_MODE_EVENT_NAME, setIsDark);
return () => chan.off(DARK_MODE_EVENT_NAME, setIsDark);
}, []);
return isDark;
}
export * from './constants';

View File

@@ -0,0 +1,26 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as React from 'react';
import { Addon_TypesEnum } from 'storybook/internal/types';
import { addons } from 'storybook/manager-api';
import { themes } from 'storybook/theming';
import Tool, { prefersDark, store } from '../Tool';
const currentStore = store();
const currentTheme = currentStore.current || (prefersDark.matches && 'dark') || 'light';
addons.setConfig({
theme: {
...themes[currentTheme],
...currentStore[currentTheme],
},
});
addons.register('storybook/dark-mode', (api) => {
addons.add('storybook/dark-mode', {
title: 'dark mode',
type: Addon_TypesEnum.TOOL,
match: ({ viewMode }) => viewMode === 'story' || viewMode === 'docs',
render: () => <Tool api={api} />,
});
});

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDeclarationOnly": true,
"outDir": "./dist/ts",
"rootDir": "./src"
},
"exclude": ["node_modules", "dist", "**/*.spec.*", "**/*.stories*"]
}

View File

@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist/cjs",
"rootDir": "./src",
"declaration": false,
"declarationMap": false
},
"exclude": ["node_modules", "dist", "**/*.spec.*", "**/*.stories*"]
}

View File

@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./dist/esm",
"rootDir": "./src",
"declaration": false,
"declarationMap": false
},
"exclude": ["node_modules", "dist", "**/*.spec.*", "**/*.stories*"]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "@rocket.chat/tsconfig/client.json",
"compilerOptions": {
"jsx": "react"
},
"include": ["src/**/*"]
}