From 588776c6a79376e61ed8287200388b2255f39569 Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 20:23:53 +0800 Subject: [PATCH 01/20] feat(Theme): add Fluent UI theme - add fluent.min.css theme pack ported from microsoft/fluentui (Fluent 2) design tokens - register Fluent UI in the theme chooser with a site chrome companion css - fix theme list fixed height clipping the 6th theme item --- .../Components/ThemeChooser.razor.css | 9 +- src/BootstrapBlazor.Server/appsettings.json | 8 + .../wwwroot/css/fluent.css | 23 + .../wwwroot/css/fluent.min.css | 557 ++++++++++++++++++ 4 files changed, 593 insertions(+), 4 deletions(-) create mode 100644 src/BootstrapBlazor.Server/wwwroot/css/fluent.css create mode 100644 src/BootstrapBlazor/wwwroot/css/fluent.min.css diff --git a/src/BootstrapBlazor.Server/Components/Components/ThemeChooser.razor.css b/src/BootstrapBlazor.Server/Components/Components/ThemeChooser.razor.css index 14e158574b7..eb9a8f53b15 100644 --- a/src/BootstrapBlazor.Server/Components/Components/ThemeChooser.razor.css +++ b/src/BootstrapBlazor.Server/Components/Components/ThemeChooser.razor.css @@ -1,4 +1,4 @@ -.theme-list { +.theme-list { position: fixed; z-index: 47; bottom: 12rem; @@ -6,14 +6,15 @@ background: var(--bs-body-bg); border-radius: var(--bs-border-radius); width: 260px; - height: 0; + max-height: 0; overflow: hidden; - transition: height .3s ease-in-out; + transition: max-height .3s ease-in-out; box-shadow: var(--bb-layout-button-shadow); } .theme-list.is-open { - height: 306px; + max-height: min(70vh, 480px); + overflow-y: auto; } .theme-header { diff --git a/src/BootstrapBlazor.Server/appsettings.json b/src/BootstrapBlazor.Server/appsettings.json index fe5a1b5ddaa..c269e04dfde 100644 --- a/src/BootstrapBlazor.Server/appsettings.json +++ b/src/BootstrapBlazor.Server/appsettings.json @@ -112,6 +112,14 @@ "files": [ "./css/devui.css" ] + }, + { + "key": "fluent", + "name": "Fluent UI", + "files": [ + "_content/BootstrapBlazor/css/fluent.min.css", + "./css/fluent.css" + ] } ] }, diff --git a/src/BootstrapBlazor.Server/wwwroot/css/fluent.css b/src/BootstrapBlazor.Server/wwwroot/css/fluent.css new file mode 100644 index 00000000000..d150ed20b96 --- /dev/null +++ b/src/BootstrapBlazor.Server/wwwroot/css/fluent.css @@ -0,0 +1,23 @@ +/* + * Fluent UI 主题演示站配套样式(仅覆盖站点外观变量) + * 组件主题本体随组件库分发:_content/BootstrapBlazor/css/fluent.min.css + * 令牌来源:https://github.com/microsoft/fluentui @fluentui/tokens (brandWeb) + */ +:root { + --bb-sidebar-navbar-bg: #115ea3; /* brand70 */ + --bb-sidebar-navbar-bar-bg: #0f6cbd; /* brand80 colorBrandBackground */ + --bb-layout-button-bg: #0f6cbd; + --bb-layout-button-hover-bg: #115ea3; /* colorBrandBackgroundHover */ + --bb-layout-button-active-bg: #0c3b5e; /* colorBrandBackgroundPressed */ + --bb-layout-button-shadow: 0 0 6px rgba(15,108,189,.5); + --bb-layout-button-color: #fff; + --bb-layout-button-update-bg: #479ef5; /* brand100 */ + --bb-button-theme-list-header-color: #fff; + --bb-button-theme-item-bg: #f5f5f5; + --bb-button-theme-item-color: var(--bs-body-color); + --bb-button-theme-item-hover-color: #fff; + --bb-button-theme-item-hover-bg: #115ea3; + --bb-button-theme-item-active-color: #fff; + --bb-button-theme-item-active-bg: #0f6cbd; + --bb-button-chat-color: #fff; +} diff --git a/src/BootstrapBlazor/wwwroot/css/fluent.min.css b/src/BootstrapBlazor/wwwroot/css/fluent.min.css new file mode 100644 index 00000000000..590fa3a3f5d --- /dev/null +++ b/src/BootstrapBlazor/wwwroot/css/fluent.min.css @@ -0,0 +1,557 @@ +/* + * Fluent UI Web theme for BootstrapBlazor + * Ported from Microsoft Fluent UI (Fluent 2) design tokens + * Source: https://github.com/microsoft/fluentui + * @fluentui/tokens webLightTheme / webDarkTheme (brandWeb ramp #0f6cbd) + * packages/tokens/src/themes/web, global/brandColors.ts, alias/lightColor.ts, alias/darkColor.ts + * + * Usage: add after bootstrap.blazor.bundle.min.css + * + */ + +/* ===== Light theme ===== */ +:root, [data-bs-theme='light'] { + /* Neutral colors — Fluent global grey ramp */ + --bs-gray: #616161; + --bs-gray-dark: #242424; + --bs-gray-100: #f5f5f5; + --bs-gray-200: #f0f0f0; + --bs-gray-300: #e0e0e0; + --bs-gray-400: #d1d1d1; + --bs-gray-500: #b3b3b3; + --bs-gray-600: #757575; + --bs-gray-700: #616161; + --bs-gray-800: #424242; + --bs-gray-900: #242424; + + /* Theme colors — Fluent brandWeb ramp & shared palettes */ + --bs-primary: #0f6cbd; /* colorBrandBackground (brand80) */ + --bs-secondary: #ebebeb; /* colorNeutralBackground5 (grey92) */ + --bs-success: #107c10; /* colorStatusSuccessBackground3 (green) */ + --bs-info: #0099bc; /* colorPaletteCyanBackground3 */ + --bs-warning: #f7630c; /* colorStatusWarningBackground3 (orange) */ + --bs-danger: #c50f1f; /* colorStatusDangerBackground3 (cranberry) */ + --bs-light: #fafafa; /* grey98 */ + --bs-dark: #242424; /* grey14 */ + --bs-primary-rgb: 15,108,189; + --bs-secondary-rgb: 235,235,235; + --bs-success-rgb: 16,124,16; + --bs-info-rgb: 0,153,188; + --bs-warning-rgb: 247,99,12; + --bs-danger-rgb: 197,15,31; + --bs-light-rgb: 250,250,250; + --bs-dark-rgb: 36,36,36; + + /* Body — colorNeutralForeground1 / colorNeutralBackground1 */ + --bs-body-color: #242424; + --bs-body-color-rgb: 36,36,36; + --bs-body-bg: #ffffff; + --bs-body-bg-rgb: 255,255,255; + + /* Links — colorBrandForegroundLink */ + --bs-link-color: #115ea3; /* brand70 */ + --bs-link-color-rgb: 17,94,163; + --bs-link-hover-color: #0f548c; /* brand60 */ + --bs-link-hover-color-rgb: 15,84,140; + + /* Typography — fontFamilyBase / fontFamilyMonospace */ + --bs-font-sans-serif: 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif; + --bs-font-monospace: Consolas, 'Courier New', Courier, monospace; + + /* Borders — colorNeutralStroke1 & borderRadius tokens */ + --bs-border-color: #d1d1d1; + --bs-border-radius-sm: 2px; + --bs-border-radius: 4px; + --bs-border-radius-lg: 6px; + --bs-border-radius-xl: 8px; + --bs-border-radius-xxl: 12px; + + /* Shadows — Fluent shadow tokens (ambient 0.12 / key 0.14) */ + --bs-box-shadow-sm: 0 0 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.14); + --bs-box-shadow: 0 0 2px rgba(0,0,0,0.12), 0 8px 16px rgba(0,0,0,0.14); + --bs-box-shadow-lg: 0 0 8px rgba(0,0,0,0.12), 0 32px 64px rgba(0,0,0,0.14); + + /* Text emphasis — Fluent foreground tokens */ + --bs-primary-text-emphasis: #0f6cbd; /* colorBrandForeground1 */ + --bs-secondary-text-emphasis: #616161; /* colorNeutralForeground3 */ + --bs-success-text-emphasis: #0e700e; /* colorStatusSuccessForeground1 */ + --bs-info-text-emphasis: #008aa9; /* colorPaletteCyanForeground1 */ + --bs-warning-text-emphasis: #bc4b09; /* colorStatusWarningForeground1 */ + --bs-danger-text-emphasis: #b10e1c; /* colorStatusDangerForeground1 */ + --bs-light-text-emphasis: #616161; + --bs-dark-text-emphasis: #242424; + + /* Background subtle — Fluent Background1/Background2 tokens */ + --bs-primary-bg-subtle: #ebf3fc; /* colorBrandBackground2 (brand160) */ + --bs-secondary-bg-subtle: #f5f5f5; + --bs-success-bg-subtle: #f1faf1; /* colorStatusSuccessBackground1 */ + --bs-info-bg-subtle: #f2fafc; /* colorPaletteCyanBackground1 */ + --bs-warning-bg-subtle: #fff9f5; /* colorStatusWarningBackground1 */ + --bs-danger-bg-subtle: #fdf3f4; /* colorStatusDangerBackground1 */ + --bs-light-bg-subtle: #fafafa; + --bs-dark-bg-subtle: #e0e0e0; + + /* Border subtle — Fluent Border1/Stroke2 tokens */ + --bs-primary-border-subtle: #b4d6fa; /* colorBrandStroke2 (brand140) */ + --bs-secondary-border-subtle: #e0e0e0; + --bs-success-border-subtle: #9fd89f; /* colorStatusSuccessBorder1 */ + --bs-info-border-subtle: #a4deeb; /* colorPaletteCyanBorder1 */ + --bs-warning-border-subtle: #fdcfb4; /* colorStatusWarningBorder1 */ + --bs-danger-border-subtle: #eeacb2; /* colorStatusDangerBorder1 */ + --bs-light-border-subtle: #f0f0f0; + --bs-dark-border-subtle: #b3b3b3; + + /* BootstrapBlazor root variables */ + --bb-primary-color: #0f6cbd; + --bb-primary-color-rgb: 15,108,189; + --bb-disabled-bg: #f0f0f0; /* colorNeutralBackgroundDisabled */ + --bb-border-focus-color: #0f6cbd; /* colorCompoundBrandStroke */ + --bb-border-hover-color: #c7c7c7; /* colorNeutralStroke1Hover */ + --bb-shadow: 0 0 2px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.14); /* shadow4 */ + --bb-hover-shadow: 0 0 2px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.14); /* shadow8 */ +} + +/* ===== Dark theme ===== */ +[data-bs-theme='dark'] { + /* Text emphasis — Fluent dark foreground tokens */ + --bs-primary-text-emphasis: #479ef5; /* colorBrandForeground1 (brand100) */ + --bs-secondary-text-emphasis: #adadad; /* colorNeutralForeground3 (grey68) */ + --bs-success-text-emphasis: #54b054; /* colorStatusSuccessForeground1 (green tint30) */ + --bs-info-text-emphasis: #56bfd7; /* colorPaletteCyanForeground1 (cyan tint30) */ + --bs-warning-text-emphasis: #faa06b; /* colorStatusWarningForeground1 (orange tint30) */ + --bs-danger-text-emphasis: #dc626d; /* colorStatusDangerForeground1 (cranberry tint30) */ + --bs-light-text-emphasis: #f5f5f5; + --bs-dark-text-emphasis: #d6d6d6; + + /* Background subtle — Fluent dark shade40 tokens */ + --bs-primary-bg-subtle: #082338; /* colorBrandBackground2 (brand20) */ + --bs-secondary-bg-subtle: #1f1f1f; /* grey12 */ + --bs-success-bg-subtle: #052505; /* colorStatusSuccessBackground1 */ + --bs-info-bg-subtle: #002e38; /* colorPaletteCyanBackground1 */ + --bs-warning-bg-subtle: #4a1e04; /* colorStatusWarningBackground1 */ + --bs-danger-bg-subtle: #3b0509; /* colorStatusDangerBackground1 */ + --bs-light-bg-subtle: #3d3d3d; /* grey24 */ + --bs-dark-bg-subtle: #141414; /* grey8 */ + + /* Border subtle — Fluent dark stroke tokens */ + --bs-primary-border-subtle: #0e4775; /* colorBrandStroke2 (brand50) */ + --bs-secondary-border-subtle: #424242; /* grey26 */ + --bs-success-border-subtle: #094509; /* green shade30 */ + --bs-info-border-subtle: #005669; /* cyan shade30 */ + --bs-warning-border-subtle: #8a3707; /* orange shade30 */ + --bs-danger-border-subtle: #6e0811; /* cranberry shade30 */ + --bs-light-border-subtle: #525252; + --bs-dark-border-subtle: #3d3d3d; + + /* Body — colorNeutralForeground1 / colorNeutralBackground1 (dark) */ + --bs-body-color: #ffffff; + --bs-body-color-rgb: 255,255,255; + --bs-body-bg: #292929; /* grey16 */ + --bs-body-bg-rgb: 41,41,41; + + /* Links — colorBrandForegroundLink (dark) */ + --bs-link-color: #479ef5; /* brand100 */ + --bs-link-color-rgb: 71,158,245; + --bs-link-hover-color: #62abf5; /* brand110 */ + --bs-link-hover-color-rgb: 98,171,245; + + /* Borders & shadows — colorNeutralStroke1 & dark shadow tokens (ambient 0.24 / key 0.28) */ + --bs-border-color: #666666; /* grey40 */ + --bs-box-shadow-sm: 0 0 2px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.28); + --bs-box-shadow: 0 0 2px rgba(0,0,0,0.24), 0 8px 16px rgba(0,0,0,0.28); + --bs-box-shadow-lg: 0 0 8px rgba(0,0,0,0.24), 0 32px 64px rgba(0,0,0,0.28); + + /* BootstrapBlazor root variables */ + --bb-disabled-bg: #141414; /* colorNeutralBackgroundDisabled (grey8) */ + --bb-border-focus-color: #479ef5; /* colorCompoundBrandStroke (brand100) */ + --bb-border-hover-color: #757575; /* colorNeutralStroke1Hover (grey46) */ + --bb-shadow: 0 0 2px rgba(0,0,0,0.24), 0 2px 4px rgba(0,0,0,0.28); + --bb-hover-shadow: 0 0 2px rgba(0,0,0,0.24), 0 4px 8px rgba(0,0,0,0.28); +} + +/* ===== Component variable overrides — re-point hard-coded accent colors to theme tokens ===== */ + +.layout { + --bb-layout-header-background: var(--bs-primary); + --bb-layout-header-border-color: var(--bs-primary); + --bb-layout-headerbar-background: var(--bs-primary); + --bb-layout-sidebar-banner-background: var(--bs-primary); + --bb-layout-banner-border-color: var(--bs-primary); + --bb-layout-menu-user-banner-background: var(--bs-primary); + --bb-layout-menu-user-border-color: #115ea3; /* brand70 */ + --bb-layout-menu-item-hover-bg: var(--bs-primary); + --bb-layout-logo-bg: var(--bs-primary); +} + +.menu { + --bb-menu-active-color: var(--bs-primary); + --bb-menu-bar-bg: var(--bs-primary); + --bb-menu-item-hover-bg: var(--bs-primary); +} + +.tabs { + --bb-tabs-item-active-color: var(--bs-primary); + --bb-tabs-item-hover-color: var(--bs-primary); + --bb-tabs-bar-bg: var(--bs-primary); +} + +.calendar { + --bb-calendar-toolbar-hover-color: var(--bs-primary); + --bb-calendar-toolbar-focus-color: var(--bs-primary); + --bb-calendar-toolbar-active-color: var(--bs-primary); + --bb-calendar-today-color: var(--bs-primary); + --bb-calendar-today-border-color: var(--bs-primary); + --bb-calendar-selected-bg: var(--bs-primary); + --bb-calendar-week-today-color: var(--bs-primary); + --bb-calendar-week-today-border-color: var(--bs-primary); +} + +[data-bs-theme='dark'] .calendar { + --bb-calendar-selected-bg: var(--bs-primary); +} + +.picker-panel { + --bb-picker-hover-color: var(--bs-primary); + --bb-picker-panel-today-bg: var(--bs-primary); +} + +.bb-clock-picker { + --bb-time-text-color: var(--bs-primary); +} + +.table-container { + --bb-table-header-icon-color: rgba(var(--bs-body-color-rgb),.4); + --bb-table-header-icon-active-color: var(--bs-primary); + --bb-table-header-icon-hover-color: var(--bs-body-color); + --bb-table-header-icon-hover-bg: rgba(var(--bs-body-color-rgb),.1); + --bb-table-header-hover-bg: rgba(var(--bs-body-color-rgb),.08); +} + +.slide-button { + --bb-slide-list-bg: var(--bs-body-bg); + --bb-slide-item-active-bg: var(--bs-primary); +} + +.drawer { + --bb-drawer-bar-hover-color: var(--bs-primary); + --bb-drawer-bar-drag-color: var(--bs-primary); +} + +.filter-item { + --bb-filter-item-btn-hover-color: var(--bs-primary); + --bb-filter-item-btn-hover-border-color: var(--bs-primary); +} + +.listview { + --bb-lv-header-bg: var(--bs-secondary-bg); + --bb-lv-item-border-hover-color: var(--bs-primary); +} + +.dropdown-logout { + --bb-logout-menu-border-color: var(--bs-primary); + --bb-logout-user-bg: var(--bs-primary); +} + +.ribbon-tab { + --bb-ribbon-menu-hover-color: var(--bs-primary); + --bb-ribbon-button-hover-bg: rgba(var(--bs-primary-rgb),.24); + --bb-ribbon-button-hover-border-color: rgba(var(--bs-primary-rgb),.5); + --bb-ribbon-button-active-bg: rgba(var(--bs-primary-rgb),.32); + --bb-ribbon-button-active-border-color: rgba(var(--bs-primary-rgb),.6); +} + +.select, +.popover-dropdown { + --bb-dropdown-link-pre-active-bg: var(--bs-primary); +} + +.tree-view { + --bb-tree-item-hover-color: var(--bs-primary); + --bb-tree-drop-preview-color: var(--bs-primary); +} + +.upload { + --bb-upload-body-list-item-hover-color: var(--bs-primary); +} + +.bb-transfer { + --bb-transfer-filter-focus-border-color: var(--bs-primary); +} + +.layout-split-bar .layout-split-bar-body { + --bb-split-bar-body-drag-hover-bg: var(--bs-primary); +} + +.search.auto-complete { + --bb-search-prefix-icon-color: var(--bs-secondary-color); +} + +.layout-gotop { + --bb-gotop-bg: rgba(var(--bs-body-color-rgb),.08); + --bb-gotop-hover-bg: var(--bs-primary); + --bb-gotop-hover-color: #fff; +} + +.captcha { + --bb-captcha-bar-invalid-bg: var(--bs-danger); + --bb-captcha-bar-invalid-mask-bg: rgba(var(--bs-danger-rgb),.15); + --bb-captcha-bar-valid-bg: var(--bs-success); + --bb-captcha-bar-valid-mask-bg: rgba(var(--bs-success-rgb),.15); +} + +.switch { + --bb-switch-bg: #e0e0e0; /* colorNeutralStroke2 */ + --bb-switch-border-color: #c7c7c7; /* colorNeutralStroke1Hover */ +} + +[data-bs-theme='dark'] .switch { + --bb-switch-bg: #3d3d3d; /* grey24 */ + --bb-switch-border-color: #666666; /* colorNeutralStroke1 (dark) */ + --bb-switch-inner-off-text-color: #d6d6d6; +} + +/* ===== Buttons — colorBrandBackground / colorStatus*Background3 ===== */ +.btn-primary { + --bs-btn-bg: var(--bs-primary); + --bs-btn-border-color: var(--bs-primary); + --bs-btn-hover-bg: #115ea3; /* colorBrandBackgroundHover */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c3b5e; /* colorBrandBackgroundPressed */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-primary); + --bs-btn-disabled-border-color: var(--bs-primary); +} + +.btn-secondary { + --bs-btn-color: #424242; /* colorNeutralForeground2 */ + --bs-btn-bg: var(--bs-secondary); + --bs-btn-border-color: var(--bs-secondary); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #f5f5f5; /* colorNeutralBackground5Hover */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #f0f0f0; /* colorNeutralBackground5Pressed */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-secondary); + --bs-btn-disabled-border-color: var(--bs-secondary); +} + +.btn-success { + --bs-btn-bg: var(--bs-success); + --bs-btn-border-color: var(--bs-success); + --bs-btn-hover-bg: #0e700e; /* green shade10 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c5e0c; /* green shade20 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-success); + --bs-btn-disabled-border-color: var(--bs-success); +} + +.btn-info { + --bs-btn-color: #fff; + --bs-btn-bg: var(--bs-info); + --bs-btn-border-color: var(--bs-info); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #008aa9; /* cyan shade10 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #00748f; /* cyan shade20 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-info); + --bs-btn-disabled-border-color: var(--bs-info); +} + +.btn-warning { + --bs-btn-color: #fff; + --bs-btn-bg: var(--bs-warning); + --bs-btn-border-color: var(--bs-warning); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #de590b; /* orange shade10 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #bc4b09; /* orange shade20 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-warning); + --bs-btn-disabled-border-color: var(--bs-warning); +} + +.btn-danger { + --bs-btn-bg: var(--bs-danger); + --bs-btn-border-color: var(--bs-danger); + --bs-btn-hover-bg: #b10e1c; /* colorStatusDangerBackground3Hover */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #960b18; /* colorStatusDangerBackground3Pressed */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-danger); + --bs-btn-disabled-border-color: var(--bs-danger); +} + +.btn-dark { + --bs-btn-bg: var(--bs-dark); + --bs-btn-border-color: var(--bs-dark); + --bs-btn-hover-bg: #3d3d3d; /* grey24 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #1a1a1a; /* grey10 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-dark); + --bs-btn-disabled-border-color: var(--bs-dark); +} + +.btn-light { + --bs-btn-color: #242424; + --bs-btn-bg: var(--bs-light); + --bs-btn-border-color: var(--bs-light); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #f0f0f0; /* grey94 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #e0e0e0; /* grey88 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-light); + --bs-btn-disabled-border-color: var(--bs-light); +} + +.btn-link { + text-decoration: none; + --bs-btn-color: var(--bs-link-color); + --bs-btn-hover-color: var(--bs-link-hover-color); +} + +.btn-outline-primary { + --bs-btn-color: var(--bs-primary); + --bs-btn-border-color: var(--bs-primary); + --bs-btn-hover-bg: #115ea3; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c3b5e; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-primary); + --bs-btn-disabled-border-color: var(--bs-primary); +} + +.btn-outline-secondary { + --bs-btn-color: #616161; + --bs-btn-border-color: #d1d1d1; /* colorNeutralStroke1 */ + --bs-btn-hover-color: #424242; + --bs-btn-hover-bg: #f5f5f5; + --bs-btn-hover-border-color: #c7c7c7; /* colorNeutralStroke1Hover */ + --bs-btn-active-color: #424242; + --bs-btn-active-bg: #e0e0e0; + --bs-btn-active-border-color: var(--bs-btn-hover-border-color); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-border-color: var(--bs-btn-border-color); +} + +.btn-outline-success { + --bs-btn-color: var(--bs-success); + --bs-btn-border-color: var(--bs-success); + --bs-btn-hover-bg: #0e700e; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c5e0c; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-success); + --bs-btn-disabled-border-color: var(--bs-success); +} + +.btn-outline-info { + --bs-btn-color: var(--bs-info); + --bs-btn-border-color: var(--bs-info); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #008aa9; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #00748f; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-info); + --bs-btn-disabled-border-color: var(--bs-info); +} + +.btn-outline-warning { + --bs-btn-color: var(--bs-warning); + --bs-btn-border-color: var(--bs-warning); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #de590b; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #bc4b09; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-warning); + --bs-btn-disabled-border-color: var(--bs-warning); +} + +.btn-outline-danger { + --bs-btn-color: var(--bs-danger); + --bs-btn-border-color: var(--bs-danger); + --bs-btn-hover-bg: #b10e1c; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #960b18; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-danger); + --bs-btn-disabled-border-color: var(--bs-danger); +} + +.btn-outline-dark { + --bs-btn-color: var(--bs-dark); + --bs-btn-border-color: var(--bs-dark); + --bs-btn-hover-bg: #3d3d3d; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #1a1a1a; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-dark); + --bs-btn-disabled-border-color: var(--bs-dark); +} + +.btn-outline-light { + --bs-btn-color: var(--bs-light); + --bs-btn-border-color: var(--bs-light); + --bs-btn-hover-color: #242424; + --bs-btn-hover-bg: #f0f0f0; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #e0e0e0; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-light); + --bs-btn-disabled-border-color: var(--bs-light); +} + +/* ===== Table — Fluent DataGrid row hover (colorNeutralBackground1Hover) ===== */ +.table { + --bs-table-striped-bg: rgba(250, 250, 250, 0.75); + --bs-table-hover-bg: #f5f5f5; +} + +[data-bs-theme='dark'] .table { + --bs-table-striped-bg: rgba(255, 255, 255, 0.03); + --bs-table-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +/* ===== Modal — Fluent dialog shadow64 ===== */ +.modal-content { + box-shadow: 0 0 8px rgba(0,0,0,0.12), 0 32px 64px rgba(0,0,0,0.14); +} + +[data-bs-theme='dark'] .modal-content { + box-shadow: 0 0 8px rgba(0,0,0,0.24), 0 32px 64px rgba(0,0,0,0.28); +} + +/* ===== Validation — Fluent status strokes ===== */ +.form-control.is-valid, .was-validated .form-control:valid { + border-color: var(--bs-success); +} + +.was-validated .form-control:valid:focus, .form-control.is-valid:focus { + border-color: var(--bs-success); + box-shadow: none; +} + +.form-control.is-invalid, .was-validated .form-control:invalid { + border-color: var(--bs-danger); +} + +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { + border-color: var(--bs-danger); + box-shadow: none; +} From 56f8d339c099ace6ba83642e4c090d498a3b1d4f Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 20:30:55 +0800 Subject: [PATCH 02/20] docs(Theme): add Fluent UI theme to theme page --- .../Components/Pages/Theme.razor | 14 ++++++++++---- src/BootstrapBlazor.Server/Locales/en-US.json | 15 +++++++++------ src/BootstrapBlazor.Server/Locales/zh-CN.json | 15 +++++++++------ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/BootstrapBlazor.Server/Components/Pages/Theme.razor b/src/BootstrapBlazor.Server/Components/Pages/Theme.razor index 8523cb49829..70f2e989d3e 100644 --- a/src/BootstrapBlazor.Server/Components/Pages/Theme.razor +++ b/src/BootstrapBlazor.Server/Components/Pages/Theme.razor @@ -1,4 +1,4 @@ -@page "/theme" +@page "/theme" @inject IStringLocalizer Localizer

@Localizer["H1"]

@@ -9,6 +9,7 @@
  • @Localizer["P2"]
  • +
  • @Localizer["P11"]
  • @Localizer["P3"]
  • @Localizer["P4"]
  • @Localizer["P5"]
  • @@ -16,9 +17,14 @@

    @Localizer["P6"]

    -

    @Localizer["P7"] Motronic @Localizer["P8"] head @Localizer["P9"]:

    +

    @Localizer["P12"] _content/BootstrapBlazor/css/ @Localizer["P13"] head @Localizer["P14"]:

    <link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
     
    -// @Localizer["P10"]
    -<link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css">
    +// @Localizer["P15"] Motronic +<link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css"> + +// @Localizer["P15"] Fluent UI +<link rel="stylesheet" href="_content/BootstrapBlazor/css/fluent.min.css"> + +

    @Localizer["P16"] data-bs-theme @Localizer["P17"]

    diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 75adee79cce..15fce54f3ef 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -723,17 +723,20 @@ }, "BootstrapBlazor.Server.Components.Pages.Theme": { "H1": "Component theme", - "H2": "Test", + "H2": "Built-in Themes", "P1": "The current website theme supports the following", - "P10": "add this line", + "P11": "Fluent UI (integrated)", + "P12": "The built-in theme packs are located in the", + "P13": "directory. In the original", + "P14": "section, add the theme stylesheet after the base bundle", + "P15": "Enable", + "P16": "The Fluent UI theme adapts to both light and dark modes via the", + "P17": "attribute. You can preview it live via the theme button at the bottom-right corner of this site", "P2": "Motronic (integrated)", "P3": "LayUI (in progress)", "P4": "Bluma (in progress)", "P5": "Ant Design (in progress)", - "P6": "Theme switch", - "P7": "If switch to", - "P8": "theme, please use the original", - "P9": "The added style file is as follows" + "P6": "Theme switch" }, "BootstrapBlazor.Server.Components.Samples.Affixs": { "AffixIntro": "Fix the element to a specific visible area", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index c2433f83b3f..78806b94375 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -723,17 +723,20 @@ }, "BootstrapBlazor.Server.Components.Pages.Theme": { "H1": "组件主题", - "H2": "测试", + "H2": "内置主题", "P1": "目前网站主题支持如下", - "P10": "增加此行", + "P11": "Fluent UI (已集成)", + "P12": "内置主题包位于", + "P13": "目录,在原有", + "P14": "内、基础样式之后增加对应的主题样式文件即可", + "P15": "启用", + "P16": "Fluent UI 主题已适配明暗两种模式,随", + "P17": "属性自动切换,可点击本站右下角主题按钮实时预览效果", "P2": "Motronic (已集成)", "P3": "LayUI (完善中)", "P4": "Bluma (完善中)", "P5": "Ant Design (完善中)", - "P6": "主题切换", - "P7": "如切换为", - "P8": "主题,则请在原有", - "P9": "内增加样式文件如下" + "P6": "主题切换" }, "BootstrapBlazor.Server.Components.Samples.Affixs": { "AffixIntro": "将页面元素钉在可视范围", From e8e8efcb261a6c35b25b4f3d0e3b708a9c8f6c35 Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 21:16:22 +0800 Subject: [PATCH 03/20] feat(Theme): adapt flyout surfaces to Fluent UI style --- .../wwwroot/css/fluent.min.css | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/BootstrapBlazor/wwwroot/css/fluent.min.css b/src/BootstrapBlazor/wwwroot/css/fluent.min.css index 590fa3a3f5d..e83ad1e3f7a 100644 --- a/src/BootstrapBlazor/wwwroot/css/fluent.min.css +++ b/src/BootstrapBlazor/wwwroot/css/fluent.min.css @@ -555,3 +555,109 @@ border-color: var(--bs-danger); box-shadow: none; } + +/* ===== Flyout surfaces — Fluent Menu / Popover / Tooltip ===== + * Native Fluent flyouts: no arrow, borderRadiusMedium (4px), + * shadow16, colorNeutralStroke1 border, neutral item hover/selected. + * Covers: Select, MultiSelect, SelectTree/Table/Object, Cascader, + * AutoComplete, AutoFill, Search, Dropdown, TopMenu, Logout, Tab, + * Table toolbars and filter buttons. + */ +.dropdown-menu { + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-border-color: var(--bs-border-color); + --bs-dropdown-border-radius: var(--bs-border-radius); /* 4px */ + --bs-dropdown-box-shadow: var(--bs-box-shadow); /* shadow16 */ + --bs-dropdown-divider-bg: #e0e0e0; /* colorNeutralStroke2 */ + --bs-dropdown-padding-x: 4px; + --bs-dropdown-padding-y: 4px; + --bs-dropdown-item-padding-x: 12px; + --bs-dropdown-item-padding-y: 6px; + --bs-dropdown-link-hover-color: var(--bs-body-color); + --bs-dropdown-link-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bs-dropdown-link-active-color: var(--bs-body-color); + --bs-dropdown-link-active-bg: #ebebeb; /* colorNeutralBackground1Selected */ + --bs-dropdown-link-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ + --bs-dropdown-header-color: #616161; /* colorNeutralForeground3 */ + --bb-select-search-border-color: #e0e0e0; +} + +/* Fluent menus have no arrow */ +.dropdown-menu-arrow { + display: none !important; +} + +/* Fluent flyout offset: 4px */ +.cascade, +.select { + --bb-select-dropdown-menu-margin-top: 4px; +} + +/* Selected option — Fluent Listbox: reserved gutter + brand check mark */ +.select:not(.multi-select) .dropdown-menu .dropdown-item { + position: relative; + padding-inline-start: 28px; +} + +.select:not(.multi-select) .dropdown-menu .dropdown-item.active::before { + content: "\2713"; + position: absolute; + inset-inline-start: 10px; + color: var(--bs-primary); /* colorCompoundBrandForeground */ +} + +/* Popover card — Fluent Popover keeps its arrow, radius 4px, shadow16 */ +.popover { + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-color: var(--bs-body-color); + --bs-popover-border-color: var(--bs-border-color); + --bs-popover-border-radius: var(--bs-border-radius); + --bs-popover-box-shadow: var(--bs-box-shadow); + --bs-popover-header-bg: transparent; + --bs-popover-header-color: var(--bs-body-color); + --bs-popover-header-padding-x: 12px; + --bs-popover-header-padding-y: 10px; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-body-padding-x: 12px; + --bs-popover-body-padding-y: 10px; +} + +.popover-header { + font-weight: 600; + border-block-end: none; + padding-block-end: 0; +} + +/* Picker panels (DateTimePicker/DateTimeRange/SelectObject/SelectTable/filters) + * are menu-like flyouts in Fluent — no arrow */ +.popover-dropdown .popover-arrow { + display: none; +} + +/* Tooltip — Fluent dark surface, radius 4px, no arrow */ +.tooltip { + --bs-tooltip-bg: #242424; /* grey14 */ + --bs-tooltip-color: #ffffff; + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-padding-x: 8px; + --bs-tooltip-padding-y: 4px; + --bs-tooltip-opacity: 1; +} + +.tooltip .tooltip-arrow { + display: none; +} + +[data-bs-theme='dark'] .dropdown-menu { + --bs-dropdown-divider-bg: #4d4d4d; /* colorNeutralStroke2 (dark) */ + --bs-dropdown-link-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ + --bs-dropdown-link-active-bg: #3d3d3d; /* colorNeutralBackground1Selected (dark) */ + --bs-dropdown-link-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ + --bs-dropdown-header-color: #adadad; /* colorNeutralForeground3 (dark) */ + --bb-select-search-border-color: #4d4d4d; +} + +[data-bs-theme='dark'] .tooltip .tooltip-inner { + border: var(--bs-border-width) solid var(--bs-border-color); +} From 7ea0dd6a216b1a079f3eb8c74a1ee812c7f61af0 Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 21:16:22 +0800 Subject: [PATCH 04/20] docs: add Fluent UI parity review report --- docs/fluentui-parity-review.md | 401 +++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 docs/fluentui-parity-review.md diff --git a/docs/fluentui-parity-review.md b/docs/fluentui-parity-review.md new file mode 100644 index 00000000000..fb20fb92bde --- /dev/null +++ b/docs/fluentui-parity-review.md @@ -0,0 +1,401 @@ +# BootstrapBlazor × Fluent UI 审查报告 + +本文档合并三份审查结果: + +- **第一部分:组件匹配度审查**——全组件库(`src/BootstrapBlazor/Components/` 120 个组件目录)与微软 Fluent UI Blazor 的组件覆盖对照,回答"组件层面对得上多少"。 +- **第二部分:Fluent UI 主题完善计划**——针对 `lee/feat-fluent-ui-theme` 分支的主题审查结论,回答"主题层面对得上多少、如何补齐"。 +- **第三部分:弹出层(Flyout)差异统计与 Fluent 适配**——Select 等组件的弹出层与原生 Fluent 的形态差异盘点,及已落地的 CSS 适配。 + +两部分的关联:第一部分指出的语义错位(Badge/CounterBadge、Anchor 等)同样适用于第二部分第一阶段的 token 对齐工作——对照 Fluent 官方 token 时需按语义而非名字匹配。 + +--- + +## 第一部分:组件匹配度审查 + +### 审查范围与方法 + +- **本库侧**:`src/BootstrapBlazor/Components/` 下全部 120 个组件目录(含其中的子组件,如 `Button/` 下的 `PopConfirmButton`、`DialButton` 等),逐一核对实际组件类。 +- **Fluent 侧**:以微软官方 Blazor 组件库 [microsoft/fluentui-blazor](https://github.com/microsoft/fluentui-blazor)(dev 分支,`src/Core/Components`,约 66 个面向用户的组件)为基准。"Fluent UI" 在此指该 Blazor 组件库,而非 React 版 Fluent UI。 +- **匹配等级**: + - ✅ 直接对应:功能定位一致,可平替 + - 🟡 部分对应:概念相近但能力/粒度/语义有差异,需要改造或组合实现 + - ❌ 无对应:对方没有同类组件 +- 卫星扩展包(`src/Extensions/Components/` 下 Chart、Markdown、DockView、Gantt 等 39 个独立 NuGet 包)不计入主表,见本部分附录。 + +### 总体结论 + +以本库全部 119 个统计项为基准: + +| 匹配度 | 数量 | 占比 | +|---|---|---| +| ✅ 直接对应 | 44 | 37% | +| 🟡 部分对应 | 19 | 16% | +| ❌ 无对应(本库独有/Fluent 缺口) | 56 | 47% | + +反向以 Fluent UI Blazor 约 66 个用户组件为基准: + +| 本库覆盖情况 | 数量 | 占比 | +|---|---|---| +| ✅ 有对应 | ~42 | 64% | +| 🟡 可组合/近似实现 | ~10 | 15% | +| ❌ 本库缺失 | ~14 | 21% | + +**解读**:两套库的"核心 60%"(布局、基础表单、表格、弹窗、反馈组件)高度重合,可以互相迁移;差异主要在两端的特色组件上——本库偏企业应用/国产化场景(Transfer、Cascader、QueryBuilder、Captcha、水印、IP 定位等),Fluent 偏设计系统一致性(设计令牌、NavMenu、Wizard、SortableList、Persona 等)。本库的 ❌ 大多是"本库有、Fluent 没有",即功能广度上本库明显更大;真正的反向缺口(Fluent 有、本库没有)只有约 14 个。 + +### 详细对照表 + +#### 1. 布局组件 + +| BootstrapBlazor | Fluent UI Blazor | 匹配 | 说明 | +|---|---|---|---| +| Layout / Header / Footer / Side / Main / LayoutSplitBar | FluentMainLayout / FluentLayout / FluentHeader / FluentFooter / FluentMain / FluentBodyContent | ✅ | 一一对应 | +| Row(栅格) | FluentGrid / FluentGridItem | ✅ | 同为 12/24 栅格思路 | +| Stack | FluentStack | ✅ | | +| Split | FluentSplitter / FluentMultiSplitter | ✅ | | +| Toolbar / ToolbarSpace | FluentToolbar / FluentSpacer | ✅ | | +| Waterfall(瀑布流) | — | ❌ | Fluent 无 | +| GroupBox | — | ❌ | 可用 Card/fieldset 替代 | +| Scroll(自定义滚动条) | — | ❌ | | + +#### 2. 导航组件 + +| BootstrapBlazor | Fluent UI Blazor | 匹配 | 说明 | +|---|---|---|---| +| Menu / SideMenu / SubMenu / MenuLink | FluentNavMenu / FluentNavLink / FluentNavGroup / FluentNavMenuTree | ✅ | 侧边导航完全对应 | +| TopMenu(横向菜单) | — | 🟡 | FluentNavMenu 无横向模式,需自建 | +| Dropdown(菜单场景) | FluentMenu / FluentMenuButton | ✅ | | +| ContextMenu | FluentMenu | 🟡 | Fluent 无专用右键菜单,可用 Menu+定位组合 | +| Breadcrumb | FluentBreadcrumb | ✅ | | +| Tab / TabLink | FluentTabs / FluentTab | ✅ | | +| Nav | — | 🟡 | 纯样式化导航链接组,Fluent 用 Tabs/NavMenu 代替 | +| Navbar | — | ❌ | Fluent 无导航条组件 | +| Anchor / AnchorLink | FluentAnchor | 🟡 | 语义不同:本库是页面锚点滚动导航,FluentAnchor 只是超链接样式 | +| Pagination / GotoNavigator | FluentPaginator | 🟡 | FluentPaginator 依附 DataGrid 设计,独立分页能力弱 | +| Step(步骤条) | FluentWizard | 🟡 | FluentWizard 是带内容联动的向导;本库 Step 仅展示步骤 | +| RibbonTab | — | ❌ | | + +#### 3. 表单组件 + +| BootstrapBlazor | Fluent UI Blazor | 匹配 | 说明 | +|---|---|---|---| +| BootstrapInput | FluentTextField | ✅ | | +| BootstrapInputGroup | — | 🟡 | FluentTextField 有 start/end 插槽可部分实现 | +| FloatingLabel | — | ❌ | Fluent 采用固定顶部标签 | +| OtpInput | — | ❌ | | +| Textarea | FluentTextArea | ✅ | | +| InputNumber | FluentNumberField | ✅ | | +| Search | FluentSearch | ✅ | | +| AutoComplete | FluentAutocomplete | ✅ | | +| Select / SelectGeneric | FluentSelect / FluentListbox / FluentCombobox | ✅ | | +| MultiSelect / MultiSelectGeneric | FluentAutocomplete(多选模式) | ✅ | | +| SelectTree / SelectTable / SelectObject | — | ❌ | 树/表格/对象选择器是 Fluent 空白 | +| Cascader | — | ❌ | 级联选择 Fluent 无 | +| Checkbox | FluentCheckbox | ✅ | | +| CheckboxList | — | 🟡 | Fluent 无列表式复选组,需循环组合 | +| Radio / RadioList | FluentRadio / FluentRadioGroup | ✅ | | +| Switch | FluentSwitch | ✅ | | +| NullSwitch(三态) | FluentCheckbox(indeterminate) | 🟡 | | +| Toggle | FluentSwitch | 🟡 | 语义相近、形态不同 | +| Slider | FluentSlider | ✅ | | +| Rate | FluentRating | ✅ | | +| DateTimePicker | FluentDatePicker + FluentTimePicker | ✅ | 本库日期时间一体,Fluent 拆分两个组件 | +| TimePicker | FluentTimePicker | ✅ | | +| ClockPicker(模拟表盘) | — | ❌ | | +| DateTimeRange | — | ❌ | Fluent 知名的长期缺口 | +| ColorPicker | — | ❌ | Fluent 同样没有 | +| Upload 族(Button/Input/Avatar/Card/DropUpload) | FluentInputFile | ✅ | FluentInputFile 支持拖拽与预览;本库形态更多 | +| AutoFill | — | ❌ | | +| Captcha | — | ❌ | | +| IpAddress | — | ❌ | | +| Handwritten(手写签名) | — | ❌ | | +| ValidateForm / EditorForm | FluentEditForm / FluentValidationMessage / FluentValidationSummary / FluentField | 🟡 | 验证能力对等;但本库可按模型自动生成编辑器,Fluent 需手写表单 | +| SearchForm | — | ❌ | | +| QueryBuilder | — | ❌ | | +| Display | — | 🟡 | 只读展示可用只读输入框/文本替代 | +| Segmented | — | ❌ | | +| Transfer | — | ❌ | | +| Button | FluentButton | ✅ | | +| PopConfirmButton | Fluent MessageBox(服务) | 🟡 | 气泡确认 vs 模态确认 | +| DialButton / PulseButton / SlideButton | — | ❌ | | +| SwitchButton / ToggleButton | — | 🟡 | 可用 FluentSwitch 或自建 | + +#### 4. 数据展示组件 + +| BootstrapBlazor | Fluent UI Blazor | 匹配 | 说明 | +|---|---|---|---| +| Table | FluentDataGrid | ✅ | 双方旗舰组件;本库内置 CRUD/工具栏/筛选/导出,FluentDataGrid 更轻量、周边能力需自建 | +| Tree / TreeView | FluentTreeView / FluentTreeItem | ✅ | | +| Calendar | FluentCalendar | ✅ | | +| Card | FluentCard | ✅ | | +| Collapse | FluentAccordion / FluentCollapsibleRegion | ✅ | | +| Skeleton 族(Avatar/Paragraph/Table/Tree/Editor) | FluentSkeleton | ✅ | 本库预设形态更多 | +| Badge(计数角标) | FluentCounterBadge | ✅ | 注意:两侧"Badge"语义错位,见下 | +| Tag | FluentBadge | 🟡 | 本库 Tag ≈ FluentBadge;本库 Badge ≈ FluentCounterBadge | +| ShieldBadge(缎带徽章) | — | ❌ | | +| Avatar | FluentPersona | 🟡 | Fluent 无独立 Avatar,Persona = 头像+姓名+状态 | +| ListView | — | ❌ | | +| ListGroup | — | ❌ | | +| Timeline | — | ❌ | | +| Empty | — | ❌ | | +| Carousel | — | ❌ | FluentFlipper 只是翻页箭头元件 | +| ImageViewer / ImagePreviewer | — | ❌ | | +| FileIcon | — | ❌ | | +| Console | — | ❌ | | +| CountUp | — | ❌ | | +| FlipClock | — | ❌ | | +| Marquee | — | ❌ | | +| Typed(打字机) | — | ❌ | | +| Light(指示灯) | — | ❌ | | +| Watermark | — | ❌ | | +| SpeechWave | — | ❌ | | + +#### 5. 反馈组件 + +| BootstrapBlazor | Fluent UI Blazor | 匹配 | 说明 | +|---|---|---|---| +| Alert | FluentMessageBar | ✅ | | +| Message(全局消息) | FluentMessageBarProvider + MessageService | ✅ | | +| Toast | FluentToast / FluentToastProvider / ToastService | ✅ | Fluent 另有 Communication/Confirmation/Progress 三类预置 Toast | +| Modal / ModalDialog | FluentDialog | ✅ | | +| Dialog 服务族(EditDialog / SearchDialog / ResultDialog) | Fluent DialogService | ✅ | 本库内置编辑/搜索/结果对话框模板,Fluent 需自建内容组件 | +| SweetAlert | Fluent MessageBox | ✅ | | +| Popover | FluentPopover | ✅ | | +| Tooltip | FluentTooltip | ✅ | | +| Progress | FluentProgress | ✅ | | +| Circle(环形进度) | FluentProgressRing | ✅ | | +| Spinner | FluentProgressRing(indeterminate) | 🟡 | | +| Mask | FluentOverlay | ✅ | | +| Drawer | — | ❌ | Fluent 无抽屉组件 | + +#### 6. 工具 / 服务 / 其他 + +| BootstrapBlazor | Fluent UI Blazor | 匹配 | 说明 | +|---|---|---|---| +| Icon / SvgIcon / BootstrapBlazorIcon | FluentIcon | ✅ | Fluent 图标体系(Fluent System Icons)是独立资产包 | +| ThemeProvider | FluentDesignTheme / FluentDesignSystemProvider | 🟡 | 都能切明暗主题;Fluent 的设计令牌体系更完整 | +| Dropzone(DragDrap) | FluentDragContainer / FluentDropZone | 🟡 | 都是拖放容器,交互模型不同 | +| Logout / LogoutLink | FluentProfileMenu | 🟡 | | +| FullScreen / GoTop / Affix / Transition | — | ❌ | | +| Clipboard / Download / Print | — | ❌ | 浏览器工具服务 | +| Geolocation / IPLocator / NetworkMonitor | — | ❌ | 设备/网络服务 | +| Camera | — | ❌ | | +| WebSpeech / Speech 服务 | — | ❌ | | +| Title | — | ❌ | Blazor 内置 PageTitle 可替代 | +| Timer / Block / Reconnector / ErrorLogger | — | ❌ | 框架层设施 | +| Redirect / AutoRedirect | — | ❌ | | +| IntersectionObserver / LoadMore / LazyLoad | — | ❌ | | +| Ajax / ConnectionHub | — | ❌ | | +| IFrame | — | ❌ | | + +### Fluent UI Blazor 有、本库缺失的组件(反向缺口,约 14 项) + +| Fluent UI Blazor | 本库现状 | 说明 | +|---|---|---| +| FluentWizard / FluentWizardStep | 🟡 Step 仅步骤展示 | 无"步骤+内容联动+上一步/下一步"的向导容器 | +| FluentSortableList | ❌ 仅有 `ISortableList` 接口 | 本库没有独立的可排序列表组件(排序能力集成在 Table 内) | +| FluentAppBar / FluentAppBarItem | ❌ | 移动风格底部应用栏 | +| FluentAnchoredRegion | ❌ | 锚点定位容器(本库 Anchor 是另一概念) | +| FluentHorizontalScroll | ❌ | | +| FluentOverflow / FluentOverflowItem | ❌ | 空间不足自动折叠到溢出菜单 | +| FluentHighlighter | ❌ | 搜索关键字高亮 | +| FluentKeyCode / FluentKeyCodeProvider | ❌ | 全局快捷键 | +| FluentPullToRefresh | ❌ | 移动端下拉刷新 | +| FluentPersona | 🟡 Avatar 近似 | 头像+姓名+在线状态一体 | +| FluentPresenceBadge | ❌ | 在线状态徽标 | +| FluentProfileMenu | 🟡 Logout 近似 | 用户资料菜单 | +| FluentEmoji | ❌ | Fluent Emoji 资产组件 | +| FluentSplashScreen / FluentAccessibilityStatus / FluentPageScript | ❌ | 启动屏/无障碍状态/页脚本,多为模板与框架层设施 | + +### 结论要点 + +1. **核心组件高度重合**:布局、基础输入、选择器、日期时间、表格、树、选项卡、对话框、Toast、Tooltip 等约 44 项可一一平替,占本库组件的 37%;加上可组合实现的 19 项,超过一半的组件在 Fluent 侧有着落。 +2. **两库的设计取向不同**:本库是"全家桶"——表格自带 CRUD/筛选/导出、表单可自动生成、大量企业场景组件(Transfer、Cascader、QueryBuilder、Captcha、Handwritten、Watermark)开箱即用;Fluent UI Blazor 走"设计系统 + 轻内核"路线,能力依赖 Fluent 设计令牌与自行组合。 +3. **若从本库迁往 Fluent,主要障碍**(Fluent 无对应且常用):DateTimeRange、Drawer、Transfer、Cascader、Segmented、Timeline、Empty、Carousel、ImageViewer、Upload 多形态预览、EditorForm 自动生成表单、Table 内置 CRUD 工具链。 +4. **若从 Fluent 迁往本库,缺口很小**:真正需要补的只有 Wizard 向导容器、SortableList 独立组件、Overflow、HorizontalScroll、Highlighter、KeyCode、AppBar、PullToRefresh 等约 14 项,且多数是边缘场景。 +5. **注意语义错位**:`Badge`(本库=计数角标 ↔ FluentCounterBadge;本库 Tag ↔ FluentBadge)、`Anchor`(本库=滚动锚点导航 ↔ FluentAnchor=超链接样式),迁移时需按语义而非名字对应。 + +### 附:卫星扩展包(未计入主表) + +`src/Extensions/Components/` 下另有 39 个独立组件/服务包:Chart、Markdown、CherryMarkdown、SummerNote、CodeEditor、Dock/DockView、Gantt、Player、Topology、WinBox、BarCode、MouseFollower、Live2DDisplay、Html2Pdf、SVGEditor、DriverJs、Splitting、FloatingUI、TableExport、Holiday、各图标包(FontAwesome/Bootstrap/MaterialDesign/AntDesign/Element/IconPark)、Azure/Baidu AI 服务包等。Fluent 侧对应的补充是官方图标/Emoji 资产包与 DataGrid 生态,这些扩展包在 Fluent 侧基本全部无对应,属于本库独有生态。 + +*数据来源:本仓库 `src/BootstrapBlazor/Components/` 目录实测;[fluentui-blazor dev 分支组件目录](https://github.com/microsoft/fluentui-blazor/tree/dev/src/Core/Components)(GitHub API,2026-08 抓取)。* + +--- + +## 第二部分:Fluent UI 主题完善计划 + +> 针对 `lee/feat-fluent-ui-theme` 分支(即当前工作分支)。现状核对(2026-08,已实测): +> +> - 组件库主题文件:`src/BootstrapBlazor/wwwroot/css/fluent.min.css`、`src/BootstrapBlazor/wwwroot/css/motronic.min.css` +> - 官网未压缩源文件:`src/BootstrapBlazor.Server/wwwroot/css/fluent.css`(其文件头注明主题本体随组件库分发) +> - Motronic 目前确为全局固定加载:`src/BootstrapBlazor.Server/Components/App.razor:28` +> - Fluent 主题已在站点主题列表中注册:`src/BootstrapBlazor.Server/appsettings.json:120-121` +> +> 以下计划内容按原审查结论收录。 + +### 目标 + +完善 `lee/feat-fluent-ui-theme` 分支,使 Fluent UI 主题具备完整的明暗模式支持、稳定的主题切换机制和可复用的发布质量。 + +### 第一阶段:补齐主题 Token + +- 补充 Fluent light/dark 模式下的 Bootstrap 语义变量: + - `--bs-emphasis-color` + - `--bs-secondary-color` + - `--bs-tertiary-color` + - `--bs-secondary-bg` + - `--bs-tertiary-bg` + - 对应 RGB 变量 +- 统一正文、边框、链接、禁用态、焦点态和阴影颜色。 +- 检查 Fluent token 与 Microsoft Fluent UI 官方 token 的对应关系。 + +验收标准:常用 BootstrapBlazor 组件不再出现 Bootstrap 默认灰色或 Motronic 残留色。 + +### 第二阶段:调整主题加载机制 + +- 将 `motronic.min.css` 从全局固定加载改为可选主题(现状:固定加载于 `src/BootstrapBlazor.Server/Components/App.razor:28`)。 +- 保留 Bootstrap 基础 CSS 作为唯一固定样式。 +- 主题切换时只加载当前主题 CSS。 +- 确保切换 Fluent、Motronic、Bootstrap 时不会残留上一个主题的变量。 + +验收标准:主题之间可以互相切换,页面刷新后样式一致,没有旧主题颜色残留。 + +### 第三阶段:完善组件覆盖 + +按组件类别逐项检查和补充: + +- Layout、Menu、Tabs +- Button、Input、Form、Validation +- Table、ListView、TreeView +- Modal、Drawer、Popover +- Calendar、Picker、Upload +- Switch、Captcha、Transfer +- Hover、Focus、Active、Disabled 状态 + +验收标准:所有核心组件的默认态、悬浮态、激活态、禁用态和校验态均符合 Fluent UI 风格。 + +### 第四阶段:建设主题预览与测试 + +- 扩展主题页面,增加核心组件展示。 +- 分别验证: + - Fluent Light + - Fluent Dark + - Bootstrap + - Motronic +- 增加 CSS 变量检查或基础视觉回归测试。 +- 验证移动端主题选择器和滚动行为。 + +验收标准:主题页面可以覆盖主要组件和状态,明暗模式切换无明显布局或颜色问题。 + +### 第五阶段:整理发布文件 + +- 将 `fluent.min.css` 改为真正压缩产物。 +- 保留未压缩的 `fluent.css` 作为源码版本(现状:未压缩版在 `src/BootstrapBlazor.Server/wwwroot/css/fluent.css`,压缩版随组件库分发)。 +- 明确组件库主题 CSS 与官网专用 CSS 的边界。 +- 更新主题使用文档,说明加载顺序和 `data-bs-theme` 用法。 + +验收标准:主题可以独立复制到其他 BootstrapBlazor 项目使用,文档示例可直接运行。 + +### 第六阶段:最终验证 + +执行以下检查: + +```powershell +git diff --check +dotnet build +dotnet test +``` + +并手动验证: + +- 主题切换 +- 明暗模式切换 +- 页面刷新 +- 移动端显示 +- 主题选择器展开、滚动、关闭 +- 组件库静态资源路径 +- NuGet 包内是否包含 `fluent.min.css` + +### 建议优先级 + +1. 补齐语义变量,解决颜色混用问题。 +2. 修正 Motronic 永久加载问题。 +3. 完善核心组件覆盖。 +4. 增加主题预览和视觉验证。 +5. 最后处理压缩、文档和发布整理。 + + +--- + +## 第三部分:弹出层(Flyout)差异统计与 Fluent 适配 + +BootstrapBlazor 的弹出层基于 Bootstrap 的 `.dropdown-menu` / `.popover` / `.tooltip` 三种原语,与原生 Fluent 的 Menu / Popover / Tooltip 形态不同。本部分盘点全部弹出组件的差异,并记录已落地的适配。 + +### 弹出原语差异总览 + +| 原语 | BootstrapBlazor(Bootstrap 风格) | 原生 Fluent UI | 核心差异 | +|---|---|---|---| +| `.dropdown-menu` 菜单浮层 | 可带小三角箭头(`.dropdown-menu-arrow`)、圆角偏大、选中项蓝底白字 | Menu/Listbox 浮层:**无箭头**、圆角 4px(borderRadiusMedium)、shadow16、1px 中性描边、选中项左侧 ✓(品牌色)+ 中性灰底 | 箭头、圆角、阴影、选中态、内边距 | +| `.popover` 卡片浮层 | 带箭头、圆角偏大、头部有分隔线 | Popover(v9):**保留箭头**、圆角 4px、shadow16、标题半粗无分隔线 | 圆角、阴影、头部分隔线 | +| `.popover-dropdown` 面板容器(picker 类) | 带箭头 | DatePicker 等面板浮层在 Fluent 中属 Menu 类:**无箭头** | 箭头 | +| `.tooltip` 提示 | 带箭头、黑色底、半透明 | Tooltip:**无箭头**、深灰面(grey14)、圆角 4px、不透明 | 箭头、背景、不透明度 | + +### 弹出层组件逐项统计 + +**A. `.dropdown-menu` 系(菜单浮层)** + +| 组件 | 箭头 | 备注 | +|---|---|---| +| Select / SelectGeneric | 有(`.dropdown-menu-arrow`) | 选中项 `.dropdown-item.active` 蓝底;支持搜索框 | +| MultiSelect / MultiSelectGeneric | 无 | 项内自带复选框,无需 ✓ 适配 | +| SelectTree | 有 | 树形下拉 | +| Cascader | 有 | 级联面板,项带右侧子菜单箭头 | +| SelectTable / SelectObject | 无(dropdown 模式)/ 走 popover 模式 | 面板宽度自定义 | +| AutoComplete / AutoFill / Search | 无 | 候选列表 | +| Dropdown / DropdownWidget | 无 | 通用下拉菜单 | +| TopMenu(SubMenu) | 无 | 顶部菜单下拉 | +| Logout | 无 | 用户菜单 | +| Tab(溢出菜单) | 无 | | +| Table 工具栏 / TableToolbar / FilterButton | 无 | 表格列菜单、筛选面板 | + +**B. `.popover` 系(卡片浮层)** + +| 组件 | 原生 Fluent 对应 | 箭头处理 | +|---|---|---| +| Popover | FluentPopover(有箭头) | 保留 | +| PopConfirmButton | Fluent Popover 确认 / MessageBox | 保留 | +| DateTimePicker / DateTimeRange(`.picker-panel`,经 `popover-dropdown` 容器弹出) | FluentDatePicker 日历浮层(无箭头) | 隐藏 | +| SelectObject / SelectTable(popover 模式) | 无直接对应 | 隐藏 | +| TableColumnFilter(`popover-dropdown`) | FluentDataGrid 列筛选浮层(无箭头) | 隐藏 | + +**C. `.tooltip` 系** + +| 组件/使用处 | 适配 | +|---|---| +| Tooltip 组件、GoTop、Layout 侧栏折叠按钮、BootstrapLabel 标签提示 | 去箭头、深灰面、圆角 4px | + +**D. 不纳入适配** + +| 组件 | 原因 | +|---|---| +| ColorPicker | 使用浏览器原生 `` 弹层,无法用 CSS 主题化;Fluent 侧也无对应组件 | +| IpAddress | 纯行内输入框,无浮层 | +| Modal / Dialog / Drawer / SweetAlert | 属对话框层面,Fluent 无菜单式浮层差异;Modal 已适配 shadow64 | + +### 已落地的适配(`src/BootstrapBlazor/wwwroot/css/fluent.min.css` 新增 "Flyout surfaces" 段) + +| 适配项 | 实现 | Fluent token 来源 | +|---|---|---| +| 浮层面板 | `--bs-dropdown-bg/border-color/border-radius(4px)/box-shadow` 重指向主题变量 | colorNeutralBackground1、colorNeutralStroke1、borderRadiusMedium、shadow16 | +| 去除菜单箭头 | `.dropdown-menu-arrow { display: none }`;`.popover-dropdown .popover-arrow` 隐藏 | Fluent Menu 无箭头 | +| 浮层间距 | `--bb-select-dropdown-menu-margin-top: 8px → 4px` | spacingVerticalXS | +| 菜单项 | padding 4px 容器 / 6px 12px 项;hover `#f5f5f5`、选中 `#ebebeb`(dark 均 `#3d3d3d`) | colorNeutralBackground1Hover / Selected | +| 选中项 ✓ | `.select:not(.multi-select)` 项预留 28px 左槽,`::before` 品牌色 ✓(绝对定位不影响布局) | Fluent Listbox Option 选中态 | +| Popover 卡片 | 圆角 4px、shadow16、标题半粗去分隔线(箭头保留) | Fluent Popover v9 | +| Tooltip | 去箭头、`#242424` 深灰面、不透明、圆角 4px | Fluent Tooltip | +| 菜单内搜索框分隔线 | `--bb-select-search-border-color` 指向中性描边 | colorNeutralStroke2 | + +### 待验证(建议并入第二部分第四阶段) + +- 站点主题页切至 Fluent,逐项打开上述弹出组件,核对明暗两种模式。 +- 重点核对:Select 选中 ✓ 与分组标题(Divider)的混排、SelectTable/SelectObject 宽面板的定位、TableColumnFilter 去箭头后的偏移、Tooltip 在 dark 模式下的描边可读性。 From bfbfc535167183343a41cd7a779740aaf4caa5c3 Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 22:56:41 +0800 Subject: [PATCH 05/20] feat(Theme): complete Fluent UI theme tokens and component coverage --- src/BootstrapBlazor/wwwroot/css/fluent.css | 1871 +++++++++++++++++ .../wwwroot/css/fluent.min.css | 654 +----- 2 files changed, 1872 insertions(+), 653 deletions(-) create mode 100644 src/BootstrapBlazor/wwwroot/css/fluent.css diff --git a/src/BootstrapBlazor/wwwroot/css/fluent.css b/src/BootstrapBlazor/wwwroot/css/fluent.css new file mode 100644 index 00000000000..3f18d23ab4c --- /dev/null +++ b/src/BootstrapBlazor/wwwroot/css/fluent.css @@ -0,0 +1,1871 @@ +/* + * Fluent UI Web theme for BootstrapBlazor + * Ported from Microsoft Fluent UI (Fluent 2) design tokens + * Source: https://github.com/microsoft/fluentui + * @fluentui/tokens webLightTheme / webDarkTheme (brandWeb ramp #0f6cbd) + * packages/tokens/src/themes/web, global/brandColors.ts, alias/lightColor.ts, alias/darkColor.ts + * + * Usage: add after bootstrap.blazor.bundle.min.css + * + */ + +/* ===== Light theme ===== */ +:root, [data-bs-theme='light'] { + /* Neutral colors — Fluent global grey ramp */ + --bs-gray: #616161; + --bs-gray-dark: #242424; + --bs-gray-100: #f5f5f5; + --bs-gray-200: #f0f0f0; + --bs-gray-300: #e0e0e0; + --bs-gray-400: #d1d1d1; + --bs-gray-500: #b3b3b3; + --bs-gray-600: #757575; + --bs-gray-700: #616161; + --bs-gray-800: #424242; + --bs-gray-900: #242424; + + /* Theme colors — Fluent brandWeb ramp & shared palettes */ + --bs-primary: #0f6cbd; /* colorBrandBackground (brand80) */ + --bs-secondary: #ebebeb; /* colorNeutralBackground5 (grey92) */ + --bs-success: #107c10; /* colorStatusSuccessBackground3 (green) */ + --bs-info: #0099bc; /* colorPaletteCyanBackground3 */ + --bs-warning: #f7630c; /* colorStatusWarningBackground3 (orange) */ + --bs-danger: #c50f1f; /* colorStatusDangerBackground3 (cranberry) */ + --bs-light: #fafafa; /* grey98 */ + --bs-dark: #242424; /* grey14 */ + --bs-primary-rgb: 15,108,189; + --bs-secondary-rgb: 235,235,235; + --bs-success-rgb: 16,124,16; + --bs-info-rgb: 0,153,188; + --bs-warning-rgb: 247,99,12; + --bs-danger-rgb: 197,15,31; + --bs-light-rgb: 250,250,250; + --bs-dark-rgb: 36,36,36; + + /* Body — colorNeutralForeground1 / colorNeutralBackground1 */ + --bs-body-color: #242424; + --bs-body-color-rgb: 36,36,36; + --bs-body-bg: #ffffff; + --bs-body-bg-rgb: 255,255,255; + + /* Emphasis — colorNeutralForeground1 (tables, .text-bg-*, close buttons) */ + --bs-emphasis-color: #242424; + --bs-emphasis-color-rgb: 36,36,36; + + /* Secondary/tertiary text — colorNeutralForeground3 / colorNeutralForeground4 */ + --bs-secondary-color: #616161; + --bs-secondary-color-rgb: 97,97,97; + --bs-tertiary-color: #707070; + --bs-tertiary-color-rgb: 112,112,112; + + /* Secondary/tertiary surfaces — colorNeutralBackground3 / colorNeutralBackground2 */ + --bs-secondary-bg: #f5f5f5; + --bs-secondary-bg-rgb: 245,245,245; + --bs-tertiary-bg: #fafafa; + --bs-tertiary-bg-rgb: 250,250,250; + + /* Code — colorNeutralForeground2 (no Fluent code token; replaces Bootstrap pink #d63384) */ + --bs-code-color: #424242; + + /* Focus ring — colorCompoundBrandStroke (brand80) at the default 0.25 opacity */ + --bs-focus-ring-color: rgba(15,108,189,0.25); + + /* Validation feedback — colorStatusSuccessBackground3 / colorStatusDangerBackground3 */ + --bs-form-valid-color: #107c10; + --bs-form-valid-border-color: #107c10; + --bs-form-invalid-color: #c50f1f; + --bs-form-invalid-border-color: #c50f1f; + + /* Links — colorBrandForegroundLink */ + --bs-link-color: #115ea3; /* brand70 */ + --bs-link-color-rgb: 17,94,163; + --bs-link-hover-color: #0f548c; /* brand60 */ + --bs-link-hover-color-rgb: 15,84,140; + + /* Typography — fontFamilyBase / fontFamilyMonospace */ + --bs-font-sans-serif: 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif; + --bs-font-monospace: Consolas, 'Courier New', Courier, monospace; + + /* Borders — colorNeutralStroke1 & borderRadius tokens */ + --bs-border-color: #d1d1d1; + --bs-border-radius-sm: 2px; + --bs-border-radius: 4px; + --bs-border-radius-lg: 6px; + --bs-border-radius-xl: 8px; + --bs-border-radius-xxl: 12px; + + /* Shadows — Fluent shadow tokens (ambient 0.12 / key 0.14) */ + --bs-box-shadow-sm: 0 0 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.14); + --bs-box-shadow: 0 0 2px rgba(0,0,0,0.12), 0 8px 16px rgba(0,0,0,0.14); + --bs-box-shadow-lg: 0 0 8px rgba(0,0,0,0.12), 0 32px 64px rgba(0,0,0,0.14); + + /* Text emphasis — Fluent foreground tokens */ + --bs-primary-text-emphasis: #0f6cbd; /* colorBrandForeground1 */ + --bs-secondary-text-emphasis: #616161; /* colorNeutralForeground3 */ + --bs-success-text-emphasis: #0e700e; /* colorStatusSuccessForeground1 */ + --bs-info-text-emphasis: #008aa9; /* colorPaletteCyanForeground1 */ + --bs-warning-text-emphasis: #bc4b09; /* colorStatusWarningForeground1 */ + --bs-danger-text-emphasis: #b10e1c; /* colorStatusDangerForeground1 */ + --bs-light-text-emphasis: #616161; + --bs-dark-text-emphasis: #242424; + + /* Background subtle — Fluent Background1/Background2 tokens */ + --bs-primary-bg-subtle: #ebf3fc; /* colorBrandBackground2 (brand160) */ + --bs-secondary-bg-subtle: #f5f5f5; + --bs-success-bg-subtle: #f1faf1; /* colorStatusSuccessBackground1 */ + --bs-info-bg-subtle: #f2fafc; /* colorPaletteCyanBackground1 */ + --bs-warning-bg-subtle: #fff9f5; /* colorStatusWarningBackground1 */ + --bs-danger-bg-subtle: #fdf3f4; /* colorStatusDangerBackground1 */ + --bs-light-bg-subtle: #fafafa; + --bs-dark-bg-subtle: #e0e0e0; + + /* Border subtle — Fluent Border1/Stroke2 tokens */ + --bs-primary-border-subtle: #b4d6fa; /* colorBrandStroke2 (brand140) */ + --bs-secondary-border-subtle: #e0e0e0; + --bs-success-border-subtle: #9fd89f; /* colorStatusSuccessBorder1 */ + --bs-info-border-subtle: #a4deeb; /* colorPaletteCyanBorder1 */ + --bs-warning-border-subtle: #fdcfb4; /* colorStatusWarningBorder1 */ + --bs-danger-border-subtle: #eeacb2; /* colorStatusDangerBorder1 */ + --bs-light-border-subtle: #f0f0f0; + --bs-dark-border-subtle: #b3b3b3; + + /* BootstrapBlazor root variables */ + --bb-primary-color: #0f6cbd; + --bb-primary-color-rgb: 15,108,189; + --bb-disabled-bg: #f0f0f0; /* colorNeutralBackgroundDisabled */ + --bb-border-focus-color: #0f6cbd; /* colorCompoundBrandStroke */ + --bb-border-hover-color: #c7c7c7; /* colorNeutralStroke1Hover */ + --bb-shadow: 0 0 2px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.14); /* shadow4 */ + --bb-hover-shadow: 0 0 2px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.14); /* shadow8 */ +} + +/* ===== Dark theme ===== */ +[data-bs-theme='dark'] { + /* Text emphasis — Fluent dark foreground tokens */ + --bs-primary-text-emphasis: #479ef5; /* colorBrandForeground1 (brand100) */ + --bs-secondary-text-emphasis: #adadad; /* colorNeutralForeground3 (grey68) */ + --bs-success-text-emphasis: #54b054; /* colorStatusSuccessForeground1 (green tint30) */ + --bs-info-text-emphasis: #56bfd7; /* colorPaletteCyanForeground1 (cyan tint30) */ + --bs-warning-text-emphasis: #faa06b; /* colorStatusWarningForeground1 (orange tint30) */ + --bs-danger-text-emphasis: #dc626d; /* colorStatusDangerForeground1 (cranberry tint30) */ + --bs-light-text-emphasis: #f5f5f5; + --bs-dark-text-emphasis: #d6d6d6; + + /* Background subtle — Fluent dark shade40 tokens */ + --bs-primary-bg-subtle: #082338; /* colorBrandBackground2 (brand20) */ + --bs-secondary-bg-subtle: #1f1f1f; /* grey12 */ + --bs-success-bg-subtle: #052505; /* colorStatusSuccessBackground1 */ + --bs-info-bg-subtle: #002e38; /* colorPaletteCyanBackground1 */ + --bs-warning-bg-subtle: #4a1e04; /* colorStatusWarningBackground1 */ + --bs-danger-bg-subtle: #3b0509; /* colorStatusDangerBackground1 */ + --bs-light-bg-subtle: #3d3d3d; /* grey24 */ + --bs-dark-bg-subtle: #141414; /* grey8 */ + + /* Border subtle — Fluent dark stroke tokens */ + --bs-primary-border-subtle: #0e4775; /* colorBrandStroke2 (brand50) */ + --bs-secondary-border-subtle: #424242; /* grey26 */ + --bs-success-border-subtle: #094509; /* green shade30 */ + --bs-info-border-subtle: #005669; /* cyan shade30 */ + --bs-warning-border-subtle: #8a3707; /* orange shade30 */ + --bs-danger-border-subtle: #6e0811; /* cranberry shade30 */ + --bs-light-border-subtle: #525252; + --bs-dark-border-subtle: #3d3d3d; + + /* Body — colorNeutralForeground1 / colorNeutralBackground1 (dark) */ + --bs-body-color: #ffffff; + --bs-body-color-rgb: 255,255,255; + --bs-body-bg: #292929; /* grey16 */ + --bs-body-bg-rgb: 41,41,41; + + /* Emphasis — colorNeutralForeground1 (dark) */ + --bs-emphasis-color: #ffffff; + --bs-emphasis-color-rgb: 255,255,255; + + /* Secondary/tertiary text — colorNeutralForeground3 (grey68) / colorNeutralForeground4 (grey60) */ + --bs-secondary-color: #adadad; + --bs-secondary-color-rgb: 173,173,173; + --bs-tertiary-color: #999999; + --bs-tertiary-color-rgb: 153,153,153; + + /* Secondary/tertiary surfaces — colorNeutralBackground3 (grey8) / colorNeutralBackground2 (grey12) */ + --bs-secondary-bg: #141414; + --bs-secondary-bg-rgb: 20,20,20; + --bs-tertiary-bg: #1f1f1f; + --bs-tertiary-bg-rgb: 31,31,31; + + /* Code — colorNeutralForeground2 (grey84) */ + --bs-code-color: #d6d6d6; + + /* Focus ring — colorCompoundBrandStroke (brand100) at the default 0.25 opacity */ + --bs-focus-ring-color: rgba(71,158,245,0.25); + + /* Validation feedback — colorStatusSuccessForeground1 / colorStatusDangerForeground1 (dark) */ + --bs-form-valid-color: #54b054; + --bs-form-valid-border-color: #54b054; + --bs-form-invalid-color: #dc626d; + --bs-form-invalid-border-color: #dc626d; + + /* Links — colorBrandForegroundLink (dark) */ + --bs-link-color: #479ef5; /* brand100 */ + --bs-link-color-rgb: 71,158,245; + --bs-link-hover-color: #62abf5; /* brand110 */ + --bs-link-hover-color-rgb: 98,171,245; + + /* Borders & shadows — colorNeutralStroke1 & dark shadow tokens (ambient 0.24 / key 0.28) */ + --bs-border-color: #666666; /* grey40 */ + --bs-box-shadow-sm: 0 0 2px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.28); + --bs-box-shadow: 0 0 2px rgba(0,0,0,0.24), 0 8px 16px rgba(0,0,0,0.28); + --bs-box-shadow-lg: 0 0 8px rgba(0,0,0,0.24), 0 32px 64px rgba(0,0,0,0.28); + + /* BootstrapBlazor root variables */ + --bb-disabled-bg: #141414; /* colorNeutralBackgroundDisabled (grey8) */ + --bb-border-focus-color: #479ef5; /* colorCompoundBrandStroke (brand100) */ + --bb-border-hover-color: #757575; /* colorNeutralStroke1Hover (grey46) */ + --bb-shadow: 0 0 2px rgba(0,0,0,0.24), 0 2px 4px rgba(0,0,0,0.28); + --bb-hover-shadow: 0 0 2px rgba(0,0,0,0.24), 0 4px 8px rgba(0,0,0,0.28); +} + +/* ===== Component variable overrides — re-point hard-coded accent colors to theme tokens ===== */ + +.layout { + --bb-layout-header-background: var(--bs-primary); + --bb-layout-header-border-color: var(--bs-primary); + --bb-layout-headerbar-background: var(--bs-primary); + --bb-layout-sidebar-banner-background: var(--bs-primary); + --bb-layout-banner-border-color: var(--bs-primary); + --bb-layout-menu-user-banner-background: var(--bs-primary); + --bb-layout-menu-user-border-color: #115ea3; /* brand70 */ + --bb-layout-menu-item-hover-bg: var(--bs-primary); + --bb-layout-logo-bg: var(--bs-primary); + --bb-layout-logo-border-color: var(--bs-border-color); /* colorNeutralStroke1 (bundle hard-codes #d5d5d5) */ +} + +/* Dark: the bundle's own dark block still falls back to legacy greys + * (#606266 / #2c3034) — re-point to Fluent dark surface/stroke tokens */ +[data-bs-theme='dark'] .layout { + --bb-layout-headerbar-background: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ + --bb-layout-menu-item-hover-bg: #3d3d3d; + --bb-layout-menu-user-border-color: var(--bs-border-color); /* colorNeutralStroke1 (dark) */ + --bb-layout-logo-bg: #3d3d3d; /* grey24 */ +} + +.menu { + --bb-menu-active-color: var(--bs-primary); + --bb-menu-bar-bg: var(--bs-primary); + --bb-menu-item-hover-bg: var(--bs-primary); + --bb-menu-item-hover-color: #ffffff; /* colorNeutralForegroundInverted — legible on the themed brand hover fill */ + --bs-nav-link-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +/* Dark: brand80 #0f6cbd is too dim as a foreground on dark surfaces — use brand100 */ +[data-bs-theme='dark'] .menu { + --bb-menu-active-color: #479ef5; /* colorBrandForeground1 (brand100) */ + --bb-menu-bar-bg: #479ef5; /* colorCompoundBrandStroke (dark) */ + --bs-nav-link-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +.tabs { + --bb-tabs-item-active-color: var(--bs-primary); + --bb-tabs-item-hover-color: var(--bs-primary); + --bb-tabs-bar-bg: var(--bs-primary); +} + +[data-bs-theme='dark'] .tabs { + --bb-tabs-item-active-color: #479ef5; /* colorBrandForeground1 (brand100) */ + --bb-tabs-item-hover-color: #479ef5; + --bb-tabs-bar-bg: #479ef5; /* colorCompoundBrandStroke (dark) */ +} + +.calendar { + --bb-calendar-toolbar-hover-color: var(--bs-primary); + --bb-calendar-toolbar-focus-color: var(--bs-primary); + --bb-calendar-toolbar-active-color: var(--bs-primary); + --bb-calendar-today-color: var(--bs-primary); + --bb-calendar-today-border-color: var(--bs-primary); + --bb-calendar-selected-bg: var(--bs-primary); + --bb-calendar-week-today-color: var(--bs-primary); + --bb-calendar-week-today-border-color: var(--bs-primary); + --bb-calendar-toolbar-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bb-calendar-toolbar-focus-bg: #e0e0e0; /* colorNeutralBackground1Pressed */ + --bb-calendar-cell-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ +} + +[data-bs-theme='dark'] .calendar { + --bb-calendar-selected-bg: var(--bs-primary); + --bb-calendar-toolbar-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ + --bb-calendar-toolbar-focus-bg: #3d3d3d; + --bb-calendar-cell-hover-bg: #3d3d3d; +} + +.picker-panel { + --bb-picker-hover-color: var(--bs-primary); + --bb-picker-panel-today-bg: var(--bs-primary); + box-shadow: var(--bs-box-shadow); /* Fluent flyout surface: shadow16 */ +} + +.bb-clock-picker { + --bb-time-text-color: var(--bs-primary); + --bb-time-footer-btn-border: var(--bs-border-width) solid var(--bs-border-color); /* colorNeutralStroke1 */ + --bb-time-footer-btn-color: var(--bs-body-color); /* colorNeutralForeground1 */ + --bb-time-footer-btn-hover-border-color: var(--bs-primary); /* colorBrandBackground */ + --bb-time-footer-btn-hover-bg-color: var(--bs-primary); + --bb-time-footer-btn-active-color: var(--bs-primary); + --bb-time-footer-btn-active-border-color: var(--bs-primary); +} + +.table-container { + --bb-table-header-icon-color: rgba(var(--bs-body-color-rgb),.4); + --bb-table-header-icon-active-color: var(--bs-primary); + --bb-table-header-icon-hover-color: var(--bs-body-color); + --bb-table-header-icon-hover-bg: rgba(var(--bs-body-color-rgb),.1); + --bb-table-header-hover-bg: rgba(var(--bs-body-color-rgb),.08); + --bb-table-column-resizer-color: #e0e0e0; /* colorNeutralStroke2 */ + --bb-table-column-resizer-hover-color: var(--bs-primary); /* colorCompoundBrandStroke */ +} + +.slide-button { + --bb-slide-list-bg: var(--bs-body-bg); + --bb-slide-item-active-bg: var(--bs-primary); + --bb-slide-list-shadow: var(--bs-box-shadow); /* shadow16 */ + --bb-slide-list-border: var(--bs-border-width) solid var(--bs-border-color); /* colorNeutralStroke1 */ + --bb-slide-item-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ +} + +[data-bs-theme='dark'] .slide-button { + --bb-slide-item-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +.drawer { + --bb-drawer-bar-hover-color: var(--bs-primary); + --bb-drawer-bar-drag-color: var(--bs-primary); + --bb-drawer-body-shadow: var(--bs-box-shadow); /* shadow16 */ +} + +.filter-item { + --bb-filter-item-btn-hover-color: var(--bs-primary); + --bb-filter-item-btn-hover-border-color: var(--bs-primary); +} + +.listview { + --bb-lv-header-bg: var(--bs-secondary-bg); + --bb-lv-item-border-hover-color: var(--bs-primary); +} + +.dropdown-logout { + --bb-logout-menu-border-color: var(--bs-primary); + --bb-logout-user-bg: var(--bs-primary); +} + +.ribbon-tab { + --bb-ribbon-menu-hover-color: var(--bs-primary); + --bb-ribbon-button-hover-bg: rgba(var(--bs-primary-rgb),.24); + --bb-ribbon-button-hover-border-color: rgba(var(--bs-primary-rgb),.5); + --bb-ribbon-button-active-bg: rgba(var(--bs-primary-rgb),.32); + --bb-ribbon-button-active-border-color: rgba(var(--bs-primary-rgb),.6); +} + +.select, +.popover-dropdown { + --bb-dropdown-link-pre-active-bg: var(--bs-primary); +} + +.tree-view { + --bb-tree-item-hover-color: var(--bs-primary); + --bb-tree-drop-preview-color: var(--bs-primary); + --bb-tree-item-active-color: var(--bs-primary); /* colorBrandForeground1 */ +} + +.upload { + --bb-upload-body-list-item-hover-color: var(--bs-primary); + --bb-upload-card-shadow: var(--bb-shadow); /* shadow4 — Fluent card rest elevation */ +} + +.bb-transfer { + --bb-transfer-filter-focus-border-color: var(--bs-primary); +} + +.layout-split-bar .layout-split-bar-body { + --bb-split-bar-body-drag-hover-bg: var(--bs-primary); + --bb-split-bar-body-hover-bg: rgba(var(--bs-body-color-rgb),.08); /* neutral overlay, matches .layout-gotop */ +} + +.search.auto-complete { + --bb-search-prefix-icon-color: var(--bs-secondary-color); +} + +.layout-gotop { + --bb-gotop-bg: rgba(var(--bs-body-color-rgb),.08); + --bb-gotop-hover-bg: var(--bs-primary); + --bb-gotop-hover-color: #fff; +} + +.captcha { + --bb-captcha-bar-invalid-bg: var(--bs-danger); + --bb-captcha-bar-invalid-mask-bg: rgba(var(--bs-danger-rgb),.15); + --bb-captcha-bar-valid-bg: var(--bs-success); + --bb-captcha-bar-valid-mask-bg: rgba(var(--bs-success-rgb),.15); + --bb-captcha-radius: var(--bs-border-radius); /* borderRadiusMedium 4px */ + --bb-captcha-bar-shadow: var(--bs-box-shadow-sm); /* shadow2 */ +} + +.switch { + --bb-switch-bg: #e0e0e0; /* colorNeutralStroke2 */ + --bb-switch-border-color: #c7c7c7; /* colorNeutralStroke1Hover */ + --bb-switch-inner-off-text-color: #616161; /* colorNeutralForeground3 */ +} + +[data-bs-theme='dark'] .switch { + --bb-switch-bg: #3d3d3d; /* grey24 */ + --bb-switch-border-color: #666666; /* colorNeutralStroke1 (dark) */ + --bb-switch-inner-off-text-color: #d6d6d6; +} + +/* ===== Buttons — colorBrandBackground / colorStatus*Background3 ===== */ +.btn-primary { + --bs-btn-bg: var(--bs-primary); + --bs-btn-border-color: var(--bs-primary); + --bs-btn-hover-bg: #115ea3; /* colorBrandBackgroundHover */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c3b5e; /* colorBrandBackgroundPressed */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-primary); + --bs-btn-disabled-border-color: var(--bs-primary); +} + +.btn-secondary { + --bs-btn-color: #424242; /* colorNeutralForeground2 */ + --bs-btn-bg: var(--bs-secondary); + --bs-btn-border-color: var(--bs-secondary); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #f5f5f5; /* colorNeutralBackground5Hover */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #f0f0f0; /* colorNeutralBackground5Pressed */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-secondary); + --bs-btn-disabled-border-color: var(--bs-secondary); +} + +.btn-success { + --bs-btn-bg: var(--bs-success); + --bs-btn-border-color: var(--bs-success); + --bs-btn-hover-bg: #0e700e; /* green shade10 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c5e0c; /* green shade20 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-success); + --bs-btn-disabled-border-color: var(--bs-success); +} + +.btn-info { + --bs-btn-color: #fff; + --bs-btn-bg: var(--bs-info); + --bs-btn-border-color: var(--bs-info); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #008aa9; /* cyan shade10 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #00748f; /* cyan shade20 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-info); + --bs-btn-disabled-border-color: var(--bs-info); +} + +.btn-warning { + --bs-btn-color: #fff; + --bs-btn-bg: var(--bs-warning); + --bs-btn-border-color: var(--bs-warning); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #de590b; /* orange shade10 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #bc4b09; /* orange shade20 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-warning); + --bs-btn-disabled-border-color: var(--bs-warning); +} + +.btn-danger { + --bs-btn-bg: var(--bs-danger); + --bs-btn-border-color: var(--bs-danger); + --bs-btn-hover-bg: #b10e1c; /* colorStatusDangerBackground3Hover */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #960b18; /* colorStatusDangerBackground3Pressed */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-danger); + --bs-btn-disabled-border-color: var(--bs-danger); +} + +.btn-dark { + --bs-btn-bg: var(--bs-dark); + --bs-btn-border-color: var(--bs-dark); + --bs-btn-hover-bg: #3d3d3d; /* grey24 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #1a1a1a; /* grey10 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-bg: var(--bs-dark); + --bs-btn-disabled-border-color: var(--bs-dark); +} + +.btn-light { + --bs-btn-color: #242424; + --bs-btn-bg: var(--bs-light); + --bs-btn-border-color: var(--bs-light); + --bs-btn-hover-color: var(--bs-btn-color); + --bs-btn-hover-bg: #f0f0f0; /* grey94 */ + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-color); + --bs-btn-active-bg: #e0e0e0; /* grey88 */ + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-bg: var(--bs-light); + --bs-btn-disabled-border-color: var(--bs-light); +} + +.btn-link { + text-decoration: none; + --bs-btn-color: var(--bs-link-color); + --bs-btn-hover-color: var(--bs-link-hover-color); +} + +.btn-outline-primary { + --bs-btn-color: var(--bs-primary); + --bs-btn-border-color: var(--bs-primary); + --bs-btn-hover-bg: #115ea3; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c3b5e; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-primary); + --bs-btn-disabled-border-color: var(--bs-primary); +} + +.btn-outline-secondary { + --bs-btn-color: #616161; + --bs-btn-border-color: #d1d1d1; /* colorNeutralStroke1 */ + --bs-btn-hover-color: #424242; + --bs-btn-hover-bg: #f5f5f5; + --bs-btn-hover-border-color: #c7c7c7; /* colorNeutralStroke1Hover */ + --bs-btn-active-color: #424242; + --bs-btn-active-bg: #e0e0e0; + --bs-btn-active-border-color: var(--bs-btn-hover-border-color); + --bs-btn-disabled-color: var(--bs-btn-color); + --bs-btn-disabled-border-color: var(--bs-btn-border-color); +} + +.btn-outline-success { + --bs-btn-color: var(--bs-success); + --bs-btn-border-color: var(--bs-success); + --bs-btn-hover-bg: #0e700e; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #0c5e0c; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-success); + --bs-btn-disabled-border-color: var(--bs-success); +} + +.btn-outline-info { + --bs-btn-color: var(--bs-info); + --bs-btn-border-color: var(--bs-info); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #008aa9; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #00748f; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-info); + --bs-btn-disabled-border-color: var(--bs-info); +} + +.btn-outline-warning { + --bs-btn-color: var(--bs-warning); + --bs-btn-border-color: var(--bs-warning); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #de590b; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #bc4b09; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-warning); + --bs-btn-disabled-border-color: var(--bs-warning); +} + +.btn-outline-danger { + --bs-btn-color: var(--bs-danger); + --bs-btn-border-color: var(--bs-danger); + --bs-btn-hover-bg: #b10e1c; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #960b18; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-danger); + --bs-btn-disabled-border-color: var(--bs-danger); +} + +.btn-outline-dark { + --bs-btn-color: var(--bs-dark); + --bs-btn-border-color: var(--bs-dark); + --bs-btn-hover-bg: #3d3d3d; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-bg: #1a1a1a; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-dark); + --bs-btn-disabled-border-color: var(--bs-dark); +} + +.btn-outline-light { + --bs-btn-color: var(--bs-light); + --bs-btn-border-color: var(--bs-light); + --bs-btn-hover-color: #242424; + --bs-btn-hover-bg: #f0f0f0; + --bs-btn-hover-border-color: var(--bs-btn-hover-bg); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #e0e0e0; + --bs-btn-active-border-color: var(--bs-btn-hover-bg); + --bs-btn-disabled-color: var(--bs-light); + --bs-btn-disabled-border-color: var(--bs-light); +} + +/* ===== Table — Fluent DataGrid row hover (colorNeutralBackground1Hover) ===== */ +.table { + --bs-table-striped-bg: rgba(250, 250, 250, 0.75); + --bs-table-hover-bg: #f5f5f5; +} + +[data-bs-theme='dark'] .table { + --bs-table-striped-bg: rgba(255, 255, 255, 0.03); + --bs-table-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +/* ===== Modal — Fluent dialog shadow64 ===== */ +.modal-content { + box-shadow: 0 0 8px rgba(0,0,0,0.12), 0 32px 64px rgba(0,0,0,0.14); +} + +[data-bs-theme='dark'] .modal-content { + box-shadow: 0 0 8px rgba(0,0,0,0.24), 0 32px 64px rgba(0,0,0,0.28); +} + +/* ===== Validation — Fluent status strokes (ValidateForm / EditorForm) ===== */ +.form-control.is-valid, .was-validated .form-control:valid { + border-color: var(--bs-success); +} + +.form-control.is-invalid, .was-validated .form-control:invalid { + border-color: var(--bs-danger); +} + +/* Status-colored bottom stroke on focus, no ring glow */ +.was-validated .form-control:valid:focus, .form-control.is-valid:focus, +.was-validated .form-select:valid:focus, .form-select.is-valid:focus { + border-color: var(--bs-success); /* colorStatusSuccessBackground3 */ + box-shadow: inset 0 -1px 0 var(--bs-success); +} + +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, +.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus { + border-color: var(--bs-danger); /* colorStatusDangerBackground3 */ + box-shadow: inset 0 -1px 0 var(--bs-danger); /* danger bottom-stroke, no ring glow */ +} + +.validation-message { + color: var(--bs-danger-text-emphasis); /* colorStatusDangerForeground1 */ + font-size: 0.75rem; /* fontSizeBase200 */ +} + +/* ===== Flyout surfaces — Fluent Menu / Popover / Tooltip ===== + * Native Fluent flyouts: no arrow, borderRadiusMedium (4px), + * shadow16, colorNeutralStroke1 border, neutral item hover/selected. + * Covers: Select, MultiSelect, SelectTree/Table/Object, Cascader, + * AutoComplete, AutoFill, Search, Dropdown, TopMenu, Logout, Tab, + * Table toolbars and filter buttons. + */ +.dropdown-menu { + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-border-color: var(--bs-border-color); + --bs-dropdown-border-radius: var(--bs-border-radius); /* 4px */ + --bs-dropdown-box-shadow: var(--bs-box-shadow); /* shadow16 */ + --bs-dropdown-divider-bg: #e0e0e0; /* colorNeutralStroke2 */ + --bs-dropdown-padding-x: 4px; + --bs-dropdown-padding-y: 4px; + --bs-dropdown-item-padding-x: 12px; + --bs-dropdown-item-padding-y: 6px; + --bs-dropdown-link-hover-color: var(--bs-body-color); + --bs-dropdown-link-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bs-dropdown-link-active-color: var(--bs-body-color); + --bs-dropdown-link-active-bg: #ebebeb; /* colorNeutralBackground1Selected */ + --bs-dropdown-link-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ + --bs-dropdown-header-color: #616161; /* colorNeutralForeground3 */ + --bb-select-search-border-color: #e0e0e0; +} + +/* Fluent menus have no arrow */ +.dropdown-menu-arrow { + display: none !important; +} + +/* Fluent flyout offset: 4px */ +.cascade, +.select { + --bb-select-dropdown-menu-margin-top: 4px; +} + +/* Selected option — Fluent Listbox: reserved gutter + brand check mark */ +.select:not(.multi-select) .dropdown-menu .dropdown-item { + position: relative; + padding-inline-start: 28px; +} + +.select:not(.multi-select) .dropdown-menu .dropdown-item.active::before { + content: "\2713"; + position: absolute; + inset-inline-start: 10px; + color: var(--bs-primary); /* colorCompoundBrandForeground */ +} + +/* Popover card — Fluent Popover keeps its arrow, radius 4px, shadow16 */ +.popover { + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-color: var(--bs-body-color); + --bs-popover-border-color: var(--bs-border-color); + --bs-popover-border-radius: var(--bs-border-radius); + --bs-popover-box-shadow: var(--bs-box-shadow); + --bs-popover-header-bg: transparent; + --bs-popover-header-color: var(--bs-body-color); + --bs-popover-header-padding-x: 12px; + --bs-popover-header-padding-y: 10px; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-body-padding-x: 12px; + --bs-popover-body-padding-y: 10px; +} + +.popover-header { + font-weight: 600; + border-block-end: none; + padding-block-end: 0; +} + +/* Picker panels (DateTimePicker/DateTimeRange/SelectObject/SelectTable/filters) + * are menu-like flyouts in Fluent — no arrow */ +.popover-dropdown .popover-arrow { + display: none; +} + +/* Tooltip — Fluent dark surface, radius 4px, no arrow */ +.tooltip { + --bs-tooltip-bg: #242424; /* grey14 */ + --bs-tooltip-color: #ffffff; + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-padding-x: 8px; + --bs-tooltip-padding-y: 4px; + --bs-tooltip-opacity: 1; +} + +.tooltip .tooltip-arrow { + display: none; +} + +[data-bs-theme='dark'] .dropdown-menu { + --bs-dropdown-divider-bg: #4d4d4d; /* colorNeutralStroke2 (dark) */ + --bs-dropdown-link-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ + --bs-dropdown-link-active-bg: #3d3d3d; /* colorNeutralBackground1Selected (dark) */ + --bs-dropdown-link-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ + --bs-dropdown-header-color: #adadad; /* colorNeutralForeground3 (dark) */ + --bb-select-search-border-color: #4d4d4d; +} + +[data-bs-theme='dark'] .tooltip .tooltip-inner { + border: var(--bs-border-width) solid var(--bs-border-color); +} + +/* ===== Layout & Navigation — Fluent menu flyouts, neutral foregrounds ===== */ + +/* Fluent Menu flyout: shadow16 (bundle hard-codes 0 2px 12px rgba(0,0,0,.1)) */ +.menu .nav .sub-menu, +.cascade .has-leaf .sub-menu { + box-shadow: var(--bs-box-shadow); /* shadow16 */ +} + +/* Fluent flyout offset: 4px (bundle hard-codes 10px for menu dropdowns) */ +.menu .nav .dropdown-menu { + margin-block-start: 4px; +} + +/* Navbar — Fluent foreground ramp (Bootstrap default is rgba emphasis) */ +.navbar { + --bs-navbar-color: #424242; /* colorNeutralForeground2 */ + --bs-navbar-hover-color: #242424; /* colorNeutralForeground1 */ + --bs-navbar-active-color: #242424; /* colorNeutralForeground1 */ + --bs-navbar-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ + --bs-navbar-brand-color: #242424; /* colorNeutralForeground1 */ + --bs-navbar-brand-hover-color: #242424; +} + +[data-bs-theme='dark'] .navbar { + --bs-navbar-color: #d6d6d6; /* colorNeutralForeground2 (grey84) */ + --bs-navbar-hover-color: #ffffff; /* colorNeutralForeground1 (dark) */ + --bs-navbar-active-color: #ffffff; + --bs-navbar-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ + --bs-navbar-brand-color: #ffffff; + --bs-navbar-brand-hover-color: #ffffff; +} + +/* Breadcrumb — Fluent Breadcrumb links are neutral, not brand */ +.breadcrumb { + --bs-breadcrumb-divider-color: #616161; /* colorNeutralForeground3 */ + --bs-breadcrumb-item-active-color: #242424; /* colorNeutralForeground1 */ +} + +.breadcrumb-item a { + color: #424242; /* colorNeutralForeground2 */ +} + +.breadcrumb-item a:hover { + color: #242424; /* colorNeutralForeground2Hover */ +} + +[data-bs-theme='dark'] .breadcrumb { + --bs-breadcrumb-divider-color: #adadad; /* colorNeutralForeground3 (grey68) */ + --bs-breadcrumb-item-active-color: #ffffff; /* colorNeutralForeground1 (dark) */ +} + +[data-bs-theme='dark'] .breadcrumb-item a { + color: #d6d6d6; /* colorNeutralForeground2 (grey84) */ +} + +[data-bs-theme='dark'] .breadcrumb-item a:hover { + color: #ffffff; /* colorNeutralForeground2Hover (dark) */ +} + +/* Tab close-button hover — Fluent neutral fill (bundle hard-codes #e4e7ed) */ +.tabs .tabs-item-close:hover { + background-color: #e0e0e0; /* colorNeutralBackground1Pressed */ +} + +[data-bs-theme='dark'] .tabs .tabs-item-close:hover { + background-color: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +/* Border-card elevation — Fluent shadow4; --bb-shadow already carries the dark variant */ +.tabs.tabs-border-card { + box-shadow: var(--bb-shadow); /* shadow4 */ +} + +/* Step — pending states re-pointed to Fluent foreground/stroke tokens; + * the component default var(--bs-secondary) is a surface tone in this theme, + * so pending step circles/text would render nearly invisible */ +.step { + --bb-step-item-color: #616161; /* colorNeutralForeground3 */ + --bb-step-item-line-bg: #e0e0e0; /* colorNeutralStroke2 */ +} + +[data-bs-theme='dark'] .step { + --bb-step-item-color: #adadad; /* colorNeutralForeground3 (grey68) */ + --bb-step-item-line-bg: #4d4d4d; /* colorNeutralStroke2 (dark) */ +} + +/* Pagination — Fluent neutral pages (current page keeps the themed brand fill) */ +.pagination { + --bs-pagination-color: #424242; /* colorNeutralForeground2 */ + --bs-pagination-hover-color: #242424; /* colorNeutralForeground1 */ + --bs-pagination-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bs-pagination-focus-color: #242424; + --bs-pagination-focus-bg: #f5f5f5; + --bs-pagination-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +[data-bs-theme='dark'] .pagination { + --bs-pagination-color: #d6d6d6; /* colorNeutralForeground2 (grey84) */ + --bs-pagination-hover-color: #ffffff; /* colorNeutralForeground1 (dark) */ + --bs-pagination-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ + --bs-pagination-focus-color: #ffffff; + --bs-pagination-focus-bg: #3d3d3d; + --bs-pagination-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +/* ===== Buttons — Fluent label weight, focus stroke, neutral disabled ===== */ +.btn { + --bs-btn-font-weight: 600; /* fontWeightSemibold */ +} + +/* Fluent focus indicator: 2px colorStrokeFocus1 gap + 2px colorStrokeFocus2 ring (keyboard only) */ +.btn:focus-visible, +.btn-check:focus-visible + .btn { + box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #000000; /* colorStrokeFocus1 / colorStrokeFocus2 */ +} + +[data-bs-theme='dark'] .btn:focus-visible, +[data-bs-theme='dark'] .btn-check:focus-visible + .btn { + box-shadow: 0 0 0 2px #000000, 0 0 0 4px #ffffff; /* colorStrokeFocus1 / colorStrokeFocus2 (dark) */ +} + +/* Fluent disabled looks identical for every appearance — no opacity fade */ +.btn:disabled, +.btn.disabled, +.btn-check:disabled + .btn, +fieldset:disabled .btn { + color: #bdbdbd; /* colorNeutralForegroundDisabled */ + background-color: var(--bb-disabled-bg); /* colorNeutralBackgroundDisabled */ + border-color: var(--bb-disabled-bg); + opacity: 1; +} + +/* Transparent appearances stay transparent when disabled */ +.btn-link:disabled, +.btn-link.disabled, +.link-button:disabled, +.link-button.disabled { + background-color: transparent; + border-color: transparent; +} + +[data-bs-theme='dark'] .btn:disabled, +[data-bs-theme='dark'] .btn.disabled, +[data-bs-theme='dark'] .btn-check:disabled + .btn, +[data-bs-theme='dark'] fieldset:disabled .btn { + color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +/* ===== DialButton — Fluent FAB menu items: neutral hover, shadow4 ===== */ +.dial-button { + --bb-dial-item-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bb-dial-item-shadow: var(--bb-shadow); /* shadow4 */ +} + +[data-bs-theme='dark'] .dial-button { + --bb-dial-item-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +/* ===== Text inputs — Fluent Input: stroke1 outline + brand bottom-stroke on focus ===== + * Covers BootstrapInput, InputNumber, Textarea, AutoComplete trigger, + * Select trigger (input carries .form-select + .form-control). + * Input elements cannot render ::after, so the strokeWidthThick (2px) + * underline is composed of the 1px bottom border (recolored to + * colorCompoundBrandStroke) plus a 1px inset box-shadow stacked on it. + */ +.form-control::placeholder { + color: var(--bs-tertiary-color); /* colorNeutralForeground4 */ + opacity: 1; +} + +/* Fluent motion — durationNormal / curveEasyEase */ +.form-control, +.form-select { + transition: border-color .2s cubic-bezier(.33,0,.67,1), box-shadow .2s cubic-bezier(.33,0,.67,1), background-color .2s cubic-bezier(.33,0,.67,1); +} + +.form-control:hover:not(:disabled):not([readonly]):not(:focus), +.form-select:hover:not(:disabled):not(:focus) { + border-color: var(--bb-border-hover-color); /* colorNeutralStroke1Hover */ +} + +/* The :not() chain mirrors the bundle hover rule so equal specificity + later order wins */ +.form-control:not(.disabled):not(:disabled):not(.is-valid):not(.is-invalid):not(.no-border):not(.is-display):focus, +.form-select:not(.disabled):not(:disabled):not(.is-valid):not(.is-invalid):not(.no-border):not(.is-display):focus { + border-color: var(--bs-border-color); /* keep colorNeutralStroke1 outline on focus */ + border-bottom-color: var(--bb-border-focus-color); /* colorCompoundBrandStroke */ + box-shadow: inset 0 -1px 0 var(--bb-border-focus-color); /* strokeWidthThick brand underline */ +} + +/* Fluent disabled: backgroundDisabled fill, strokeDisabled outline, foregroundDisabled text */ +.form-control:disabled, +.form-control.disabled, +.form-select:disabled, +.multi-select .dropdown-toggle.disabled { + color: #bdbdbd; /* colorNeutralForegroundDisabled */ + background-color: var(--bb-disabled-bg); /* colorNeutralBackgroundDisabled */ + border-color: #e0e0e0; /* colorNeutralStrokeDisabled */ + opacity: 1; +} + +[data-bs-theme='dark'] .form-control:disabled, +[data-bs-theme='dark'] .form-control.disabled, +[data-bs-theme='dark'] .form-select:disabled, +[data-bs-theme='dark'] .multi-select .dropdown-toggle.disabled { + color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ + border-color: #424242; /* colorNeutralStrokeDisabled (grey26) */ +} + +/* Search — the trigger is a .form-control wrapper around a borderless input */ +.search.auto-complete .form-control-group:not(.disabled):not(:disabled):not(.is-valid):not(.is-invalid):not(.no-border):not(.is-display):focus-within { + border-color: var(--bs-border-color); + border-bottom-color: var(--bb-border-focus-color); /* colorCompoundBrandStroke */ + box-shadow: inset 0 -1px 0 var(--bb-border-focus-color); +} + +/* ===== Select / MultiSelect trigger — append icon + placeholder + focus underline ===== */ +.select, +.auto-complete { + --bb-select-append-color: #616161; /* colorNeutralForeground3 — chevron / clear icon */ +} + +[data-bs-theme='dark'] .select, +[data-bs-theme='dark'] .auto-complete { + --bb-select-append-color: #adadad; /* colorNeutralForeground3 (grey68) */ +} + +.multi-select .multi-select-ph { + color: var(--bs-tertiary-color); /* colorNeutralForeground4 */ +} + +/* MultiSelect trigger is a focusable div — same bottom-stroke focus language */ +.multi-select .dropdown-toggle:not(.disabled):focus, +.multi-select .dropdown-toggle:not(.disabled):focus-within { + border-color: var(--bs-border-color); + border-bottom-color: var(--bb-border-focus-color); /* colorCompoundBrandStroke */ + box-shadow: inset 0 -1px 0 var(--bb-border-focus-color); + outline: none; +} + +/* ===== OtpInput — same underline focus per cell, status colors win ===== */ +.bb-otp-input { + --bb-otp-item-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +.bb-otp-input .bb-otp-item:focus { + border-bottom-color: var(--bb-border-focus-color); /* colorCompoundBrandStroke */ + box-shadow: inset 0 -1px 0 var(--bb-border-focus-color); +} + +.bb-otp-input .bb-otp-item.is-valid:focus { + border-bottom-color: var(--bs-success); + box-shadow: inset 0 -1px 0 var(--bs-success); +} + +.bb-otp-input .bb-otp-item.is-invalid:focus { + border-bottom-color: var(--bs-danger); + box-shadow: inset 0 -1px 0 var(--bs-danger); +} + +[data-bs-theme='dark'] .bb-otp-input { + --bb-otp-item-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +/* ===== Checkbox — Fluent Checkbox: strokeAccessible outline, borderRadiusSmall, brand fill ===== */ +.form-check { + --bb-checkbox-input-focus-border-color: #616161; /* colorNeutralStrokeAccessible */ +} + +.form-check .form-check-input { + border-color: #616161; /* colorNeutralStrokeAccessible */ +} + +.form-check-input[type='checkbox'] { + border-radius: var(--bs-border-radius-sm); /* borderRadiusSmall (2px) */ +} + +.form-check .form-check-input:hover:not(:disabled):not(:checked):not(:indeterminate) { + border-color: #575757; /* colorNeutralStrokeAccessibleHover */ +} + +/* Checked hover deepens the brand fill; colored variants keep their own hue */ +.form-check:not(.form-check-success):not(.form-check-danger):not(.form-check-warning):not(.form-check-info):not(.form-check-secondary):not(.form-check-dark) .form-check-input:checked:hover:not(:disabled), +.form-check:not(.form-check-success):not(.form-check-danger):not(.form-check-warning):not(.form-check-info):not(.form-check-secondary):not(.form-check-dark) .form-check-input[type='checkbox']:indeterminate:hover:not(:disabled) { + background-color: #115ea3; /* colorCompoundBrandBackgroundHover */ + border-color: #115ea3; +} + +/* Fluent focus ring instead of the recolored border glow */ +.form-check .form-check-input:focus-visible { + box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #000000; /* colorStrokeFocus1 / colorStrokeFocus2 */ +} + +[data-bs-theme='dark'] .form-check { + --bb-checkbox-input-focus-border-color: #adadad; /* colorNeutralStrokeAccessible (grey68) */ +} + +[data-bs-theme='dark'] .form-check .form-check-input { + border-color: #adadad; /* colorNeutralStrokeAccessible (grey68) */ +} + +[data-bs-theme='dark'] .form-check .form-check-input:hover:not(:disabled):not(:checked):not(:indeterminate) { + border-color: #bdbdbd; /* colorNeutralStrokeAccessibleHover (grey74) */ +} + +[data-bs-theme='dark'] .form-check .form-check-input:focus-visible { + box-shadow: 0 0 0 2px #000000, 0 0 0 4px #ffffff; /* colorStrokeFocus1 / colorStrokeFocus2 (dark) */ +} + +/* ===== Radio — Fluent RadioButton: brand ring + brand dot instead of a filled disc ===== + * --bs-primary is not redefined in the dark block, so the brand dot fill + * matches the themed border in both themes. */ +.form-check-input:checked[type='radio'] { + background-color: transparent; + border-color: var(--bs-primary); /* colorCompoundBrandBackground */ + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.5' fill='%230f6cbd'/%3e%3c/svg%3e"); /* brand80 dot */ +} + +.form-check-input:checked[type='radio']:hover:not(:disabled) { + border-color: #115ea3; /* colorCompoundBrandBackgroundHover */ + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.5' fill='%23115ea3'/%3e%3c/svg%3e"); /* brand70 dot */ +} + +/* ===== Slider — Fluent Slider: strokeAccessible rail, brand donut thumb ===== + * WebKit cannot paint the filled rail portion without JS; Firefox gets it + * via ::-moz-range-progress. */ +.form-range::-webkit-slider-runnable-track { + height: 4px; /* Fluent medium rail */ + background-color: #616161; /* colorNeutralStrokeAccessible */ + border-radius: var(--bs-border-radius-sm); +} + +.form-range::-webkit-slider-thumb { + width: 20px; + height: 20px; + margin-top: -8px; /* center the 20px thumb on the 4px rail */ + background-color: var(--bs-primary); /* colorCompoundBrandBackground */ + box-shadow: inset 0 0 0 4px var(--bs-body-bg); /* Fluent donut thumb */ +} + +.form-range::-webkit-slider-thumb:hover { + background-color: #115ea3; /* colorCompoundBrandBackgroundHover */ +} + +.form-range::-webkit-slider-thumb:active { + background-color: #0c3b5e; /* colorCompoundBrandBackgroundPressed */ + box-shadow: inset 0 0 0 6px var(--bs-body-bg); /* pressed: inner dot shrinks */ +} + +.form-range:focus::-webkit-slider-thumb { + box-shadow: inset 0 0 0 4px var(--bs-body-bg), 0 0 0 2px #ffffff, 0 0 0 4px #000000; /* donut + colorStrokeFocus2 ring */ +} + +.form-range::-moz-range-track { + height: 4px; + background-color: #616161; /* colorNeutralStrokeAccessible */ + border-radius: var(--bs-border-radius-sm); +} + +.form-range::-moz-range-progress { + height: 4px; + background-color: var(--bs-primary); /* colorCompoundBrandBackground fill */ + border-radius: var(--bs-border-radius-sm); +} + +.form-range::-moz-range-thumb { + width: 20px; + height: 20px; + background-color: var(--bs-primary); /* colorCompoundBrandBackground */ + box-shadow: inset 0 0 0 4px var(--bs-body-bg); +} + +.form-range::-moz-range-thumb:hover { + background-color: #115ea3; /* colorCompoundBrandBackgroundHover */ +} + +.form-range::-moz-range-thumb:active { + background-color: #0c3b5e; /* colorCompoundBrandBackgroundPressed */ + box-shadow: inset 0 0 0 6px var(--bs-body-bg); +} + +.form-range:focus::-moz-range-thumb { + box-shadow: inset 0 0 0 4px var(--bs-body-bg), 0 0 0 2px #ffffff, 0 0 0 4px #000000; +} + +.form-range:disabled::-webkit-slider-runnable-track { + background-color: #e0e0e0; /* colorNeutralStrokeDisabled */ +} + +.form-range:disabled::-webkit-slider-thumb { + background-color: #bdbdbd; /* colorNeutralForegroundDisabled */ + box-shadow: inset 0 0 0 4px var(--bs-body-bg); +} + +.form-range:disabled::-moz-range-track, +.form-range:disabled::-moz-range-progress { + background-color: #e0e0e0; /* colorNeutralStrokeDisabled */ +} + +.form-range:disabled::-moz-range-thumb { + background-color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +[data-bs-theme='dark'] .form-range::-webkit-slider-runnable-track { + background-color: #adadad; /* colorNeutralStrokeAccessible (grey68) */ +} + +[data-bs-theme='dark'] .form-range::-moz-range-track { + background-color: #adadad; /* colorNeutralStrokeAccessible (grey68) */ +} + +[data-bs-theme='dark'] .form-range:focus::-webkit-slider-thumb { + box-shadow: inset 0 0 0 4px var(--bs-body-bg), 0 0 0 2px #000000, 0 0 0 4px #ffffff; /* colorStrokeFocus2 (dark) */ +} + +[data-bs-theme='dark'] .form-range:focus::-moz-range-thumb { + box-shadow: inset 0 0 0 4px var(--bs-body-bg), 0 0 0 2px #000000, 0 0 0 4px #ffffff; +} + +[data-bs-theme='dark'] .form-range:disabled::-webkit-slider-runnable-track { + background-color: #4d4d4d; /* colorNeutralStrokeDisabled (grey30) */ +} + +[data-bs-theme='dark'] .form-range:disabled::-webkit-slider-thumb { + background-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +[data-bs-theme='dark'] .form-range:disabled::-moz-range-track, +[data-bs-theme='dark'] .form-range:disabled::-moz-range-progress { + background-color: #4d4d4d; /* colorNeutralStrokeDisabled (grey30) */ +} + +[data-bs-theme='dark'] .form-range:disabled::-moz-range-thumb { + background-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +/* ===== Rate — Fluent Rating: marigold filled stars, neutral empty stars ===== */ +.rate { + --bb-rate-color: #d1d1d1; /* colorNeutralStroke1 — unfilled star */ + --bb-rate-active-color: #eaa300; /* colorPaletteMarigoldBackground3 */ + --bb-rate-hover-color: #eaa300; /* hover preview fill */ +} + +.rate.disabled .rate-item i { + color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +[data-bs-theme='dark'] .rate { + --bb-rate-color: #666666; /* colorNeutralStroke1 (grey40) */ +} + +[data-bs-theme='dark'] .rate.disabled .rate-item i { + color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +/* ===== Table — Fluent DataGrid details ===== + * Header icons, header hover bg and striped/hover row bg are already themed + * above; below: remaining hard-coded Element/Bootstrap accents. + */ + +/* Active column-resize indicator — colorCompoundBrandStroke */ +.table-resize .border-resize { + border-right-color: var(--bs-primary); +} + +/* Detail-row expand caret hover — colorNeutralBackground1Hover */ +.table .is-bar .fa-caret-right:hover { + background-color: #f5f5f5; +} + +[data-bs-theme='dark'] .table .is-bar .fa-caret-right:hover { + background-color: #3d3d3d; /* grey24 */ +} + +/* Drag-over row — colorNeutralBackground1Selected */ +.table-drag { + background-color: #ebebeb !important; +} + +[data-bs-theme='dark'] .table-drag { + background-color: #3d3d3d !important; /* grey24 */ +} + +/* Cell placeholder block — colorNeutralBackground4 */ +.is-ph { + background-color: #f0f0f0; +} + +[data-bs-theme='dark'] .is-ph { + background-color: #3d3d3d; /* grey24 */ +} + +/* Toolbar gear flyout — neutral foreground & stroke instead of hard-coded greys */ +.btn-gear .dropdown-menu .dropdown-item:not(.disabled) { + color: var(--bs-body-color); /* colorNeutralForeground1 */ +} + +.btn-gear .dropdown-menu .dropdown-item:not(:first-child) { + border-inline-start-color: var(--bs-border-color); /* colorNeutralStroke1 */ +} + +/* Search shortcut key hint — colorPaletteCyanBackground3 */ +.search-input-tooltip kbd { + background: var(--bs-info); +} + +/* ===== Tree (legacy .tree) — Fluent TreeView hover/selected surfaces ===== + * .tree-view (hover color, drop preview, active color) is themed above; + * the legacy .tree highlight bars were hard-coded Element blues. + */ +.tree .tree-item.hover:before { + background-color: #f5f5f5; /* colorNeutralBackground1Hover */ +} + +.tree .tree-item.active:before { + background-color: #ebebeb; /* colorNeutralBackground1Selected */ +} + +[data-bs-theme='dark'] .tree .tree-item.hover:before { + background-color: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +[data-bs-theme='dark'] .tree .tree-item.active:before { + background-color: #3d3d3d; /* colorNeutralBackground1Selected (grey24) */ +} + +/* ===== Card — Fluent stroke instead of translucent Bootstrap border ===== + * Radius (borderRadiusMedium 4px) and shadows (shadow4/shadow8) already + * come from the themed --bs-border-radius / --bb-shadow tokens. + */ +.card { + --bs-card-border-color: var(--bs-border-color); /* colorNeutralStroke1 */ +} + +/* ===== Avatar — Fluent Avatar "neutral" recipe ===== */ +.avatar { + --bb-avatar-bg: #ebebeb; /* colorNeutralBackground6 (grey92) */ + --bb-avatar-color: #616161; /* colorNeutralForeground3 */ +} + +[data-bs-theme='dark'] .avatar { + --bb-avatar-bg: #333333; /* colorNeutralBackground6 (grey20, dark) */ + --bb-avatar-color: #adadad; /* colorNeutralForeground3 (dark) */ +} + +/* ===== ShieldBadge — re-point hard-coded blue to brand ===== */ +.shield-badge { + --bb-shield-badge-text-bg: var(--bs-primary); /* colorBrandBackground */ +} + +/* ===== Timeline — neutral stroke/foreground tokens ===== */ +.timeline { + --bb-timeline-node-bg: #d1d1d1; /* colorNeutralStroke1 */ + --bb-timeline-timestamp-color: #616161; /* colorNeutralForeground3 */ +} + +[data-bs-theme='dark'] .timeline { + --bb-timeline-node-bg: #666666; /* colorNeutralStroke1 (dark) */ + --bb-timeline-timestamp-color: #adadad; /* colorNeutralForeground3 (dark) */ +} + +/* ===== ListGroup — Fluent list hover/pressed; fix compiled #0d6efd active ===== + * bootstrap.blazor.bundle.min.css hard-codes --bs-list-group-active-bg: #0d6efd; + * re-point to the themed brand token. + */ +.list-group { + --bs-list-group-active-bg: var(--bs-primary); /* colorBrandBackground */ + --bs-list-group-active-border-color: var(--bs-primary); + --bs-list-group-action-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bs-list-group-action-active-bg: #e0e0e0; /* colorNeutralBackground1Pressed */ +} + +[data-bs-theme='dark'] .list-group { + --bs-list-group-action-hover-bg: #3d3d3d; /* grey24 */ + --bs-list-group-action-active-bg: #1f1f1f; /* grey12 */ +} + +/* ===== Display — Fluent read-only field surface ===== + * Light uses the themed --bs-secondary-bg (colorNeutralBackground3); + * dark lifts the surface to colorNeutralBackground2 for contrast. + */ +[data-bs-theme='dark'] .form-control.is-display { + --bs-secondary-bg: #1f1f1f; /* colorNeutralBackground2 (grey12) */ +} + +/* ===== Modal/Dialog — Fluent DialogSurface: borderRadiusXLarge (12px), transparent stroke ===== + * shadow64 elevation on .modal-content is already themed above. + */ +.modal { + --bs-modal-border-width: 0; /* colorTransparentStroke */ + --bs-modal-border-radius: var(--bs-border-radius-xxl); /* borderRadiusXLarge */ + --bs-modal-header-border-color: transparent; /* DialogTitle: no divider */ + --bs-modal-footer-border-color: transparent; /* DialogActions: no divider */ +} + +.modal-title { + font-weight: 600; /* DialogTitle: fontWeightSemibold */ +} + +/* Fluent Dialog backdrop — colorBackgroundOverlay rgba(0,0,0,.4) */ +.modal-backdrop { + --bs-backdrop-opacity: .4; +} + +[data-bs-theme='dark'] .modal-backdrop { + --bs-backdrop-opacity: .5; /* colorBackgroundOverlay (dark) */ +} + +/* ===== Toast — Fluent Toast: transparent stroke, shadow8, single neutral surface ===== */ +.toast { + --bs-toast-border-color: transparent; /* colorTransparentStroke */ + --bs-toast-box-shadow: var(--bb-hover-shadow); /* shadow8 */ + --bs-toast-header-bg: transparent; /* colorNeutralBackground1 surface */ + --bs-toast-header-color: var(--bs-body-color); /* colorNeutralForeground1 */ +} + +/* ===== Alert / Message — Fluent InfoBar: no visible border ===== */ +.alert { + --bs-alert-border: 0; /* InfoBar: border replaced by status background tint */ +} + +/* Fluent InfoBar "info" intent = neutral surface, not a cyan tint */ +.alert-info { + --bs-alert-color: var(--bs-body-color); /* colorNeutralForeground1 */ + --bs-alert-bg: #f5f5f5; /* colorNeutralBackground3 */ +} + +[data-bs-theme='dark'] .alert-info { + --bs-alert-bg: #141414; /* colorNeutralBackground3 (dark, grey8) */ +} + +/* ===== SweetAlert — Fluent status foregrounds & neutral text ===== */ +.swal2-title { + color: var(--bs-body-color); /* colorNeutralForeground1 */ + font-size: 1.25rem; /* DialogTitle: fontSizeBase500 */ + font-weight: 600; /* fontWeightSemibold */ +} + +.swal2-content { + color: var(--bs-body-color); /* DialogContent: colorNeutralForeground1 */ + font-weight: 400; /* DialogContent: fontWeightRegular */ +} + +.swal2-footer { + border-top-color: #e0e0e0; /* colorNeutralStroke2 */ + color: #616161; /* colorNeutralForeground3 */ +} + +.swal2-icon.swal2-success { + border-color: var(--bs-success-text-emphasis); /* colorStatusSuccessForeground1 */ + color: var(--bs-success-text-emphasis); +} + +.swal2-icon.swal2-success .swal2-success-ring { + border-color: rgba(var(--bs-success-rgb), .3); +} + +.swal2-icon.swal2-success [class^='swal2-success-line'] { + background-color: var(--bs-success-text-emphasis); +} + +.swal2-icon.swal2-error { + border-color: var(--bs-danger-text-emphasis); /* colorStatusDangerForeground1 */ + color: var(--bs-danger-text-emphasis); +} + +.swal2-icon.swal2-error [class^='swal2-x-mark-line'] { + background-color: var(--bs-danger-text-emphasis); +} + +.swal2-icon.swal2-warning { + border-color: var(--bs-warning-text-emphasis); /* colorStatusWarningForeground1 */ + color: var(--bs-warning-text-emphasis); +} + +.swal2-icon.swal2-info { + border-color: var(--bs-info-text-emphasis); /* colorPaletteCyanForeground1 */ + color: var(--bs-info-text-emphasis); +} + +.swal2-icon.swal2-question { + border-color: var(--bs-secondary-text-emphasis); /* colorNeutralForeground3 */ + color: var(--bs-secondary-text-emphasis); +} + +[data-bs-theme='dark'] .swal2-footer { + border-top-color: #424242; /* colorNeutralStroke2 (dark, grey26) */ + color: #adadad; /* colorNeutralForeground3 (dark, grey68) */ +} + +/* ===== Progress — Fluent ProgressBar: rail colorNeutralBackground6, bar colorCompoundBrandBackground ===== */ +.progress { + --bs-progress-bg: #e6e6e6; /* colorNeutralBackground6 (grey90) */ +} + +[data-bs-theme='dark'] .progress { + --bs-progress-bg: #333333; /* colorNeutralBackground6 (dark, grey20) */ +} + +/* ===== Circle — Fluent ProgressBar rail color for the track ring ===== */ +.circle { + --bb-circle-stroke-color: #e6e6e6; /* colorNeutralBackground6 (grey90) */ +} + +[data-bs-theme='dark'] .circle { + --bb-circle-stroke-color: #333333; /* colorNeutralBackground6 (dark, grey20) */ +} + +/* ===== Mask — Fluent overlay: colorBackgroundOverlay ===== */ +.bb-mask { + --bb-mask-opacity: .4; /* colorBackgroundOverlay rgba(0,0,0,.4) */ +} + +[data-bs-theme='dark'] .bb-mask { + --bb-mask-opacity: .5; /* colorBackgroundOverlay (dark) */ +} + +/* ===== Picker panel (DateTimePicker / DateTimeRange flyout) ===== */ + +/* Sidebar (range shortcut list) — themed divider, brand text hover */ +.picker-panel-sidebar { + border-right-color: var(--bs-border-color); /* colorNeutralStroke1 */ +} + +.picker-panel .picker-panel-sidebar .sidebar-item:hover, +.picker-panel-sidebar .sidebar-item:hover { + color: var(--bb-picker-hover-color); +} + +/* Day cell hover — brand foreground */ +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td:not(.prev-month):not(.next-month):hover { + color: var(--bb-picker-hover-color); +} + +/* Selected day with lunar text — re-point hard-coded #409eff marker */ +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.current:not(.disabled) .cell:has(.bb-picker-body-lunar-text):before, +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.start:not(.disabled) .cell:has(.bb-picker-body-lunar-text):before, +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.end:not(.disabled) .cell:has(.bb-picker-body-lunar-text):before { + background-color: var(--bb-picker-panel-today-bg); +} + +/* Range band — brand160 light / brand20 dark (via themed subtle bg) */ +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.range:before { + background-color: var(--bs-primary-bg-subtle); /* colorBrandBackground2 */ +} + +/* Adjacent-month & disabled days — colorNeutralForegroundDisabled */ +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.next-month, +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.prev-month, +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.disabled .cell { + color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +/* Holiday / workday corner badges (adjacent months) — neutral fill */ +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-holiday, +.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-workday { + background-color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +[data-bs-theme='dark'] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.next-month, +[data-bs-theme='dark'] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.prev-month, +[data-bs-theme='dark'] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.disabled .cell { + color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +[data-bs-theme='dark'] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-holiday, +[data-bs-theme='dark'] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-workday { + background-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ +} + +/* Footer link buttons — brand hover instead of legacy #409eff */ +.picker-panel-footer .picker-panel-link-btn:hover { + border-color: var(--bb-picker-hover-color); + color: var(--bb-picker-hover-color); +} + +.picker-panel-footer .picker-timer:hover { + color: var(--bb-picker-hover-color); +} + +/* ===== DateTimePicker / DateTimeRange trigger icons ===== */ +.datetime-picker { + --bb-dt-picker-bar-color: #707070; /* colorNeutralForeground4 */ +} + +.datetime-range { + --bb-dt-range-bar-color: #707070; /* colorNeutralForeground4 */ +} + +[data-bs-theme='dark'] .datetime-picker { + --bb-dt-picker-bar-color: #adadad; /* colorNeutralForeground3 (dark) */ +} + +[data-bs-theme='dark'] .datetime-range { + --bb-dt-range-bar-color: #adadad; /* colorNeutralForeground3 (dark) */ +} + +/* Disabled range trigger — colorNeutralBackgroundDisabled */ +.datetime-range.disabled, +.datetime-range-control .datetime-range-input.disabled { + background-color: var(--bb-disabled-bg); +} + +/* ===== TimePicker — Fluent flyout shadow ===== */ +.bb-time-picker { + --bb-time-picker-shadow: var(--bs-box-shadow); /* shadow16 */ +} + +/* ClockPicker hand pivot dot follows foreground (was hard-coded black) */ +.bb-clock-picker .bb-time-body .bb-clock-panel .bb-clock-point .bb-clock-point-star { + background-color: var(--bs-body-color); +} + +/* ===== ColorPicker — trigger only (native popup is OS-rendered, cannot be themed) ===== */ +.bb-color-picker .form-control-color.disabled { + background-color: var(--bb-disabled-bg); /* colorNeutralBackgroundDisabled */ +} + +/* ===== Upload family (Button/Input/Avatar/Card/Drop + preview list) ===== */ + +/* Preview list rows — Fluent neutral hover fill */ +.upload .upload-body.is-list .upload-item:hover { + background-color: #f5f5f5; /* colorNeutralBackground1Hover */ +} + +[data-bs-theme='dark'] .upload .upload-body.is-list .upload-item:hover { + background-color: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ +} + +/* Drop zone — resting neutral dashed stroke, brand only while dragging */ +.upload.is-drop .upload-drop-body { + border-color: var(--bs-border-color); /* colorNeutralStroke1 */ +} + +.upload.is-drop .upload-drop-body .upload-drop-icon > i { + color: #616161; /* colorNeutralForeground3 */ +} + +.upload.is-drop:not(.disabled) .upload-drop-body em { + color: var(--bs-link-color); /* colorBrandForegroundLink */ +} + +.upload.is-drop.dropping .upload-drop-body { + border-color: var(--bs-primary); /* colorBrandBackground */ + background-color: var(--bs-primary-bg-subtle); /* colorBrandBackground2 (brand160) */ +} + +[data-bs-theme='dark'] .upload.is-drop .upload-drop-body .upload-drop-icon > i { + color: #adadad; /* colorNeutralForeground3 (dark) */ +} + +/* ===== ImagePreviewer — overlay chrome ===== */ +.bb-previewer { + --bb-viewer-button-bg: #3d3d3d; /* grey24 — Fluent dark chrome on overlay */ +} + +/* Fluent colorBackgroundOverlay is rgba(0,0,0,.4) */ +.bb-previewer .bb-viewer-mask { + opacity: .4; +} + +/* ===== Switch / NullSwitch — Fluent Switch: pill track, brand when on ===== + * The on-state is rendered via bg-success/border-success utilities (OnColor + * defaults to Color.Success); re-point --bs-success-rgb to the brand ramp + * instead of fighting the utility's !important. Custom OnColor is untouched. + */ +.switch .switch-core { + transition: border-color .2s cubic-bezier(.33,0,.67,1), background-color .2s cubic-bezier(.33,0,.67,1); /* durationNormal / curveEasyEase */ +} + +/* Off hover/pressed */ +.switch:not(.disabled):not(.is-checked):hover .switch-core { + background-color: #d1d1d1; /* colorNeutralStroke1 */ + border-color: #c7c7c7; /* colorNeutralStroke1Hover */ +} + +.switch:not(.disabled):not(.is-checked):active .switch-core { + background-color: #c7c7c7; /* colorNeutralStroke1Hover */ + border-color: #bdbdbd; /* colorNeutralStroke1Pressed */ +} + +/* On — colorCompoundBrandBackground / Hover / Pressed (scoped to .switch-core) */ +.switch .switch-core.bg-success { + --bs-success-rgb: var(--bs-primary-rgb); /* brand80 #0f6cbd */ +} + +.switch:not(.disabled):hover .switch-core.bg-success { + --bs-success-rgb: 17,94,163; /* brand70 colorCompoundBrandBackgroundHover */ +} + +.switch:not(.disabled):active .switch-core.bg-success { + --bs-success-rgb: 12,59,142; /* brand40 colorCompoundBrandBackgroundPressed */ +} + +/* Disabled — Fluent uses disabled tokens, not an opacity fade */ +.switch.disabled { + opacity: 1; +} + +.switch.disabled .switch-core { + background-color: var(--bb-disabled-bg) !important; /* colorNeutralBackgroundDisabled */ + border-color: #e0e0e0 !important; /* colorNeutralStrokeDisabled */ + cursor: not-allowed; +} + +.switch.disabled .switch-core:after { + background-color: #bdbdbd; /* colorNeutralForegroundDisabled thumb */ +} + +.switch.disabled .switch-core:before, +.switch.disabled .switch-label { + color: #bdbdbd; /* colorNeutralForegroundDisabled */ + cursor: not-allowed; +} + +[data-bs-theme='dark'] .switch:not(.disabled):not(.is-checked):hover .switch-core { + background-color: #4d4d4d; /* grey30 */ + border-color: #757575; /* colorNeutralStroke1Hover (dark) */ +} + +[data-bs-theme='dark'] .switch:not(.disabled):not(.is-checked):active .switch-core { + background-color: #525252; /* grey32 */ + border-color: #757575; +} + +[data-bs-theme='dark'] .switch .switch-core.bg-success { + --bs-success-rgb: 71,158,245; /* brand100 colorCompoundBrandBackground (dark) */ +} + +[data-bs-theme='dark'] .switch:not(.disabled):hover .switch-core.bg-success { + --bs-success-rgb: 98,171,245; /* brand110 colorCompoundBrandBackgroundHover (dark) */ +} + +[data-bs-theme='dark'] .switch:not(.disabled):active .switch-core.bg-success { + --bs-success-rgb: 40,134,222; /* brand90 colorCompoundBrandBackgroundPressed (dark) */ +} + +[data-bs-theme='dark'] .switch.disabled .switch-core { + border-color: #424242 !important; /* colorNeutralStrokeDisabled (dark) */ +} + +[data-bs-theme='dark'] .switch.disabled .switch-core:after { + background-color: #5c5c5c; /* colorNeutralForegroundDisabled (dark) */ +} + +[data-bs-theme='dark'] .switch.disabled .switch-core:before, +[data-bs-theme='dark'] .switch.disabled .switch-label { + color: #5c5c5c; /* colorNeutralForegroundDisabled (dark) */ +} + +/* ===== Transfer — Fluent list hover & neutral foregrounds ===== */ +.transfer-panel-item { + transition: background-color .2s cubic-bezier(.33,0,.67,1); /* durationNormal / curveEasyEase */ +} + +.transfer-panel-item:hover { + background-color: #f5f5f5; /* colorNeutralBackground1Hover */ +} + +/* Filter input hard-codes Element-UI greys — re-point to Fluent foreground tokens */ +.transfer-panel-filter .input-inner { + color: var(--bs-body-color); /* colorNeutralForeground1 */ +} + +.transfer-panel-filter .input-inner:hover:not(:focus) { + border-color: var(--bb-border-hover-color); /* colorNeutralStroke1Hover */ +} + +.transfer-panel-filter .input-prefix { + color: #616161; /* colorNeutralForeground3 */ +} + +[data-bs-theme='dark'] .transfer-panel-item:hover { + background-color: #3d3d3d; /* colorNeutralBackground1Hover (dark) */ +} + +[data-bs-theme='dark'] .transfer-panel-filter .input-prefix { + color: #adadad; /* colorNeutralForeground3 (dark) */ +} + +/* ===== Segmented — Fluent neutral hover/pressed/disabled ===== + * Component default pressed is lighter than hover; Fluent pressed is darker. + */ +.segmented { + --bb-segmented-item-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ + --bb-segmented-item-active-bg: #e0e0e0; /* colorNeutralBackground1Pressed */ +} + +.segmented .segmented-item.disabled { + color: #bdbdbd; /* colorNeutralForegroundDisabled */ +} + +[data-bs-theme='dark'] .segmented { + --bb-segmented-item-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (dark) */ + --bb-segmented-item-active-bg: #4d4d4d; /* colorNeutralBackground1Pressed (dark, grey30) */ +} + +[data-bs-theme='dark'] .segmented .segmented-item.disabled { + color: #5c5c5c; /* colorNeutralForegroundDisabled (dark) */ +} + +/* ===== Light — Fluent brand / status ramps (indicator dot) ===== */ +.bb-light { + --bb-light-primary-bg: radial-gradient(circle, #62abf5, #0f6cbd, #0c3b5e); /* brand110/80/40 */ + --bb-light-primary-hover-bg: radial-gradient(circle, #77b7f7, #115ea3, #0c3b5e); /* brand120/70/40 */ + --bb-light-success-bg: radial-gradient(circle, #54b054, #107c10, #094509); /* green tint30/base/shade30 */ + --bb-light-success-hover-bg: radial-gradient(circle, #9fd89f, #0e700e, #094509); /* green tint60/shade10/shade30 */ + --bb-light-info-bg: radial-gradient(circle, #56bfd7, #0099bc, #005669); /* cyan tint30/base/shade30 */ + --bb-light-info-hover-bg: radial-gradient(circle, #a4deeb, #008aa9, #005669); /* cyan tint60/shade10/shade30 */ + --bb-light-warning-bg: radial-gradient(circle, #faa06b, #f7630c, #8a3707); /* orange tint30/base/shade30 */ + --bb-light-warning-hover-bg: radial-gradient(circle, #fdcfb4, #de590b, #8a3707); /* orange tint60/shade10/shade30 */ + --bb-light-danger-bg: radial-gradient(circle, #dc626d, #c50f1f, #6e0811); /* cranberry tint30/base/shade30 */ + --bb-light-danger-hover-bg: radial-gradient(circle, #eeacb2, #b10e1c, #6e0811); /* cranberry tint60/shade10/shade30 */ + --bb-light-secondary-bg: radial-gradient(circle, #b3b3b3, #757575, #424242); /* neutral grey ramp */ + --bb-light-secondary-hover-bg: radial-gradient(circle, #d1d1d1, #616161, #424242); + --bb-light-dark-bg: radial-gradient(circle, #757575, #424242, #1a1a1a); /* neutral dark greys */ + --bb-light-dark-hover-bg: radial-gradient(circle, #999999, #242424, #1a1a1a); +} + +/* Flash animations — component keyframes use the old Bootstrap palette; + * re-declare with Fluent ramps (later keyframe definition wins) */ +@keyframes light-primary { + 0%, 55% { + background: radial-gradient(circle, #62abf5, #0f6cbd, #0c3b5e); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} + +@keyframes light-success { + 0%, 55% { + background: radial-gradient(circle, #54b054, #107c10, #094509); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} + +@keyframes light-info { + 0%, 55% { + background: radial-gradient(circle, #56bfd7, #0099bc, #005669); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} + +@keyframes light-warning { + 0%, 55% { + background: radial-gradient(circle, #faa06b, #f7630c, #8a3707); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} + +@keyframes light-danger { + 0%, 55% { + background: radial-gradient(circle, #dc626d, #c50f1f, #6e0811); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} + +@keyframes light-secondary { + 0%, 55% { + background: radial-gradient(circle, #b3b3b3, #757575, #424242); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} + +@keyframes light-dark { + 0%, 55% { + background: radial-gradient(circle, #757575, #424242, #1a1a1a); + } + + 100% { + background: radial-gradient(circle, #fff, #aaa, #333); + } +} diff --git a/src/BootstrapBlazor/wwwroot/css/fluent.min.css b/src/BootstrapBlazor/wwwroot/css/fluent.min.css index e83ad1e3f7a..c64fb3fa472 100644 --- a/src/BootstrapBlazor/wwwroot/css/fluent.min.css +++ b/src/BootstrapBlazor/wwwroot/css/fluent.min.css @@ -8,656 +8,4 @@ * Usage: add after bootstrap.blazor.bundle.min.css * */ - -/* ===== Light theme ===== */ -:root, [data-bs-theme='light'] { - /* Neutral colors — Fluent global grey ramp */ - --bs-gray: #616161; - --bs-gray-dark: #242424; - --bs-gray-100: #f5f5f5; - --bs-gray-200: #f0f0f0; - --bs-gray-300: #e0e0e0; - --bs-gray-400: #d1d1d1; - --bs-gray-500: #b3b3b3; - --bs-gray-600: #757575; - --bs-gray-700: #616161; - --bs-gray-800: #424242; - --bs-gray-900: #242424; - - /* Theme colors — Fluent brandWeb ramp & shared palettes */ - --bs-primary: #0f6cbd; /* colorBrandBackground (brand80) */ - --bs-secondary: #ebebeb; /* colorNeutralBackground5 (grey92) */ - --bs-success: #107c10; /* colorStatusSuccessBackground3 (green) */ - --bs-info: #0099bc; /* colorPaletteCyanBackground3 */ - --bs-warning: #f7630c; /* colorStatusWarningBackground3 (orange) */ - --bs-danger: #c50f1f; /* colorStatusDangerBackground3 (cranberry) */ - --bs-light: #fafafa; /* grey98 */ - --bs-dark: #242424; /* grey14 */ - --bs-primary-rgb: 15,108,189; - --bs-secondary-rgb: 235,235,235; - --bs-success-rgb: 16,124,16; - --bs-info-rgb: 0,153,188; - --bs-warning-rgb: 247,99,12; - --bs-danger-rgb: 197,15,31; - --bs-light-rgb: 250,250,250; - --bs-dark-rgb: 36,36,36; - - /* Body — colorNeutralForeground1 / colorNeutralBackground1 */ - --bs-body-color: #242424; - --bs-body-color-rgb: 36,36,36; - --bs-body-bg: #ffffff; - --bs-body-bg-rgb: 255,255,255; - - /* Links — colorBrandForegroundLink */ - --bs-link-color: #115ea3; /* brand70 */ - --bs-link-color-rgb: 17,94,163; - --bs-link-hover-color: #0f548c; /* brand60 */ - --bs-link-hover-color-rgb: 15,84,140; - - /* Typography — fontFamilyBase / fontFamilyMonospace */ - --bs-font-sans-serif: 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif; - --bs-font-monospace: Consolas, 'Courier New', Courier, monospace; - - /* Borders — colorNeutralStroke1 & borderRadius tokens */ - --bs-border-color: #d1d1d1; - --bs-border-radius-sm: 2px; - --bs-border-radius: 4px; - --bs-border-radius-lg: 6px; - --bs-border-radius-xl: 8px; - --bs-border-radius-xxl: 12px; - - /* Shadows — Fluent shadow tokens (ambient 0.12 / key 0.14) */ - --bs-box-shadow-sm: 0 0 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.14); - --bs-box-shadow: 0 0 2px rgba(0,0,0,0.12), 0 8px 16px rgba(0,0,0,0.14); - --bs-box-shadow-lg: 0 0 8px rgba(0,0,0,0.12), 0 32px 64px rgba(0,0,0,0.14); - - /* Text emphasis — Fluent foreground tokens */ - --bs-primary-text-emphasis: #0f6cbd; /* colorBrandForeground1 */ - --bs-secondary-text-emphasis: #616161; /* colorNeutralForeground3 */ - --bs-success-text-emphasis: #0e700e; /* colorStatusSuccessForeground1 */ - --bs-info-text-emphasis: #008aa9; /* colorPaletteCyanForeground1 */ - --bs-warning-text-emphasis: #bc4b09; /* colorStatusWarningForeground1 */ - --bs-danger-text-emphasis: #b10e1c; /* colorStatusDangerForeground1 */ - --bs-light-text-emphasis: #616161; - --bs-dark-text-emphasis: #242424; - - /* Background subtle — Fluent Background1/Background2 tokens */ - --bs-primary-bg-subtle: #ebf3fc; /* colorBrandBackground2 (brand160) */ - --bs-secondary-bg-subtle: #f5f5f5; - --bs-success-bg-subtle: #f1faf1; /* colorStatusSuccessBackground1 */ - --bs-info-bg-subtle: #f2fafc; /* colorPaletteCyanBackground1 */ - --bs-warning-bg-subtle: #fff9f5; /* colorStatusWarningBackground1 */ - --bs-danger-bg-subtle: #fdf3f4; /* colorStatusDangerBackground1 */ - --bs-light-bg-subtle: #fafafa; - --bs-dark-bg-subtle: #e0e0e0; - - /* Border subtle — Fluent Border1/Stroke2 tokens */ - --bs-primary-border-subtle: #b4d6fa; /* colorBrandStroke2 (brand140) */ - --bs-secondary-border-subtle: #e0e0e0; - --bs-success-border-subtle: #9fd89f; /* colorStatusSuccessBorder1 */ - --bs-info-border-subtle: #a4deeb; /* colorPaletteCyanBorder1 */ - --bs-warning-border-subtle: #fdcfb4; /* colorStatusWarningBorder1 */ - --bs-danger-border-subtle: #eeacb2; /* colorStatusDangerBorder1 */ - --bs-light-border-subtle: #f0f0f0; - --bs-dark-border-subtle: #b3b3b3; - - /* BootstrapBlazor root variables */ - --bb-primary-color: #0f6cbd; - --bb-primary-color-rgb: 15,108,189; - --bb-disabled-bg: #f0f0f0; /* colorNeutralBackgroundDisabled */ - --bb-border-focus-color: #0f6cbd; /* colorCompoundBrandStroke */ - --bb-border-hover-color: #c7c7c7; /* colorNeutralStroke1Hover */ - --bb-shadow: 0 0 2px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.14); /* shadow4 */ - --bb-hover-shadow: 0 0 2px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.14); /* shadow8 */ -} - -/* ===== Dark theme ===== */ -[data-bs-theme='dark'] { - /* Text emphasis — Fluent dark foreground tokens */ - --bs-primary-text-emphasis: #479ef5; /* colorBrandForeground1 (brand100) */ - --bs-secondary-text-emphasis: #adadad; /* colorNeutralForeground3 (grey68) */ - --bs-success-text-emphasis: #54b054; /* colorStatusSuccessForeground1 (green tint30) */ - --bs-info-text-emphasis: #56bfd7; /* colorPaletteCyanForeground1 (cyan tint30) */ - --bs-warning-text-emphasis: #faa06b; /* colorStatusWarningForeground1 (orange tint30) */ - --bs-danger-text-emphasis: #dc626d; /* colorStatusDangerForeground1 (cranberry tint30) */ - --bs-light-text-emphasis: #f5f5f5; - --bs-dark-text-emphasis: #d6d6d6; - - /* Background subtle — Fluent dark shade40 tokens */ - --bs-primary-bg-subtle: #082338; /* colorBrandBackground2 (brand20) */ - --bs-secondary-bg-subtle: #1f1f1f; /* grey12 */ - --bs-success-bg-subtle: #052505; /* colorStatusSuccessBackground1 */ - --bs-info-bg-subtle: #002e38; /* colorPaletteCyanBackground1 */ - --bs-warning-bg-subtle: #4a1e04; /* colorStatusWarningBackground1 */ - --bs-danger-bg-subtle: #3b0509; /* colorStatusDangerBackground1 */ - --bs-light-bg-subtle: #3d3d3d; /* grey24 */ - --bs-dark-bg-subtle: #141414; /* grey8 */ - - /* Border subtle — Fluent dark stroke tokens */ - --bs-primary-border-subtle: #0e4775; /* colorBrandStroke2 (brand50) */ - --bs-secondary-border-subtle: #424242; /* grey26 */ - --bs-success-border-subtle: #094509; /* green shade30 */ - --bs-info-border-subtle: #005669; /* cyan shade30 */ - --bs-warning-border-subtle: #8a3707; /* orange shade30 */ - --bs-danger-border-subtle: #6e0811; /* cranberry shade30 */ - --bs-light-border-subtle: #525252; - --bs-dark-border-subtle: #3d3d3d; - - /* Body — colorNeutralForeground1 / colorNeutralBackground1 (dark) */ - --bs-body-color: #ffffff; - --bs-body-color-rgb: 255,255,255; - --bs-body-bg: #292929; /* grey16 */ - --bs-body-bg-rgb: 41,41,41; - - /* Links — colorBrandForegroundLink (dark) */ - --bs-link-color: #479ef5; /* brand100 */ - --bs-link-color-rgb: 71,158,245; - --bs-link-hover-color: #62abf5; /* brand110 */ - --bs-link-hover-color-rgb: 98,171,245; - - /* Borders & shadows — colorNeutralStroke1 & dark shadow tokens (ambient 0.24 / key 0.28) */ - --bs-border-color: #666666; /* grey40 */ - --bs-box-shadow-sm: 0 0 2px rgba(0,0,0,0.24), 0 1px 2px rgba(0,0,0,0.28); - --bs-box-shadow: 0 0 2px rgba(0,0,0,0.24), 0 8px 16px rgba(0,0,0,0.28); - --bs-box-shadow-lg: 0 0 8px rgba(0,0,0,0.24), 0 32px 64px rgba(0,0,0,0.28); - - /* BootstrapBlazor root variables */ - --bb-disabled-bg: #141414; /* colorNeutralBackgroundDisabled (grey8) */ - --bb-border-focus-color: #479ef5; /* colorCompoundBrandStroke (brand100) */ - --bb-border-hover-color: #757575; /* colorNeutralStroke1Hover (grey46) */ - --bb-shadow: 0 0 2px rgba(0,0,0,0.24), 0 2px 4px rgba(0,0,0,0.28); - --bb-hover-shadow: 0 0 2px rgba(0,0,0,0.24), 0 4px 8px rgba(0,0,0,0.28); -} - -/* ===== Component variable overrides — re-point hard-coded accent colors to theme tokens ===== */ - -.layout { - --bb-layout-header-background: var(--bs-primary); - --bb-layout-header-border-color: var(--bs-primary); - --bb-layout-headerbar-background: var(--bs-primary); - --bb-layout-sidebar-banner-background: var(--bs-primary); - --bb-layout-banner-border-color: var(--bs-primary); - --bb-layout-menu-user-banner-background: var(--bs-primary); - --bb-layout-menu-user-border-color: #115ea3; /* brand70 */ - --bb-layout-menu-item-hover-bg: var(--bs-primary); - --bb-layout-logo-bg: var(--bs-primary); -} - -.menu { - --bb-menu-active-color: var(--bs-primary); - --bb-menu-bar-bg: var(--bs-primary); - --bb-menu-item-hover-bg: var(--bs-primary); -} - -.tabs { - --bb-tabs-item-active-color: var(--bs-primary); - --bb-tabs-item-hover-color: var(--bs-primary); - --bb-tabs-bar-bg: var(--bs-primary); -} - -.calendar { - --bb-calendar-toolbar-hover-color: var(--bs-primary); - --bb-calendar-toolbar-focus-color: var(--bs-primary); - --bb-calendar-toolbar-active-color: var(--bs-primary); - --bb-calendar-today-color: var(--bs-primary); - --bb-calendar-today-border-color: var(--bs-primary); - --bb-calendar-selected-bg: var(--bs-primary); - --bb-calendar-week-today-color: var(--bs-primary); - --bb-calendar-week-today-border-color: var(--bs-primary); -} - -[data-bs-theme='dark'] .calendar { - --bb-calendar-selected-bg: var(--bs-primary); -} - -.picker-panel { - --bb-picker-hover-color: var(--bs-primary); - --bb-picker-panel-today-bg: var(--bs-primary); -} - -.bb-clock-picker { - --bb-time-text-color: var(--bs-primary); -} - -.table-container { - --bb-table-header-icon-color: rgba(var(--bs-body-color-rgb),.4); - --bb-table-header-icon-active-color: var(--bs-primary); - --bb-table-header-icon-hover-color: var(--bs-body-color); - --bb-table-header-icon-hover-bg: rgba(var(--bs-body-color-rgb),.1); - --bb-table-header-hover-bg: rgba(var(--bs-body-color-rgb),.08); -} - -.slide-button { - --bb-slide-list-bg: var(--bs-body-bg); - --bb-slide-item-active-bg: var(--bs-primary); -} - -.drawer { - --bb-drawer-bar-hover-color: var(--bs-primary); - --bb-drawer-bar-drag-color: var(--bs-primary); -} - -.filter-item { - --bb-filter-item-btn-hover-color: var(--bs-primary); - --bb-filter-item-btn-hover-border-color: var(--bs-primary); -} - -.listview { - --bb-lv-header-bg: var(--bs-secondary-bg); - --bb-lv-item-border-hover-color: var(--bs-primary); -} - -.dropdown-logout { - --bb-logout-menu-border-color: var(--bs-primary); - --bb-logout-user-bg: var(--bs-primary); -} - -.ribbon-tab { - --bb-ribbon-menu-hover-color: var(--bs-primary); - --bb-ribbon-button-hover-bg: rgba(var(--bs-primary-rgb),.24); - --bb-ribbon-button-hover-border-color: rgba(var(--bs-primary-rgb),.5); - --bb-ribbon-button-active-bg: rgba(var(--bs-primary-rgb),.32); - --bb-ribbon-button-active-border-color: rgba(var(--bs-primary-rgb),.6); -} - -.select, -.popover-dropdown { - --bb-dropdown-link-pre-active-bg: var(--bs-primary); -} - -.tree-view { - --bb-tree-item-hover-color: var(--bs-primary); - --bb-tree-drop-preview-color: var(--bs-primary); -} - -.upload { - --bb-upload-body-list-item-hover-color: var(--bs-primary); -} - -.bb-transfer { - --bb-transfer-filter-focus-border-color: var(--bs-primary); -} - -.layout-split-bar .layout-split-bar-body { - --bb-split-bar-body-drag-hover-bg: var(--bs-primary); -} - -.search.auto-complete { - --bb-search-prefix-icon-color: var(--bs-secondary-color); -} - -.layout-gotop { - --bb-gotop-bg: rgba(var(--bs-body-color-rgb),.08); - --bb-gotop-hover-bg: var(--bs-primary); - --bb-gotop-hover-color: #fff; -} - -.captcha { - --bb-captcha-bar-invalid-bg: var(--bs-danger); - --bb-captcha-bar-invalid-mask-bg: rgba(var(--bs-danger-rgb),.15); - --bb-captcha-bar-valid-bg: var(--bs-success); - --bb-captcha-bar-valid-mask-bg: rgba(var(--bs-success-rgb),.15); -} - -.switch { - --bb-switch-bg: #e0e0e0; /* colorNeutralStroke2 */ - --bb-switch-border-color: #c7c7c7; /* colorNeutralStroke1Hover */ -} - -[data-bs-theme='dark'] .switch { - --bb-switch-bg: #3d3d3d; /* grey24 */ - --bb-switch-border-color: #666666; /* colorNeutralStroke1 (dark) */ - --bb-switch-inner-off-text-color: #d6d6d6; -} - -/* ===== Buttons — colorBrandBackground / colorStatus*Background3 ===== */ -.btn-primary { - --bs-btn-bg: var(--bs-primary); - --bs-btn-border-color: var(--bs-primary); - --bs-btn-hover-bg: #115ea3; /* colorBrandBackgroundHover */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #0c3b5e; /* colorBrandBackgroundPressed */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-bg: var(--bs-primary); - --bs-btn-disabled-border-color: var(--bs-primary); -} - -.btn-secondary { - --bs-btn-color: #424242; /* colorNeutralForeground2 */ - --bs-btn-bg: var(--bs-secondary); - --bs-btn-border-color: var(--bs-secondary); - --bs-btn-hover-color: var(--bs-btn-color); - --bs-btn-hover-bg: #f5f5f5; /* colorNeutralBackground5Hover */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-color); - --bs-btn-active-bg: #f0f0f0; /* colorNeutralBackground5Pressed */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-btn-color); - --bs-btn-disabled-bg: var(--bs-secondary); - --bs-btn-disabled-border-color: var(--bs-secondary); -} - -.btn-success { - --bs-btn-bg: var(--bs-success); - --bs-btn-border-color: var(--bs-success); - --bs-btn-hover-bg: #0e700e; /* green shade10 */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #0c5e0c; /* green shade20 */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-bg: var(--bs-success); - --bs-btn-disabled-border-color: var(--bs-success); -} - -.btn-info { - --bs-btn-color: #fff; - --bs-btn-bg: var(--bs-info); - --bs-btn-border-color: var(--bs-info); - --bs-btn-hover-color: var(--bs-btn-color); - --bs-btn-hover-bg: #008aa9; /* cyan shade10 */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-color); - --bs-btn-active-bg: #00748f; /* cyan shade20 */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-btn-color); - --bs-btn-disabled-bg: var(--bs-info); - --bs-btn-disabled-border-color: var(--bs-info); -} - -.btn-warning { - --bs-btn-color: #fff; - --bs-btn-bg: var(--bs-warning); - --bs-btn-border-color: var(--bs-warning); - --bs-btn-hover-color: var(--bs-btn-color); - --bs-btn-hover-bg: #de590b; /* orange shade10 */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-color); - --bs-btn-active-bg: #bc4b09; /* orange shade20 */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-btn-color); - --bs-btn-disabled-bg: var(--bs-warning); - --bs-btn-disabled-border-color: var(--bs-warning); -} - -.btn-danger { - --bs-btn-bg: var(--bs-danger); - --bs-btn-border-color: var(--bs-danger); - --bs-btn-hover-bg: #b10e1c; /* colorStatusDangerBackground3Hover */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #960b18; /* colorStatusDangerBackground3Pressed */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-bg: var(--bs-danger); - --bs-btn-disabled-border-color: var(--bs-danger); -} - -.btn-dark { - --bs-btn-bg: var(--bs-dark); - --bs-btn-border-color: var(--bs-dark); - --bs-btn-hover-bg: #3d3d3d; /* grey24 */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #1a1a1a; /* grey10 */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-bg: var(--bs-dark); - --bs-btn-disabled-border-color: var(--bs-dark); -} - -.btn-light { - --bs-btn-color: #242424; - --bs-btn-bg: var(--bs-light); - --bs-btn-border-color: var(--bs-light); - --bs-btn-hover-color: var(--bs-btn-color); - --bs-btn-hover-bg: #f0f0f0; /* grey94 */ - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-color); - --bs-btn-active-bg: #e0e0e0; /* grey88 */ - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-btn-color); - --bs-btn-disabled-bg: var(--bs-light); - --bs-btn-disabled-border-color: var(--bs-light); -} - -.btn-link { - text-decoration: none; - --bs-btn-color: var(--bs-link-color); - --bs-btn-hover-color: var(--bs-link-hover-color); -} - -.btn-outline-primary { - --bs-btn-color: var(--bs-primary); - --bs-btn-border-color: var(--bs-primary); - --bs-btn-hover-bg: #115ea3; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #0c3b5e; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-primary); - --bs-btn-disabled-border-color: var(--bs-primary); -} - -.btn-outline-secondary { - --bs-btn-color: #616161; - --bs-btn-border-color: #d1d1d1; /* colorNeutralStroke1 */ - --bs-btn-hover-color: #424242; - --bs-btn-hover-bg: #f5f5f5; - --bs-btn-hover-border-color: #c7c7c7; /* colorNeutralStroke1Hover */ - --bs-btn-active-color: #424242; - --bs-btn-active-bg: #e0e0e0; - --bs-btn-active-border-color: var(--bs-btn-hover-border-color); - --bs-btn-disabled-color: var(--bs-btn-color); - --bs-btn-disabled-border-color: var(--bs-btn-border-color); -} - -.btn-outline-success { - --bs-btn-color: var(--bs-success); - --bs-btn-border-color: var(--bs-success); - --bs-btn-hover-bg: #0e700e; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #0c5e0c; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-success); - --bs-btn-disabled-border-color: var(--bs-success); -} - -.btn-outline-info { - --bs-btn-color: var(--bs-info); - --bs-btn-border-color: var(--bs-info); - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #008aa9; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-hover-color); - --bs-btn-active-bg: #00748f; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-info); - --bs-btn-disabled-border-color: var(--bs-info); -} - -.btn-outline-warning { - --bs-btn-color: var(--bs-warning); - --bs-btn-border-color: var(--bs-warning); - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: #de590b; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-hover-color); - --bs-btn-active-bg: #bc4b09; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-warning); - --bs-btn-disabled-border-color: var(--bs-warning); -} - -.btn-outline-danger { - --bs-btn-color: var(--bs-danger); - --bs-btn-border-color: var(--bs-danger); - --bs-btn-hover-bg: #b10e1c; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #960b18; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-danger); - --bs-btn-disabled-border-color: var(--bs-danger); -} - -.btn-outline-dark { - --bs-btn-color: var(--bs-dark); - --bs-btn-border-color: var(--bs-dark); - --bs-btn-hover-bg: #3d3d3d; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-bg: #1a1a1a; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-dark); - --bs-btn-disabled-border-color: var(--bs-dark); -} - -.btn-outline-light { - --bs-btn-color: var(--bs-light); - --bs-btn-border-color: var(--bs-light); - --bs-btn-hover-color: #242424; - --bs-btn-hover-bg: #f0f0f0; - --bs-btn-hover-border-color: var(--bs-btn-hover-bg); - --bs-btn-active-color: var(--bs-btn-hover-color); - --bs-btn-active-bg: #e0e0e0; - --bs-btn-active-border-color: var(--bs-btn-hover-bg); - --bs-btn-disabled-color: var(--bs-light); - --bs-btn-disabled-border-color: var(--bs-light); -} - -/* ===== Table — Fluent DataGrid row hover (colorNeutralBackground1Hover) ===== */ -.table { - --bs-table-striped-bg: rgba(250, 250, 250, 0.75); - --bs-table-hover-bg: #f5f5f5; -} - -[data-bs-theme='dark'] .table { - --bs-table-striped-bg: rgba(255, 255, 255, 0.03); - --bs-table-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ -} - -/* ===== Modal — Fluent dialog shadow64 ===== */ -.modal-content { - box-shadow: 0 0 8px rgba(0,0,0,0.12), 0 32px 64px rgba(0,0,0,0.14); -} - -[data-bs-theme='dark'] .modal-content { - box-shadow: 0 0 8px rgba(0,0,0,0.24), 0 32px 64px rgba(0,0,0,0.28); -} - -/* ===== Validation — Fluent status strokes ===== */ -.form-control.is-valid, .was-validated .form-control:valid { - border-color: var(--bs-success); -} - -.was-validated .form-control:valid:focus, .form-control.is-valid:focus { - border-color: var(--bs-success); - box-shadow: none; -} - -.form-control.is-invalid, .was-validated .form-control:invalid { - border-color: var(--bs-danger); -} - -.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { - border-color: var(--bs-danger); - box-shadow: none; -} - -/* ===== Flyout surfaces — Fluent Menu / Popover / Tooltip ===== - * Native Fluent flyouts: no arrow, borderRadiusMedium (4px), - * shadow16, colorNeutralStroke1 border, neutral item hover/selected. - * Covers: Select, MultiSelect, SelectTree/Table/Object, Cascader, - * AutoComplete, AutoFill, Search, Dropdown, TopMenu, Logout, Tab, - * Table toolbars and filter buttons. - */ -.dropdown-menu { - --bs-dropdown-bg: var(--bs-body-bg); - --bs-dropdown-color: var(--bs-body-color); - --bs-dropdown-border-color: var(--bs-border-color); - --bs-dropdown-border-radius: var(--bs-border-radius); /* 4px */ - --bs-dropdown-box-shadow: var(--bs-box-shadow); /* shadow16 */ - --bs-dropdown-divider-bg: #e0e0e0; /* colorNeutralStroke2 */ - --bs-dropdown-padding-x: 4px; - --bs-dropdown-padding-y: 4px; - --bs-dropdown-item-padding-x: 12px; - --bs-dropdown-item-padding-y: 6px; - --bs-dropdown-link-hover-color: var(--bs-body-color); - --bs-dropdown-link-hover-bg: #f5f5f5; /* colorNeutralBackground1Hover */ - --bs-dropdown-link-active-color: var(--bs-body-color); - --bs-dropdown-link-active-bg: #ebebeb; /* colorNeutralBackground1Selected */ - --bs-dropdown-link-disabled-color: #bdbdbd; /* colorNeutralForegroundDisabled */ - --bs-dropdown-header-color: #616161; /* colorNeutralForeground3 */ - --bb-select-search-border-color: #e0e0e0; -} - -/* Fluent menus have no arrow */ -.dropdown-menu-arrow { - display: none !important; -} - -/* Fluent flyout offset: 4px */ -.cascade, -.select { - --bb-select-dropdown-menu-margin-top: 4px; -} - -/* Selected option — Fluent Listbox: reserved gutter + brand check mark */ -.select:not(.multi-select) .dropdown-menu .dropdown-item { - position: relative; - padding-inline-start: 28px; -} - -.select:not(.multi-select) .dropdown-menu .dropdown-item.active::before { - content: "\2713"; - position: absolute; - inset-inline-start: 10px; - color: var(--bs-primary); /* colorCompoundBrandForeground */ -} - -/* Popover card — Fluent Popover keeps its arrow, radius 4px, shadow16 */ -.popover { - --bs-popover-bg: var(--bs-body-bg); - --bs-popover-color: var(--bs-body-color); - --bs-popover-border-color: var(--bs-border-color); - --bs-popover-border-radius: var(--bs-border-radius); - --bs-popover-box-shadow: var(--bs-box-shadow); - --bs-popover-header-bg: transparent; - --bs-popover-header-color: var(--bs-body-color); - --bs-popover-header-padding-x: 12px; - --bs-popover-header-padding-y: 10px; - --bs-popover-body-color: var(--bs-body-color); - --bs-popover-body-padding-x: 12px; - --bs-popover-body-padding-y: 10px; -} - -.popover-header { - font-weight: 600; - border-block-end: none; - padding-block-end: 0; -} - -/* Picker panels (DateTimePicker/DateTimeRange/SelectObject/SelectTable/filters) - * are menu-like flyouts in Fluent — no arrow */ -.popover-dropdown .popover-arrow { - display: none; -} - -/* Tooltip — Fluent dark surface, radius 4px, no arrow */ -.tooltip { - --bs-tooltip-bg: #242424; /* grey14 */ - --bs-tooltip-color: #ffffff; - --bs-tooltip-border-radius: var(--bs-border-radius); - --bs-tooltip-padding-x: 8px; - --bs-tooltip-padding-y: 4px; - --bs-tooltip-opacity: 1; -} - -.tooltip .tooltip-arrow { - display: none; -} - -[data-bs-theme='dark'] .dropdown-menu { - --bs-dropdown-divider-bg: #4d4d4d; /* colorNeutralStroke2 (dark) */ - --bs-dropdown-link-hover-bg: #3d3d3d; /* colorNeutralBackground1Hover (grey24) */ - --bs-dropdown-link-active-bg: #3d3d3d; /* colorNeutralBackground1Selected (dark) */ - --bs-dropdown-link-disabled-color: #5c5c5c; /* colorNeutralForegroundDisabled (grey36) */ - --bs-dropdown-header-color: #adadad; /* colorNeutralForeground3 (dark) */ - --bb-select-search-border-color: #4d4d4d; -} - -[data-bs-theme='dark'] .tooltip .tooltip-inner { - border: var(--bs-border-width) solid var(--bs-border-color); -} +:root,[data-bs-theme=light]{--bs-gray:#616161;--bs-gray-dark:#242424;--bs-gray-100:#f5f5f5;--bs-gray-200:#f0f0f0;--bs-gray-300:#e0e0e0;--bs-gray-400:#d1d1d1;--bs-gray-500:#b3b3b3;--bs-gray-600:#757575;--bs-gray-700:#616161;--bs-gray-800:#424242;--bs-gray-900:#242424;--bs-primary:#0f6cbd;--bs-secondary:#ebebeb;--bs-success:#107c10;--bs-info:#0099bc;--bs-warning:#f7630c;--bs-danger:#c50f1f;--bs-light:#fafafa;--bs-dark:#242424;--bs-primary-rgb:15,108,189;--bs-secondary-rgb:235,235,235;--bs-success-rgb:16,124,16;--bs-info-rgb:0,153,188;--bs-warning-rgb:247,99,12;--bs-danger-rgb:197,15,31;--bs-light-rgb:250,250,250;--bs-dark-rgb:36,36,36;--bs-body-color:#242424;--bs-body-color-rgb:36,36,36;--bs-body-bg:#ffffff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#242424;--bs-emphasis-color-rgb:36,36,36;--bs-secondary-color:#616161;--bs-secondary-color-rgb:97,97,97;--bs-tertiary-color:#707070;--bs-tertiary-color-rgb:112,112,112;--bs-secondary-bg:#f5f5f5;--bs-secondary-bg-rgb:245,245,245;--bs-tertiary-bg:#fafafa;--bs-tertiary-bg-rgb:250,250,250;--bs-code-color:#424242;--bs-focus-ring-color:rgba(15,108,189,0.25);--bs-form-valid-color:#107c10;--bs-form-valid-border-color:#107c10;--bs-form-invalid-color:#c50f1f;--bs-form-invalid-border-color:#c50f1f;--bs-link-color:#115ea3;--bs-link-color-rgb:17,94,163;--bs-link-hover-color:#0f548c;--bs-link-hover-color-rgb:15,84,140;--bs-font-sans-serif:'Segoe UI','Segoe UI Web (West European)',-apple-system,BlinkMacSystemFont,Roboto,'Helvetica Neue',sans-serif;--bs-font-monospace:Consolas,'Courier New',Courier,monospace;--bs-border-color:#d1d1d1;--bs-border-radius-sm:2px;--bs-border-radius:4px;--bs-border-radius-lg:6px;--bs-border-radius-xl:8px;--bs-border-radius-xxl:12px;--bs-box-shadow-sm:0 0 2px rgba(0,0,0,0.12),0 1px 2px rgba(0,0,0,0.14);--bs-box-shadow:0 0 2px rgba(0,0,0,0.12),0 8px 16px rgba(0,0,0,0.14);--bs-box-shadow-lg:0 0 8px rgba(0,0,0,0.12),0 32px 64px rgba(0,0,0,0.14);--bs-primary-text-emphasis:#0f6cbd;--bs-secondary-text-emphasis:#616161;--bs-success-text-emphasis:#0e700e;--bs-info-text-emphasis:#008aa9;--bs-warning-text-emphasis:#bc4b09;--bs-danger-text-emphasis:#b10e1c;--bs-light-text-emphasis:#616161;--bs-dark-text-emphasis:#242424;--bs-primary-bg-subtle:#ebf3fc;--bs-secondary-bg-subtle:#f5f5f5;--bs-success-bg-subtle:#f1faf1;--bs-info-bg-subtle:#f2fafc;--bs-warning-bg-subtle:#fff9f5;--bs-danger-bg-subtle:#fdf3f4;--bs-light-bg-subtle:#fafafa;--bs-dark-bg-subtle:#e0e0e0;--bs-primary-border-subtle:#b4d6fa;--bs-secondary-border-subtle:#e0e0e0;--bs-success-border-subtle:#9fd89f;--bs-info-border-subtle:#a4deeb;--bs-warning-border-subtle:#fdcfb4;--bs-danger-border-subtle:#eeacb2;--bs-light-border-subtle:#f0f0f0;--bs-dark-border-subtle:#b3b3b3;--bb-primary-color:#0f6cbd;--bb-primary-color-rgb:15,108,189;--bb-disabled-bg:#f0f0f0;--bb-border-focus-color:#0f6cbd;--bb-border-hover-color:#c7c7c7;--bb-shadow:0 0 2px rgba(0,0,0,0.12),0 2px 4px rgba(0,0,0,0.14);--bb-hover-shadow:0 0 2px rgba(0,0,0,0.12),0 4px 8px rgba(0,0,0,0.14)}[data-bs-theme=dark]{--bs-primary-text-emphasis:#479ef5;--bs-secondary-text-emphasis:#adadad;--bs-success-text-emphasis:#54b054;--bs-info-text-emphasis:#56bfd7;--bs-warning-text-emphasis:#faa06b;--bs-danger-text-emphasis:#dc626d;--bs-light-text-emphasis:#f5f5f5;--bs-dark-text-emphasis:#d6d6d6;--bs-primary-bg-subtle:#082338;--bs-secondary-bg-subtle:#1f1f1f;--bs-success-bg-subtle:#052505;--bs-info-bg-subtle:#002e38;--bs-warning-bg-subtle:#4a1e04;--bs-danger-bg-subtle:#3b0509;--bs-light-bg-subtle:#3d3d3d;--bs-dark-bg-subtle:#141414;--bs-primary-border-subtle:#0e4775;--bs-secondary-border-subtle:#424242;--bs-success-border-subtle:#094509;--bs-info-border-subtle:#005669;--bs-warning-border-subtle:#8a3707;--bs-danger-border-subtle:#6e0811;--bs-light-border-subtle:#525252;--bs-dark-border-subtle:#3d3d3d;--bs-body-color:#ffffff;--bs-body-color-rgb:255,255,255;--bs-body-bg:#292929;--bs-body-bg-rgb:41,41,41;--bs-emphasis-color:#ffffff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:#adadad;--bs-secondary-color-rgb:173,173,173;--bs-tertiary-color:#999999;--bs-tertiary-color-rgb:153,153,153;--bs-secondary-bg:#141414;--bs-secondary-bg-rgb:20,20,20;--bs-tertiary-bg:#1f1f1f;--bs-tertiary-bg-rgb:31,31,31;--bs-code-color:#d6d6d6;--bs-focus-ring-color:rgba(71,158,245,0.25);--bs-form-valid-color:#54b054;--bs-form-valid-border-color:#54b054;--bs-form-invalid-color:#dc626d;--bs-form-invalid-border-color:#dc626d;--bs-link-color:#479ef5;--bs-link-color-rgb:71,158,245;--bs-link-hover-color:#62abf5;--bs-link-hover-color-rgb:98,171,245;--bs-border-color:#666666;--bs-box-shadow-sm:0 0 2px rgba(0,0,0,0.24),0 1px 2px rgba(0,0,0,0.28);--bs-box-shadow:0 0 2px rgba(0,0,0,0.24),0 8px 16px rgba(0,0,0,0.28);--bs-box-shadow-lg:0 0 8px rgba(0,0,0,0.24),0 32px 64px rgba(0,0,0,0.28);--bb-disabled-bg:#141414;--bb-border-focus-color:#479ef5;--bb-border-hover-color:#757575;--bb-shadow:0 0 2px rgba(0,0,0,0.24),0 2px 4px rgba(0,0,0,0.28);--bb-hover-shadow:0 0 2px rgba(0,0,0,0.24),0 4px 8px rgba(0,0,0,0.28)}.layout{--bb-layout-header-background:var(--bs-primary);--bb-layout-header-border-color:var(--bs-primary);--bb-layout-headerbar-background:var(--bs-primary);--bb-layout-sidebar-banner-background:var(--bs-primary);--bb-layout-banner-border-color:var(--bs-primary);--bb-layout-menu-user-banner-background:var(--bs-primary);--bb-layout-menu-user-border-color:#115ea3;--bb-layout-menu-item-hover-bg:var(--bs-primary);--bb-layout-logo-bg:var(--bs-primary);--bb-layout-logo-border-color:var(--bs-border-color)}[data-bs-theme=dark] .layout{--bb-layout-headerbar-background:#3d3d3d;--bb-layout-menu-item-hover-bg:#3d3d3d;--bb-layout-menu-user-border-color:var(--bs-border-color);--bb-layout-logo-bg:#3d3d3d}.menu{--bb-menu-active-color:var(--bs-primary);--bb-menu-bar-bg:var(--bs-primary);--bb-menu-item-hover-bg:var(--bs-primary);--bb-menu-item-hover-color:#ffffff;--bs-nav-link-disabled-color:#bdbdbd}[data-bs-theme=dark] .menu{--bb-menu-active-color:#479ef5;--bb-menu-bar-bg:#479ef5;--bs-nav-link-disabled-color:#5c5c5c}.tabs{--bb-tabs-item-active-color:var(--bs-primary);--bb-tabs-item-hover-color:var(--bs-primary);--bb-tabs-bar-bg:var(--bs-primary)}[data-bs-theme=dark] .tabs{--bb-tabs-item-active-color:#479ef5;--bb-tabs-item-hover-color:#479ef5;--bb-tabs-bar-bg:#479ef5}.calendar{--bb-calendar-toolbar-hover-color:var(--bs-primary);--bb-calendar-toolbar-focus-color:var(--bs-primary);--bb-calendar-toolbar-active-color:var(--bs-primary);--bb-calendar-today-color:var(--bs-primary);--bb-calendar-today-border-color:var(--bs-primary);--bb-calendar-selected-bg:var(--bs-primary);--bb-calendar-week-today-color:var(--bs-primary);--bb-calendar-week-today-border-color:var(--bs-primary);--bb-calendar-toolbar-hover-bg:#f5f5f5;--bb-calendar-toolbar-focus-bg:#e0e0e0;--bb-calendar-cell-hover-bg:#f5f5f5}[data-bs-theme=dark] .calendar{--bb-calendar-selected-bg:var(--bs-primary);--bb-calendar-toolbar-hover-bg:#3d3d3d;--bb-calendar-toolbar-focus-bg:#3d3d3d;--bb-calendar-cell-hover-bg:#3d3d3d}.picker-panel{--bb-picker-hover-color:var(--bs-primary);--bb-picker-panel-today-bg:var(--bs-primary);box-shadow:var(--bs-box-shadow)}.bb-clock-picker{--bb-time-text-color:var(--bs-primary);--bb-time-footer-btn-border:var(--bs-border-width) solid var(--bs-border-color);--bb-time-footer-btn-color:var(--bs-body-color);--bb-time-footer-btn-hover-border-color:var(--bs-primary);--bb-time-footer-btn-hover-bg-color:var(--bs-primary);--bb-time-footer-btn-active-color:var(--bs-primary);--bb-time-footer-btn-active-border-color:var(--bs-primary)}.table-container{--bb-table-header-icon-color:rgba(var(--bs-body-color-rgb),.4);--bb-table-header-icon-active-color:var(--bs-primary);--bb-table-header-icon-hover-color:var(--bs-body-color);--bb-table-header-icon-hover-bg:rgba(var(--bs-body-color-rgb),.1);--bb-table-header-hover-bg:rgba(var(--bs-body-color-rgb),.08);--bb-table-column-resizer-color:#e0e0e0;--bb-table-column-resizer-hover-color:var(--bs-primary)}.slide-button{--bb-slide-list-bg:var(--bs-body-bg);--bb-slide-item-active-bg:var(--bs-primary);--bb-slide-list-shadow:var(--bs-box-shadow);--bb-slide-list-border:var(--bs-border-width) solid var(--bs-border-color);--bb-slide-item-hover-bg:#f5f5f5}[data-bs-theme=dark] .slide-button{--bb-slide-item-hover-bg:#3d3d3d}.drawer{--bb-drawer-bar-hover-color:var(--bs-primary);--bb-drawer-bar-drag-color:var(--bs-primary);--bb-drawer-body-shadow:var(--bs-box-shadow)}.filter-item{--bb-filter-item-btn-hover-color:var(--bs-primary);--bb-filter-item-btn-hover-border-color:var(--bs-primary)}.listview{--bb-lv-header-bg:var(--bs-secondary-bg);--bb-lv-item-border-hover-color:var(--bs-primary)}.dropdown-logout{--bb-logout-menu-border-color:var(--bs-primary);--bb-logout-user-bg:var(--bs-primary)}.ribbon-tab{--bb-ribbon-menu-hover-color:var(--bs-primary);--bb-ribbon-button-hover-bg:rgba(var(--bs-primary-rgb),.24);--bb-ribbon-button-hover-border-color:rgba(var(--bs-primary-rgb),.5);--bb-ribbon-button-active-bg:rgba(var(--bs-primary-rgb),.32);--bb-ribbon-button-active-border-color:rgba(var(--bs-primary-rgb),.6)}.popover-dropdown,.select{--bb-dropdown-link-pre-active-bg:var(--bs-primary)}.tree-view{--bb-tree-item-hover-color:var(--bs-primary);--bb-tree-drop-preview-color:var(--bs-primary);--bb-tree-item-active-color:var(--bs-primary)}.upload{--bb-upload-body-list-item-hover-color:var(--bs-primary);--bb-upload-card-shadow:var(--bb-shadow)}.bb-transfer{--bb-transfer-filter-focus-border-color:var(--bs-primary)}.layout-split-bar .layout-split-bar-body{--bb-split-bar-body-drag-hover-bg:var(--bs-primary);--bb-split-bar-body-hover-bg:rgba(var(--bs-body-color-rgb),.08)}.search.auto-complete{--bb-search-prefix-icon-color:var(--bs-secondary-color)}.layout-gotop{--bb-gotop-bg:rgba(var(--bs-body-color-rgb),.08);--bb-gotop-hover-bg:var(--bs-primary);--bb-gotop-hover-color:#fff}.captcha{--bb-captcha-bar-invalid-bg:var(--bs-danger);--bb-captcha-bar-invalid-mask-bg:rgba(var(--bs-danger-rgb),.15);--bb-captcha-bar-valid-bg:var(--bs-success);--bb-captcha-bar-valid-mask-bg:rgba(var(--bs-success-rgb),.15);--bb-captcha-radius:var(--bs-border-radius);--bb-captcha-bar-shadow:var(--bs-box-shadow-sm)}.switch{--bb-switch-bg:#e0e0e0;--bb-switch-border-color:#c7c7c7;--bb-switch-inner-off-text-color:#616161}[data-bs-theme=dark] .switch{--bb-switch-bg:#3d3d3d;--bb-switch-border-color:#666666;--bb-switch-inner-off-text-color:#d6d6d6}.btn-primary{--bs-btn-bg:var(--bs-primary);--bs-btn-border-color:var(--bs-primary);--bs-btn-hover-bg:#115ea3;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#0c3b5e;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-bg:var(--bs-primary);--bs-btn-disabled-border-color:var(--bs-primary)}.btn-secondary{--bs-btn-color:#424242;--bs-btn-bg:var(--bs-secondary);--bs-btn-border-color:var(--bs-secondary);--bs-btn-hover-color:var(--bs-btn-color);--bs-btn-hover-bg:#f5f5f5;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-color);--bs-btn-active-bg:#f0f0f0;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-btn-color);--bs-btn-disabled-bg:var(--bs-secondary);--bs-btn-disabled-border-color:var(--bs-secondary)}.btn-success{--bs-btn-bg:var(--bs-success);--bs-btn-border-color:var(--bs-success);--bs-btn-hover-bg:#0e700e;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#0c5e0c;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-bg:var(--bs-success);--bs-btn-disabled-border-color:var(--bs-success)}.btn-info{--bs-btn-color:#fff;--bs-btn-bg:var(--bs-info);--bs-btn-border-color:var(--bs-info);--bs-btn-hover-color:var(--bs-btn-color);--bs-btn-hover-bg:#008aa9;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-color);--bs-btn-active-bg:#00748f;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-btn-color);--bs-btn-disabled-bg:var(--bs-info);--bs-btn-disabled-border-color:var(--bs-info)}.btn-warning{--bs-btn-color:#fff;--bs-btn-bg:var(--bs-warning);--bs-btn-border-color:var(--bs-warning);--bs-btn-hover-color:var(--bs-btn-color);--bs-btn-hover-bg:#de590b;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-color);--bs-btn-active-bg:#bc4b09;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-btn-color);--bs-btn-disabled-bg:var(--bs-warning);--bs-btn-disabled-border-color:var(--bs-warning)}.btn-danger{--bs-btn-bg:var(--bs-danger);--bs-btn-border-color:var(--bs-danger);--bs-btn-hover-bg:#b10e1c;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#960b18;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-bg:var(--bs-danger);--bs-btn-disabled-border-color:var(--bs-danger)}.btn-dark{--bs-btn-bg:var(--bs-dark);--bs-btn-border-color:var(--bs-dark);--bs-btn-hover-bg:#3d3d3d;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#1a1a1a;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-bg:var(--bs-dark);--bs-btn-disabled-border-color:var(--bs-dark)}.btn-light{--bs-btn-color:#242424;--bs-btn-bg:var(--bs-light);--bs-btn-border-color:var(--bs-light);--bs-btn-hover-color:var(--bs-btn-color);--bs-btn-hover-bg:#f0f0f0;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-color);--bs-btn-active-bg:#e0e0e0;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-btn-color);--bs-btn-disabled-bg:var(--bs-light);--bs-btn-disabled-border-color:var(--bs-light)}.btn-link{text-decoration:none;--bs-btn-color:var(--bs-link-color);--bs-btn-hover-color:var(--bs-link-hover-color)}.btn-outline-primary{--bs-btn-color:var(--bs-primary);--bs-btn-border-color:var(--bs-primary);--bs-btn-hover-bg:#115ea3;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#0c3b5e;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-primary);--bs-btn-disabled-border-color:var(--bs-primary)}.btn-outline-secondary{--bs-btn-color:#616161;--bs-btn-border-color:#d1d1d1;--bs-btn-hover-color:#424242;--bs-btn-hover-bg:#f5f5f5;--bs-btn-hover-border-color:#c7c7c7;--bs-btn-active-color:#424242;--bs-btn-active-bg:#e0e0e0;--bs-btn-active-border-color:var(--bs-btn-hover-border-color);--bs-btn-disabled-color:var(--bs-btn-color);--bs-btn-disabled-border-color:var(--bs-btn-border-color)}.btn-outline-success{--bs-btn-color:var(--bs-success);--bs-btn-border-color:var(--bs-success);--bs-btn-hover-bg:#0e700e;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#0c5e0c;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-success);--bs-btn-disabled-border-color:var(--bs-success)}.btn-outline-info{--bs-btn-color:var(--bs-info);--bs-btn-border-color:var(--bs-info);--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#008aa9;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-hover-color);--bs-btn-active-bg:#00748f;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-info);--bs-btn-disabled-border-color:var(--bs-info)}.btn-outline-warning{--bs-btn-color:var(--bs-warning);--bs-btn-border-color:var(--bs-warning);--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#de590b;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-hover-color);--bs-btn-active-bg:#bc4b09;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-warning);--bs-btn-disabled-border-color:var(--bs-warning)}.btn-outline-danger{--bs-btn-color:var(--bs-danger);--bs-btn-border-color:var(--bs-danger);--bs-btn-hover-bg:#b10e1c;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#960b18;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-danger);--bs-btn-disabled-border-color:var(--bs-danger)}.btn-outline-dark{--bs-btn-color:var(--bs-dark);--bs-btn-border-color:var(--bs-dark);--bs-btn-hover-bg:#3d3d3d;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-bg:#1a1a1a;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-dark);--bs-btn-disabled-border-color:var(--bs-dark)}.btn-outline-light{--bs-btn-color:var(--bs-light);--bs-btn-border-color:var(--bs-light);--bs-btn-hover-color:#242424;--bs-btn-hover-bg:#f0f0f0;--bs-btn-hover-border-color:var(--bs-btn-hover-bg);--bs-btn-active-color:var(--bs-btn-hover-color);--bs-btn-active-bg:#e0e0e0;--bs-btn-active-border-color:var(--bs-btn-hover-bg);--bs-btn-disabled-color:var(--bs-light);--bs-btn-disabled-border-color:var(--bs-light)}.table{--bs-table-striped-bg:rgba(250, 250, 250, 0.75);--bs-table-hover-bg:#f5f5f5}[data-bs-theme=dark] .table{--bs-table-striped-bg:rgba(255, 255, 255, 0.03);--bs-table-hover-bg:#3d3d3d}.modal-content{box-shadow:0 0 8px rgba(0,0,0,.12),0 32px 64px rgba(0,0,0,.14)}[data-bs-theme=dark] .modal-content{box-shadow:0 0 8px rgba(0,0,0,.24),0 32px 64px rgba(0,0,0,.28)}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-success)}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-danger)}.form-control.is-valid:focus,.form-select.is-valid:focus,.was-validated .form-control:valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-success);box-shadow:inset 0 -1px 0 var(--bs-success)}.form-control.is-invalid:focus,.form-select.is-invalid:focus,.was-validated .form-control:invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-danger);box-shadow:inset 0 -1px 0 var(--bs-danger)}.validation-message{color:var(--bs-danger-text-emphasis);font-size:.75rem}.dropdown-menu{--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-border-color:var(--bs-border-color);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-divider-bg:#e0e0e0;--bs-dropdown-padding-x:4px;--bs-dropdown-padding-y:4px;--bs-dropdown-item-padding-x:12px;--bs-dropdown-item-padding-y:6px;--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:#f5f5f5;--bs-dropdown-link-active-color:var(--bs-body-color);--bs-dropdown-link-active-bg:#ebebeb;--bs-dropdown-link-disabled-color:#bdbdbd;--bs-dropdown-header-color:#616161;--bb-select-search-border-color:#e0e0e0}.dropdown-menu-arrow{display:none!important}.cascade,.select{--bb-select-dropdown-menu-margin-top:4px}.select:not(.multi-select) .dropdown-menu .dropdown-item{position:relative;padding-inline-start:28px}.select:not(.multi-select) .dropdown-menu .dropdown-item.active::before{content:"\2713";position:absolute;inset-inline-start:10px;color:var(--bs-primary)}.popover{--bs-popover-bg:var(--bs-body-bg);--bs-popover-color:var(--bs-body-color);--bs-popover-border-color:var(--bs-border-color);--bs-popover-border-radius:var(--bs-border-radius);--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-bg:transparent;--bs-popover-header-color:var(--bs-body-color);--bs-popover-header-padding-x:12px;--bs-popover-header-padding-y:10px;--bs-popover-body-color:var(--bs-body-color);--bs-popover-body-padding-x:12px;--bs-popover-body-padding-y:10px}.popover-header{font-weight:600;border-block-end:none;padding-block-end:0}.popover-dropdown .popover-arrow{display:none}.tooltip{--bs-tooltip-bg:#242424;--bs-tooltip-color:#ffffff;--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-padding-x:8px;--bs-tooltip-padding-y:4px;--bs-tooltip-opacity:1}.tooltip .tooltip-arrow{display:none}[data-bs-theme=dark] .dropdown-menu{--bs-dropdown-divider-bg:#4d4d4d;--bs-dropdown-link-hover-bg:#3d3d3d;--bs-dropdown-link-active-bg:#3d3d3d;--bs-dropdown-link-disabled-color:#5c5c5c;--bs-dropdown-header-color:#adadad;--bb-select-search-border-color:#4d4d4d}[data-bs-theme=dark] .tooltip .tooltip-inner{border:var(--bs-border-width) solid var(--bs-border-color)}.cascade .has-leaf .sub-menu,.menu .nav .sub-menu{box-shadow:var(--bs-box-shadow)}.menu .nav .dropdown-menu{margin-block-start:4px}.navbar{--bs-navbar-color:#424242;--bs-navbar-hover-color:#242424;--bs-navbar-active-color:#242424;--bs-navbar-disabled-color:#bdbdbd;--bs-navbar-brand-color:#242424;--bs-navbar-brand-hover-color:#242424}[data-bs-theme=dark] .navbar{--bs-navbar-color:#d6d6d6;--bs-navbar-hover-color:#ffffff;--bs-navbar-active-color:#ffffff;--bs-navbar-disabled-color:#5c5c5c;--bs-navbar-brand-color:#ffffff;--bs-navbar-brand-hover-color:#ffffff}.breadcrumb{--bs-breadcrumb-divider-color:#616161;--bs-breadcrumb-item-active-color:#242424}.breadcrumb-item a{color:#424242}.breadcrumb-item a:hover{color:#242424}[data-bs-theme=dark] .breadcrumb{--bs-breadcrumb-divider-color:#adadad;--bs-breadcrumb-item-active-color:#ffffff}[data-bs-theme=dark] .breadcrumb-item a{color:#d6d6d6}[data-bs-theme=dark] .breadcrumb-item a:hover{color:#fff}.tabs .tabs-item-close:hover{background-color:#e0e0e0}[data-bs-theme=dark] .tabs .tabs-item-close:hover{background-color:#3d3d3d}.tabs.tabs-border-card{box-shadow:var(--bb-shadow)}.step{--bb-step-item-color:#616161;--bb-step-item-line-bg:#e0e0e0}[data-bs-theme=dark] .step{--bb-step-item-color:#adadad;--bb-step-item-line-bg:#4d4d4d}.pagination{--bs-pagination-color:#424242;--bs-pagination-hover-color:#242424;--bs-pagination-hover-bg:#f5f5f5;--bs-pagination-focus-color:#242424;--bs-pagination-focus-bg:#f5f5f5;--bs-pagination-disabled-color:#bdbdbd}[data-bs-theme=dark] .pagination{--bs-pagination-color:#d6d6d6;--bs-pagination-hover-color:#ffffff;--bs-pagination-hover-bg:#3d3d3d;--bs-pagination-focus-color:#ffffff;--bs-pagination-focus-bg:#3d3d3d;--bs-pagination-disabled-color:#5c5c5c}.btn{--bs-btn-font-weight:600}.btn-check:focus-visible+.btn,.btn:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 4px #000}[data-bs-theme=dark] .btn-check:focus-visible+.btn,[data-bs-theme=dark] .btn:focus-visible{box-shadow:0 0 0 2px #000,0 0 0 4px #fff}.btn-check:disabled+.btn,.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:#bdbdbd;background-color:var(--bb-disabled-bg);border-color:var(--bb-disabled-bg);opacity:1}.btn-link.disabled,.btn-link:disabled,.link-button.disabled,.link-button:disabled{background-color:transparent;border-color:transparent}[data-bs-theme=dark] .btn-check:disabled+.btn,[data-bs-theme=dark] .btn.disabled,[data-bs-theme=dark] .btn:disabled,[data-bs-theme=dark] fieldset:disabled .btn{color:#5c5c5c}.dial-button{--bb-dial-item-hover-bg:#f5f5f5;--bb-dial-item-shadow:var(--bb-shadow)}[data-bs-theme=dark] .dial-button{--bb-dial-item-hover-bg:#3d3d3d}.form-control::placeholder{color:var(--bs-tertiary-color);opacity:1}.form-control,.form-select{transition:border-color .2s cubic-bezier(.33,0,.67,1),box-shadow .2s cubic-bezier(.33,0,.67,1),background-color .2s cubic-bezier(.33,0,.67,1)}.form-control:hover:not(:disabled):not([readonly]):not(:focus),.form-select:hover:not(:disabled):not(:focus){border-color:var(--bb-border-hover-color)}.form-control:not(.disabled):not(:disabled):not(.is-valid):not(.is-invalid):not(.no-border):not(.is-display):focus,.form-select:not(.disabled):not(:disabled):not(.is-valid):not(.is-invalid):not(.no-border):not(.is-display):focus{border-color:var(--bs-border-color);border-bottom-color:var(--bb-border-focus-color);box-shadow:inset 0 -1px 0 var(--bb-border-focus-color)}.form-control.disabled,.form-control:disabled,.form-select:disabled,.multi-select .dropdown-toggle.disabled{color:#bdbdbd;background-color:var(--bb-disabled-bg);border-color:#e0e0e0;opacity:1}[data-bs-theme=dark] .form-control.disabled,[data-bs-theme=dark] .form-control:disabled,[data-bs-theme=dark] .form-select:disabled,[data-bs-theme=dark] .multi-select .dropdown-toggle.disabled{color:#5c5c5c;border-color:#424242}.search.auto-complete .form-control-group:not(.disabled):not(:disabled):not(.is-valid):not(.is-invalid):not(.no-border):not(.is-display):focus-within{border-color:var(--bs-border-color);border-bottom-color:var(--bb-border-focus-color);box-shadow:inset 0 -1px 0 var(--bb-border-focus-color)}.auto-complete,.select{--bb-select-append-color:#616161}[data-bs-theme=dark] .auto-complete,[data-bs-theme=dark] .select{--bb-select-append-color:#adadad}.multi-select .multi-select-ph{color:var(--bs-tertiary-color)}.multi-select .dropdown-toggle:not(.disabled):focus,.multi-select .dropdown-toggle:not(.disabled):focus-within{border-color:var(--bs-border-color);border-bottom-color:var(--bb-border-focus-color);box-shadow:inset 0 -1px 0 var(--bb-border-focus-color);outline:0}.bb-otp-input{--bb-otp-item-disabled-color:#bdbdbd}.bb-otp-input .bb-otp-item:focus{border-bottom-color:var(--bb-border-focus-color);box-shadow:inset 0 -1px 0 var(--bb-border-focus-color)}.bb-otp-input .bb-otp-item.is-valid:focus{border-bottom-color:var(--bs-success);box-shadow:inset 0 -1px 0 var(--bs-success)}.bb-otp-input .bb-otp-item.is-invalid:focus{border-bottom-color:var(--bs-danger);box-shadow:inset 0 -1px 0 var(--bs-danger)}[data-bs-theme=dark] .bb-otp-input{--bb-otp-item-disabled-color:#5c5c5c}.form-check{--bb-checkbox-input-focus-border-color:#616161}.form-check .form-check-input{border-color:#616161}.form-check-input[type=checkbox]{border-radius:var(--bs-border-radius-sm)}.form-check .form-check-input:hover:not(:disabled):not(:checked):not(:indeterminate){border-color:#575757}.form-check:not(.form-check-success):not(.form-check-danger):not(.form-check-warning):not(.form-check-info):not(.form-check-secondary):not(.form-check-dark) .form-check-input:checked:hover:not(:disabled),.form-check:not(.form-check-success):not(.form-check-danger):not(.form-check-warning):not(.form-check-info):not(.form-check-secondary):not(.form-check-dark) .form-check-input[type=checkbox]:indeterminate:hover:not(:disabled){background-color:#115ea3;border-color:#115ea3}.form-check .form-check-input:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 4px #000}[data-bs-theme=dark] .form-check{--bb-checkbox-input-focus-border-color:#adadad}[data-bs-theme=dark] .form-check .form-check-input{border-color:#adadad}[data-bs-theme=dark] .form-check .form-check-input:hover:not(:disabled):not(:checked):not(:indeterminate){border-color:#bdbdbd}[data-bs-theme=dark] .form-check .form-check-input:focus-visible{box-shadow:0 0 0 2px #000,0 0 0 4px #fff}.form-check-input:checked[type=radio]{background-color:transparent;border-color:var(--bs-primary);--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.5' fill='%230f6cbd'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]:hover:not(:disabled){border-color:#115ea3;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.5' fill='%23115ea3'/%3e%3c/svg%3e")}.form-range::-webkit-slider-runnable-track{height:4px;background-color:#616161;border-radius:var(--bs-border-radius-sm)}.form-range::-webkit-slider-thumb{width:20px;height:20px;margin-top:-8px;background-color:var(--bs-primary);box-shadow:inset 0 0 0 4px var(--bs-body-bg)}.form-range::-webkit-slider-thumb:hover{background-color:#115ea3}.form-range::-webkit-slider-thumb:active{background-color:#0c3b5e;box-shadow:inset 0 0 0 6px var(--bs-body-bg)}.form-range:focus::-webkit-slider-thumb{box-shadow:inset 0 0 0 4px var(--bs-body-bg),0 0 0 2px #fff,0 0 0 4px #000}.form-range::-moz-range-track{height:4px;background-color:#616161;border-radius:var(--bs-border-radius-sm)}.form-range::-moz-range-progress{height:4px;background-color:var(--bs-primary);border-radius:var(--bs-border-radius-sm)}.form-range::-moz-range-thumb{width:20px;height:20px;background-color:var(--bs-primary);box-shadow:inset 0 0 0 4px var(--bs-body-bg)}.form-range::-moz-range-thumb:hover{background-color:#115ea3}.form-range::-moz-range-thumb:active{background-color:#0c3b5e;box-shadow:inset 0 0 0 6px var(--bs-body-bg)}.form-range:focus::-moz-range-thumb{box-shadow:inset 0 0 0 4px var(--bs-body-bg),0 0 0 2px #fff,0 0 0 4px #000}.form-range:disabled::-webkit-slider-runnable-track{background-color:#e0e0e0}.form-range:disabled::-webkit-slider-thumb{background-color:#bdbdbd;box-shadow:inset 0 0 0 4px var(--bs-body-bg)}.form-range:disabled::-moz-range-progress,.form-range:disabled::-moz-range-track{background-color:#e0e0e0}.form-range:disabled::-moz-range-thumb{background-color:#bdbdbd}[data-bs-theme=dark] .form-range::-webkit-slider-runnable-track{background-color:#adadad}[data-bs-theme=dark] .form-range::-moz-range-track{background-color:#adadad}[data-bs-theme=dark] .form-range:focus::-webkit-slider-thumb{box-shadow:inset 0 0 0 4px var(--bs-body-bg),0 0 0 2px #000,0 0 0 4px #fff}[data-bs-theme=dark] .form-range:focus::-moz-range-thumb{box-shadow:inset 0 0 0 4px var(--bs-body-bg),0 0 0 2px #000,0 0 0 4px #fff}[data-bs-theme=dark] .form-range:disabled::-webkit-slider-runnable-track{background-color:#4d4d4d}[data-bs-theme=dark] .form-range:disabled::-webkit-slider-thumb{background-color:#5c5c5c}[data-bs-theme=dark] .form-range:disabled::-moz-range-progress,[data-bs-theme=dark] .form-range:disabled::-moz-range-track{background-color:#4d4d4d}[data-bs-theme=dark] .form-range:disabled::-moz-range-thumb{background-color:#5c5c5c}.rate{--bb-rate-color:#d1d1d1;--bb-rate-active-color:#eaa300;--bb-rate-hover-color:#eaa300}.rate.disabled .rate-item i{color:#bdbdbd}[data-bs-theme=dark] .rate{--bb-rate-color:#666666}[data-bs-theme=dark] .rate.disabled .rate-item i{color:#5c5c5c}.table-resize .border-resize{border-right-color:var(--bs-primary)}.table .is-bar .fa-caret-right:hover{background-color:#f5f5f5}[data-bs-theme=dark] .table .is-bar .fa-caret-right:hover{background-color:#3d3d3d}.table-drag{background-color:#ebebeb!important}[data-bs-theme=dark] .table-drag{background-color:#3d3d3d!important}.is-ph{background-color:#f0f0f0}[data-bs-theme=dark] .is-ph{background-color:#3d3d3d}.btn-gear .dropdown-menu .dropdown-item:not(.disabled){color:var(--bs-body-color)}.btn-gear .dropdown-menu .dropdown-item:not(:first-child){border-inline-start-color:var(--bs-border-color)}.search-input-tooltip kbd{background:var(--bs-info)}.tree .tree-item.hover:before{background-color:#f5f5f5}.tree .tree-item.active:before{background-color:#ebebeb}[data-bs-theme=dark] .tree .tree-item.hover:before{background-color:#3d3d3d}[data-bs-theme=dark] .tree .tree-item.active:before{background-color:#3d3d3d}.card{--bs-card-border-color:var(--bs-border-color)}.avatar{--bb-avatar-bg:#ebebeb;--bb-avatar-color:#616161}[data-bs-theme=dark] .avatar{--bb-avatar-bg:#333333;--bb-avatar-color:#adadad}.shield-badge{--bb-shield-badge-text-bg:var(--bs-primary)}.timeline{--bb-timeline-node-bg:#d1d1d1;--bb-timeline-timestamp-color:#616161}[data-bs-theme=dark] .timeline{--bb-timeline-node-bg:#666666;--bb-timeline-timestamp-color:#adadad}.list-group{--bs-list-group-active-bg:var(--bs-primary);--bs-list-group-active-border-color:var(--bs-primary);--bs-list-group-action-hover-bg:#f5f5f5;--bs-list-group-action-active-bg:#e0e0e0}[data-bs-theme=dark] .list-group{--bs-list-group-action-hover-bg:#3d3d3d;--bs-list-group-action-active-bg:#1f1f1f}[data-bs-theme=dark] .form-control.is-display{--bs-secondary-bg:#1f1f1f}.modal{--bs-modal-border-width:0;--bs-modal-border-radius:var(--bs-border-radius-xxl);--bs-modal-header-border-color:transparent;--bs-modal-footer-border-color:transparent}.modal-title{font-weight:600}.modal-backdrop{--bs-backdrop-opacity:.4}[data-bs-theme=dark] .modal-backdrop{--bs-backdrop-opacity:.5}.toast{--bs-toast-border-color:transparent;--bs-toast-box-shadow:var(--bb-hover-shadow);--bs-toast-header-bg:transparent;--bs-toast-header-color:var(--bs-body-color)}.alert{--bs-alert-border:0}.alert-info{--bs-alert-color:var(--bs-body-color);--bs-alert-bg:#f5f5f5}[data-bs-theme=dark] .alert-info{--bs-alert-bg:#141414}.swal2-title{color:var(--bs-body-color);font-size:1.25rem;font-weight:600}.swal2-content{color:var(--bs-body-color);font-weight:400}.swal2-footer{border-top-color:#e0e0e0;color:#616161}.swal2-icon.swal2-success{border-color:var(--bs-success-text-emphasis);color:var(--bs-success-text-emphasis)}.swal2-icon.swal2-success .swal2-success-ring{border-color:rgba(var(--bs-success-rgb),.3)}.swal2-icon.swal2-success [class^=swal2-success-line]{background-color:var(--bs-success-text-emphasis)}.swal2-icon.swal2-error{border-color:var(--bs-danger-text-emphasis);color:var(--bs-danger-text-emphasis)}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{background-color:var(--bs-danger-text-emphasis)}.swal2-icon.swal2-warning{border-color:var(--bs-warning-text-emphasis);color:var(--bs-warning-text-emphasis)}.swal2-icon.swal2-info{border-color:var(--bs-info-text-emphasis);color:var(--bs-info-text-emphasis)}.swal2-icon.swal2-question{border-color:var(--bs-secondary-text-emphasis);color:var(--bs-secondary-text-emphasis)}[data-bs-theme=dark] .swal2-footer{border-top-color:#424242;color:#adadad}.progress{--bs-progress-bg:#e6e6e6}[data-bs-theme=dark] .progress{--bs-progress-bg:#333333}.circle{--bb-circle-stroke-color:#e6e6e6}[data-bs-theme=dark] .circle{--bb-circle-stroke-color:#333333}.bb-mask{--bb-mask-opacity:.4}[data-bs-theme=dark] .bb-mask{--bb-mask-opacity:.5}.picker-panel-sidebar{border-right-color:var(--bs-border-color)}.picker-panel .picker-panel-sidebar .sidebar-item:hover,.picker-panel-sidebar .sidebar-item:hover{color:var(--bb-picker-hover-color)}.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td:not(.prev-month):not(.next-month):hover{color:var(--bb-picker-hover-color)}.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.current:not(.disabled) .cell:has(.bb-picker-body-lunar-text):before,.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.end:not(.disabled) .cell:has(.bb-picker-body-lunar-text):before,.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.start:not(.disabled) .cell:has(.bb-picker-body-lunar-text):before{background-color:var(--bb-picker-panel-today-bg)}.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.range:before{background-color:var(--bs-primary-bg-subtle)}.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.next-month,.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.prev-month,.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.disabled .cell{color:#bdbdbd}.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-holiday,.picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-workday{background-color:#bdbdbd}[data-bs-theme=dark] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.next-month,[data-bs-theme=dark] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content .date-table td.prev-month,[data-bs-theme=dark] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td.disabled .cell{color:#5c5c5c}[data-bs-theme=dark] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-holiday,[data-bs-theme=dark] .picker-panel .picker-panel-body .picker-panel-body-main .picker-panel-content table td .cell span.is-workday{background-color:#5c5c5c}.picker-panel-footer .picker-panel-link-btn:hover{border-color:var(--bb-picker-hover-color);color:var(--bb-picker-hover-color)}.picker-panel-footer .picker-timer:hover{color:var(--bb-picker-hover-color)}.datetime-picker{--bb-dt-picker-bar-color:#707070}.datetime-range{--bb-dt-range-bar-color:#707070}[data-bs-theme=dark] .datetime-picker{--bb-dt-picker-bar-color:#adadad}[data-bs-theme=dark] .datetime-range{--bb-dt-range-bar-color:#adadad}.datetime-range-control .datetime-range-input.disabled,.datetime-range.disabled{background-color:var(--bb-disabled-bg)}.bb-time-picker{--bb-time-picker-shadow:var(--bs-box-shadow)}.bb-clock-picker .bb-time-body .bb-clock-panel .bb-clock-point .bb-clock-point-star{background-color:var(--bs-body-color)}.bb-color-picker .form-control-color.disabled{background-color:var(--bb-disabled-bg)}.upload .upload-body.is-list .upload-item:hover{background-color:#f5f5f5}[data-bs-theme=dark] .upload .upload-body.is-list .upload-item:hover{background-color:#3d3d3d}.upload.is-drop .upload-drop-body{border-color:var(--bs-border-color)}.upload.is-drop .upload-drop-body .upload-drop-icon>i{color:#616161}.upload.is-drop:not(.disabled) .upload-drop-body em{color:var(--bs-link-color)}.upload.is-drop.dropping .upload-drop-body{border-color:var(--bs-primary);background-color:var(--bs-primary-bg-subtle)}[data-bs-theme=dark] .upload.is-drop .upload-drop-body .upload-drop-icon>i{color:#adadad}.bb-previewer{--bb-viewer-button-bg:#3d3d3d}.bb-previewer .bb-viewer-mask{opacity:.4}.switch .switch-core{transition:border-color .2s cubic-bezier(.33,0,.67,1),background-color .2s cubic-bezier(.33,0,.67,1)}.switch:not(.disabled):not(.is-checked):hover .switch-core{background-color:#d1d1d1;border-color:#c7c7c7}.switch:not(.disabled):not(.is-checked):active .switch-core{background-color:#c7c7c7;border-color:#bdbdbd}.switch .switch-core.bg-success{--bs-success-rgb:var(--bs-primary-rgb)}.switch:not(.disabled):hover .switch-core.bg-success{--bs-success-rgb:17,94,163}.switch:not(.disabled):active .switch-core.bg-success{--bs-success-rgb:12,59,142}.switch.disabled{opacity:1}.switch.disabled .switch-core{background-color:var(--bb-disabled-bg)!important;border-color:#e0e0e0!important;cursor:not-allowed}.switch.disabled .switch-core:after{background-color:#bdbdbd}.switch.disabled .switch-core:before,.switch.disabled .switch-label{color:#bdbdbd;cursor:not-allowed}[data-bs-theme=dark] .switch:not(.disabled):not(.is-checked):hover .switch-core{background-color:#4d4d4d;border-color:#757575}[data-bs-theme=dark] .switch:not(.disabled):not(.is-checked):active .switch-core{background-color:#525252;border-color:#757575}[data-bs-theme=dark] .switch .switch-core.bg-success{--bs-success-rgb:71,158,245}[data-bs-theme=dark] .switch:not(.disabled):hover .switch-core.bg-success{--bs-success-rgb:98,171,245}[data-bs-theme=dark] .switch:not(.disabled):active .switch-core.bg-success{--bs-success-rgb:40,134,222}[data-bs-theme=dark] .switch.disabled .switch-core{border-color:#424242!important}[data-bs-theme=dark] .switch.disabled .switch-core:after{background-color:#5c5c5c}[data-bs-theme=dark] .switch.disabled .switch-core:before,[data-bs-theme=dark] .switch.disabled .switch-label{color:#5c5c5c}.transfer-panel-item{transition:background-color .2s cubic-bezier(.33,0,.67,1)}.transfer-panel-item:hover{background-color:#f5f5f5}.transfer-panel-filter .input-inner{color:var(--bs-body-color)}.transfer-panel-filter .input-inner:hover:not(:focus){border-color:var(--bb-border-hover-color)}.transfer-panel-filter .input-prefix{color:#616161}[data-bs-theme=dark] .transfer-panel-item:hover{background-color:#3d3d3d}[data-bs-theme=dark] .transfer-panel-filter .input-prefix{color:#adadad}.segmented{--bb-segmented-item-hover-bg:#f5f5f5;--bb-segmented-item-active-bg:#e0e0e0}.segmented .segmented-item.disabled{color:#bdbdbd}[data-bs-theme=dark] .segmented{--bb-segmented-item-hover-bg:#3d3d3d;--bb-segmented-item-active-bg:#4d4d4d}[data-bs-theme=dark] .segmented .segmented-item.disabled{color:#5c5c5c}.bb-light{--bb-light-primary-bg:radial-gradient(circle, #62abf5, #0f6cbd, #0c3b5e);--bb-light-primary-hover-bg:radial-gradient(circle, #77b7f7, #115ea3, #0c3b5e);--bb-light-success-bg:radial-gradient(circle, #54b054, #107c10, #094509);--bb-light-success-hover-bg:radial-gradient(circle, #9fd89f, #0e700e, #094509);--bb-light-info-bg:radial-gradient(circle, #56bfd7, #0099bc, #005669);--bb-light-info-hover-bg:radial-gradient(circle, #a4deeb, #008aa9, #005669);--bb-light-warning-bg:radial-gradient(circle, #faa06b, #f7630c, #8a3707);--bb-light-warning-hover-bg:radial-gradient(circle, #fdcfb4, #de590b, #8a3707);--bb-light-danger-bg:radial-gradient(circle, #dc626d, #c50f1f, #6e0811);--bb-light-danger-hover-bg:radial-gradient(circle, #eeacb2, #b10e1c, #6e0811);--bb-light-secondary-bg:radial-gradient(circle, #b3b3b3, #757575, #424242);--bb-light-secondary-hover-bg:radial-gradient(circle, #d1d1d1, #616161, #424242);--bb-light-dark-bg:radial-gradient(circle, #757575, #424242, #1a1a1a);--bb-light-dark-hover-bg:radial-gradient(circle, #999999, #242424, #1a1a1a)}@keyframes light-primary{0%,55%{background:radial-gradient(circle,#62abf5,#0f6cbd,#0c3b5e)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}}@keyframes light-success{0%,55%{background:radial-gradient(circle,#54b054,#107c10,#094509)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}}@keyframes light-info{0%,55%{background:radial-gradient(circle,#56bfd7,#0099bc,#005669)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}}@keyframes light-warning{0%,55%{background:radial-gradient(circle,#faa06b,#f7630c,#8a3707)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}}@keyframes light-danger{0%,55%{background:radial-gradient(circle,#dc626d,#c50f1f,#6e0811)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}}@keyframes light-secondary{0%,55%{background:radial-gradient(circle,#b3b3b3,#757575,#424242)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}}@keyframes light-dark{0%,55%{background:radial-gradient(circle,#757575,#424242,#1a1a1a)}100%{background:radial-gradient(circle,#fff,#aaa,#333)}} From 45db86f1f0f8b2383cdaa34295fc069dd5c6a0c3 Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 22:56:41 +0800 Subject: [PATCH 06/20] feat(Theme): make Motronic an optional theme instead of global stylesheet --- src/BootstrapBlazor.Server/Components/App.razor | 1 - src/BootstrapBlazor.Server/appsettings.json | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor.Server/Components/App.razor b/src/BootstrapBlazor.Server/Components/App.razor index 3ac0591918d..74a06059ae9 100644 --- a/src/BootstrapBlazor.Server/Components/App.razor +++ b/src/BootstrapBlazor.Server/Components/App.razor @@ -25,7 +25,6 @@ - diff --git a/src/BootstrapBlazor.Server/appsettings.json b/src/BootstrapBlazor.Server/appsettings.json index c269e04dfde..1ebe805b39f 100644 --- a/src/BootstrapBlazor.Server/appsettings.json +++ b/src/BootstrapBlazor.Server/appsettings.json @@ -120,6 +120,13 @@ "_content/BootstrapBlazor/css/fluent.min.css", "./css/fluent.css" ] + }, + { + "key": "motronic", + "name": "Motronic", + "files": [ + "_content/BootstrapBlazor/css/motronic.min.css" + ] } ] }, From b48f3be8af13812be9b761560ab7846d3efdd9f9 Mon Sep 17 00:00:00 2001 From: lee Date: Mon, 17 Aug 2026 22:56:42 +0800 Subject: [PATCH 07/20] docs(Theme): add component preview wall and update theme usage docs --- .../Components/Pages/Install.razor | 6 +- .../Components/Pages/Theme.razor | 289 +++++++++++++++++- .../Components/Pages/Theme.razor.css | 5 + src/BootstrapBlazor.Server/Locales/en-US.json | 13 +- src/BootstrapBlazor.Server/Locales/zh-CN.json | 13 +- 5 files changed, 315 insertions(+), 11 deletions(-) create mode 100644 src/BootstrapBlazor.Server/Components/Pages/Theme.razor.css diff --git a/src/BootstrapBlazor.Server/Components/Pages/Install.razor b/src/BootstrapBlazor.Server/Components/Pages/Install.razor index ca5c1dbe0a1..6a615335bb2 100644 --- a/src/BootstrapBlazor.Server/Components/Pages/Install.razor +++ b/src/BootstrapBlazor.Server/Components/Pages/Install.razor @@ -1,4 +1,4 @@ -@page "/install" +@page "/install" @inject IStringLocalizer Localizer @inject IOptions WebsiteOption @@ -30,6 +30,8 @@ <link rel="stylesheet" href="@@Assets["_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css"]" /> @Localizer["BootstrapStyle"] <link rel="stylesheet" href="@@Assets["_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css"]" /> +@Localizer["FluentDesc"] +<link rel="stylesheet" href="@@Assets["_content/BootstrapBlazor/css/fluent.min.css"]" /> @Localizer["MotronicDesc"] <link rel="stylesheet" href="@@Assets["_content/BootstrapBlazor/css/motronic.min.css"]" /> @@ -38,6 +40,8 @@ <link rel="stylesheet" href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" /> @Localizer["BootstrapStyle"] <link rel="stylesheet" href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" /> +@Localizer["FluentDesc"] +<link rel="stylesheet" href="_content/BootstrapBlazor/css/fluent.min.css" /> @Localizer["MotronicDesc"] <link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css" /> diff --git a/src/BootstrapBlazor.Server/Components/Pages/Theme.razor b/src/BootstrapBlazor.Server/Components/Pages/Theme.razor index 70f2e989d3e..bc814587e18 100644 --- a/src/BootstrapBlazor.Server/Components/Pages/Theme.razor +++ b/src/BootstrapBlazor.Server/Components/Pages/Theme.razor @@ -1,5 +1,7 @@ @page "/theme" @inject IStringLocalizer Localizer +@inject MessageService MessageService +@inject ToastService ToastService

    @Localizer["H1"]

    @@ -19,12 +21,287 @@

    @Localizer["P12"] _content/BootstrapBlazor/css/ @Localizer["P13"] head @Localizer["P14"]:

    -
    <link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
    -
    -// @Localizer["P15"] Motronic
    -<link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css">
    +
    // @Localizer["P23"]
    +<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
     
     // @Localizer["P15"] Fluent UI
    -<link rel="stylesheet" href="_content/BootstrapBlazor/css/fluent.min.css">
    +<link rel="stylesheet" href="_content/BootstrapBlazor/css/fluent.min.css"> + +// @Localizer["P19"] Motronic +<link rel="stylesheet" href="_content/BootstrapBlazor/css/motronic.min.css">
    + +

    @Localizer["P24"] fluent.css @Localizer["P25"] fluent.min.css @Localizer["P26"]

    + +

    @Localizer["P16"] data-bs-theme @Localizer["P17"] <html> @Localizer["P20"] data-bs-theme="dark" @Localizer["P21"] ThemeProvider @Localizer["P22"]

    + +

    Component preview

    + +

    Sections below exercise the core components with minimal parameters. Use the theme button at the bottom-right corner of this site to switch between Fluent UI, Motronic and Bootstrap in light or dark mode and eyeball the result in one place.

    + +

    Buttons

    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +

    Forms

    + +
    +
    + +
    +
    + +
    +
    +