From d67eb4911043bb3e3021b3337eec342d084f8f78 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 24 May 2026 21:04:39 +0200 Subject: [PATCH] style(api-docs): dark/ultra theme for Swagger UI Override every visual surface Swagger does not theme on its own: opblocks, tables, model boxes, form inputs, code blocks, modals, Servers dropdown, per-endpoint padlocks and expand chevrons. Replaces Swagger's default light-arrow chevron on selects with a light-fill SVG positioned at the corner so the dark background-color is visible. Also disables deepLinking to silence the noisy v4 underscore warning; not used in our panel. --- frontend/src/pages/api-docs/ApiDocsPage.css | 366 +++++++++++++++++++- frontend/src/pages/api-docs/ApiDocsPage.tsx | 2 +- 2 files changed, 350 insertions(+), 18 deletions(-) diff --git a/frontend/src/pages/api-docs/ApiDocsPage.css b/frontend/src/pages/api-docs/ApiDocsPage.css index ef560d43..f6ac4f7b 100644 --- a/frontend/src/pages/api-docs/ApiDocsPage.css +++ b/frontend/src/pages/api-docs/ApiDocsPage.css @@ -8,11 +8,28 @@ .api-docs-page.is-dark { --bg-page: #1a1b1f; --bg-card: #23252b; + --sw-bg: #1f2026; + --sw-bg-soft: #25272e; + --sw-bg-input: #15161a; + --sw-bg-code: #0d0e12; + --sw-border: rgba(255, 255, 255, 0.08); + --sw-border-strong: rgba(255, 255, 255, 0.15); + --sw-text: rgba(255, 255, 255, 0.88); + --sw-text-muted: rgba(255, 255, 255, 0.6); + --sw-text-dim: rgba(255, 255, 255, 0.45); + --sw-accent: #58a6ff; + color-scheme: dark; } .api-docs-page.is-dark.is-ultra { --bg-page: #000; --bg-card: #101013; + --sw-bg: #0a0a0d; + --sw-bg-soft: #131316; + --sw-bg-input: #050507; + --sw-bg-code: #000; + --sw-border: rgba(255, 255, 255, 0.06); + --sw-border-strong: rgba(255, 255, 255, 0.12); } .api-docs-page .content-shell { @@ -42,59 +59,374 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } +/* ────────────────────────────────────────────────────────────────── + Dark mode — Swagger UI does not ship a dark theme, so every visual + surface needs an explicit override. Method-color chips (GET / POST / + …) are left untouched because they carry meaning at a glance. + ────────────────────────────────────────────────────────────────── */ + .api-docs-page.is-dark .swagger-ui, .api-docs-page.is-dark .swagger-ui .info .title, +.api-docs-page.is-dark .swagger-ui .info .title small pre, .api-docs-page.is-dark .swagger-ui .info p, +.api-docs-page.is-dark .swagger-ui .info li, +.api-docs-page.is-dark .swagger-ui .info table, .api-docs-page.is-dark .swagger-ui .opblock-tag, +.api-docs-page.is-dark .swagger-ui .opblock-tag small, .api-docs-page.is-dark .swagger-ui .opblock .opblock-summary-path, +.api-docs-page.is-dark .swagger-ui .opblock .opblock-summary-path__deprecated, .api-docs-page.is-dark .swagger-ui .opblock .opblock-summary-description, +.api-docs-page.is-dark .swagger-ui .opblock-description-wrapper p, +.api-docs-page.is-dark .swagger-ui .opblock-external-docs-wrapper p, +.api-docs-page.is-dark .swagger-ui .opblock-title_normal p, .api-docs-page.is-dark .swagger-ui table thead tr td, .api-docs-page.is-dark .swagger-ui table thead tr th, +.api-docs-page.is-dark .swagger-ui table tbody tr td, .api-docs-page.is-dark .swagger-ui .parameter__name, .api-docs-page.is-dark .swagger-ui .parameter__type, +.api-docs-page.is-dark .swagger-ui .parameter__in, +.api-docs-page.is-dark .swagger-ui .parameter__extension, .api-docs-page.is-dark .swagger-ui .response-col_status, .api-docs-page.is-dark .swagger-ui .response-col_description, +.api-docs-page.is-dark .swagger-ui .response-col_links, +.api-docs-page.is-dark .swagger-ui .responses-inner h4, +.api-docs-page.is-dark .swagger-ui .responses-inner h5, .api-docs-page.is-dark .swagger-ui label, .api-docs-page.is-dark .swagger-ui .tab li, +.api-docs-page.is-dark .swagger-ui .tab li button, +.api-docs-page.is-dark .swagger-ui .markdown, .api-docs-page.is-dark .swagger-ui .markdown p, -.api-docs-page.is-dark .swagger-ui .markdown li { - color: rgba(255, 255, 255, 0.85); +.api-docs-page.is-dark .swagger-ui .markdown li, +.api-docs-page.is-dark .swagger-ui .renderedmarkdown p, +.api-docs-page.is-dark .swagger-ui .renderedmarkdown li, +.api-docs-page.is-dark .swagger-ui .model-title, +.api-docs-page.is-dark .swagger-ui .model, +.api-docs-page.is-dark .swagger-ui .model-toggle:after, +.api-docs-page.is-dark .swagger-ui section.models h4, +.api-docs-page.is-dark .swagger-ui section.models h5, +.api-docs-page.is-dark .swagger-ui .auth-container h4, +.api-docs-page.is-dark .swagger-ui .auth-container h6, +.api-docs-page.is-dark .swagger-ui .scopes h2, +.api-docs-page.is-dark .swagger-ui .dialog-ux .modal-ux-header h3, +.api-docs-page.is-dark .swagger-ui .dialog-ux .modal-ux-content h4, +.api-docs-page.is-dark .swagger-ui .dialog-ux .modal-ux-content p, +.api-docs-page.is-dark .swagger-ui .servers-title { + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .opblock-tag small, +.api-docs-page.is-dark .swagger-ui .opblock-summary-description, +.api-docs-page.is-dark .swagger-ui .parameter__in, +.api-docs-page.is-dark .swagger-ui .parameter__type, +.api-docs-page.is-dark .swagger-ui .parameter__extension, +.api-docs-page.is-dark .swagger-ui .opblock-title_normal small, +.api-docs-page.is-dark .swagger-ui .servers > label, +.api-docs-page.is-dark .swagger-ui .servers > label span, +.api-docs-page.is-dark .swagger-ui .response-control-media-type__accept-message { + color: var(--sw-text-muted); +} + +.api-docs-page.is-dark .swagger-ui .opblock-tag { + border-bottom-color: var(--sw-border); } .api-docs-page.is-dark .swagger-ui .opblock { - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.08); + background: var(--sw-bg-soft); + border-color: var(--sw-border); + box-shadow: none; } .api-docs-page.is-dark .swagger-ui .opblock .opblock-section-header { - background: rgba(255, 255, 255, 0.04); - box-shadow: none; + background: rgba(255, 255, 255, 0.03); + box-shadow: inset 0 -1px 0 var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui .opblock .opblock-section-header h4, +.api-docs-page.is-dark .swagger-ui .opblock .opblock-section-header label { + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .opblock .opblock-summary { + border-bottom-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui .opblock-body pre.microlight, +.api-docs-page.is-dark .swagger-ui .highlight-code, +.api-docs-page.is-dark .swagger-ui .microlight, +.api-docs-page.is-dark .swagger-ui pre.example, +.api-docs-page.is-dark .swagger-ui code { + background: var(--sw-bg-code); + color: rgba(255, 255, 255, 0.92); +} + +.api-docs-page.is-dark .swagger-ui .highlight-code .copy-to-clipboard { + background: rgba(255, 255, 255, 0.06); } .api-docs-page.is-dark .swagger-ui input[type=text], -.api-docs-page.is-dark .swagger-ui textarea, +.api-docs-page.is-dark .swagger-ui input[type=password], +.api-docs-page.is-dark .swagger-ui input[type=search], +.api-docs-page.is-dark .swagger-ui input[type=email], +.api-docs-page.is-dark .swagger-ui input[type=file], +.api-docs-page.is-dark .swagger-ui textarea { + background: var(--sw-bg-input); + color: var(--sw-text); + border-color: var(--sw-border-strong); +} + .api-docs-page.is-dark .swagger-ui select { - background: rgba(0, 0, 0, 0.3); - color: rgba(255, 255, 255, 0.9); - border-color: rgba(255, 255, 255, 0.15); + background-color: var(--sw-bg-input); + background-image: url("data:image/svg+xml;charset=utf-8,"); + background-position: right 10px center; + background-repeat: no-repeat; + background-size: 20px; + color: var(--sw-text); + border-color: var(--sw-border-strong); +} + +.api-docs-page.is-dark .swagger-ui select option { + background-color: var(--sw-bg-input); + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui textarea { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; +} + +.api-docs-page.is-dark .swagger-ui input::placeholder, +.api-docs-page.is-dark .swagger-ui textarea::placeholder { + color: var(--sw-text-dim); } .api-docs-page.is-dark .swagger-ui .scheme-container { - background: rgba(255, 255, 255, 0.03); - box-shadow: none; + background: var(--sw-bg-soft); + box-shadow: inset 0 -1px 0 var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui .auth-wrapper .authorize { + color: var(--sw-text); + border-color: var(--sw-border-strong); +} + +.api-docs-page.is-dark .swagger-ui .auth-wrapper .authorize svg { + fill: var(--sw-text); } .api-docs-page.is-dark .swagger-ui .model-box, .api-docs-page.is-dark .swagger-ui section.models { - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.08); + background: var(--sw-bg-soft); + border-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui section.models.is-open h4 { + border-bottom-color: var(--sw-border); } .api-docs-page.is-dark .swagger-ui section.models .model-container { background: rgba(255, 255, 255, 0.02); + border-color: var(--sw-border); } -.api-docs-page.is-dark .swagger-ui .highlight-code, -.api-docs-page.is-dark .swagger-ui .microlight { - background: #0d0d10; +.api-docs-page.is-dark .swagger-ui section.models .model-container:hover { + background: rgba(255, 255, 255, 0.04); +} + +.api-docs-page.is-dark .swagger-ui .prop-type, +.api-docs-page.is-dark .swagger-ui .prop-format { + color: var(--sw-accent); +} + +.api-docs-page.is-dark .swagger-ui .property.primitive { + color: var(--sw-text-muted); +} + +.api-docs-page.is-dark .swagger-ui .opblock-title_normal h4 { + color: var(--sw-text); + border-bottom-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui table.parameters, +.api-docs-page.is-dark .swagger-ui table.responses-table, +.api-docs-page.is-dark .swagger-ui table thead tr { + border-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui table tbody tr td { + border-bottom-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui table thead tr { + border-bottom-color: var(--sw-border-strong); +} + +.api-docs-page.is-dark .swagger-ui .response-col_status { + font-weight: 600; +} + +.api-docs-page.is-dark .swagger-ui .btn { + background: rgba(255, 255, 255, 0.06); + color: var(--sw-text); + border-color: var(--sw-border-strong); + box-shadow: none; +} + +.api-docs-page.is-dark .swagger-ui .btn:hover { + background: rgba(255, 255, 255, 0.1); +} + +.api-docs-page.is-dark .swagger-ui .btn.execute { + background: var(--sw-accent); + color: #0d1117; + border-color: var(--sw-accent); +} + +.api-docs-page.is-dark .swagger-ui .btn.execute:hover { + background: #79b9ff; +} + +.api-docs-page.is-dark .swagger-ui .btn.authorize { + color: #52c41a; + border-color: rgba(82, 196, 26, 0.4); + background: rgba(82, 196, 26, 0.08); +} + +.api-docs-page.is-dark .swagger-ui .btn.authorize svg { + fill: #52c41a; +} + +.api-docs-page.is-dark .swagger-ui .authorization__btn svg, +.api-docs-page.is-dark .swagger-ui .expand-operation svg, +.api-docs-page.is-dark .swagger-ui .opblock-control-arrow svg { + fill: var(--sw-text); + opacity: 1; +} + +.api-docs-page.is-dark .swagger-ui .authorization__btn .locked, +.api-docs-page.is-dark .swagger-ui .authorization__btn .unlocked { + opacity: 1; +} + +.api-docs-page.is-dark .swagger-ui .btn.cancel { + color: #ff7875; + border-color: rgba(255, 120, 117, 0.4); + background: rgba(255, 120, 117, 0.08); +} + +.api-docs-page.is-dark .swagger-ui .btn.btn-clear, +.api-docs-page.is-dark .swagger-ui .btn-clear, +.api-docs-page.is-dark .swagger-ui .try-out__btn { + color: var(--sw-text); + background: rgba(255, 255, 255, 0.06); + border-color: var(--sw-border-strong); +} + +.api-docs-page.is-dark .swagger-ui .btn.btn-clear:hover, +.api-docs-page.is-dark .swagger-ui .btn-clear:hover, +.api-docs-page.is-dark .swagger-ui .try-out__btn:hover { + background: rgba(255, 255, 255, 0.1); +} + +.api-docs-page.is-dark .swagger-ui .filter .operation-filter-input { + background: var(--sw-bg-input); + border-color: var(--sw-border-strong); + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .dialog-ux .modal-ux { + background: var(--sw-bg); + border-color: var(--sw-border-strong); +} + +.api-docs-page.is-dark .swagger-ui .dialog-ux .modal-ux-header { + border-bottom-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui .dialog-ux .modal-ux-content { + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .arrow, +.api-docs-page.is-dark .swagger-ui svg.arrow { + fill: var(--sw-text-muted); +} + +.api-docs-page.is-dark .swagger-ui .opblock-summary-control:focus { + outline-color: var(--sw-accent); +} + +.api-docs-page.is-dark .swagger-ui a, +.api-docs-page.is-dark .swagger-ui .info a, +.api-docs-page.is-dark .swagger-ui .info hgroup.main a, +.api-docs-page.is-dark .swagger-ui .info .base-url, +.api-docs-page.is-dark .swagger-ui .info__contact a, +.api-docs-page.is-dark .swagger-ui .info__license a, +.api-docs-page.is-dark .swagger-ui .info__tos a { + color: var(--sw-accent); +} + +.api-docs-page.is-dark .swagger-ui a:hover { + color: #79b9ff; +} + +.api-docs-page.is-dark .swagger-ui .info .title small { + background: rgba(255, 255, 255, 0.08); + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .info .title small pre { + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .response-control-media-type--accept-controller select { + border-color: rgba(82, 196, 26, 0.5); +} + +.api-docs-page.is-dark .swagger-ui .loading-container .loading:before { + border-color: var(--sw-accent) transparent transparent; +} + +.api-docs-page.is-dark .swagger-ui .json-schema-form-item input, +.api-docs-page.is-dark .swagger-ui .json-schema-form-item select { + background: var(--sw-bg-input); + color: var(--sw-text); + border-color: var(--sw-border-strong); +} + +.api-docs-page.is-dark .swagger-ui .topbar { + background: var(--sw-bg); +} + +.api-docs-page.is-dark .swagger-ui .information-container { + background: transparent; +} + +.api-docs-page.is-dark .swagger-ui .opblock-summary-method { + text-shadow: none; +} + +.api-docs-page.is-dark .swagger-ui .auth-btn-wrapper { + border-top-color: var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui .servers .computed-url, +.api-docs-page.is-dark .swagger-ui .computed-url { + background: var(--sw-bg-code); + color: var(--sw-text); + border: 1px solid var(--sw-border); +} + +.api-docs-page.is-dark .swagger-ui .computed-url code, +.api-docs-page.is-dark .swagger-ui .servers .computed-url code { + background: transparent; + color: var(--sw-text); +} + +.api-docs-page.is-dark .swagger-ui .errors-wrapper { + background: rgba(255, 77, 79, 0.08); + border-color: rgba(255, 77, 79, 0.3); +} + +.api-docs-page.is-dark .swagger-ui .errors-wrapper .errors h4, +.api-docs-page.is-dark .swagger-ui .errors-wrapper .errors small { + color: var(--sw-text); } diff --git a/frontend/src/pages/api-docs/ApiDocsPage.tsx b/frontend/src/pages/api-docs/ApiDocsPage.tsx index d539d09f..e708b45c 100644 --- a/frontend/src/pages/api-docs/ApiDocsPage.tsx +++ b/frontend/src/pages/api-docs/ApiDocsPage.tsx @@ -32,7 +32,7 @@ export default function ApiDocsPage() {