--- lang: ja-jp breaks: true --- # Fluent UI Blazor デモにある `CodeSnippet` を テーマの変更に対応する 2024-03-20 > Fluent UI Blazor デモにある `CodeSnippet` を`net8.0` のプロジェクトテンプレートでも動作するようにする 2024-03-19 > https://hackmd.io/mIuigrIMQYWF0iLVBd4yQg これの続き。 ## `CodeSnippet` > microsoft/fluentui-blazor > https://github.com/microsoft/fluentui-blazor  ### デモプロジェクト > fluentui-blazor/examples/Demo/ > https://github.com/microsoft/fluentui-blazor/tree/main/examples/Demo ## テーマを変更するパネルの実装 ### デモプロジェクトより、必要なファイルをコピーして貼り付ける * CacheStorageAccessor.js、CacheStorageAccessor.cs * 名前空間、jsファイルのパスを変更する。  * SiteSettings.razor、SiteSettingsPanel.razor 一式 * 名前空間を変更する。  ### LoadingThemeJs.razor を新規追加 * LoadingThemeJs.razor * 中身は空っぽ。 * LoadingThemeJs.razor.cs ```csharp= using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace HighlightJsApp.Components; public partial class LoadingThemeJs { private IJSObjectReference _jsModule = default!; [Inject] protected IJSRuntime JSRuntime { get; set; } = default!; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>( "import", "/Components/LoadingThemeJs.razor.js" ); // `customElements.define("loading-theme", LoadingTheme);` を呼び出す。 await _jsModule.InvokeVoidAsync("evalLoadingThemeJs"); } } } ``` * LoadingThemeJs.razor.js ```csharp= export function evalLoadingThemeJs() { fetch('/_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js') .then(r => r.text()) .then(t => { // `t` に loading-theme.js のファイル内容が格納されているので、その内容を実行する。 // ※`customElements.define("loading-theme", LoadingTheme);` を呼び出す。 eval(t); }); } ```  ### NavMenu.razor.js を新規追加 ```javascript= function onclick_sitenav( event ) { let navmenu_toggle = document.getElementById('navmenu-toggle-label'); if (navmenu_toggle == null) { return; } if (navmenu_toggle.computedStyleMap().get('display').value != 'none') { var doClose = true; var count = 0; var target = event.target; while (target != null) { var classList = target.classList; if (classList == null) { break; } if (classList.contains('fluent-nav-item')) { if (classList.contains('fluent-nav-group')) { doClose = false; } break; } target = target.parentElement; count++; if (count > 5) { break; } } if (doClose) { navmenu_toggle.click(); } } } ```  :::info * ~~ファイルのプロパティより、「新しい場合はコピーする」に変更する。~~ * 2024-03-21 「コピーしない」でも動作するっぽい。  ::: ### Program.cs の変更 * `builder.Services.AddScoped<CacheStorageAccessor>();` を追加する。 ```csharp= ・・・ builder.Services.AddFluentUIComponents(); builder.Services.AddScoped<CacheStorageAccessor>(); var app = builder.Build(); ・・・ ``` ### App.razor の変更 * `body`内に、以下のように追加。 ```html= ・・・ <body> <FluentDesignTheme StorageName="theme" @rendermode="InteractiveServer" /> <!-- Set the default theme ("mode" overrides the "storage-name") --> <!-- <script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js" type="text/javascript"></script> --> <LoadingThemeJs @rendermode="InteractiveServer" /> <loading-theme storage-name="theme"></loading-theme> <!-- End --> <Routes /> <script src="_framework/blazor.web.js"></script> </body> ・・・ ``` ### MainLayout.razor の変更 ```csharp= @inherits LayoutComponentBase <FluentLayout> <FluentHeader Class="siteheader"> <a href="/"> HighlightJsApp </a> <FluentSpacer /> <div class="settings"> <SiteSettings @rendermode="@RenderMode.InteractiveServer" /> </div> </FluentHeader> <FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%"> <NavMenu /> <FluentBodyContent> <div class="content"> <article id="article"> @Body </article> <FluentDialogProvider @rendermode="@RenderMode.InteractiveServer" /> </div> </FluentBodyContent> </FluentStack> ・・・ ``` ### NavMenu.razor の変更 ```html= @rendermode InteractiveAuto <div class="navmenu"> <!-- <input type="checkbox" id="navmenu-toggle" /> --> <input type="checkbox" title="Menu expand/collapse toggle" id="navmenu-toggle" class="navmenu-icon" /> <label for="navmenu-toggle" id="navmenu-toggle-label" class="navmenu-icon"><FluentIcon Value="@(new Icons.Regular.Size20.Navigation())" Color="Color.Neutral" /></label> <!-- <nav class="sitenav" aria-labelledby="main-menu" onclick="document.getElementById('navmenu-toggle').click();"> --> <script src="/Layout/NavMenu.razor.js"></script> <nav class="sitenav" aria-labelledby="main-menu" onclick="onclick_sitenav(event)"> <FluentNavMenu Id="main-menu" Width="250" Collapsible="true" Title="Navigation menu" @bind-Expanded="expanded"> <FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink> <FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink> <FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink> </FluentNavMenu> </nav> </div> @code { private bool expanded = true; } ``` ### NavMenu.razor.css の変更 * 以下のように、`navmenu-toggle` 関連のスタイルを追加する。 ```css= ・・・ /* */ nav.sitenav { background-color: var(--neutral-layer-1); padding: 1.5rem 1rem; height: calc(100dvh - 90px); /* width: 18rem; */ overflow-y: auto; } /* */ #navmenu-toggle { display: none; } .navmenu-icon { display: none; } #navmenu-toggle:checked > nav { width: 0px; } [dir="rtl"] #navmenu-toggle:checked ~ nav { right: 0px; } @media (max-width: 600px) { #navmenu-toggle { appearance: none; } .navmenu-icon { cursor: pointer; z-index: 10; display: block; position: absolute; top: 15px; left: unset; right: 20px; width: 20px; height: 20px; border: none; } [dir="rtl"] .navmenu-icon { left: 20px; right: unset } #navmenu-toggle ~ nav { display: none; } #navmenu-toggle:checked ~ nav { display: block; } #navmenu-toggle ~ article { display: block; } #navmenu-toggle:checked ~ article { display: none; } } ``` ### app.css の変更 * `navmenu-toggle` 関連のスタイルを削除する。 ```css= ・・・ /* #navmenu-toggle { display: none; } */ ・・・ @media (max-width: 600px) { ・・・ .navmenu { width: 100%; } /* #navmenu-toggle { appearance: none; cursor: pointer; z-index: 10; display: block; visibility: visible; position: absolute; top: 16px; right: 1.5rem; width: 20px; height: 20px; border: none; color: white; background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' style='width: 20px; fill: white;' focusable='false' viewBox='0 0 20 20'><path d='M2 4.5c0-.28.22-.5.5-.5h15a.5.5 0 0 1 0 1h-15a.5.5 0 0 1-.5-.5Zm0 5c0-.28.22-.5.5-.5h15a.5.5 0 0 1 0 1h-15a.5.5 0 0 1-.5-.5Zm.5 4.5a.5.5 0 0 0 0 1h15a.5.5 0 0 0 0-1h-15Z'></path></svg>"); } #navmenu-toggle ~ nav { display: none; } #navmenu-toggle:checked ~ nav { display: block; } */ } ・・・ ``` ### highlight-extensions.js の変更 * `utils.js` を追加。 *  ```javascript= export class Utils { static sleepAsync(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } ``` * `loading-theme.js`によるDOMの変更を `setTimeout(() => { }, 100)` により待つ。 ```javascript= import { Utils } from '/js/utils.js'; export async function hljs_OnAfterRender() { ・・・ // Add custom code highlight.onload = async () => { const hljsRazor = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/highlightjs-cshtml-razor@2.1.1/dist/cshtml-razor.min.js'); // Switch highlight Dark/Light theme let theme = document.querySelector('loading-theme > fluent-design-theme'); while (theme == null) { // Wait for loading-theme.js to be loaded //console.log('Wait for loading-theme.js'); await Utils.sleepAsync(100); theme = document.querySelector('loading-theme > fluent-design-theme'); } if (theme != null) { theme.addEventListener('onchange', (e) => { if (e.detail.name == 'mode') { if (e.detail.newValue === 'undefined') return; const isDark = e.detail.newValue.includes('dark'); hljs_ColorSwitcher(isDark); } }); } // Detect system theme changing window.matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', async (e) => { await hljs_ColorSystem(); }); // First/default theme await hljs_ColorSystem(); } } ・・・ ``` * `loading-theme.js`によるDOMの変更を `await Utils.sleepAsync(100);` により待つ。 * `theme.getAttribute('mode').value === undefined` があると正常に動作しないので削除。 * システムのテーマだけではなく、ユーザが変更したテーマも適用するように変更する。 ```javascript= async function hljs_ColorSystem() { let theme = document.querySelector('loading-theme > fluent-design-theme'); while (theme == null) { // Wait for loading-theme.js to be loaded //console.log('Wait for loading-theme.js'); await Utils.sleepAsync(100); theme = document.querySelector('loading-theme > fluent-design-theme'); } if (theme != null) { if (theme.getAttribute('mode') == 'null' || theme.getAttribute('mode') == null /*|| theme.getAttribute('mode').value === undefined*/ ) { const isSystemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; hljs_ColorSwitcher(isSystemDark); } else { switch (theme.getAttribute('mode')) { case 'light': hljs_ColorSwitcher(false); break; case 'dark': hljs_ColorSwitcher(true); break; } } } } ``` ## 実行結果     ###### tags: `Fluent UI Blazor` `CodeSnippet` `highlight.js` `loading-theme.js`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up