From 1a60e350dee442223379eef950d393638d1aa7c1 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 21 Mar 2026 11:48:15 -0400 Subject: [PATCH 1/7] feat: refresh docs theme styling and icons --- .gitignore | 1 + docusaurus.config.ts | 2 +- i18n/de/docusaurus-theme-classic/footer.json | 2 +- i18n/en/docusaurus-theme-classic/footer.json | 2 +- i18n/es/docusaurus-theme-classic/footer.json | 2 +- i18n/fr/docusaurus-theme-classic/footer.json | 2 +- i18n/zh/docusaurus-theme-classic/footer.json | 2 +- package.json | 1 + pnpm-lock.yaml | 18 +++ src/css/custom.css | 115 ++++++++++++++++++- src/theme/Admonition/Icon/Danger.tsx | 6 + src/theme/Admonition/Icon/Info.tsx | 6 + src/theme/Admonition/Icon/Note.tsx | 6 + src/theme/Admonition/Icon/Tip.tsx | 6 + src/theme/Admonition/Icon/Warning.tsx | 6 + src/theme/Footer/Copyright/index.tsx | 19 +++ src/theme/Icon/Arrow/index.tsx | 6 + src/theme/Icon/Close/index.tsx | 19 +++ src/theme/Icon/Copy/index.tsx | 6 + src/theme/Icon/DarkMode/index.tsx | 6 + src/theme/Icon/Edit/index.tsx | 6 + src/theme/Icon/ExternalLink/index.tsx | 23 ++++ src/theme/Icon/Home/index.tsx | 6 + src/theme/Icon/Language/index.tsx | 10 ++ src/theme/Icon/LightMode/index.tsx | 6 + src/theme/Icon/Menu/index.tsx | 10 ++ src/theme/Icon/Success/index.tsx | 6 + src/theme/Icon/SystemColorMode/index.tsx | 6 + src/theme/Icon/WordWrap/index.tsx | 6 + src/theme/Icon/shared.tsx | 32 ++++++ src/theme/Layout/IframeNavigation.tsx | 2 +- 31 files changed, 335 insertions(+), 11 deletions(-) create mode 100644 src/theme/Admonition/Icon/Danger.tsx create mode 100644 src/theme/Admonition/Icon/Info.tsx create mode 100644 src/theme/Admonition/Icon/Note.tsx create mode 100644 src/theme/Admonition/Icon/Tip.tsx create mode 100644 src/theme/Admonition/Icon/Warning.tsx create mode 100644 src/theme/Footer/Copyright/index.tsx create mode 100644 src/theme/Icon/Arrow/index.tsx create mode 100644 src/theme/Icon/Close/index.tsx create mode 100644 src/theme/Icon/Copy/index.tsx create mode 100644 src/theme/Icon/DarkMode/index.tsx create mode 100644 src/theme/Icon/Edit/index.tsx create mode 100644 src/theme/Icon/ExternalLink/index.tsx create mode 100644 src/theme/Icon/Home/index.tsx create mode 100644 src/theme/Icon/Language/index.tsx create mode 100644 src/theme/Icon/LightMode/index.tsx create mode 100644 src/theme/Icon/Menu/index.tsx create mode 100644 src/theme/Icon/Success/index.tsx create mode 100644 src/theme/Icon/SystemColorMode/index.tsx create mode 100644 src/theme/Icon/WordWrap/index.tsx create mode 100644 src/theme/Icon/shared.tsx diff --git a/.gitignore b/.gitignore index b5d3accf7e5..dd631d5aca8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* +.gstack/ diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 97a80f0d4e2..0b026a95575 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -155,7 +155,7 @@ const config: Config = { metadata: [ { name: "theme-color", - content: "#242526", + content: "#171616", media: "(prefers-color-scheme: dark)", }, // matches docusaurus theme rather than unraid specific color { diff --git a/i18n/de/docusaurus-theme-classic/footer.json b/i18n/de/docusaurus-theme-classic/footer.json index 977bf61f20d..cd4c71b21e4 100644 --- a/i18n/de/docusaurus-theme-classic/footer.json +++ b/i18n/de/docusaurus-theme-classic/footer.json @@ -52,7 +52,7 @@ "description": "The label of footer link with label=CC BY-SA 4.0 linking to https://creativecommons.org/licenses/by-sa/4.0/" }, "copyright": { - "message": "Urheberrecht © 2005-2025 Lime Technology, Inc.
Unraid® ist ein eingetragenes Warenzeichen von Lime Technology, Inc.
", + "message": "Urheberrecht © 2005-{currentYear} Lime Technology, Inc.
Unraid® ist ein eingetragenes Warenzeichen von Lime Technology, Inc.
", "description": "The footer copyright" }, "logo.alt": { diff --git a/i18n/en/docusaurus-theme-classic/footer.json b/i18n/en/docusaurus-theme-classic/footer.json index b117eb32d1a..7a0b28dcc8c 100644 --- a/i18n/en/docusaurus-theme-classic/footer.json +++ b/i18n/en/docusaurus-theme-classic/footer.json @@ -52,7 +52,7 @@ "description": "The label of footer link with label=CC BY-SA 4.0 linking to https://creativecommons.org/licenses/by-sa/4.0/" }, "copyright": { - "message": "Copyright © 2005-2025 Lime Technology, Inc.
Unraid® is a registered trademark of Lime Technology, Inc.
", + "message": "Copyright © 2005-{currentYear} Lime Technology, Inc.
Unraid® is a registered trademark of Lime Technology, Inc.
", "description": "The footer copyright" }, "logo.alt": { diff --git a/i18n/es/docusaurus-theme-classic/footer.json b/i18n/es/docusaurus-theme-classic/footer.json index f62676c3928..c7d0ad49450 100644 --- a/i18n/es/docusaurus-theme-classic/footer.json +++ b/i18n/es/docusaurus-theme-classic/footer.json @@ -52,7 +52,7 @@ "description": "The label of footer link with label=CC BY-SA 4.0 linking to https://creativecommons.org/licenses/by-sa/4.0/" }, "copyright": { - "message": "Copyright © 2005-2025 Lime Technology, Inc.
Unraid® es una marca registrada de Lime Technology, Inc.
", + "message": "Copyright © 2005-{currentYear} Lime Technology, Inc.
Unraid® es una marca registrada de Lime Technology, Inc.
", "description": "The footer copyright" }, "logo.alt": { diff --git a/i18n/fr/docusaurus-theme-classic/footer.json b/i18n/fr/docusaurus-theme-classic/footer.json index c6d226111ef..c6fd86a0d8c 100644 --- a/i18n/fr/docusaurus-theme-classic/footer.json +++ b/i18n/fr/docusaurus-theme-classic/footer.json @@ -52,7 +52,7 @@ "description": "The label of footer link with label=CC BY-SA 4.0 linking to https://creativecommons.org/licenses/by-sa/4.0/" }, "copyright": { - "message": "Droits d'auteur © 2005-2025 Lime Technology, Inc.
Unraid® est une marque déposée de Lime Technology, Inc.
", + "message": "Droits d'auteur © 2005-{currentYear} Lime Technology, Inc.
Unraid® est une marque déposée de Lime Technology, Inc.
", "description": "The footer copyright" }, "logo.alt": { diff --git a/i18n/zh/docusaurus-theme-classic/footer.json b/i18n/zh/docusaurus-theme-classic/footer.json index 91032e0aa5d..521024fb759 100644 --- a/i18n/zh/docusaurus-theme-classic/footer.json +++ b/i18n/zh/docusaurus-theme-classic/footer.json @@ -52,7 +52,7 @@ "description": "The label of footer link with label=CC BY-SA 4.0 linking to https://creativecommons.org/licenses/by-sa/4.0/" }, "copyright": { - "message": "版权所有 © 2005-2025 Lime Technology, Inc.
Unraid® 是 Lime Technology, Inc. 的注册商标。
", + "message": "版权所有 © 2005-{currentYear} Lime Technology, Inc.
Unraid® 是 Lime Technology, Inc. 的注册商标。
", "description": "The footer copyright" }, "logo.alt": { diff --git a/package.json b/package.json index 4e59b5d49a7..f28dc173a84 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@fontsource-variable/atkinson-hyperlegible-next": "^5.2.6", "@mdx-js/react": "^3.1.1", "@renatonagliati/remark-auto-glossary": "github:renatonagliati/remark-auto-glossary", + "@tabler/icons-react": "^3.40.0", "clsx": "^2.1.1", "docusaurus-plugin-image-zoom": "^3.0.1", "prism-react-renderer": "^2.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8cc7d14e2f..384b854a001 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@renatonagliati/remark-auto-glossary': specifier: github:renatonagliati/remark-auto-glossary version: https://codeload.github.com/renatonagliati/remark-auto-glossary/tar.gz/b99c75f38d194dd08285a96942cd69513ac24623(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tabler/icons-react': + specifier: ^3.40.0 + version: 3.40.0(react@19.2.4) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -2018,6 +2021,14 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} + '@tabler/icons-react@3.40.0': + resolution: {integrity: sha512-oO5+6QCnna4a//mYubx4euZfECtzQZFDGsDMIdzZUhbdyBCT+3bRVFBPueGIcemWld4Vb/0UQ39C/cmGfGylAg==} + peerDependencies: + react: '>= 16' + + '@tabler/icons@3.40.0': + resolution: {integrity: sha512-V/Q4VgNPKubRTiLdmWjV/zscYcj5IIk+euicUtaVVqF6luSC9rDngYWgST5/yh3Mrg/mYUwRv1YVTk71Jp0twQ==} + '@tsconfig/docusaurus@2.0.9': resolution: {integrity: sha512-0jVxZCgy2v7TnJrW9kVNikNwJHeiWb68Zhiiw2vHw4tzBFSP5vS2zn3O5EY2oPEVz5dXZRkK7MnWG3Ay5q0mIg==} @@ -9285,6 +9296,13 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tabler/icons-react@3.40.0(react@19.2.4)': + dependencies: + '@tabler/icons': 3.40.0 + react: 19.2.4 + + '@tabler/icons@3.40.0': {} + '@tsconfig/docusaurus@2.0.9': {} '@tybys/wasm-util@0.10.1': diff --git a/src/css/custom.css b/src/css/custom.css index 72c4c5948c1..d26a76c5794 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -13,6 +13,10 @@ :root { /* Keep Unraid primary colors for branding */ --unraid-brand-font-family: "nudista-web", -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --unraid-tabler-chevron-up: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 15 6-6 6 6'/%3E%3C/svg%3E"); + --unraid-tabler-external-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 7H7a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-5'/%3E%3Cpath d='M16 5h3v3'/%3E%3Cpath d='m10 14 9-9'/%3E%3C/svg%3E"); + --unraid-tabler-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='10' cy='10' r='7'/%3E%3Cpath d='m21 21-6-6'/%3E%3C/svg%3E"); + --unraid-tabler-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E"); --ifm-font-family-base: "Atkinson Hyperlegible Next Variable", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --ifm-heading-font-family: var(--ifm-font-family-base); --ifm-heading-font-weight: 600; @@ -24,9 +28,10 @@ --ifm-color-primary-light: #ec5a44; --ifm-color-primary-lighter: #ed6350; --ifm-color-primary-lightest: #f08575; + --ifm-menu-link-sublist-icon: var(--unraid-tabler-chevron-up); } -[data-theme='dark'] { +html[data-theme='dark'] { --ifm-color-primary: #F3622D; --ifm-color-primary-dark: #e55a28; --ifm-color-primary-darker: #df5525; @@ -34,6 +39,44 @@ --ifm-color-primary-light: #f47a42; --ifm-color-primary-lighter: #f5824a; --ifm-color-primary-lightest: #f7a06e; + --ifm-background-color: #171616; + --ifm-background-surface-color: #171616; + --ifm-navbar-background-color: #1c1b1b; + --ifm-footer-background-color: #171616; +} + +html[data-theme='dark']:root { + --docsearch-primary-color: #F3622D; + --docsearch-soft-primary-color: rgba(243, 98, 45, .16); + --docsearch-text-color: #c4c7dc; + --docsearch-secondary-text-color: #b6b7d5; + --docsearch-subtle-color: #2d2c2c; + --docsearch-error-color: #ef5350; + --docsearch-success-color: rgba(67, 160, 71, .2); + --docsearch-highlight-color: #F3622D; + --docsearch-focus-color: #f7a06e; + --docsearch-background-color: #2a2929; + --docsearch-icon-color: #b6b7d5; + --docsearch-container-background: rgba(23, 22, 22, .82); + --docsearch-modal-background: #1c1b1b; + --docsearch-modal-shadow: inset 1px 1px 0 0 #2a2929, 0 3px 8px 0 rgba(0, 0, 0, .45); + --docsearch-searchbox-background: rgba(23, 22, 22, .72); + --docsearch-searchbox-focus-background: rgba(23, 22, 22, .72); + --docsearch-hit-color: #bec3c9; + --docsearch-hit-shadow: none; + --docsearch-hit-background: #171616; + --docsearch-key-background: #2a2929; + --docsearch-key-color: #b6b7d5; + --docsearch-key-pressed-shadow: inset 0 2px 4px rgba(12, 13, 20, .4); + --docsearch-footer-background: rgba(23, 22, 22, .72); + --docsearch-footer-shadow: inset 0 1px 0 0 rgba(78, 74, 72, .5), 0 -4px 8px 0 rgba(0, 0, 0, .2); + --docsearch-logo-color: #F3622D; + --docsearch-muted-color: #7f8497; + --shimmer-bg: linear-gradient(90deg, #e0e3e8 0%, var(--docsearch-muted-color) 20%, var(--docsearch-muted-color) 60%, #e0e3e8 95%); + --docsearch-dropdown-menu-background: var(--docsearch-hit-background); + --docsearch-dropdown-menu-item-hover-background: var(--docsearch-modal-background); + --docsearch-search-button-background: #1c1b1b; + --docsearch-search-button-text-color: var(--docsearch-text-color); } html, @@ -93,6 +136,67 @@ h2 { line-height: 1.4; } +.dropdown > .navbar__link:after { + border: 0; + content: ""; + display: inline-block; + width: 0.8rem; + height: 0.8rem; + margin-left: 0.35rem; + position: relative; + top: 1px; + background: var(--unraid-tabler-chevron-up) 50% / 0.8rem 0.8rem no-repeat; + filter: var(--ifm-menu-link-sublist-icon-filter); + transform: rotate(180deg); +} + +.menu__link--sublist-caret:after, +.menu__caret:before { + min-width: 1rem; + width: 1rem; + height: 1rem; + background-size: 0.9rem 0.9rem; +} + +.DocSearch-Button-Container .DocSearch-Search-Icon, +.DocSearch-MagnifierLabel .DocSearch-Search-Icon, +.DocSearch-Close svg { + display: none; +} + +.DocSearch-Button-Container::before, +.DocSearch-MagnifierLabel::before, +.DocSearch-Close::before { + content: ""; + flex: none; + width: 20px; + height: 20px; + background-color: currentColor; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; +} + +.DocSearch-Button-Container::before, +.DocSearch-MagnifierLabel::before { + mask-image: var(--unraid-tabler-search); + -webkit-mask-image: var(--unraid-tabler-search); +} + +.DocSearch-Close { + align-items: center; + display: flex; + justify-content: center; +} + +.DocSearch-Close::before { + mask-image: var(--unraid-tabler-x); + -webkit-mask-image: var(--unraid-tabler-x); +} + /* =========================== Glossary Terms - Preserved Functionality =========================== */ @@ -252,11 +356,14 @@ a[title*="definition"]:hover { /* External link icons */ a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"])::after { - content: "↗"; + content: ""; display: inline-block; margin-left: 0.25em; - font-size: 0.8em; - font-weight: bold; + width: 0.9em; + height: 0.9em; + mask: var(--unraid-tabler-external-link) 50% / contain no-repeat; + -webkit-mask: var(--unraid-tabler-external-link) 50% / contain no-repeat; + background-color: currentColor; text-decoration: none; transition: transform 0.2s ease; } diff --git a/src/theme/Admonition/Icon/Danger.tsx b/src/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 00000000000..5bdf8e1be2b --- /dev/null +++ b/src/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,6 @@ +import { IconFlame } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../../Icon/shared"; + +export default function AdmonitionIconDanger(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Admonition/Icon/Info.tsx b/src/theme/Admonition/Icon/Info.tsx new file mode 100644 index 00000000000..fb98d81e8b3 --- /dev/null +++ b/src/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,6 @@ +import { IconInfoCircle } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../../Icon/shared"; + +export default function AdmonitionIconInfo(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Admonition/Icon/Note.tsx b/src/theme/Admonition/Icon/Note.tsx new file mode 100644 index 00000000000..7f2d1fab93f --- /dev/null +++ b/src/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,6 @@ +import { IconFileDescriptionFilled } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../../Icon/shared"; + +export default function AdmonitionIconNote(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Admonition/Icon/Tip.tsx b/src/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 00000000000..662ca213370 --- /dev/null +++ b/src/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,6 @@ +import { IconBulb } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../../Icon/shared"; + +export default function AdmonitionIconTip(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Admonition/Icon/Warning.tsx b/src/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 00000000000..0768c6a8156 --- /dev/null +++ b/src/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,6 @@ +import { IconAlertSquareRoundedFilled } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../../Icon/shared"; + +export default function AdmonitionIconWarning(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Footer/Copyright/index.tsx b/src/theme/Footer/Copyright/index.tsx new file mode 100644 index 00000000000..44d0b01706c --- /dev/null +++ b/src/theme/Footer/Copyright/index.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Footer/Copyright"; + +const CURRENT_YEAR_TOKEN_PATTERN = /\{currentYear\}/g; +const COPYRIGHT_YEAR_PATTERN = /2005-\d{4}/; + +export default function FooterCopyright({ copyright }: Props): ReactNode { + const currentYear = new Date().getFullYear().toString(); + const resolvedCopyright = copyright + .replace(CURRENT_YEAR_TOKEN_PATTERN, currentYear) + .replace(COPYRIGHT_YEAR_PATTERN, `2005-${currentYear}`); + + return ( +
+ ); +} diff --git a/src/theme/Icon/Arrow/index.tsx b/src/theme/Icon/Arrow/index.tsx new file mode 100644 index 00000000000..a342652238d --- /dev/null +++ b/src/theme/Icon/Arrow/index.tsx @@ -0,0 +1,6 @@ +import { IconChevronsLeft } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconArrow(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/Close/index.tsx b/src/theme/Icon/Close/index.tsx new file mode 100644 index 00000000000..524a1ff8031 --- /dev/null +++ b/src/theme/Icon/Close/index.tsx @@ -0,0 +1,19 @@ +import { IconX } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconClose({ + width = 21, + height = 21, + strokeWidth = 1.5, + ...props +}: ThemeIconProps) { + return ( + + ); +} diff --git a/src/theme/Icon/Copy/index.tsx b/src/theme/Icon/Copy/index.tsx new file mode 100644 index 00000000000..910704c4a32 --- /dev/null +++ b/src/theme/Icon/Copy/index.tsx @@ -0,0 +1,6 @@ +import { IconCopy as TablerIconCopy } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconCopy(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/DarkMode/index.tsx b/src/theme/Icon/DarkMode/index.tsx new file mode 100644 index 00000000000..7bb86263d54 --- /dev/null +++ b/src/theme/Icon/DarkMode/index.tsx @@ -0,0 +1,6 @@ +import { IconMoon } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconDarkMode(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/Edit/index.tsx b/src/theme/Icon/Edit/index.tsx new file mode 100644 index 00000000000..c8680a0c637 --- /dev/null +++ b/src/theme/Icon/Edit/index.tsx @@ -0,0 +1,6 @@ +import { IconEdit as TablerIconEdit } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconEdit(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/ExternalLink/index.tsx b/src/theme/Icon/ExternalLink/index.tsx new file mode 100644 index 00000000000..4c041084b50 --- /dev/null +++ b/src/theme/Icon/ExternalLink/index.tsx @@ -0,0 +1,23 @@ +import { IconExternalLink as TablerIconExternalLink } from "@tabler/icons-react"; +import { translate } from "@docusaurus/Translate"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconExternalLink({ + width = 13.5, + height = 13.5, + ...props +}: ThemeIconProps) { + return ( + + ); +} diff --git a/src/theme/Icon/Home/index.tsx b/src/theme/Icon/Home/index.tsx new file mode 100644 index 00000000000..b9169e530b4 --- /dev/null +++ b/src/theme/Icon/Home/index.tsx @@ -0,0 +1,6 @@ +import { IconHome as TablerIconHome } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconHome(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/Language/index.tsx b/src/theme/Icon/Language/index.tsx new file mode 100644 index 00000000000..f378fcac6c8 --- /dev/null +++ b/src/theme/Icon/Language/index.tsx @@ -0,0 +1,10 @@ +import { IconLanguage as TablerIconLanguage } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconLanguage({ + width = 20, + height = 20, + ...props +}: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/LightMode/index.tsx b/src/theme/Icon/LightMode/index.tsx new file mode 100644 index 00000000000..6082639398b --- /dev/null +++ b/src/theme/Icon/LightMode/index.tsx @@ -0,0 +1,6 @@ +import { IconSun } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconLightMode(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/Menu/index.tsx b/src/theme/Icon/Menu/index.tsx new file mode 100644 index 00000000000..4cdc20029a0 --- /dev/null +++ b/src/theme/Icon/Menu/index.tsx @@ -0,0 +1,10 @@ +import { IconMenu2 } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconMenu({ + width = 30, + height = 30, + ...props +}: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/Success/index.tsx b/src/theme/Icon/Success/index.tsx new file mode 100644 index 00000000000..34f965144a3 --- /dev/null +++ b/src/theme/Icon/Success/index.tsx @@ -0,0 +1,6 @@ +import { IconCheck } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconSuccess(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/SystemColorMode/index.tsx b/src/theme/Icon/SystemColorMode/index.tsx new file mode 100644 index 00000000000..235738b3d35 --- /dev/null +++ b/src/theme/Icon/SystemColorMode/index.tsx @@ -0,0 +1,6 @@ +import { IconDeviceDesktop } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconSystemColorMode(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/WordWrap/index.tsx b/src/theme/Icon/WordWrap/index.tsx new file mode 100644 index 00000000000..ef9958d9e1a --- /dev/null +++ b/src/theme/Icon/WordWrap/index.tsx @@ -0,0 +1,6 @@ +import { IconTextWrap } from "@tabler/icons-react"; +import { ThemeIcon, type ThemeIconProps } from "../shared"; + +export default function IconWordWrap(props: ThemeIconProps) { + return ; +} diff --git a/src/theme/Icon/shared.tsx b/src/theme/Icon/shared.tsx new file mode 100644 index 00000000000..31886f08681 --- /dev/null +++ b/src/theme/Icon/shared.tsx @@ -0,0 +1,32 @@ +import type { ComponentType, ReactElement, SVGProps } from "react"; + +export type ThemeIconProps = SVGProps; + +type ThemeIconWrapperProps = ThemeIconProps & { + defaultWidth?: number | string; + defaultHeight?: number | string; + icon: ComponentType>; + label?: string; +}; + +export function ThemeIcon({ + defaultWidth, + defaultHeight, + icon: Icon, + label, + width, + height, + ...props +}: ThemeIconWrapperProps): ReactElement { + const ariaHidden = label ? undefined : props["aria-hidden"] ?? true; + + return ( + + ); +} diff --git a/src/theme/Layout/IframeNavigation.tsx b/src/theme/Layout/IframeNavigation.tsx index 2a0a7446d90..306537e49d4 100644 --- a/src/theme/Layout/IframeNavigation.tsx +++ b/src/theme/Layout/IframeNavigation.tsx @@ -104,7 +104,7 @@ export function IframeNavigation(): ReactElement | null { > From 00bc3648c89184e686bd631d7e3ef12436f36f45 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 21 Mar 2026 11:51:30 -0400 Subject: [PATCH 2/7] fix: avoid duplicate external link icons --- src/css/custom.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index d26a76c5794..7bfe3707678 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -354,8 +354,10 @@ a[title*="definition"]:hover { scroll-margin-top: 128px; } -/* External link icons */ -a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"])::after { +/* External link icons for prose links. + Navbar, footer, and sidebar links already render @theme/Icon/ExternalLink. */ +.theme-doc-markdown a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"])::after, +.theme-blog-markdown a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"])::after { content: ""; display: inline-block; margin-left: 0.25em; @@ -368,7 +370,8 @@ a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"] transition: transform 0.2s ease; } -a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"]):hover::after { +.theme-doc-markdown a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"]):hover::after, +.theme-blog-markdown a[href^="http"]:not([href*="docs.unraid.net"]):not([href*="releases.unraid.net"]):not([href*="localhost"]):not([href*="127.0.0.1"]):hover::after { transform: translate(1px, -1px); } From f663d1c796b0d4ebf7d2e4f96e91c882e4d07726 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 21 Mar 2026 11:54:42 -0400 Subject: [PATCH 3/7] refactor: move CSS icon data URIs to SVG assets --- src/css/custom.css | 8 ++++---- static/img/icons/tabler-chevron-up.svg | 3 +++ static/img/icons/tabler-external-link.svg | 5 +++++ static/img/icons/tabler-search.svg | 4 ++++ static/img/icons/tabler-x.svg | 4 ++++ 5 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 static/img/icons/tabler-chevron-up.svg create mode 100644 static/img/icons/tabler-external-link.svg create mode 100644 static/img/icons/tabler-search.svg create mode 100644 static/img/icons/tabler-x.svg diff --git a/src/css/custom.css b/src/css/custom.css index 7bfe3707678..cc7db24c9f0 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -13,10 +13,10 @@ :root { /* Keep Unraid primary colors for branding */ --unraid-brand-font-family: "nudista-web", -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - --unraid-tabler-chevron-up: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 15 6-6 6 6'/%3E%3C/svg%3E"); - --unraid-tabler-external-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 7H7a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-5'/%3E%3Cpath d='M16 5h3v3'/%3E%3Cpath d='m10 14 9-9'/%3E%3C/svg%3E"); - --unraid-tabler-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='10' cy='10' r='7'/%3E%3Cpath d='m21 21-6-6'/%3E%3C/svg%3E"); - --unraid-tabler-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E"); + --unraid-tabler-chevron-up: url("/img/icons/tabler-chevron-up.svg"); + --unraid-tabler-external-link: url("/img/icons/tabler-external-link.svg"); + --unraid-tabler-search: url("/img/icons/tabler-search.svg"); + --unraid-tabler-x: url("/img/icons/tabler-x.svg"); --ifm-font-family-base: "Atkinson Hyperlegible Next Variable", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --ifm-heading-font-family: var(--ifm-font-family-base); --ifm-heading-font-weight: 600; diff --git a/static/img/icons/tabler-chevron-up.svg b/static/img/icons/tabler-chevron-up.svg new file mode 100644 index 00000000000..728a7a06301 --- /dev/null +++ b/static/img/icons/tabler-chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/icons/tabler-external-link.svg b/static/img/icons/tabler-external-link.svg new file mode 100644 index 00000000000..853481cf438 --- /dev/null +++ b/static/img/icons/tabler-external-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/icons/tabler-search.svg b/static/img/icons/tabler-search.svg new file mode 100644 index 00000000000..0179db70dc2 --- /dev/null +++ b/static/img/icons/tabler-search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/img/icons/tabler-x.svg b/static/img/icons/tabler-x.svg new file mode 100644 index 00000000000..9c11ecce6a3 --- /dev/null +++ b/static/img/icons/tabler-x.svg @@ -0,0 +1,4 @@ + + + + From 4552272555bdee2cddd11ce727cc101d4b855a23 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 21 Mar 2026 12:15:16 -0400 Subject: [PATCH 4/7] Add a floating docs feedback widget --- AGENTS.md | 7 + src/components/ResponsiveEmbed/index.tsx | 16 +- src/css/custom.css | 333 ++++++++++++++++++++--- src/theme/Layout/FeedbackWidget.tsx | 132 +++++++++ src/theme/Layout/IframeNavigation.tsx | 9 +- src/theme/Layout/Provider/index.tsx | 2 + 6 files changed, 451 insertions(+), 48 deletions(-) create mode 100644 AGENTS.md create mode 100644 src/theme/Layout/FeedbackWidget.tsx diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..2381d777955 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,7 @@ +# AGENTS.md + +## Validation + +- Never validate changes in this repo by running the full build. +- The build is intentionally considered too expensive and inefficient for routine validation. +- Prefer targeted verification instead, such as reviewing the changed files, running narrow checks relevant to the edit, or using lightweight local validation where available. diff --git a/src/components/ResponsiveEmbed/index.tsx b/src/components/ResponsiveEmbed/index.tsx index 393bdd910ad..5cadd6f7e49 100644 --- a/src/components/ResponsiveEmbed/index.tsx +++ b/src/components/ResponsiveEmbed/index.tsx @@ -7,6 +7,9 @@ type ResponsiveEmbedProps = { aspectRatio?: `${number}/${number}` | `${number} / ${number}`; allow?: string; referrerPolicy?: HTMLIFrameElement["referrerPolicy"]; + embedClassName?: string; + frameClassName?: string; + iframeClassName?: string; }; export default function ResponsiveEmbed({ @@ -15,17 +18,24 @@ export default function ResponsiveEmbed({ aspectRatio = "16 / 9", allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", referrerPolicy = "strict-origin-when-cross-origin", + embedClassName, + frameClassName, + iframeClassName, }: ResponsiveEmbedProps) { const frameStyle = { aspectRatio, } satisfies CSSProperties; + const embedClasses = [styles.embed, embedClassName].filter(Boolean).join(" "); + const frameClasses = [styles.frame, frameClassName].filter(Boolean).join(" "); + const iframeClasses = [styles.iframe, iframeClassName].filter(Boolean).join(" "); + return ( -
-
+
+