diff --git a/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx index 92ffe74b11c5..667c7559d9d7 100644 --- a/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx @@ -1,145 +1,46 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, {type ReactNode} from 'react'; + import React from 'react'; import clsx from 'clsx'; -import useIsBrowser from '@docusaurus/useIsBrowser'; import {translate} from '@docusaurus/Translate'; -import IconLightMode from '@theme/Icon/LightMode'; -import IconDarkMode from '@theme/Icon/DarkMode'; -import IconSystemColorMode from '@theme/Icon/SystemColorMode'; +import Icon from '@theme/Icon'; import type {Props} from '@theme/ColorModeToggle'; -import type {ColorMode} from '@docusaurus/theme-common'; - import styles from './styles.module.css'; -// The order of color modes is defined here, and can be customized with swizzle -function getNextColorMode( - colorMode: ColorMode | null, - respectPrefersColorScheme: boolean, -) { - // 2-value transition - if (!respectPrefersColorScheme) { - return colorMode === 'dark' ? 'light' : 'dark'; - } - - // 3-value transition - switch (colorMode) { - case null: - return 'light'; - case 'light': - return 'dark'; - case 'dark': - return null; - default: - throw new Error(`unexpected color mode ${colorMode}`); - } -} - -function getColorModeLabel(colorMode: ColorMode | null): string { - switch (colorMode) { - case null: - return translate({ - message: 'system mode', - id: 'theme.colorToggle.ariaLabel.mode.system', - description: 'The name for the system color mode', - }); - case 'light': - return translate({ - message: 'light mode', - id: 'theme.colorToggle.ariaLabel.mode.light', - description: 'The name for the light color mode', - }); - case 'dark': - return translate({ - message: 'dark mode', - id: 'theme.colorToggle.ariaLabel.mode.dark', - description: 'The name for the dark color mode', - }); - default: - throw new Error(`unexpected color mode ${colorMode}`); - } -} +export default function ColorModeToggle({ + className, + value, + onChange, +}: Props): React.JSX.Element { + const isDark = value === 'dark'; -function getColorModeAriaLabel(colorMode: ColorMode | null) { - return translate( - { - message: 'Switch between dark and light mode (currently {mode})', - id: 'theme.colorToggle.ariaLabel', - description: 'The ARIA label for the color mode toggle', - }, + const title = translate( { - mode: getColorModeLabel(colorMode), + message: 'Switch between dark and light mode (current is {mode})', + id: 'theme.colorModeToggle.ariaLabel', + description: 'The ARIA label for the navbar color mode toggle button', }, + {mode: isDark ? 'dark mode' : 'light mode'}, ); -} -function CurrentColorModeIcon(): ReactNode { - // 3 icons are always rendered for technical reasons - // We use "data-theme-choice" to render the correct one - // This must work even before React hydrates return ( - <> - - - - - ); -} - -function ColorModeToggle({ - className, - buttonClassName, - respectPrefersColorScheme, - value, - onChange, -}: Props): ReactNode { - const isBrowser = useIsBrowser(); - return ( -
+
+ +
+ {isDark ? 'Switch to light mode' : 'Switch to dark mode'} +
); } -export default React.memo(ColorModeToggle); diff --git a/website/src/css/custom.css b/website/src/css/custom.css index f19f426cd1b4..200e4e6d9156 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -246,3 +246,76 @@ html[data-navbar='false'] { html[data-red-border] div#__docusaurus { border: red solid thick; } +/* CSS Anchor Positioning for Tooltips */ +:root { + --docusaurus-tooltip-bg: #1b1b1d; +} + +[data-tooltip-trigger] { + anchor-name: --docusaurus-tooltip; +} + +[data-tooltip-content] { + position: fixed; + position-anchor: --docusaurus-tooltip; + top: anchor(bottom); + left: anchor(center); + transform: translateX(-50%); + margin-top: 8px; + padding: 6px 10px; + background-color: var(--docusaurus-tooltip-bg); + color: #fff; + border-radius: 4px; + font-size: 0.85rem; + z-index: 100; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease-in-out; +} + +[data-tooltip-trigger]:hover + [data-tooltip-content] { + opacity: 1; +} + +/* Existing repository styles remain untouched up here */ + +.toggleContainer { + position: relative; + display: inline-flex; +} +.toggleButton { + anchor-name: --color-toggle-anchor; +} +.toggleTooltip { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + margin-top: 8px; + padding: 6px 10px; + background-color: #1b1b1d; + color: #ffffff; + border-radius: 4px; + font-size: 0.85rem; + z-index: 150; + opacity: 0; + pointer-events: none; + white-space: nowrap; + transition: opacity 0.15s ease-in-out; + border: 1px solid rgba(255, 255, 255, 0.1); +} +/* Modern Engine Positioning with safe fallback layer */ +@supports (anchor-name: --test) { + .toggleTooltip { + position: fixed; + position-anchor: --color-toggle-anchor; + top: anchor(bottom); + left: anchor(center); + transform: translateX(-50%); + } + } + +.toggleButton:hover + .toggleTooltip, +.toggleButton:focus-visible + .toggleTooltip { + opacity: 1; +}